From c6d7c0fca7751772bf409865ab57249d72aaefaf Mon Sep 17 00:00:00 2001 From: Benjamin Mako Hill Date: Mon, 5 Oct 2020 10:22:03 -0700 Subject: [PATCH 1/1] initial version of colcall bot - basic student information for preferred names - simple discord bot - coldcall library to maintain records and choose folks --- .gitignore | 2 + coldcall.py | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ coldcallbot.py | 43 +++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 .gitignore create mode 100644 coldcall.py create mode 100644 coldcallbot.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b5e2e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +__pycache__ diff --git a/coldcall.py b/coldcall.py new file mode 100644 index 0000000..8cae6ca --- /dev/null +++ b/coldcall.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +from collections import defaultdict +from datetime import datetime +from random import choices +from os import listdir +from csv import DictReader + +import os.path +import re +import discord + +class ColdCall(): + def __init__ (self): + self.today = str(datetime.date(datetime.now())) + # how much less likely should it be that a student is called upon? + self.weight = 2 + + # filenames + self.__fn_studentinfo = "data/student_information.tsv" + self.__fn_daily_calllist = f"data/call_list-{self.today}.tsv" + self.__fn_daily_attendence = f"data/attendence-{self.today}.tsv" + + def __load_prev_questions(self): + previous_questions = defaultdict(int) + + for fn in listdir("./data/"): + if re.match("call_list-\d{4}-\d{2}-\d{2}.tsv", fn): + with open(f"./data/{fn}", 'r') as f: + for row in DictReader(f, delimiter="\t"): + previous_questions[row["discord_name"]] += 1 + + return previous_questions + + def __get_preferred_name(self, selected_student): + # translate the discord name into the preferred students name, + # if possible, otherwise return the discord name + + preferred_names = {} + with open(self.__fn_studentinfo, 'r') as f: + for line in f.readlines(): + x = line.strip().split("\t") + if x[0] == "Timestamp": + continue + + preferred_names[x[4]] = x[2] + + if selected_student in preferred_names: + return preferred_names[selected_student] + else: + return None + + def __select_student_from_list (self, students_present): + prev_questions = self.__load_prev_questions() + + # created a weighted list by starting out with everybody 1 + weights = {s : 1 for s in students_present} + + for s in students_present: + for i in range(0, prev_questions[s]): + # reduce the weight by a factor of 1/weight each time the student has been called upon + weights[s] = weights[s] / self.weight + + # choose one student from the weighted list + print(weights) + return choices(list(weights.keys()), weights=list(weights.values()), k=1)[0] + + def __record_attendence(self, students_present): + # if it's the first one of the day, write it out + if not os.path.exists(self.__fn_daily_attendence): + with open(self.__fn_daily_attendence, "w") as f: + print("\t".join(["timestamp", "attendence_list"]), file=f) + + # open for appending the student + with open(self.__fn_daily_attendence, "a") as f: + print("\t".join([str(datetime.now()), + ",".join(students_present)]), + file=f) + + def __record_coldcall(self, selected_student): + # if it's the first one of the day, write it out + if not os.path.exists(self.__fn_daily_calllist): + with open(self.__fn_daily_calllist, "w") as f: + print("\t".join(["discord_name", "timestamp", "answered", "assessment"]), file=f) + + # open for appending the student + with open(self.__fn_daily_calllist, "a") as f: + print("\t".join([selected_student, str(datetime.now()), + "MISSING", "MISSING"]), file=f) + + def coldcall(self, students_present): + selected_student = self.__select_student_from_list(students_present) + + # record the called-upon student in the right place + self.__record_attendence(students_present) + self.__record_coldcall(selected_student) + + preferred_name = self.__get_preferred_name(selected_student) + if preferred_name: + coldcall_message = f"{preferred_name} (@{selected_student}), you're up!" + else: + coldcall_message = f"@{selected_student}, you're up!" + return coldcall_message + +# cc = ColdCall() + +# test_student_list = ["Jordan", "Kristen Larrick", "Madison Heisterman", "Maria.Au20", "Laura (Alia) Levi", "Leona Aklipi", "Linya Feng", "anne", "kirst", "emmaaitelli", "ashleylee", "allie_partridge", "Tiana_Cole", "Hamin", "Ella Qu", "Angel Su", "Shizuka", "Ben Baird", "Kim Do", "Isaacm24", "Sam Bell", "Courtneylg", "TasnimHasan"] +# cc.coldcall(test_student_list) + +# test_student_list = ["Jordan", "Kristen Larrick", "Mako"] +# cc.coldcall(test_student_list) + +# test_student_list = ["Jordan", "Kristen Larrick"] +# cc.coldcall(test_student_list) diff --git a/coldcallbot.py b/coldcallbot.py new file mode 100644 index 0000000..392028a --- /dev/null +++ b/coldcallbot.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +from coldcall import ColdCall +import re +import discord + +## create the coldcall object +cc = ColdCall() + +class ColdCallBot (discord.Client): + async def on_ready(self): + print(f'Logged on as {self.user}! Ready for class!') + + async def on_message(self, message): + if message.author == self.user: + return + + if message.content.startswith('$next'): + classroom = discord.utils.get(message.guild.voice_channels, name='Classroom Voice') + + present_students = [] + for member in classroom.members: + if 'Students' in [r.name for r in member.roles]: + present_students.append(re.sub(r'^(.*)\#.*$', r'\1', member.name)) + + # print who is online + print(f'currently online: {",".join(present_students)}') + + if len(present_students) < 1: + msg_text = "I don't see any students currently in the Classroom Voice channel!" + else: + msg_text = cc.coldcall(present_students) + + await message.channel.send(msg_text) + +# this is necessary to get information about who is online +intents = discord.Intents.default() +intents.members = True +intents.presences = True + +ccb = ColdCallBot(intents=intents) +ccb.run('CHANGEME') + -- 2.39.5