18
0

2 Commits

5 changed files with 179 additions and 15 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*~ *~
__pycache__ __pycache__
config.py

2
README
View File

@@ -18,7 +18,7 @@ the steps there, with one important exception:
need to enable both "Privileged Gateway Intents." This allows the bot need to enable both "Privileged Gateway Intents." This allows the bot
to see who is present and active in the channel. to see who is present and active in the channel.
Finally, you need to copy your bot'ss Token (also found on the "Bot" tab) Finally, you need to copy your bot's Token (also found on the "Bot" tab)
into coldcallbot.py. Pass it as the argument to `ccb.run()`. into coldcallbot.py. Pass it as the argument to `ccb.run()`.

View File

@@ -11,22 +11,26 @@ import re
import discord import discord
class ColdCall(): class ColdCall():
def __init__ (self): def __init__ (self, course = ''):
self.course = course
self.today = str(datetime.date(datetime.now())) self.today = str(datetime.date(datetime.now()))
# how much less likely should it be that a student is called upon? # how much less likely should it be that a student is called upon?
self.weight = 2 self.weight = 2
self.__set_filenames()
def __set_filenames(self):
# filenames # filenames
self.__fn_studentinfo = "data/student_information.tsv" self.__fn_studentinfo = f"data/{self.course}/student_information.tsv"
self.__fn_daily_calllist = f"data/call_list-{self.today}.tsv" self.__fn_daily_calllist = f"data/{self.course}/call_list-{self.today}.tsv"
self.__fn_daily_attendance = f"data/attendance-{self.today}.tsv" self.__fn_daily_attendance = f"data/{self.course}/attendance-{self.today}.tsv"
def __load_prev_questions(self): def __load_prev_questions(self):
previous_questions = defaultdict(int) previous_questions = defaultdict(int)
for fn in listdir("./data/"): for fn in listdir(f"./data/{self.course}/"):
if re.match("call_list-\d{4}-\d{2}-\d{2}.tsv", fn): if re.match("call_list-\d{4}-\d{2}-\d{2}.tsv", fn):
with open(f"./data/{fn}", 'r') as f: with open(f"./data/{self.course}/{fn}", 'r') as f:
for row in DictReader(f, delimiter="\t"): for row in DictReader(f, delimiter="\t"):
if not row["answered"] == "FALSE": if not row["answered"] == "FALSE":
previous_questions[row["discord_name"]] += 1 previous_questions[row["discord_name"]] += 1
@@ -40,7 +44,7 @@ class ColdCall():
preferred_names = {} preferred_names = {}
with open(self.__fn_studentinfo, 'r') as f: with open(self.__fn_studentinfo, 'r') as f:
for row in DictReader(f, delimiter="\t"): for row in DictReader(f, delimiter="\t"):
preferred_names[row["Your username on the class Discord server"]] = row["Name you'd like to go by in class"] preferred_names[row["discord_name"]] = row["name"]
if selected_student in preferred_names: if selected_student in preferred_names:
return preferred_names[selected_student] return preferred_names[selected_student]
@@ -99,6 +103,10 @@ class ColdCall():
coldcall_message = f"@{selected_student}, you're up!" coldcall_message = f"@{selected_student}, you're up!"
return coldcall_message return coldcall_message
def update_course(self, course_name):
self.course = course_name
self.__set_filenames()
# cc = ColdCall() # cc = ColdCall()
# test_student_list = ["jordan", "Kristen Larrick", "Madison Heisterman", "Maria.Au20", "Laura (Alia) Levi", "Leona Aklipi", "anne", "emmaaitelli", "ashleylee", "allie_partridge", "Tiana_Cole", "Hamin", "Ella Qu", "Shizuka", "Ben Baird", "Kim Do", "Isaacm24", "Sam Bell", "Courtneylg"] # test_student_list = ["jordan", "Kristen Larrick", "Madison Heisterman", "Maria.Au20", "Laura (Alia) Levi", "Leona Aklipi", "anne", "emmaaitelli", "ashleylee", "allie_partridge", "Tiana_Cole", "Hamin", "Ella Qu", "Shizuka", "Ben Baird", "Kim Do", "Isaacm24", "Sam Bell", "Courtneylg"]

View File

@@ -3,6 +3,8 @@
from coldcall import ColdCall from coldcall import ColdCall
import re import re
import discord import discord
import config
import random
## create the coldcall object ## create the coldcall object
cc = ColdCall() cc = ColdCall()
@@ -11,13 +13,16 @@ class ColdCallBot (discord.Client):
async def on_ready(self): async def on_ready(self):
print(f'Logged on as {self.user}! Ready for class!') print(f'Logged on as {self.user}! Ready for class!')
async def on_message(self, message): async def on_message(self, message, voice_channel = 'Class Sessions'):
if message.author == self.user: if message.author == self.user:
return return
if message.content.startswith('$next'): if message.content.startswith('$next'):
classroom = discord.utils.get(message.guild.voice_channels, name='Classroom Voice') if message.channel.category:
if cc.course != message.channel.category:
cc.update_course(message.channel.category)
classroom = [x for x in message.guild.voice_channels if x.name == voice_channel and x.category_id == message.channel.category_id][0]
present_students = [] present_students = []
for member in classroom.members: for member in classroom.members:
if 'Students' in [r.name for r in member.roles]: if 'Students' in [r.name for r in member.roles]:
@@ -30,8 +35,156 @@ class ColdCallBot (discord.Client):
msg_text = "I don't see any students currently in the Classroom Voice channel!" msg_text = "I don't see any students currently in the Classroom Voice channel!"
else: else:
msg_text = cc.coldcall(present_students) msg_text = cc.coldcall(present_students)
await message.channel.send(msg_text) await message.channel.send(msg_text)
# TODO: Only let admin send this command
if (message.content.startswith('$network game')) and ('Teachers' in [r.name for r in message.author.roles]):
print("Starting the game")
if message.channel.category:
if cc.course != message.channel.category:
cc.update_course(message.channel.category)
classroom = [x for x in message.guild.voice_channels if x.name == voice_channel and x.category_id == message.channel.category_id][0]
present_students = []
for member in classroom.members:
if 'Students' in [r.name for r in member.roles]:
present_students.append(member)
self.assignments = get_assignments(present_students, edgelist = './network_game/test_edgelist.csv')
# Build a mapping from names to user objects so that people can refer to users
self.active_list = {x.name: x for x in self.assignments}
self.observers = [x for x in classroom.members if x not in self.assignments]
if self.assignments is not None:
for student in self.assignments:
await student.send(f"You are allowed to talk to:")
for neighbor in self.assignments[student]['neighbors']:
await student.send(f"{neighbor.mention}")
await student.send(f"You have these resources: {self.assignments[student]['has']}.")
await student.send(f"You need: {self.assignments[student]['needs']}.")
else:
for student in present_students:
await student.send("Not enough students to play")
if message.content.startswith('$send'):
try:
_, resource, u_to = message.content.split(' ')
except:
await message.author.send("Badly formed command. It has to be '$send resource @user'")
if u_to not in self.active_list:
await message.author.send(f"I can't find {u_to} in the list of users. Make sure the command is formatted as $send resource @user")
else:
u_to = self.active_list[u_to]
gave_resource = self.give_resource(resource, message.author, u_to)
if gave_resource == True:
finished = self.is_finished(u_to)
await message.author.send(f"{resource} sent to {u_to}")
await message.author.send(f"You now have {self.assignments[message.author]['has']} and you need {self.assignments[message.author]['needs']}")
if finished:
await u_to.send("You have everything you need! Well done! You can keep passing resources and talking with your 'neighbors' if you like. Just make sure to keep the resources that you need!")
else:
await u_to.send(f"You now have {self.assignments[u_to]['has']} and you need {self.assignments[u_to]['needs']}")
for o in self.observers:
await o.send(f"{message.author.name} sent {resource} to {u_to.name}")
if finished:
await o.send(f"{u_to.name} has everything they need!!!")
else:
await message.author.send(f"Resource not sent. Are you sure you have {resource}?")
def is_finished(self, u_to):
if set(self.assignments[u_to]['needs']) <= set(self.assignments[u_to]['has']):
return True
return False
def give_resource(self, resource, u_from, u_to):
if resource not in self.assignments[u_from]['has']:
return False
else:
self.assignments[u_from]['has'].remove(resource)
self.assignments[u_to]['has'].append(resource)
return True
def get_assignments(student_list,
edgelist = './network_game/edgelist.csv',
resource_prefix = './network_game/resources_'
):
def _add_connection(node1, node2):
node1 = int(node1)
node2 = int(node2)
for i in range(len(mapping[node1])):
s1 = mapping[node1][i]
s2 = mapping[node2][i]
if s1 in assignments:
assignments[s1]['neighbors'].append(s2)
else:
assignments[s1] = {'neighbors': [s2]}
def _add_resources():
fn = f"{resource_prefix}{group_size}.csv"
with open(fn, 'r') as f:
i = 1
for line in f.readlines():
resources = line.strip().split(',')
curr_students = mapping[i]
for s in curr_students:
assignments[s]['has'] = resources[:3]
assignments[s]['needs'] = resources[3:]
i += 1
assignments = {}
group_size = _get_group_size(len(student_list))
if len(student_list) < group_size:
return None
mapping = _make_mapping(student_list, group_size)
with open(edgelist, 'r') as f:
for line in f.readlines():
node1, node2 = line.strip().split(',')
if int(node2) <= group_size:
_add_connection(node1, node2)
_add_connection(node2, node1)
_add_resources()
return assignments
def _make_mapping(students, group_size):
random.shuffle(students)
n_observers = len(students) % group_size
mapping = {}
if n_observers > 0:
mapping['observers'] = students[-n_observers:]
for i, student in enumerate(students[-n_observers:]):
j = i % group_size
idx = j + 1
if idx in mapping:
mapping[idx].append(student)
else:
mapping[idx] = [student]
return mapping
def _get_group_size(n):
min_observers = None
for x in range(7,10):
observers = n % x
if min_observers is None or observers < min_observers:
best_fit = x
min_observers = observers
return best_fit
# this is necessary to get information about who is online # this is necessary to get information about who is online
intents = discord.Intents.default() intents = discord.Intents.default()
@@ -39,5 +192,4 @@ intents.members = True
intents.presences = True intents.presences = True
ccb = ColdCallBot(intents=intents) ccb = ColdCallBot(intents=intents)
ccb.run('CHANGEME') ccb.run(config.key)

3
update_student_info.sh Executable file
View File

@@ -0,0 +1,3 @@
wget "https://docs.google.com/spreadsheets/d/e/2PACX-1vTBboNsATMKYdQM3WsbcIvloqRlvR9ajSgZWyHN6Bci50wfiPBjibTxaF8XcMgAJycvKNdAfR9LBHbp/pub?gid=1302288840&single=true&output=csv" -O ./data/COM\ 495\ -\ Turning\ Data\ into\ Insights\ and\ Stories/student_information.tsv
wget "https://docs.google.com/spreadsheets/d/e/2PACX-1vSPL6jKD2rXQqac6O0pMb4JRxMAOM-EdPkZw2FuebbJHiZdAl4n5Df5RCyuxoHcwOcW0VbBevnec6b-/pub?gid=1575299441&single=true&output=csv" -O ./data/COM\ 411\ -\ Communication\ and\ Social\ Networks/student_information.tsv