From ba8620985e5f435beab2b6ae612e09b5e4acb18c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 03:25:32 -0600 Subject: [PATCH 001/189] FIXED: partnering algorithm, joining, starting, partnering PMs; ADDED: help command, leaving; TODO: clean up code (prompt), fix chat logging --- CONFIG.py | 1 + santa-bot.py | 208 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 153 insertions(+), 56 deletions(-) create mode 100755 CONFIG.py diff --git a/CONFIG.py b/CONFIG.py new file mode 100755 index 0000000..0622e4f --- /dev/null +++ b/CONFIG.py @@ -0,0 +1 @@ +discord_token = 'YOUR_TOKEN_HERE' diff --git a/santa-bot.py b/santa-bot.py index e2713f1..4803452 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -2,18 +2,21 @@ import asyncio import os.path import random +import copy import discord +import CONFIG from configobj import ConfigObj class Participant(object): """class defining a participant and info associated with them""" - def __init__(self, name, idstr, usrnum, address='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.address = address #string for user's address - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner + def __init__(self, name, discriminator, idstr, usrnum, address='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.address = address #string for user's address + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner def address_is_set(self): """returns whether the user has set an address""" @@ -31,21 +34,24 @@ def pref_is_set(self): #initialize config file try: - config = ConfigObj('./files/botdata.cfg') + config = ConfigObj('./files/botdata.cfg', file_error = True) except: os.mkdir('./files/') - config = ConfigObj('./files/botdata.cfg') - config['programData'] = {'exchange_started': False, 'discord_token': 'token'} + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False, 'discord_token': CONFIG.discord_token} config['members'] = {} config.write() #initialize data from config file +server = '' usr_list = [] -total_users = 0 +highest_key = 0 exchange_started = config['programData'].as_bool('exchange_started') for key in config['members']: - total_users = total_users + 1 - usr_list.append(Participant(key[0], key[1], total_users, key[2], key[3], key[4])) + data = config['members'][str(key)] + usr_list.append(Participant(data[0], data[1], data[2], data[3], data[4], data[5], data[6])) + highest_key = int(key) def user_is_participant(usrid, usrlist=usr_list): """Takes a discord user ID string and returns whether @@ -61,11 +67,38 @@ def get_participant_object(usrid, usrlist=usr_list): """takes a discord user ID string and list of participant objects, and returns the first participant object with matching id.""" - for person in usrlist: + for (index, person) in enumerate(usrlist): if person.idstr == usrid: - return person + return (index, person) break +def propose_partner_list(usrlist): + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + #candidates.remove(user) + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + #set up discord connection debug logging client_log = logging.getLogger('discord') client_log.setLevel(logging.DEBUG) @@ -80,12 +113,17 @@ def get_participant_object(usrid, usrlist=usr_list): @client.event async def on_message(message): #declare global vars + + global curr_server global usr_list - global total_users + global highest_key global exchange_started + + curr_server = message.server #write all messages to a chatlog - with open('./files/chat.log', 'a+') as chat_log: - chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') + if((message.content[0:1]) == "$$"): + with open('./files/chat.log', 'a+') as chat_log: + chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') #ignore messages from the bot itself if message.author == client.user: @@ -101,29 +139,50 @@ async def on_message(message): await client.send_message(message.channel, '`Error: Too late, the gift exchange is already in progress.`') else: #initialize instance of participant class for the author - usr_list.append(Participant(message.author.name, message.author.id, total_users)) + highest_key = highest_key + 1 + usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) #write details of the class instance to config and increment total_users - total_users = total_users + 1 - config['members'][str(total_users)] = [message.author.name, message.author.id, total_users, '', '', ''] + config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] config.write() #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + ' Has been added to the OfficialFam Secret Santa exchange!') - await client.send_message(message.author, 'Please input your mailing address so your secret Santa can send you something!\n' + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) + await client.send_message(message.author, 'Please input your mailing address so your Secret Santa can send you something!\n' + 'Use `$$setaddress` to set your mailing adress\n' - + 'Use `$$setpreference` to set gift preferences for your secret santa') + + 'Use `$$setprefs` to set gift preferences for your secret santa') + + elif message.content.startswith('$$leave'): + if user_is_participant(message.author.id): + print(usr_list) + (index, user) = get_participant_object(message.author.id) + #del usr_list[index] + print(user) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await client.send_message(message.channel, "You're not currently a member of the secret santa") #accept address of participants elif message.content.startswith('$$setaddress'): #check if author has joined the exchange yet if user_is_participant(message.author.id): #add the input to the value in the user's class instance - user = get_participant_object(message.author.id) - user.address = message.content.replace('$$setaddress', '', 1) - print(user.address) + (index, user) = get_participant_object(message.author.id) + user.address = message.content.replace('$$setaddress ', '', 1) #save to config file - config['members'][str(user.usrnum)][3] = user.address + config['members'][str(user.usrnum)][4] = user.address config.write() + await client.delete_message(message) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.delete_message(message) + + elif message.content.startswith('$$getaddress'): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + await client.send_message(message.author, "Current address(es): " + str(user.address)) else: await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') @@ -132,18 +191,27 @@ async def on_message(message): #check if author has joined the exchange yet if user_is_participant(message.author.id): #add the input to the value in the user's class instance - user = get_participant_object(message.author.id) - user.preferences = message.content.replace('$$setpref', '', 1) + (index, user) = get_participant_object(message.author.id) + user.preferences = message.content.replace('$$setprefs ', '', 1) #save to config file - config['members'][str(user.usrnum)][4] = user.preferences + config['members'][str(user.usrnum)][5] = user.preferences config.write() + await client.delete_message(message) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.delete_message(message) + + elif message.content.startswith('$$getprefs'): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) else: await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') #command for admin to begin the secret santa partner assignmenet elif message.content.startswith('$$start'): #only allow people with admin permissions to run - if message.author.top_role == message.server.role_heirarchy[0]: + if message.author.top_role == message.server.role_hierarchy[0]: #first ensure all users have all info submitted all_fields_complete = True for user in usr_list: @@ -156,64 +224,73 @@ async def on_message(message): #select a random partner for each participant if above loop found no empty values if all_fields_complete: - partners = usr_list - for user in usr_list: - candidates = partners - candidates.remove(user) - partner = candidates[random.randint(0, len(candidates) - 1)] - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - #save to config file - config['users'][str(user.usrnum)][5] = user.partnerid + print("proposing a list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + #save to config file + print("list passed") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][6] = user.partnerid # update config file config.write() - #tell participants who their partner is - await client.send_message(user, partner.name + partner.idstr + 'Is your secret santa partner! Now pick out a gift for them and send it to them!') - await client.send_message(user, 'Their mailing address is ' + partner.address) - await client.send_message(user, 'Here are their gift preferences:') + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Now pick out a gift for them and send it to them!' + message_pt2 = 'Their mailing address is: ' + partner.address + message_pt3 = 'Here are their gift preferences: ' + partner.preferences + santa_message = message_pt1 + '\n' + message_pt2 + '\n' + message_pt3 + await client.send_message(this_user, santa_message) #set exchange_started + assoc. cfg value to True exchange_started = True config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") else: await client.send_message(message.channel, '`Error: you do not have permission to do this.`') #allows a way to exit the bot elif message.content.startswith('$$shutdown') and not message.channel.is_private: #Fonly allow ppl with admin permissions to run - if message.author.top_role == message.server.role_heirarchy[0]: + if message.author.top_role == message.server.role_hierarchy[0]: await client.send_message(message.channel, 'Curse your sudden but inevitable betrayal!') + client.close() raise KeyboardInterrupt else: await client.send_message(message.channel, '`Error: you do not have permission to do this.`') #lists off all participant names and id's elif message.content.startswith('$$listparticipants'): - if total_users == 0: + if highest_key == 0: await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') else: msg = '```The following people are signed up for the secret santa exchange:\n' for user in usr_list: - msg = msg + user.name + user.idstr + '\n' + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' msg = msg + 'Use `$$join` to enter the exchange.```' await client.send_message(message.channel, msg) #lists total number of participants elif message.content.startswith('$$totalparticipants'): - if total_users == 0: + if highest_key == 0: await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') - elif total_users == 1: + elif highest_key == 1: await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `$$join` to enter the exchange.') else: - await client.send_message(message.channel, 'A total of ' + total_users + ' users have joined the secret santa exchange so far. Use `$$join` to enter the exchange.') + await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `$$join` to enter the exchange.') #allows a user to have the details of their partner restated elif message.content.startswith('$$partnerinfo'): if exchange_started and user_is_participant(message.author.id): - userobj = get_participant_object(message.author.id) - partnerobj = get_participant_object(userobj.partnerid) - msg = 'Your partner is ' + user.partner + user.partnerid + '\n' + (usr_index, user) = get_participant_object(message.author.id) + (partner_index, partnerobj) = get_participant_object(user.partnerid) + msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' msg = msg + 'Their mailing address is ' + partnerobj.address + '\n' msg = msg + 'their gift preference is as follows:\n' msg = msg + partnerobj.preferences @@ -223,6 +300,24 @@ async def on_message(message): else: await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') + elif message.content.startswith('$$help'): + c_join = "`$$join` = join the Secret Santa" + c_leave = "`$$leave` = leave the Secret Santa" + c_setaddress = "`$$setaddress [mailing address/wishlist URL]` = set your address (replaces current)" + c_getaddress = "`$$getaddress` = bot will PM you your current address" + c_setprefs = "`$$setprefs [specific preferences, the things you like]` = set preferences (replaces current)" + c_getprefs = "`$$getprefs` = bot will PM you your current preferences" + c_listparticipants = "`$$listparticipants` = get the current participants" + c_totalparticipants = "`$$totalparticipants` = get the total number of participants" + c_partnerinfo = "`$$partnerinfo` = be DM'd your partner's information" + c_start = "`$$start` **(admin only)** = assign Secret Santa partners" + c_shutdown = "`$$shutdown` **(admin only)** = end Secret Santa" + command_list = [c_join, c_leave, c_setaddress, c_setprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_shutdown] + command_string = '' + for command in command_list: + command_string = command_string + ("{0}\n".format(command)) + await client.send_message(message.channel, command_string) + @client.event async def on_ready(): """print message when client is connected""" @@ -231,5 +326,6 @@ async def on_ready(): print(client.user.id) print('------') + #event loop and discord initiation client.run(config['programData']['discord_token']) From 9998d19f87398569550bc216ee4c121eeebf7aec Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 04:10:25 -0600 Subject: [PATCH 002/189] Added ping-pong --- .gitignore | 1 + santa-bot.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 17f73ab..9a69412 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.cfg /.vscode .pylintrc +./CONFIG.py \ No newline at end of file diff --git a/santa-bot.py b/santa-bot.py index 4803452..803adba 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -3,6 +3,7 @@ import os.path import random import copy +import time import discord import CONFIG from configobj import ConfigObj @@ -318,6 +319,10 @@ async def on_message(message): command_string = command_string + ("{0}\n".format(command)) await client.send_message(message.channel, command_string) + elif message.content.startswith('$$ping'): + """ Pong! """ + await client.send_message(message.channel, "Pong!") + @client.event async def on_ready(): """print message when client is connected""" From f860a8592235626198637f405c31c7f09de7e7a9 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 12:19:47 -0600 Subject: [PATCH 003/189] adding pause, end; revising stop_bot --- CONFIG.py | 3 +- Procfile | 1 + requirements.txt | 0 santa-bot.py | 697 ++++++++++++++++++++++++----------------------- 4 files changed, 364 insertions(+), 337 deletions(-) create mode 100755 Procfile create mode 100755 requirements.txt diff --git a/CONFIG.py b/CONFIG.py index 0622e4f..e93baae 100755 --- a/CONFIG.py +++ b/CONFIG.py @@ -1 +1,2 @@ -discord_token = 'YOUR_TOKEN_HERE' +discord_token = YOUR_TOKEN_HERE +bot_owner = BOT_OWNER_ID_HERE diff --git a/Procfile b/Procfile new file mode 100755 index 0000000..8601992 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python santa-bot.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..e69de29 diff --git a/santa-bot.py b/santa-bot.py index 803adba..661b6c4 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,336 +1,361 @@ -import logging -import asyncio -import os.path -import random -import copy -import time -import discord -import CONFIG -from configobj import ConfigObj - -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, address='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.address = address #string for user's address - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def address_is_set(self): - """returns whether the user has set an address""" - if self.address == '': - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if self.preferences == '': - return False - else: - return True - -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False, 'discord_token': CONFIG.discord_token} - config['members'] = {} - config.write() - -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr_list.append(Participant(data[0], data[1], data[2], data[3], data[4], data[5], data[6])) - highest_key = int(key) - -def user_is_participant(usrid, usrlist=usr_list): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - result = False - for person in usrlist: - if person.idstr == usrid: - result = True - break - return result - -def get_participant_object(usrid, usrlist=usr_list): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if person.idstr == usrid: - return (index, person) - break - -def propose_partner_list(usrlist): - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - #candidates.remove(user) - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - -#initialize client class instance -client = discord.Client() - -#handler for all on_message events -@client.event -async def on_message(message): - #declare global vars - - global curr_server - global usr_list - global highest_key - global exchange_started - - curr_server = message.server - #write all messages to a chatlog - if((message.content[0:1]) == "$$"): - with open('./files/chat.log', 'a+') as chat_log: - chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') - - #ignore messages from the bot itself - if message.author == client.user: - return - - #event for a user joining the secret santa - elif message.content.startswith('$$join'): - #check if message author has already joined - if user_is_participant(message.author.id): - await client.send_message(message.channel, '`Error: You have already joined.`') - #check if the exchange has already started - elif exchange_started: - await client.send_message(message.channel, '`Error: Too late, the gift exchange is already in progress.`') - else: - #initialize instance of participant class for the author - highest_key = highest_key + 1 - usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) - #write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] - config.write() - - #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) - await client.send_message(message.author, 'Please input your mailing address so your Secret Santa can send you something!\n' - + 'Use `$$setaddress` to set your mailing adress\n' - + 'Use `$$setprefs` to set gift preferences for your secret santa') - - elif message.content.startswith('$$leave'): - if user_is_participant(message.author.id): - print(usr_list) - (index, user) = get_participant_object(message.author.id) - #del usr_list[index] - print(user) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) - else: - await client.send_message(message.channel, "You're not currently a member of the secret santa") - - #accept address of participants - elif message.content.startswith('$$setaddress'): - #check if author has joined the exchange yet - if user_is_participant(message.author.id): - #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id) - user.address = message.content.replace('$$setaddress ', '', 1) - #save to config file - config['members'][str(user.usrnum)][4] = user.address - config.write() - await client.delete_message(message) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') - await client.delete_message(message) - - elif message.content.startswith('$$getaddress'): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) - await client.send_message(message.author, "Current address(es): " + str(user.address)) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') - - #accept gift preferences of participants - elif message.content.startswith('$$setprefs'): - #check if author has joined the exchange yet - if user_is_participant(message.author.id): - #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id) - user.preferences = message.content.replace('$$setprefs ', '', 1) - #save to config file - config['members'][str(user.usrnum)][5] = user.preferences - config.write() - await client.delete_message(message) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') - await client.delete_message(message) - - elif message.content.startswith('$$getprefs'): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) - await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') - - #command for admin to begin the secret santa partner assignmenet - elif message.content.startswith('$$start'): - #only allow people with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.address_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing address or gift preferences.`') - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - - #select a random partner for each participant if above loop found no empty values - if all_fields_complete: - print("proposing a list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - #save to config file - print("list passed") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][6] = user.partnerid # update config file - config.write() - #tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Now pick out a gift for them and send it to them!' - message_pt2 = 'Their mailing address is: ' + partner.address - message_pt3 = 'Here are their gift preferences: ' + partner.preferences - santa_message = message_pt1 + '\n' + message_pt2 + '\n' + message_pt3 - await client.send_message(this_user, santa_message) - #set exchange_started + assoc. cfg value to True - exchange_started = True - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - - #allows a way to exit the bot - elif message.content.startswith('$$shutdown') and not message.channel.is_private: - #Fonly allow ppl with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - await client.send_message(message.channel, 'Curse your sudden but inevitable betrayal!') - client.close() - raise KeyboardInterrupt - else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - - #lists off all participant names and id's - elif message.content.startswith('$$listparticipants'): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') - else: - msg = '```The following people are signed up for the secret santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `$$join` to enter the exchange.```' - await client.send_message(message.channel, msg) - - #lists total number of participants - elif message.content.startswith('$$totalparticipants'): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') - elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `$$join` to enter the exchange.') - else: - await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `$$join` to enter the exchange.') - - #allows a user to have the details of their partner restated - elif message.content.startswith('$$partnerinfo'): - if exchange_started and user_is_participant(message.author.id): - (usr_index, user) = get_participant_object(message.author.id) - (partner_index, partnerobj) = get_participant_object(user.partnerid) - msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' - msg = msg + 'Their mailing address is ' + partnerobj.address + '\n' - msg = msg + 'their gift preference is as follows:\n' - msg = msg + partnerobj.preferences - await client.send_message(message.author, msg) - elif exchange_started: - await client.send_message(message.channel, '`Error: partners have not been assigned yet.`') - else: - await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') - - elif message.content.startswith('$$help'): - c_join = "`$$join` = join the Secret Santa" - c_leave = "`$$leave` = leave the Secret Santa" - c_setaddress = "`$$setaddress [mailing address/wishlist URL]` = set your address (replaces current)" - c_getaddress = "`$$getaddress` = bot will PM you your current address" - c_setprefs = "`$$setprefs [specific preferences, the things you like]` = set preferences (replaces current)" - c_getprefs = "`$$getprefs` = bot will PM you your current preferences" - c_listparticipants = "`$$listparticipants` = get the current participants" - c_totalparticipants = "`$$totalparticipants` = get the total number of participants" - c_partnerinfo = "`$$partnerinfo` = be DM'd your partner's information" - c_start = "`$$start` **(admin only)** = assign Secret Santa partners" - c_shutdown = "`$$shutdown` **(admin only)** = end Secret Santa" - command_list = [c_join, c_leave, c_setaddress, c_setprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_shutdown] - command_string = '' - for command in command_list: - command_string = command_string + ("{0}\n".format(command)) - await client.send_message(message.channel, command_string) - - elif message.content.startswith('$$ping'): - """ Pong! """ - await client.send_message(message.channel, "Pong!") - -@client.event -async def on_ready(): - """print message when client is connected""" - print('Logged in as') - print(client.user.name) - print(client.user.id) - print('------') - - -#event loop and discord initiation -client.run(config['programData']['discord_token']) +import logging +import asyncio +import os.path +import random +import copy +import discord +import CONFIG +from configobj import ConfigObj + +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, address='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.address = address #string for user's address + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def address_is_set(self): + """returns whether the user has set an address""" + if self.address == '': + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if self.preferences == '': + return False + else: + return True + +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False, 'discord_token': CONFIG.discord_token} + config['members'] = {} + config.write() + +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr_list.append(Participant(data[0], data[1], data[2], data[3], data[4], data[5], data[6])) + highest_key = int(key) + +def user_is_participant(usrid, usrlist=usr_list): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + result = False + for person in usrlist: + if person.idstr == usrid: + result = True + break + return result + +def get_participant_object(usrid, usrlist=usr_list): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if person.idstr == usrid: + return (index, person) + break + +def propose_partner_list(usrlist): + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + #candidates.remove(user) + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + +#initialize client class instance +client = discord.Client() + +#handler for all on_message events +@client.event +async def on_message(message): + #declare global vars + + global curr_server + global usr_list + global highest_key + global exchange_started + + curr_server = message.server + #write all messages to a chatlog + if((message.content[0:1]) == "$$"): + with open('./files/chat.log', 'a+') as chat_log: + chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') + + #ignore messages from the bot itself + if message.author == client.user: + return + + #event for a user joining the secret santa + elif message.content.startswith('$$join'): + #check if message author has already joined + if user_is_participant(message.author.id): + await client.send_message(message.channel, '`Error: You have already joined.`') + #check if the exchange has already started + elif exchange_started: + await client.send_message(message.channel, '`Error: Too late, the gift exchange is already in progress.`') + else: + #initialize instance of participant class for the author + highest_key = highest_key + 1 + usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) + #write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] + config.write() + + #prompt user about inputting info + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) + await client.send_message(message.author, 'Please input your mailing address so your Secret Santa can send you something!\n' + + 'Use `$$setaddress` to set your mailing adress\n' + + 'Use `$$setprefs` to set gift preferences for your secret santa') + + elif message.content.startswith('$$leave'): + if user_is_participant(message.author.id): + print(usr_list) + (index, user) = get_participant_object(message.author.id) + #del usr_list[index] + print(user) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await client.send_message(message.channel, "You're not currently a member of the secret santa") + + #accept address of participants + elif message.content.startswith('$$setaddress'): + #check if author has joined the exchange yet + if user_is_participant(message.author.id): + #add the input to the value in the user's class instance + (index, user) = get_participant_object(message.author.id) + user.address = message.content.replace('$$setaddress ', '', 1) + #save to config file + config['members'][str(user.usrnum)][4] = user.address + config.write() + await client.delete_message(message) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.delete_message(message) + + elif message.content.startswith('$$getaddress'): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + await client.send_message(message.author, "Current address(es): " + str(user.address)) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + + #accept gift preferences of participants + elif message.content.startswith('$$setprefs'): + #check if author has joined the exchange yet + if user_is_participant(message.author.id): + #add the input to the value in the user's class instance + (index, user) = get_participant_object(message.author.id) + user.preferences = message.content.replace('$$setprefs ', '', 1) + #save to config file + config['members'][str(user.usrnum)][5] = user.preferences + config.write() + await client.delete_message(message) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.delete_message(message) + + elif message.content.startswith('$$getprefs'): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + + #command for admin to begin the secret santa partner assignmenet + elif message.content.startswith('$$start'): + #only allow people with admin permissions to run + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.address_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing address or gift preferences.`') + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + + #select a random partner for each participant if above loop found no empty values + if all_fields_complete: + print("proposing a list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + #save to config file + print("list passed") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][6] = user.partnerid # update config file + config.write() + #tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Now pick out a gift for them and send it to them!' + message_pt2 = 'Their mailing address is: ' + partner.address + message_pt3 = 'Here are their gift preferences: ' + partner.preferences + santa_message = message_pt1 + '\n' + message_pt2 + '\n' + message_pt3 + await client.send_message(this_user, santa_message) + #set exchange_started + assoc. cfg value to True + exchange_started = True + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + else: + await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + + #allows a way to stop the bot + elif message.content.startswith('$$stop_bot') and not message.channel.is_private: + #Fonly allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]) & (message.author.id == CONFIG.bot_owner): + exchange_started = False + config['programData']['exchange_started'] = False + await client.send_message(message.channel, 'Shutting down Secret Santa bot') + client.close() + raise KeyboardInterrupt + else: + await client.send_message(message.channel, "Only the bot owner can do that!") + + # allows a way to restart the secret santa + elif message.content.startswith('$$pause'): + #Fonly allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + await client.send_message(message.channel, 'Secret Santa paused') + else: + await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') + + #allows a way to end the secret santa + elif message.content.startswith('$$end') and not message.channel.is_private: + #Fonly allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + highest_key = 0 + usr_list.clear() + config['members'].clear() + config.write() + await client.send_message(message.channel, 'Secret Santa ended') + else: + await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + + #lists off all participant names and id's + elif message.content.startswith('$$listparticipants'): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') + else: + msg = '```The following people are signed up for the secret santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' + msg = msg + 'Use `$$join` to enter the exchange.```' + await client.send_message(message.channel, msg) + + #lists total number of participants + elif message.content.startswith('$$totalparticipants'): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') + elif highest_key == 1: + await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `$$join` to enter the exchange.') + else: + await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `$$join` to enter the exchange.') + + #allows a user to have the details of their partner restated + elif message.content.startswith('$$partnerinfo'): + if exchange_started and user_is_participant(message.author.id): + (usr_index, user) = get_participant_object(message.author.id) + (partner_index, partnerobj) = get_participant_object(user.partnerid) + msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' + msg = msg + 'Their mailing address is ' + partnerobj.address + '\n' + msg = msg + 'their gift preference is as follows:\n' + msg = msg + partnerobj.preferences + await client.send_message(message.author, msg) + elif exchange_started: + await client.send_message(message.channel, '`Error: partners have not been assigned yet.`') + else: + await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') + + elif message.content.startswith('$$help'): + c_join = "`$$join` = join the Secret Santa" + c_leave = "`$$leave` = leave the Secret Santa" + c_setaddress = "`$$setaddress [mailing address/wishlist URL]` = set your address (replaces current)" + c_getaddress = "`$$getaddress` = bot will PM you your current address" + c_setprefs = "`$$setprefs [specific preferences, the things you like]` = set preferences (replaces current)" + c_getprefs = "`$$getprefs` = bot will PM you your current preferences" + c_listparticipants = "`$$listparticipants` = get the current participants" + c_totalparticipants = "`$$totalparticipants` = get the total number of participants" + c_partnerinfo = "`$$partnerinfo` = be DM'd your partner's information" + c_start = "`$$start` **(admin only)** = assign Secret Santa partners" + c_shutdown = "`$$shutdown` **(admin only)** = end Secret Santa" + command_list = [c_join, c_leave, c_setaddress, c_setprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_shutdown] + command_string = '' + for command in command_list: + command_string = command_string + ("{0}\n".format(command)) + await client.send_message(message.channel, command_string) + + elif message.content.startswith('$$ping'): + """ Pong! """ + await client.send_message(message.channel, "Pong!") + +@client.event +async def on_ready(): + """print message when client is connected""" + print('Logged in as') + print(client.user.name) + print(client.user.id) + print('------') + + +#event loop and discord initiation +client.run(config['programData']['discord_token']) From 54eedb5961f7418356f262ff334fd485b9025bea Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 12:25:00 -0600 Subject: [PATCH 004/189] undid heroku portion of previous commit --- Procfile | 1 - requirements.txt | 0 2 files changed, 1 deletion(-) delete mode 100755 Procfile delete mode 100755 requirements.txt diff --git a/Procfile b/Procfile deleted file mode 100755 index 8601992..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -worker: python santa-bot.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100755 index e69de29..0000000 From c63ca1582b220f71983061217e0ce81dffd465be Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 12:26:44 -0600 Subject: [PATCH 005/189] quality of life --- .gitignore | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 9a69412..f96140b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -*.log -*.cfg -/.vscode -.pylintrc -./CONFIG.py \ No newline at end of file +*.log +*.cfg +/.vscode +.pylintrc +CONFIG.py +/__pycache__ \ No newline at end of file From 701ad614b03ac33dd1e5a14d9bb17d0377657049 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 13:48:42 -0600 Subject: [PATCH 006/189] so master can merge into heroku just fine --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f96140b..5c4e228 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ /.vscode .pylintrc CONFIG.py -/__pycache__ \ No newline at end of file +/__pycache__ +requirements.txt +Procfile From c3023c009b02d3c2039dda21ce87fd4909cf3262 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 17:11:10 -0600 Subject: [PATCH 007/189] Changed prefix, DM commands allowed --- santa-bot.py | 160 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 46 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 661b6c4..10130ce 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -99,6 +99,14 @@ def partners_are_valid(usrlist): result = result & (user.partnerid != '') & (user.partnerid != user.idstr) return result +## checks if the user list changed during a pause - matched if the user does not have a partner +def usr_list_changed_during_pause(usrlist=usr_list): + result = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + result = result & has_match # figures out if all users have a match + return (not result) ## if not all users have a match + #set up discord connection debug logging client_log = logging.getLogger('discord') client_log.setLevel(logging.DEBUG) @@ -119,18 +127,22 @@ async def on_message(message): global highest_key global exchange_started + message_split = message.content.split() curr_server = message.server #write all messages to a chatlog - if((message.content[0:1]) == "$$"): + if((message.content[0:2]) == "s!"): + print(message.content) with open('./files/chat.log', 'a+') as chat_log: - chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') + #chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') + pass #ignore messages from the bot itself if message.author == client.user: return #event for a user joining the secret santa - elif message.content.startswith('$$join'): + #elif message.content.startswith('s!join'): + elif(message_split[0] == "s!join"): #check if message author has already joined if user_is_participant(message.author.id): await client.send_message(message.channel, '`Error: You have already joined.`') @@ -148,10 +160,11 @@ async def on_message(message): #prompt user about inputting info await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) await client.send_message(message.author, 'Please input your mailing address so your Secret Santa can send you something!\n' - + 'Use `$$setaddress` to set your mailing adress\n' - + 'Use `$$setprefs` to set gift preferences for your secret santa') + + 'Use `s!setaddress` to set your mailing adress\n' + + 'Use `s!setprefs` to set gift preferences for your secret santa') - elif message.content.startswith('$$leave'): + #elif message.content.startswith('s!leave'): + elif(message_split[0] == "s!leave"): if user_is_participant(message.author.id): print(usr_list) (index, user) = get_participant_object(message.author.id) @@ -165,51 +178,64 @@ async def on_message(message): await client.send_message(message.channel, "You're not currently a member of the secret santa") #accept address of participants - elif message.content.startswith('$$setaddress'): + #elif message.content.startswith('s!setaddress'): + elif(message_split[0] == "s!setaddress"): #check if author has joined the exchange yet if user_is_participant(message.author.id): #add the input to the value in the user's class instance (index, user) = get_participant_object(message.author.id) - user.address = message.content.replace('$$setaddress ', '', 1) + user.address = message.content.replace('s!setaddress ', '', 1) #save to config file config['members'][str(user.usrnum)][4] = user.address config.write() - await client.delete_message(message) + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + await client.send_message(message.author, "New address: {0}".format(user.preferences)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) - elif message.content.startswith('$$getaddress'): + #elif message.content.startswith('s!getaddress'): + elif(message_split[0] == "s!getaddress"): if user_is_participant(message.author.id): (index, user) = get_participant_object(message.author.id) await client.send_message(message.author, "Current address(es): " + str(user.address)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') #accept gift preferences of participants - elif message.content.startswith('$$setprefs'): + #elif message.content.startswith('s!setprefs'): + elif(message_split[0] == "s!setprefs"): #check if author has joined the exchange yet if user_is_participant(message.author.id): #add the input to the value in the user's class instance (index, user) = get_participant_object(message.author.id) - user.preferences = message.content.replace('$$setprefs ', '', 1) + user.preferences = message.content.replace('s!setprefs ', '', 1) #save to config file config['members'][str(user.usrnum)][5] = user.preferences config.write() - await client.delete_message(message) + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) - elif message.content.startswith('$$getprefs'): + #elif message.content.startswith('s!getprefs'): + elif(message_split[0] == "s!getprefs"): if user_is_participant(message.author.id): (index, user) = get_participant_object(message.author.id) await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `$$join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') #command for admin to begin the secret santa partner assignmenet - elif message.content.startswith('$$start'): + #elif message.content.startswith('s!start'): + elif(message_split[0] == "s!start"): #only allow people with admin permissions to run if message.author.top_role == message.server.role_hierarchy[0]: #first ensure all users have all info submitted @@ -254,12 +280,37 @@ async def on_message(message): else: await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + #command allows you to restart without rematching if no change was made while s!paused + elif(message_split[0] == "s!restart"): + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.address_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing address or gift preferences.`') + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + list_changed = usr_list_changed_during_pause() + if(list_changed): + await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") + else: + exchange_started = True + config['programData']['exchange_started'] = True + config.write() + await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") + else: + await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + #allows a way to stop the bot - elif message.content.startswith('$$stop_bot') and not message.channel.is_private: + #elif message.content.startswith('s!shutdown') and not message.channel.is_private: + elif(message_split[0] == "s!shutdown") and not message.channel.is_private: #Fonly allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]) & (message.author.id == CONFIG.bot_owner): exchange_started = False config['programData']['exchange_started'] = False + config.write() await client.send_message(message.channel, 'Shutting down Secret Santa bot') client.close() raise KeyboardInterrupt @@ -267,17 +318,20 @@ async def on_message(message): await client.send_message(message.channel, "Only the bot owner can do that!") # allows a way to restart the secret santa - elif message.content.startswith('$$pause'): + #elif message.content.startswith('s!pause'): + elif(message_split[0] == "s!pause"): #Fonly allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): exchange_started = False config['programData']['exchange_started'] = False - await client.send_message(message.channel, 'Secret Santa paused') + config.write() + await client.send_message(message.channel, 'Secret Santa has been paused.') else: await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') #allows a way to end the secret santa - elif message.content.startswith('$$end') and not message.channel.is_private: + #elif message.content.startswith('s!end') and not message.channel.is_private: + elif(message_split[0] == "s!end") and not message.channel.is_private: #Fonly allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): exchange_started = False @@ -291,28 +345,31 @@ async def on_message(message): await client.send_message(message.channel, '`Error: you do not have permission to do this.`') #lists off all participant names and id's - elif message.content.startswith('$$listparticipants'): + #elif message.content.startswith('s!listparticipants'): + elif(message_split[0] == "s!listparticipants"): if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') + await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') else: msg = '```The following people are signed up for the secret santa exchange:\n' for user in usr_list: this_user = discord.User(user = user.name, id = user.idstr) msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `$$join` to enter the exchange.```' + msg = msg + 'Use `s!join` to enter the exchange.```' await client.send_message(message.channel, msg) #lists total number of participants - elif message.content.startswith('$$totalparticipants'): + #elif message.content.startswith('s!totalparticipants'): + elif(message_split[0] == "s!totalparticipants"): if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `$$join` to enter the exchange.') + await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `$$join` to enter the exchange.') + await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `s!join` to enter the exchange.') else: - await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `$$join` to enter the exchange.') + await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `s!join` to enter the exchange.') #allows a user to have the details of their partner restated - elif message.content.startswith('$$partnerinfo'): + #elif message.content.startswith('s!partnerinfo'): + elif(message_split[0] == "s!partnerinfo"): if exchange_started and user_is_participant(message.author.id): (usr_index, user) = get_participant_object(message.author.id) (partner_index, partnerobj) = get_participant_object(user.partnerid) @@ -326,28 +383,39 @@ async def on_message(message): else: await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') - elif message.content.startswith('$$help'): - c_join = "`$$join` = join the Secret Santa" - c_leave = "`$$leave` = leave the Secret Santa" - c_setaddress = "`$$setaddress [mailing address/wishlist URL]` = set your address (replaces current)" - c_getaddress = "`$$getaddress` = bot will PM you your current address" - c_setprefs = "`$$setprefs [specific preferences, the things you like]` = set preferences (replaces current)" - c_getprefs = "`$$getprefs` = bot will PM you your current preferences" - c_listparticipants = "`$$listparticipants` = get the current participants" - c_totalparticipants = "`$$totalparticipants` = get the total number of participants" - c_partnerinfo = "`$$partnerinfo` = be DM'd your partner's information" - c_start = "`$$start` **(admin only)** = assign Secret Santa partners" - c_shutdown = "`$$shutdown` **(admin only)** = end Secret Santa" - command_list = [c_join, c_leave, c_setaddress, c_setprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_shutdown] + #elif message.content.startswith('s!help'): + elif(message_split[0] == "s!help"): + c_join = "`s!join` = join the Secret Santa" + c_leave = "`s!leave` = leave the Secret Santa" + c_setaddress = "`s!setaddress [mailing address/wishlist URL]` = set your address (replaces current)" + c_getaddress = "`s!getaddress` = bot will PM you your current address" + c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current)" + c_getprefs = "`s!getprefs` = bot will PM you your current preferences" + c_listparticipants = "`s!listparticipants` = get the current participants" + c_totalparticipants = "`s!totalparticipants` = get the total number of participants" + c_partnerinfo = "`s!partnerinfo` = be DM'd your partner's information" + c_start = "`s!start` **(admin only)** = assign Secret Santa partners" + c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" + c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" + c_end = "`s!end` **(admin only)** = end Secret Santa" + c_shutdown = "`s!shutdown` **(admin only)** = shut down Secret Santa bot" + c_ping = "`s!ping` = check if bot is alive" + command_list = [c_join, c_leave, c_setaddress, c_getaddress, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] command_string = '' for command in command_list: command_string = command_string + ("{0}\n".format(command)) await client.send_message(message.channel, command_string) - elif message.content.startswith('$$ping'): + #elif message.content.startswith('s!ping'): + elif(message_split[0] == "s!ping"): """ Pong! """ await client.send_message(message.channel, "Pong!") + #elif message.content.startswith('s!invite'): + elif(message_split[0] == "s!invite"): + link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" + await client.send_message(message.channel, "Bot invite link: {0}".format(link)) + @client.event async def on_ready(): """print message when client is connected""" @@ -358,4 +426,4 @@ async def on_ready(): #event loop and discord initiation -client.run(config['programData']['discord_token']) +client.run(config['programData']['discord_token']) \ No newline at end of file From c3c539041aa4eda222776a5354dce0de2609f93d Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 17:12:09 -0600 Subject: [PATCH 008/189] removed shutdown command --- santa-bot.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 10130ce..f2eff64 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -303,20 +303,6 @@ async def on_message(message): else: await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - #allows a way to stop the bot - #elif message.content.startswith('s!shutdown') and not message.channel.is_private: - elif(message_split[0] == "s!shutdown") and not message.channel.is_private: - #Fonly allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]) & (message.author.id == CONFIG.bot_owner): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - await client.send_message(message.channel, 'Shutting down Secret Santa bot') - client.close() - raise KeyboardInterrupt - else: - await client.send_message(message.channel, "Only the bot owner can do that!") - # allows a way to restart the secret santa #elif message.content.startswith('s!pause'): elif(message_split[0] == "s!pause"): @@ -398,7 +384,6 @@ async def on_message(message): c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" c_end = "`s!end` **(admin only)** = end Secret Santa" - c_shutdown = "`s!shutdown` **(admin only)** = shut down Secret Santa bot" c_ping = "`s!ping` = check if bot is alive" command_list = [c_join, c_leave, c_setaddress, c_getaddress, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] command_string = '' From 753537a6578463081428d564ac8fae3b16c184cc Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 18:10:31 -0600 Subject: [PATCH 009/189] code cleanup --- santa-bot.py | 86 ++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index f2eff64..5a66a75 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -9,18 +9,18 @@ class Participant(object): """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, address='', preferences='', partnerid=''): + def __init__(self, name, discriminator, idstr, usrnum, wishlist='', preferences='', partnerid=''): self.name = name #string containing name of user self.discriminator = discriminator #string containing discriminant of user self.idstr = idstr #string containing id of user self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.address = address #string for user's address + self.wishlist = wishlist #string for user's wishlist self.preferences = preferences #string for user's gift preferences self.partnerid = partnerid #string for id of partner - def address_is_set(self): - """returns whether the user has set an address""" - if self.address == '': + def wishlist_is_set(self): + """returns whether the user has set an wishlist""" + if self.wishlist == '': return False else: return True @@ -47,6 +47,7 @@ def pref_is_set(self): server = '' usr_list = [] highest_key = 0 +user_left = False exchange_started = config['programData'].as_bool('exchange_started') for key in config['members']: data = config['members'][str(key)] @@ -78,7 +79,6 @@ def propose_partner_list(usrlist): ## propose partner list for user in usr_list_copy: candidates = partners - #candidates.remove(user) partner = candidates[random.randint(0, len(candidates) - 1)] while(partner.idstr == user.idstr): partner = candidates[random.randint(0, len(candidates) - 1)] @@ -99,13 +99,14 @@ def partners_are_valid(usrlist): result = result & (user.partnerid != '') & (user.partnerid != user.idstr) return result -## checks if the user list changed during a pause - matched if the user does not have a partner +## checks if the user list changed during a pause def usr_list_changed_during_pause(usrlist=usr_list): - result = True + has_changed = True for user in usrlist: has_match = (not (str(user.partnerid) == "")) - result = result & has_match # figures out if all users have a match - return (not result) ## if not all users have a match + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not user_left) + return (not has_changed) ## if not all users have a match #set up discord connection debug logging client_log = logging.getLogger('discord') @@ -126,6 +127,7 @@ async def on_message(message): global usr_list global highest_key global exchange_started + global user_left message_split = message.content.split() curr_server = message.server @@ -141,7 +143,6 @@ async def on_message(message): return #event for a user joining the secret santa - #elif message.content.startswith('s!join'): elif(message_split[0] == "s!join"): #check if message author has already joined if user_is_participant(message.author.id): @@ -159,54 +160,50 @@ async def on_message(message): #prompt user about inputting info await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) - await client.send_message(message.author, 'Please input your mailing address so your Secret Santa can send you something!\n' - + 'Use `s!setaddress` to set your mailing adress\n' + await client.send_message(message.author, 'Please input your mailing wishlist so your Secret Santa can send you something!\n' + + 'Use `s!setwishlist` to set your mailing wishlist\n' + 'Use `s!setprefs` to set gift preferences for your secret santa') - #elif message.content.startswith('s!leave'): + #event for a user to leave the secret santa list elif(message_split[0] == "s!leave"): if user_is_participant(message.author.id): - print(usr_list) (index, user) = get_participant_object(message.author.id) - #del usr_list[index] - print(user) usr_list.remove(user) popped_user = config['members'].pop(str(user.usrnum)) config.write() + user_left = True await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) else: await client.send_message(message.channel, "You're not currently a member of the secret santa") - #accept address of participants - #elif message.content.startswith('s!setaddress'): - elif(message_split[0] == "s!setaddress"): + #accept wishlist of participants + elif(message_split[0] == "s!setwishlist"): #check if author has joined the exchange yet if user_is_participant(message.author.id): #add the input to the value in the user's class instance (index, user) = get_participant_object(message.author.id) - user.address = message.content.replace('s!setaddress ', '', 1) + user.wishlist = message.content.replace('s!setwishlist ', '', 1) #save to config file - config['members'][str(user.usrnum)][4] = user.address + config['members'][str(user.usrnum)][4] = user.wishlist config.write() if(message.channel.is_private): pass else: await client.delete_message(message) - await client.send_message(message.author, "New address: {0}".format(user.preferences)) + await client.send_message(message.author, "New wishlist: {0}".format(user.wishlist)) else: await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) - #elif message.content.startswith('s!getaddress'): - elif(message_split[0] == "s!getaddress"): + #elif message.content.startswith('s!getwishlist'): + elif(message_split[0] == "s!getwishlist"): if user_is_participant(message.author.id): (index, user) = get_participant_object(message.author.id) - await client.send_message(message.author, "Current address(es): " + str(user.address)) + await client.send_message(message.author, "Current wishlist(es): " + str(user.wishlist)) else: await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') #accept gift preferences of participants - #elif message.content.startswith('s!setprefs'): elif(message_split[0] == "s!setprefs"): #check if author has joined the exchange yet if user_is_participant(message.author.id): @@ -233,19 +230,18 @@ async def on_message(message): else: await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') - #command for admin to begin the secret santa partner assignmenet - #elif message.content.startswith('s!start'): + #command for admin to begin the secret santa partner assignment elif(message_split[0] == "s!start"): #only allow people with admin permissions to run if message.author.top_role == message.server.role_hierarchy[0]: #first ensure all users have all info submitted all_fields_complete = True for user in usr_list: - if user.address_is_set() and user.pref_is_set(): + if user.wishlist_is_set() and user.pref_is_set(): pass else: all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing address or gift preferences.`') + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist or gift preferences.`') await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') #select a random partner for each participant if above loop found no empty values @@ -266,8 +262,8 @@ async def on_message(message): #tell participants who their partner is this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Now pick out a gift for them and send it to them!' - message_pt2 = 'Their mailing address is: ' + partner.address + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Mosey on over to their wishlist(s) and pick out a gift! Remember to keep it in the $10-20 range.' + message_pt2 = 'Their wishlist can be found s: ' + partner.wishlist message_pt3 = 'Here are their gift preferences: ' + partner.preferences santa_message = message_pt1 + '\n' + message_pt2 + '\n' + message_pt3 await client.send_message(this_user, santa_message) @@ -286,11 +282,11 @@ async def on_message(message): #first ensure all users have all info submitted all_fields_complete = True for user in usr_list: - if user.address_is_set() and user.pref_is_set(): + if user.wishlist_is_set() and user.pref_is_set(): pass else: all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing address or gift preferences.`') + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist or gift preferences.`') await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') list_changed = usr_list_changed_during_pause() if(list_changed): @@ -304,9 +300,8 @@ async def on_message(message): await client.send_message(message.channel, '`Error: you do not have permission to do this.`') # allows a way to restart the secret santa - #elif message.content.startswith('s!pause'): elif(message_split[0] == "s!pause"): - #Fonly allow ppl with admin permissions to run + #only allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): exchange_started = False config['programData']['exchange_started'] = False @@ -316,9 +311,8 @@ async def on_message(message): await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') #allows a way to end the secret santa - #elif message.content.startswith('s!end') and not message.channel.is_private: elif(message_split[0] == "s!end") and not message.channel.is_private: - #Fonly allow ppl with admin permissions to run + #only allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): exchange_started = False config['programData']['exchange_started'] = False @@ -331,7 +325,6 @@ async def on_message(message): await client.send_message(message.channel, '`Error: you do not have permission to do this.`') #lists off all participant names and id's - #elif message.content.startswith('s!listparticipants'): elif(message_split[0] == "s!listparticipants"): if highest_key == 0: await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') @@ -344,7 +337,6 @@ async def on_message(message): await client.send_message(message.channel, msg) #lists total number of participants - #elif message.content.startswith('s!totalparticipants'): elif(message_split[0] == "s!totalparticipants"): if highest_key == 0: await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') @@ -354,13 +346,12 @@ async def on_message(message): await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `s!join` to enter the exchange.') #allows a user to have the details of their partner restated - #elif message.content.startswith('s!partnerinfo'): elif(message_split[0] == "s!partnerinfo"): if exchange_started and user_is_participant(message.author.id): (usr_index, user) = get_participant_object(message.author.id) (partner_index, partnerobj) = get_participant_object(user.partnerid) msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' - msg = msg + 'Their mailing address is ' + partnerobj.address + '\n' + msg = msg + 'Their mailing wishlist is ' + partnerobj.wishlist + '\n' msg = msg + 'their gift preference is as follows:\n' msg = msg + partnerobj.preferences await client.send_message(message.author, msg) @@ -373,8 +364,8 @@ async def on_message(message): elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" c_leave = "`s!leave` = leave the Secret Santa" - c_setaddress = "`s!setaddress [mailing address/wishlist URL]` = set your address (replaces current)" - c_getaddress = "`s!getaddress` = bot will PM you your current address" + c_setwishlist = "`s!setwishlist [mailing wishlist/wishlist URL]` = set your wishlist (replaces current)" + c_getwishlist = "`s!getwishlist` = bot will PM you your current wishlist" c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current)" c_getprefs = "`s!getprefs` = bot will PM you your current preferences" c_listparticipants = "`s!listparticipants` = get the current participants" @@ -385,7 +376,7 @@ async def on_message(message): c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" c_end = "`s!end` **(admin only)** = end Secret Santa" c_ping = "`s!ping` = check if bot is alive" - command_list = [c_join, c_leave, c_setaddress, c_getaddress, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] + command_list = [c_join, c_leave, c_setwishlist, c_getwishlist, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] command_string = '' for command in command_list: command_string = command_string + ("{0}\n".format(command)) @@ -401,6 +392,9 @@ async def on_message(message): link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" await client.send_message(message.channel, "Bot invite link: {0}".format(link)) + else: + await client.send_message(message.channel, "Command not found") + @client.event async def on_ready(): """print message when client is connected""" From 0ea70686bb10e04cfa166d4f4a46da82a7728d4f Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 18:13:56 -0600 Subject: [PATCH 010/189] checks if user left during pause --- santa-bot.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 5a66a75..ac24716 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -47,7 +47,8 @@ def pref_is_set(self): server = '' usr_list = [] highest_key = 0 -user_left = False +user_left_during_pause = False +is_paused = False exchange_started = config['programData'].as_bool('exchange_started') for key in config['members']: data = config['members'][str(key)] @@ -101,11 +102,15 @@ def partners_are_valid(usrlist): ## checks if the user list changed during a pause def usr_list_changed_during_pause(usrlist=usr_list): + if(user_left_during_pause):# there's probably a better boolean logic way but this is easy + user_left_during_pause = False # acknowledge + return True + has_changed = True for user in usrlist: has_match = (not (str(user.partnerid) == "")) has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not user_left) + has_changed = has_changed & (not user_left_during_pause) return (not has_changed) ## if not all users have a match #set up discord connection debug logging @@ -127,7 +132,8 @@ async def on_message(message): global usr_list global highest_key global exchange_started - global user_left + global user_left_during_pause + global is_paused message_split = message.content.split() curr_server = message.server @@ -171,7 +177,9 @@ async def on_message(message): usr_list.remove(user) popped_user = config['members'].pop(str(user.usrnum)) config.write() - user_left = True + if(is_paused): + is_paused = False + user_left_during_pause = True await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) else: await client.send_message(message.channel, "You're not currently a member of the secret santa") @@ -306,6 +314,7 @@ async def on_message(message): exchange_started = False config['programData']['exchange_started'] = False config.write() + is_paused = True await client.send_message(message.channel, 'Secret Santa has been paused.') else: await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') From 9b55fabf4a11336cb3dc6fe0c3a65edbad4fc9b0 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 17 Nov 2018 20:45:16 -0600 Subject: [PATCH 011/189] better command not found, some cleanup --- santa-bot.py | 493 +++++++++++++++++++++++++-------------------------- 1 file changed, 246 insertions(+), 247 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index ac24716..0f855b5 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -9,18 +9,18 @@ class Participant(object): """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlist='', preferences='', partnerid=''): + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): self.name = name #string containing name of user self.discriminator = discriminator #string containing discriminant of user self.idstr = idstr #string containing id of user self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlist = wishlist #string for user's wishlist + self.wishlisturl = wishlisturl #string for user's wishlisturl self.preferences = preferences #string for user's gift preferences self.partnerid = partnerid #string for id of partner - def wishlist_is_set(self): - """returns whether the user has set an wishlist""" - if self.wishlist == '': + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if self.wishlisturl == '': return False else: return True @@ -144,265 +144,264 @@ async def on_message(message): #chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') pass - #ignore messages from the bot itself - if message.author == client.user: - return - - #event for a user joining the secret santa - elif(message_split[0] == "s!join"): - #check if message author has already joined - if user_is_participant(message.author.id): - await client.send_message(message.channel, '`Error: You have already joined.`') - #check if the exchange has already started - elif exchange_started: - await client.send_message(message.channel, '`Error: Too late, the gift exchange is already in progress.`') - else: - #initialize instance of participant class for the author - highest_key = highest_key + 1 - usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) - #write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] - config.write() - - #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) - await client.send_message(message.author, 'Please input your mailing wishlist so your Secret Santa can send you something!\n' - + 'Use `s!setwishlist` to set your mailing wishlist\n' - + 'Use `s!setprefs` to set gift preferences for your secret santa') - - #event for a user to leave the secret santa list - elif(message_split[0] == "s!leave"): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - is_paused = False - user_left_during_pause = True - await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) - else: - await client.send_message(message.channel, "You're not currently a member of the secret santa") - - #accept wishlist of participants - elif(message_split[0] == "s!setwishlist"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id): - #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id) - user.wishlist = message.content.replace('s!setwishlist ', '', 1) - #save to config file - config['members'][str(user.usrnum)][4] = user.wishlist - config.write() - if(message.channel.is_private): - pass + #ignore messages from the bot itself + if message.author == client.user: + return + + #event for a user joining the secret santa + elif(message_split[0] == "s!join"): + #check if message author has already joined + if user_is_participant(message.author.id): + await client.send_message(message.channel, '`Error: You have already joined.`') + #check if the exchange has already started + elif exchange_started: + await client.send_message(message.channel, '`Error: Too late, the gift exchange is already in progress.`') else: - await client.delete_message(message) - await client.send_message(message.author, "New wishlist: {0}".format(user.wishlist)) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') - await client.delete_message(message) + #initialize instance of participant class for the author + highest_key = highest_key + 1 + usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) + #write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] + config.write() + + #prompt user about inputting info + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) + await client.send_message(message.author, 'Please input your wishlist URL and preferences (through DMs) so your Secret Santa can send you something!\n' + + 'Use `s!setwishlisturl` to set your wishlist URL\n' + + 'Use `s!setprefs` to set gift preferences for your secret santa. Put N/A if none.') - #elif message.content.startswith('s!getwishlist'): - elif(message_split[0] == "s!getwishlist"): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) - await client.send_message(message.author, "Current wishlist(es): " + str(user.wishlist)) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') - - #accept gift preferences of participants - elif(message_split[0] == "s!setprefs"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id): - #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id) - user.preferences = message.content.replace('s!setprefs ', '', 1) - #save to config file - config['members'][str(user.usrnum)][5] = user.preferences - config.write() - if(message.channel.is_private): - pass + #event for a user to leave the secret santa list + elif(message_split[0] == "s!leave"): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + is_paused = False + user_left_during_pause = True + await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await client.send_message(message.channel, "You're not currently a member of the secret santa") + + #accept wishlisturl of participants + elif(message_split[0] == "s!setwishlisturl"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id): + #add the input to the value in the user's class instance + (index, user) = get_participant_object(message.author.id) + user.wishlisturl = message.content.replace('s!setwishlisturl ', '', 1) + #save to config file + config['members'][str(user.usrnum)][4] = user.wishlisturl + config.write() + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + await client.send_message(message.author, "New wishlist URL: {0}".format(user.wishlisturl)) else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) - await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') - await client.delete_message(message) - - #elif message.content.startswith('s!getprefs'): - elif(message_split[0] == "s!getprefs"): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) - await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) - else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') - - #command for admin to begin the secret santa partner assignment - elif(message_split[0] == "s!start"): - #only allow people with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlist_is_set() and user.pref_is_set(): + + # get current wishlist URL(s) + elif(message_split[0] == "s!getwishlisturl"): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + await client.send_message(message.author, "Current wishlist URL(s): " + str(user.wishlisturl)) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + + #accept gift preferences of participants + elif(message_split[0] == "s!setprefs"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id): + #add the input to the value in the user's class instance + (index, user) = get_participant_object(message.author.id) + user.preferences = message.content.replace('s!setprefs ', '', 1) + #save to config file + config['members'][str(user.usrnum)][5] = user.preferences + config.write() + if(message.channel.is_private): pass else: - all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist or gift preferences.`') - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - - #select a random partner for each participant if above loop found no empty values - if all_fields_complete: - print("proposing a list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid): + await client.delete_message(message) + await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + await client.delete_message(message) + + #get current preferences + elif(message_split[0] == "s!getprefs"): + if user_is_participant(message.author.id): + (index, user) = get_participant_object(message.author.id) + await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) + else: + await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + + #command for admin to begin the secret santa partner assignment + elif(message_split[0] == "s!start"): + #only allow people with admin permissions to run + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + + #select a random partner for each participant if above loop found no empty values + if all_fields_complete: print("proposing a list") potential_list = propose_partner_list(usr_list) - #save to config file - print("list passed") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][6] = user.partnerid # update config file + while(not partners_are_valid): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + #save to config file + print("list passed") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][6] = user.partnerid # update config file + config.write() + #tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.' + message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' + message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + await client.send_message(this_user, santa_message) + #set exchange_started + assoc. cfg value to True + exchange_started = True + config['programData']['exchange_started'] = True config.write() - #tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Mosey on over to their wishlist(s) and pick out a gift! Remember to keep it in the $10-20 range.' - message_pt2 = 'Their wishlist can be found s: ' + partner.wishlist - message_pt3 = 'Here are their gift preferences: ' + partner.preferences - santa_message = message_pt1 + '\n' + message_pt2 + '\n' + message_pt3 - await client.send_message(this_user, santa_message) - #set exchange_started + assoc. cfg value to True - exchange_started = True - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - - #command allows you to restart without rematching if no change was made while s!paused - elif(message_split[0] == "s!restart"): - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlist_is_set() and user.pref_is_set(): - pass + usr_list = copy.deepcopy(potential_list) + await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + else: + await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + + #command allows you to restart without rematching if no change was made while s!paused + elif(message_split[0] == "s!restart"): + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + list_changed = usr_list_changed_during_pause() + if(list_changed): + await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") else: - all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist or gift preferences.`') - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - list_changed = usr_list_changed_during_pause() - if(list_changed): - await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") + exchange_started = True + config['programData']['exchange_started'] = True + config.write() + await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") else: - exchange_started = True - config['programData']['exchange_started'] = True - config.write() - await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") - else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - # allows a way to restart the secret santa - elif(message_split[0] == "s!pause"): - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await client.send_message(message.channel, 'Secret Santa has been paused.') - else: - await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') + # allows a way to restart the secret santa + elif(message_split[0] == "s!pause"): + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await client.send_message(message.channel, 'Secret Santa has been paused.') + else: + await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') - #allows a way to end the secret santa - elif(message_split[0] == "s!end") and not message.channel.is_private: - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - config['programData']['exchange_started'] = False - highest_key = 0 - usr_list.clear() - config['members'].clear() - config.write() - await client.send_message(message.channel, 'Secret Santa ended') - else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - - #lists off all participant names and id's - elif(message_split[0] == "s!listparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') - else: - msg = '```The following people are signed up for the secret santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `s!join` to enter the exchange.```' - await client.send_message(message.channel, msg) - - #lists total number of participants - elif(message_split[0] == "s!totalparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') - elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `s!join` to enter the exchange.') - else: - await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `s!join` to enter the exchange.') - - #allows a user to have the details of their partner restated - elif(message_split[0] == "s!partnerinfo"): - if exchange_started and user_is_participant(message.author.id): - (usr_index, user) = get_participant_object(message.author.id) - (partner_index, partnerobj) = get_participant_object(user.partnerid) - msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' - msg = msg + 'Their mailing wishlist is ' + partnerobj.wishlist + '\n' - msg = msg + 'their gift preference is as follows:\n' - msg = msg + partnerobj.preferences - await client.send_message(message.author, msg) - elif exchange_started: - await client.send_message(message.channel, '`Error: partners have not been assigned yet.`') - else: - await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') + #allows a way to end the secret santa + elif(message_split[0] == "s!end") and not message.channel.is_private: + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + highest_key = 0 + usr_list.clear() + config['members'].clear() + config.write() + await client.send_message(message.channel, 'Secret Santa ended') + else: + await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + + #lists off all participant names and id's + elif(message_split[0] == "s!listparticipants"): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') + else: + msg = '```The following people are signed up for the secret santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' + msg = msg + 'Use `s!join` to enter the exchange.```' + await client.send_message(message.channel, msg) + + #lists total number of participants + elif(message_split[0] == "s!totalparticipants"): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') + elif highest_key == 1: + await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `s!join` to enter the exchange.') + else: + await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `s!join` to enter the exchange.') + + #allows a user to have the details of their partner restated + elif(message_split[0] == "s!partnerinfo"): + if exchange_started and user_is_participant(message.author.id): + (usr_index, user) = get_participant_object(message.author.id) + (partner_index, partnerobj) = get_participant_object(user.partnerid) + msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' + msg = msg + 'Their mailing wishlist URL is ' + partnerobj.wishlisturl + '\n' + msg = msg + 'their gift preference is as follows: ' + partnerobj.preferences + '\n' + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + await client.send_message(message.author, msg) + await client.send_message(message.channel, "The information has been sent to your DMs.") + elif exchange_started: + await client.send_message(message.channel, '`Error: partners have not been assigned yet.`') + else: + await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') - #elif message.content.startswith('s!help'): - elif(message_split[0] == "s!help"): - c_join = "`s!join` = join the Secret Santa" - c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlist = "`s!setwishlist [mailing wishlist/wishlist URL]` = set your wishlist (replaces current)" - c_getwishlist = "`s!getwishlist` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current)" - c_getprefs = "`s!getprefs` = bot will PM you your current preferences" - c_listparticipants = "`s!listparticipants` = get the current participants" - c_totalparticipants = "`s!totalparticipants` = get the total number of participants" - c_partnerinfo = "`s!partnerinfo` = be DM'd your partner's information" - c_start = "`s!start` **(admin only)** = assign Secret Santa partners" - c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" - c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" - c_end = "`s!end` **(admin only)** = end Secret Santa" - c_ping = "`s!ping` = check if bot is alive" - command_list = [c_join, c_leave, c_setwishlist, c_getwishlist, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] - command_string = '' - for command in command_list: - command_string = command_string + ("{0}\n".format(command)) - await client.send_message(message.channel, command_string) + elif(message_split[0] == "s!help"): + c_join = "`s!join` = join the Secret Santa" + c_leave = "`s!leave` = leave the Secret Santa" + c_setwishlisturl = "`s!setwishlisturl [wishlist URL]` = set your wishlist (replaces current). Required." + c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" + c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none." + c_getprefs = "`s!getprefs` = bot will PM you your current preferences" + c_listparticipants = "`s!listparticipants` = get the current participants" + c_totalparticipants = "`s!totalparticipants` = get the total number of participants" + c_partnerinfo = "`s!partnerinfo` = be DM'd your partner's information" + c_start = "`s!start` **(admin only)** = assign Secret Santa partners" + c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" + c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" + c_end = "`s!end` **(admin only)** = end Secret Santa" + c_ping = "`s!ping` = check if bot is alive" + command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] + command_string = '' + for command in command_list: + command_string = command_string + ("{0}\n".format(command)) + await client.send_message(message.channel, command_string) - #elif message.content.startswith('s!ping'): - elif(message_split[0] == "s!ping"): - """ Pong! """ - await client.send_message(message.channel, "Pong!") + elif(message_split[0] == "s!ping"): + """ Pong! """ + await client.send_message(message.channel, "Pong!") - #elif message.content.startswith('s!invite'): - elif(message_split[0] == "s!invite"): - link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" - await client.send_message(message.channel, "Bot invite link: {0}".format(link)) + elif(message_split[0] == "s!invite"): + link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" + await client.send_message(message.channel, "Bot invite link: {0}".format(link)) - else: - await client.send_message(message.channel, "Command not found") + else: + await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") @client.event async def on_ready(): From 75948b89efbaee88774bbe926f99eb3ace100245 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 18 Nov 2018 16:46:24 -0600 Subject: [PATCH 012/189] Easier data indexing, adding requirements.txt to tree --- .gitignore | 1 - idx_list.py | 7 +++++++ santa-bot.py | 15 ++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100755 idx_list.py diff --git a/.gitignore b/.gitignore index 5c4e228..30d7612 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ .pylintrc CONFIG.py /__pycache__ -requirements.txt Procfile diff --git a/idx_list.py b/idx_list.py new file mode 100755 index 0000000..e1bc3c5 --- /dev/null +++ b/idx_list.py @@ -0,0 +1,7 @@ +NAME = 1 +DISCRIMINATOR = 2 +IDSTR = 3 +USRNUM = 4 +WISHLISTURL = 5 +PREFERENCES = 6 +PARTNERID = 7 \ No newline at end of file diff --git a/santa-bot.py b/santa-bot.py index 0f855b5..c7c10c4 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -5,6 +5,7 @@ import copy import discord import CONFIG +import idx_list from configobj import ConfigObj class Participant(object): @@ -52,7 +53,7 @@ def pref_is_set(self): exchange_started = config['programData'].as_bool('exchange_started') for key in config['members']: data = config['members'][str(key)] - usr_list.append(Participant(data[0], data[1], data[2], data[3], data[4], data[5], data[6])) + usr_list.append(Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID])) highest_key = int(key) def user_is_participant(usrid, usrlist=usr_list): @@ -166,9 +167,9 @@ async def on_message(message): #prompt user about inputting info await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) - await client.send_message(message.author, 'Please input your wishlist URL and preferences (through DMs) so your Secret Santa can send you something!\n' - + 'Use `s!setwishlisturl` to set your wishlist URL\n' - + 'Use `s!setprefs` to set gift preferences for your secret santa. Put N/A if none.') + await client.send_message(message.author, 'Please input your wishlist URL and preferences (by DMing this bot) so your Secret Santa can send you something!\n' + + 'Use `s!setwishlisturl [wishlist urls separated by commas]` to set your wishlist URL\n' + + 'Use `s!setprefs [preferences separated by commas]` to set gift preferences for your secret santa. Put N/A if none.') #event for a user to leave the secret santa list elif(message_split[0] == "s!leave"): @@ -192,7 +193,7 @@ async def on_message(message): (index, user) = get_participant_object(message.author.id) user.wishlisturl = message.content.replace('s!setwishlisturl ', '', 1) #save to config file - config['members'][str(user.usrnum)][4] = user.wishlisturl + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = user.wishlisturl config.write() if(message.channel.is_private): pass @@ -219,7 +220,7 @@ async def on_message(message): (index, user) = get_participant_object(message.author.id) user.preferences = message.content.replace('s!setprefs ', '', 1) #save to config file - config['members'][str(user.usrnum)][5] = user.preferences + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = user.preferences config.write() if(message.channel.is_private): pass @@ -265,7 +266,7 @@ async def on_message(message): (temp_index, temp_user) = get_participant_object(user.idstr) (index, partner) = get_participant_object(user.partnerid, potential_list) temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][6] = user.partnerid # update config file + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file config.write() #tell participants who their partner is this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) From e79b6b9572a30dbd85e5c1a1947c2d9957bf8fcd Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 18 Nov 2018 16:48:59 -0600 Subject: [PATCH 013/189] added requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100755 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..7c9c9e5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +discord.py==0.16.12 +configobj==5.0.6 From 5e0e9a8e92ddd8b87c5c1a04c2639096bb19e3f5 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 18 Nov 2018 20:12:56 -0600 Subject: [PATCH 014/189] fixed data indexing --- idx_list.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/idx_list.py b/idx_list.py index e1bc3c5..36cd7a2 100755 --- a/idx_list.py +++ b/idx_list.py @@ -1,7 +1,7 @@ -NAME = 1 -DISCRIMINATOR = 2 -IDSTR = 3 -USRNUM = 4 -WISHLISTURL = 5 -PREFERENCES = 6 -PARTNERID = 7 \ No newline at end of file +NAME = 0 +DISCRIMINATOR = 1 +IDSTR = 2 +USRNUM = 3 +WISHLISTURL = 4 +PREFERENCES = 5 +PARTNERID = 6 From f0e80e0efa2a96aeb0205679545294fc9eb832d6 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 18 Nov 2018 20:25:06 -0600 Subject: [PATCH 015/189] Fixed issue: cannot s\!end and s\!join in succession; fine-tuned pausing and restarting --- santa-bot.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index c7c10c4..bf183cf 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -179,7 +179,6 @@ async def on_message(message): popped_user = config['members'].pop(str(user.usrnum)) config.write() if(is_paused): - is_paused = False user_left_during_pause = True await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) else: @@ -271,7 +270,7 @@ async def on_message(message): #tell participants who their partner is this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.' + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" @@ -279,6 +278,7 @@ async def on_message(message): await client.send_message(this_user, santa_message) #set exchange_started + assoc. cfg value to True exchange_started = True + is_paused = False config['programData']['exchange_started'] = True config.write() usr_list = copy.deepcopy(potential_list) @@ -288,7 +288,7 @@ async def on_message(message): #command allows you to restart without rematching if no change was made while s!paused elif(message_split[0] == "s!restart"): - if message.author.top_role == message.server.role_hierarchy[0]: + if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: #first ensure all users have all info submitted all_fields_complete = True for user in usr_list: @@ -303,6 +303,7 @@ async def on_message(message): await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") else: exchange_started = True + is_paused = False config['programData']['exchange_started'] = True config.write() await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") @@ -326,9 +327,10 @@ async def on_message(message): #only allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): exchange_started = False + is_paused = False config['programData']['exchange_started'] = False highest_key = 0 - usr_list.clear() + del usr_list[:] config['members'].clear() config.write() await client.send_message(message.channel, 'Secret Santa ended') From 3d8ec39a5997620375d691b8d9bbd6dd858f8b5c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 18 Nov 2018 20:32:26 -0600 Subject: [PATCH 016/189] Fixed issue #4: cannot s\!end and s\!join in succession; fine-tuned pausing and restarting --- santa-bot.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index c7c10c4..bf183cf 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -179,7 +179,6 @@ async def on_message(message): popped_user = config['members'].pop(str(user.usrnum)) config.write() if(is_paused): - is_paused = False user_left_during_pause = True await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) else: @@ -271,7 +270,7 @@ async def on_message(message): #tell participants who their partner is this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your secret santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.' + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" @@ -279,6 +278,7 @@ async def on_message(message): await client.send_message(this_user, santa_message) #set exchange_started + assoc. cfg value to True exchange_started = True + is_paused = False config['programData']['exchange_started'] = True config.write() usr_list = copy.deepcopy(potential_list) @@ -288,7 +288,7 @@ async def on_message(message): #command allows you to restart without rematching if no change was made while s!paused elif(message_split[0] == "s!restart"): - if message.author.top_role == message.server.role_hierarchy[0]: + if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: #first ensure all users have all info submitted all_fields_complete = True for user in usr_list: @@ -303,6 +303,7 @@ async def on_message(message): await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") else: exchange_started = True + is_paused = False config['programData']['exchange_started'] = True config.write() await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") @@ -326,9 +327,10 @@ async def on_message(message): #only allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): exchange_started = False + is_paused = False config['programData']['exchange_started'] = False highest_key = 0 - usr_list.clear() + del usr_list[:] config['members'].clear() config.write() await client.send_message(message.channel, 'Secret Santa ended') From 3c217dc69ebee684276c4b859665e067401e83a7 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 18 Nov 2018 22:12:07 -0600 Subject: [PATCH 017/189] Added playing status for help; consistent capitalization --- santa-bot.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index bf183cf..b0bf8fe 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -149,7 +149,7 @@ async def on_message(message): if message.author == client.user: return - #event for a user joining the secret santa + #event for a user joining the Secret Santa elif(message_split[0] == "s!join"): #check if message author has already joined if user_is_participant(message.author.id): @@ -169,9 +169,9 @@ async def on_message(message): await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) await client.send_message(message.author, 'Please input your wishlist URL and preferences (by DMing this bot) so your Secret Santa can send you something!\n' + 'Use `s!setwishlisturl [wishlist urls separated by commas]` to set your wishlist URL\n' - + 'Use `s!setprefs [preferences separated by commas]` to set gift preferences for your secret santa. Put N/A if none.') + + 'Use `s!setprefs [preferences separated by commas]` to set gift preferences for your Secret Santa. Put N/A if none.') - #event for a user to leave the secret santa list + #event for a user to leave the Secret Santa list elif(message_split[0] == "s!leave"): if user_is_participant(message.author.id): (index, user) = get_participant_object(message.author.id) @@ -182,7 +182,7 @@ async def on_message(message): user_left_during_pause = True await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) else: - await client.send_message(message.channel, "You're not currently a member of the secret santa") + await client.send_message(message.channel, "You're not currently a member of the Secret Santa") #accept wishlisturl of participants elif(message_split[0] == "s!setwishlisturl"): @@ -200,7 +200,7 @@ async def on_message(message): await client.delete_message(message) await client.send_message(message.author, "New wishlist URL: {0}".format(user.wishlisturl)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) # get current wishlist URL(s) @@ -209,7 +209,7 @@ async def on_message(message): (index, user) = get_participant_object(message.author.id) await client.send_message(message.author, "Current wishlist URL(s): " + str(user.wishlisturl)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') #accept gift preferences of participants elif(message_split[0] == "s!setprefs"): @@ -227,7 +227,7 @@ async def on_message(message): await client.delete_message(message) await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) #get current preferences @@ -236,9 +236,9 @@ async def on_message(message): (index, user) = get_participant_object(message.author.id) await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) else: - await client.send_message(message.author, 'Error: you have not yet joined the secret santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') - #command for admin to begin the secret santa partner assignment + #command for admin to begin the Secret Santa partner assignment elif(message_split[0] == "s!start"): #only allow people with admin permissions to run if message.author.top_role == message.server.role_hierarchy[0]: @@ -310,7 +310,7 @@ async def on_message(message): else: await client.send_message(message.channel, '`Error: you do not have permission to do this.`') - # allows a way to restart the secret santa + # allows a way to restart the Secret Santa elif(message_split[0] == "s!pause"): #only allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): @@ -322,7 +322,7 @@ async def on_message(message): else: await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') - #allows a way to end the secret santa + #allows a way to end the Secret Santa elif(message_split[0] == "s!end") and not message.channel.is_private: #only allow ppl with admin permissions to run if (message.author.top_role == message.server.role_hierarchy[0]): @@ -340,9 +340,9 @@ async def on_message(message): #lists off all participant names and id's elif(message_split[0] == "s!listparticipants"): if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') else: - msg = '```The following people are signed up for the secret santa exchange:\n' + msg = '```The following people are signed up for the Secret Santa exchange:\n' for user in usr_list: this_user = discord.User(user = user.name, id = user.idstr) msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' @@ -352,11 +352,11 @@ async def on_message(message): #lists total number of participants elif(message_split[0] == "s!totalparticipants"): if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the secret santa exchange yet. Use `s!join` to enter the exchange.') + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the secret santa exchange. Use `s!join` to enter the exchange.') + await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') else: - await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the secret santa exchange so far. Use `s!join` to enter the exchange.') + await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') #allows a user to have the details of their partner restated elif(message_split[0] == "s!partnerinfo"): @@ -412,6 +412,7 @@ async def on_ready(): print('Logged in as') print(client.user.name) print(client.user.id) + await client.change_presence(game = discord.Game(name = "s!help")) print('------') From 03c1dd7d404edc05bdad894cfd6d4efb5916168a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 19 Nov 2018 13:05:02 -0600 Subject: [PATCH 018/189] changed some message strings --- idx_list.py | 0 requirements.txt | 0 santa-bot.py | 12 ++++++------ 3 files changed, 6 insertions(+), 6 deletions(-) mode change 100755 => 100644 idx_list.py mode change 100755 => 100644 requirements.txt diff --git a/idx_list.py b/idx_list.py old mode 100755 new mode 100644 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 diff --git a/santa-bot.py b/santa-bot.py index b0bf8fe..2d76c18 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -167,9 +167,9 @@ async def on_message(message): #prompt user about inputting info await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) - await client.send_message(message.author, 'Please input your wishlist URL and preferences (by DMing this bot) so your Secret Santa can send you something!\n' - + 'Use `s!setwishlisturl [wishlist urls separated by commas]` to set your wishlist URL\n' - + 'Use `s!setprefs [preferences separated by commas]` to set gift preferences for your Secret Santa. Put N/A if none.') + await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' + + 'Use `s!setwishlisturl [wishlist urls separated by ;]` to set your wishlist URL (you may also add your mailing address).\n' + + 'Use `s!setprefs [preferences separated by ;]` to set gift preferences for your Secret Santa. Put N/A if none.') #event for a user to leave the Secret Santa list elif(message_split[0] == "s!leave"): @@ -207,7 +207,7 @@ async def on_message(message): elif(message_split[0] == "s!getwishlisturl"): if user_is_participant(message.author.id): (index, user) = get_participant_object(message.author.id) - await client.send_message(message.author, "Current wishlist URL(s): " + str(user.wishlisturl)) + await client.send_message(message.author, "Current wishlist URL(s): " + user.wishlisturl) else: await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') @@ -377,9 +377,9 @@ async def on_message(message): elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlisturl = "`s!setwishlisturl [wishlist URL]` = set your wishlist (replaces current). Required." + c_setwishlisturl = "`s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none." + c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__." c_getprefs = "`s!getprefs` = bot will PM you your current preferences" c_listparticipants = "`s!listparticipants` = get the current participants" c_totalparticipants = "`s!totalparticipants` = get the total number of participants" From 528f69786004953e078b231ba46a6437e8d55c7a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 19 Nov 2018 13:48:43 -0600 Subject: [PATCH 019/189] Fixed issue #8: no more default usrlist in function calls --- idx_list.py | 0 requirements.txt | 0 santa-bot.py | 42 ++++++++++++++++++++++-------------------- 3 files changed, 22 insertions(+), 20 deletions(-) mode change 100644 => 100755 idx_list.py mode change 100644 => 100755 requirements.txt diff --git a/idx_list.py b/idx_list.py old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/santa-bot.py b/santa-bot.py index 2d76c18..07ffb3e 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -56,7 +56,7 @@ def pref_is_set(self): usr_list.append(Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID])) highest_key = int(key) -def user_is_participant(usrid, usrlist=usr_list): +def user_is_participant(usrid, usrlist): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" result = False @@ -66,7 +66,7 @@ def user_is_participant(usrid, usrlist=usr_list): break return result -def get_participant_object(usrid, usrlist=usr_list): +def get_participant_object(usrid, usrlist): """takes a discord user ID string and list of participant objects, and returns the first participant object with matching id.""" @@ -102,7 +102,7 @@ def partners_are_valid(usrlist): return result ## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist=usr_list): +def usr_list_changed_during_pause(usrlist): if(user_left_during_pause):# there's probably a better boolean logic way but this is easy user_left_during_pause = False # acknowledge return True @@ -152,7 +152,8 @@ async def on_message(message): #event for a user joining the Secret Santa elif(message_split[0] == "s!join"): #check if message author has already joined - if user_is_participant(message.author.id): + print(len(usr_list)) + if user_is_participant(message.author.id, usr_list): await client.send_message(message.channel, '`Error: You have already joined.`') #check if the exchange has already started elif exchange_started: @@ -173,8 +174,8 @@ async def on_message(message): #event for a user to leave the Secret Santa list elif(message_split[0] == "s!leave"): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) usr_list.remove(user) popped_user = config['members'].pop(str(user.usrnum)) config.write() @@ -187,9 +188,9 @@ async def on_message(message): #accept wishlisturl of participants elif(message_split[0] == "s!setwishlisturl"): #check if author has joined the exchange yet - if user_is_participant(message.author.id): + if user_is_participant(message.author.id, usr_list): #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id) + (index, user) = get_participant_object(message.author.id, usr_list) user.wishlisturl = message.content.replace('s!setwishlisturl ', '', 1) #save to config file config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = user.wishlisturl @@ -205,8 +206,8 @@ async def on_message(message): # get current wishlist URL(s) elif(message_split[0] == "s!getwishlisturl"): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) await client.send_message(message.author, "Current wishlist URL(s): " + user.wishlisturl) else: await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') @@ -214,9 +215,9 @@ async def on_message(message): #accept gift preferences of participants elif(message_split[0] == "s!setprefs"): #check if author has joined the exchange yet - if user_is_participant(message.author.id): + if user_is_participant(message.author.id, usr_list): #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id) + (index, user) = get_participant_object(message.author.id, usr_list) user.preferences = message.content.replace('s!setprefs ', '', 1) #save to config file config['members'][str(user.usrnum)][idx_list.PREFERENCES] = user.preferences @@ -232,8 +233,8 @@ async def on_message(message): #get current preferences elif(message_split[0] == "s!getprefs"): - if user_is_participant(message.author.id): - (index, user) = get_participant_object(message.author.id) + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) else: await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') @@ -256,13 +257,13 @@ async def on_message(message): if all_fields_complete: print("proposing a list") potential_list = propose_partner_list(usr_list) - while(not partners_are_valid): + while(not partners_are_valid(potential_list)): print("proposing a list") potential_list = propose_partner_list(usr_list) #save to config file print("list passed") for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr) + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) (index, partner) = get_participant_object(user.partnerid, potential_list) temp_user.partnerid = user.partnerid config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file @@ -298,7 +299,7 @@ async def on_message(message): all_fields_complete = False await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - list_changed = usr_list_changed_during_pause() + list_changed = usr_list_changed_during_pause(usr_list) if(list_changed): await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") else: @@ -331,6 +332,7 @@ async def on_message(message): config['programData']['exchange_started'] = False highest_key = 0 del usr_list[:] + print(len(usr_list)) config['members'].clear() config.write() await client.send_message(message.channel, 'Secret Santa ended') @@ -360,9 +362,9 @@ async def on_message(message): #allows a user to have the details of their partner restated elif(message_split[0] == "s!partnerinfo"): - if exchange_started and user_is_participant(message.author.id): - (usr_index, user) = get_participant_object(message.author.id) - (partner_index, partnerobj) = get_participant_object(user.partnerid) + if exchange_started and user_is_participant(message.author.id, usr_list): + (usr_index, user) = get_participant_object(message.author.id, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' msg = msg + 'Their mailing wishlist URL is ' + partnerobj.wishlisturl + '\n' msg = msg + 'their gift preference is as follows: ' + partnerobj.preferences + '\n' From b3ac7b4aa176c269e059913ba17b266bdb34993c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 19 Nov 2018 13:57:59 -0600 Subject: [PATCH 020/189] moved message delete for wishlist and pref set in an attempt to make it faster --- santa-bot.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 07ffb3e..3d6cfe1 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -189,16 +189,16 @@ async def on_message(message): elif(message_split[0] == "s!setwishlisturl"): #check if author has joined the exchange yet if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) #add the input to the value in the user's class instance (index, user) = get_participant_object(message.author.id, usr_list) user.wishlisturl = message.content.replace('s!setwishlisturl ', '', 1) #save to config file config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = user.wishlisturl config.write() - if(message.channel.is_private): - pass - else: - await client.delete_message(message) await client.send_message(message.author, "New wishlist URL: {0}".format(user.wishlisturl)) else: await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') @@ -216,16 +216,16 @@ async def on_message(message): elif(message_split[0] == "s!setprefs"): #check if author has joined the exchange yet if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) #add the input to the value in the user's class instance (index, user) = get_participant_object(message.author.id, usr_list) user.preferences = message.content.replace('s!setprefs ', '', 1) #save to config file config['members'][str(user.usrnum)][idx_list.PREFERENCES] = user.preferences config.write() - if(message.channel.is_private): - pass - else: - await client.delete_message(message) await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) else: await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') From 94a3ce2f2f3e6d38c4d28490ed8eff39c33b4a5f Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 20 Nov 2018 00:01:31 -0600 Subject: [PATCH 021/189] centralized error messages --- BOT_ERROR.py | 1 + 1 file changed, 1 insertion(+) create mode 100755 BOT_ERROR.py diff --git a/BOT_ERROR.py b/BOT_ERROR.py new file mode 100755 index 0000000..ed11e66 --- /dev/null +++ b/BOT_ERROR.py @@ -0,0 +1 @@ +DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" \ No newline at end of file From afbe34c176f5d5fb6f7049efedf431b11e95aa9a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 20 Nov 2018 00:21:18 -0600 Subject: [PATCH 022/189] Updated README --- README.md | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b442f77..8a07e32 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,30 @@ -#SantaBot +# SantaBot A Discord bot to organize secret santa gift exchanges using the discord.py Python library -##Instructions: - -###Installation and Dependencies: - -To add this bot to your Discord server, first ensure you have [Python version 3.5 or later installed](https://www.python.org/downloads/), along with [the discord.py library](https://github.com/Rapptz/discord.py) and [the configobj library](https://www.voidspace.org.uk/python/configobj.html#downloading). The asyncio, logging, os.path, and random libraries are also required, but they should be included in most Python installations by default. - -Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). Then, open the santa-bot.py file with your favorite plaintext editor, and replace the word `'token'` in the last line that reads `client.run('token')` with the token you have generated, keeping the single quotes. The santa-bot.py file can now be excecuted, and the bot should function as normal. - -###Bot Commands: - -- `$$join` adds you to the list of secret santa participants. -- `$$setaddress` saves your mailing address for gifts to be sent to. -- `$$setprefs` saves your gift preferences so your secret santa will know what kind of things you would like to receive. Keep in mind that your exact input is sent to your secret santa as is. -- `$$listparticipants` makes the bot list all of the people currently participating in the secret santa exchange. -- `$$totalparticipants` makes the bot give the number of people currently participating in the secret santa exchange -- *`$$start` to have the bot assign each participant a partner -- *`$$shutdown` to make the bot self-terminate - -all commands marked with a * can only be run by a server admin. \ No newline at end of file +### Requirements + - python 3.5 or later (can be installed [here](https://www.python.org/downloads/)) + - pip3 + +### Steps to run: +1. Run `pip3 install -r requirements.txt` +2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). +3. Replace `discord_token` in CONFIG.py with your bot token +4. Run `python3 santa-bot.py` + +#### Bot Commands: + +- `s!join` = join the Secret Santa +- `s!leave` = leave the Secret Santa +- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. +- `s!getwishlisturl` = bot will PM you your current wishlist +- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. +- `s!getprefs` = bot will PM you your current preferences +- `s!listparticipants` = get the current participants +- `s!totalparticipants` = get the total number of participants +- `s!partnerinfo` = be DM'd your partner's information +- `s!start` **(admin only)** = assign Secret Santa partners +- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners +- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) +- `s!end` **(admin only)** = end Secret Santa +- `s!ping` = check if bot is alive \ No newline at end of file From 627e1d9626589b205cbb50c70ecae25b7a88a836 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 20 Nov 2018 00:21:58 -0600 Subject: [PATCH 023/189] multiple bug fixes --- santa-bot.py | 126 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 34 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 3d6cfe1..0dfb513 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -5,6 +5,7 @@ import copy import discord import CONFIG +import BOT_ERROR import idx_list from configobj import ConfigObj @@ -53,7 +54,8 @@ def pref_is_set(self): exchange_started = config['programData'].as_bool('exchange_started') for key in config['members']: data = config['members'][str(key)] - usr_list.append(Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID])) + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) highest_key = int(key) def user_is_participant(usrid, usrlist): @@ -168,9 +170,12 @@ async def on_message(message): #prompt user about inputting info await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) - await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' - + 'Use `s!setwishlisturl [wishlist urls separated by ;]` to set your wishlist URL (you may also add your mailing address).\n' - + 'Use `s!setprefs [preferences separated by ;]` to set gift preferences for your Secret Santa. Put N/A if none.') + try: + await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' + + 'Use `s!setwishlisturl [wishlist urls separated by ;]` to set your wishlist URL (you may also add your mailing address).\n' + + 'Use `s!setprefs [preferences separated by ;]` to set gift preferences for your Secret Santa. Put N/A if none.') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) #event for a user to leave the Secret Santa list elif(message_split[0] == "s!leave"): @@ -193,24 +198,44 @@ async def on_message(message): pass else: await client.delete_message(message) - #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id, usr_list) - user.wishlisturl = message.content.replace('s!setwishlisturl ', '', 1) - #save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = user.wishlisturl - config.write() - await client.send_message(message.author, "New wishlist URL: {0}".format(user.wishlisturl)) + new_wishlist = "" + if(message.content == "s!setwishlisturl"): + new_wishlist = "" + else: + new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) + try: + (index, user) = get_participant_object(message.author.id, usr_list) + #save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + #add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + (index, user) = get_participant_object(message.author.id, usr_list) + print(user.wishlisturl) + usr_list = usr_list + except: + try: + await client.send_message(message.author, "`Error: invalid input`") + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + try: + await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') - await client.delete_message(message) + await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') # get current wishlist URL(s) elif(message_split[0] == "s!getwishlisturl"): if user_is_participant(message.author.id, usr_list): (index, user) = get_participant_object(message.author.id, usr_list) - await client.send_message(message.author, "Current wishlist URL(s): " + user.wishlisturl) + print("Wishlist url = " + user.wishlisturl)# debugging ## WORKING JUST FINE + try: + await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) ## NOT WORKING + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') #accept gift preferences of participants elif(message_split[0] == "s!setprefs"): @@ -220,24 +245,41 @@ async def on_message(message): pass else: await client.delete_message(message) - #add the input to the value in the user's class instance - (index, user) = get_participant_object(message.author.id, usr_list) - user.preferences = message.content.replace('s!setprefs ', '', 1) #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = user.preferences - config.write() - await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) + new_prefs = "" + if(message.content == "s!setprefs"): + new_prefs = "" + else: + new_prefs = message.content.replace("s!setprefs ", "", 1) + try: + (index, user) = get_participant_object(message.author.id, usr_list) + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = new_prefs + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, "`Error: invalid input") + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') await client.delete_message(message) #get current preferences elif(message_split[0] == "s!getprefs"): if user_is_participant(message.author.id, usr_list): (index, user) = get_participant_object(message.author.id, usr_list) - await client.send_message(message.author, "Current preference(s): " + str(user.preferences)) + try: + await client.send_message(message.author, "Current preference(s): {0}" + str(user.preferences)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.author, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') #command for admin to begin the Secret Santa partner assignment elif(message_split[0] == "s!start"): @@ -250,11 +292,14 @@ async def on_message(message): pass else: all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + try: + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - #select a random partner for each participant if above loop found no empty values - if all_fields_complete: + #select a random partner for each participant if above loop found no empty values and there are enough people to do it + if all_fields_complete & (len(usr_list) > 1): print("proposing a list") potential_list = propose_partner_list(usr_list) while(not partners_are_valid(potential_list)): @@ -276,7 +321,10 @@ async def on_message(message): message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - await client.send_message(this_user, santa_message) + try: + await client.send_message(this_user, santa_message) + except: + await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) #set exchange_started + assoc. cfg value to True exchange_started = True is_paused = False @@ -284,6 +332,8 @@ async def on_message(message): config.write() usr_list = copy.deepcopy(potential_list) await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await client.send_message(message.channel, message.author.mention + " `Error: time for some love through harassment`") else: await client.send_message(message.channel, '`Error: you do not have permission to do this.`') @@ -297,8 +347,11 @@ async def on_message(message): pass else: all_fields_complete = False - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + try: + await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) list_changed = usr_list_changed_during_pause(usr_list) if(list_changed): await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") @@ -369,12 +422,17 @@ async def on_message(message): msg = msg + 'Their mailing wishlist URL is ' + partnerobj.wishlisturl + '\n' msg = msg + 'their gift preference is as follows: ' + partnerobj.preferences + '\n' msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - await client.send_message(message.author, msg) + try: + await client.send_message(message.author, msg) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) await client.send_message(message.channel, "The information has been sent to your DMs.") - elif exchange_started: + elif not exchange_started: await client.send_message(message.channel, '`Error: partners have not been assigned yet.`') + elif not user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, '`Error: You are not participating in the gift exchange.`') else: - await client.send_message(message.author, '`Error: You are not participating in the gift exchange.`') + await client.send_message(message.channel, "`Error: this shouldn't happen`") elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" From bfae29747194ca6230d7bc03b49a2fefa2600662 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 20 Nov 2018 00:31:07 -0600 Subject: [PATCH 024/189] tidying up error strings --- BOT_ERROR.py | 7 ++++++- santa-bot.py | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index ed11e66..2913884 100755 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1 +1,6 @@ -DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" \ No newline at end of file +DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" +UNJOINED = "Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange." +INVALID_INPUT = "`Error: invalid input" +EXCHANGE_IN_PROGRESS = "`Error: Too late, the gift exchange is already in progress.`" +NOT_STARTED = "`Error: partners have not been assigned yet.`" +NO_PERMISSION = "`Error: you do not have permissions to do this.`" \ No newline at end of file diff --git a/santa-bot.py b/santa-bot.py index 0dfb513..d08ed1e 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -159,7 +159,7 @@ async def on_message(message): await client.send_message(message.channel, '`Error: You have already joined.`') #check if the exchange has already started elif exchange_started: - await client.send_message(message.channel, '`Error: Too late, the gift exchange is already in progress.`') + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) else: #initialize instance of participant class for the author highest_key = highest_key + 1 @@ -188,7 +188,7 @@ async def on_message(message): user_left_during_pause = True await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) else: - await client.send_message(message.channel, "You're not currently a member of the Secret Santa") + await client.send_message(message.channel, BOT_ERROR.UNJOINED) #accept wishlisturl of participants elif(message_split[0] == "s!setwishlisturl"): @@ -215,7 +215,7 @@ async def on_message(message): usr_list = usr_list except: try: - await client.send_message(message.author, "`Error: invalid input`") + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) try: @@ -223,7 +223,7 @@ async def on_message(message): except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, ) # get current wishlist URL(s) elif(message_split[0] == "s!getwishlisturl"): @@ -235,7 +235,7 @@ async def on_message(message): except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, BOT_ERROR.UNJOINED) #accept gift preferences of participants elif(message_split[0] == "s!setprefs"): @@ -263,11 +263,11 @@ async def on_message(message): await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) except: try: - await client.send_message(message.author, "`Error: invalid input") + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, BOT_ERROR.UNJOINED) await client.delete_message(message) #get current preferences @@ -279,7 +279,7 @@ async def on_message(message): except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.channel, 'Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.') + await client.send_message(message.channel, BOT_ERROR.UNJOINED) #command for admin to begin the Secret Santa partner assignment elif(message_split[0] == "s!start"): @@ -335,7 +335,7 @@ async def on_message(message): elif not all_fields_complete: await client.send_message(message.channel, message.author.mention + " `Error: time for some love through harassment`") else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) #command allows you to restart without rematching if no change was made while s!paused elif(message_split[0] == "s!restart"): @@ -362,7 +362,7 @@ async def on_message(message): config.write() await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) # allows a way to restart the Secret Santa elif(message_split[0] == "s!pause"): @@ -374,7 +374,7 @@ async def on_message(message): is_paused = True await client.send_message(message.channel, 'Secret Santa has been paused.') else: - await client.send_message(message.channel, '`Error: you do not have permissions to do this.`') + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) #allows a way to end the Secret Santa elif(message_split[0] == "s!end") and not message.channel.is_private: @@ -388,9 +388,9 @@ async def on_message(message): print(len(usr_list)) config['members'].clear() config.write() - await client.send_message(message.channel, 'Secret Santa ended') + await client.send_message(message.channel, "Secret Santa ended") else: - await client.send_message(message.channel, '`Error: you do not have permission to do this.`') + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) #lists off all participant names and id's elif(message_split[0] == "s!listparticipants"): @@ -428,7 +428,7 @@ async def on_message(message): await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) await client.send_message(message.channel, "The information has been sent to your DMs.") elif not exchange_started: - await client.send_message(message.channel, '`Error: partners have not been assigned yet.`') + await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) elif not user_is_participant(message.author.id, usr_list): await client.send_message(message.channel, '`Error: You are not participating in the gift exchange.`') else: From 72a2bd234b5d67d1514909911088c07fba80fa84 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 20 Nov 2018 01:51:35 -0600 Subject: [PATCH 025/189] Improved error checking --- BOT_ERROR.py | 4 ++-- santa-bot.py | 26 +++++++++++--------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index 2913884..6aa4f51 100755 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1,6 +1,6 @@ DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" UNJOINED = "Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange." -INVALID_INPUT = "`Error: invalid input" +INVALID_INPUT = "`Error: invalid input`" EXCHANGE_IN_PROGRESS = "`Error: Too late, the gift exchange is already in progress.`" NOT_STARTED = "`Error: partners have not been assigned yet.`" -NO_PERMISSION = "`Error: you do not have permissions to do this.`" \ No newline at end of file +NO_PERMISSION = "`Error: you do not have permissions to do this.`" diff --git a/santa-bot.py b/santa-bot.py index d08ed1e..96969e9 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -198,30 +198,27 @@ async def on_message(message): pass else: await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) new_wishlist = "" if(message.content == "s!setwishlisturl"): new_wishlist = "" else: new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) try: - (index, user) = get_participant_object(message.author.id, usr_list) #save to config file config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist config.write() #add the input to the value in the user's class instance user.wishlisturl = new_wishlist - (index, user) = get_participant_object(message.author.id, usr_list) - print(user.wishlisturl) - usr_list = usr_list + try: + await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) except: try: await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - try: - await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: await client.send_message(message.channel, ) @@ -229,9 +226,8 @@ async def on_message(message): elif(message_split[0] == "s!getwishlisturl"): if user_is_participant(message.author.id, usr_list): (index, user) = get_participant_object(message.author.id, usr_list) - print("Wishlist url = " + user.wishlisturl)# debugging ## WORKING JUST FINE try: - await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) ## NOT WORKING + await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: @@ -245,20 +241,20 @@ async def on_message(message): pass else: await client.delete_message(message) - #save to config file + (index, user) = get_participant_object(message.author.id, usr_list) new_prefs = "" if(message.content == "s!setprefs"): new_prefs = "" else: new_prefs = message.content.replace("s!setprefs ", "", 1) try: - (index, user) = get_participant_object(message.author.id, usr_list) - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = new_prefs + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) config.write() #add the input to the value in the user's class instance user.preferences = new_prefs try: - await client.send_message(message.author, "New preferences: {0}".format(user.preferences)) + await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) except: @@ -275,7 +271,7 @@ async def on_message(message): if user_is_participant(message.author.id, usr_list): (index, user) = get_participant_object(message.author.id, usr_list) try: - await client.send_message(message.author, "Current preference(s): {0}" + str(user.preferences)) + await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: From 7082d9ef87f95e15c1b89dd55e09aba494329767 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 22 Nov 2018 02:27:00 -0600 Subject: [PATCH 026/189] message changes, s\!listparticipants only for admin --- santa-bot.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 96969e9..90fa084 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -172,8 +172,8 @@ async def on_message(message): await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) try: await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' - + 'Use `s!setwishlisturl [wishlist urls separated by ;]` to set your wishlist URL (you may also add your mailing address).\n' - + 'Use `s!setprefs [preferences separated by ;]` to set gift preferences for your Secret Santa. Put N/A if none.') + + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' + + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) @@ -390,15 +390,18 @@ async def on_message(message): #lists off all participant names and id's elif(message_split[0] == "s!listparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + if (message.author.top_role == message.server.role_hierarchy[0]): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' + msg = msg + 'Use `s!join` to enter the exchange.```' + await client.send_message(message.channel, msg) else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `s!join` to enter the exchange.```' - await client.send_message(message.channel, msg) + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) #lists total number of participants elif(message_split[0] == "s!totalparticipants"): @@ -407,7 +410,7 @@ async def on_message(message): elif highest_key == 1: await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') else: - await client.send_message(message.channel, 'A total of ' + len(usr_list) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') + await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') #allows a user to have the details of their partner restated elif(message_split[0] == "s!partnerinfo"): @@ -415,7 +418,7 @@ async def on_message(message): (usr_index, user) = get_participant_object(message.author.id, usr_list) (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' - msg = msg + 'Their mailing wishlist URL is ' + partnerobj.wishlisturl + '\n' + msgs = msg + 'Their mailing wishlist URL is ' + partnerobj.wishlisturl + '\n' msg = msg + 'their gift preference is as follows: ' + partnerobj.preferences + '\n' msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" try: @@ -433,11 +436,11 @@ async def on_message(message): elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlisturl = "`s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." + c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__." + c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." c_getprefs = "`s!getprefs` = bot will PM you your current preferences" - c_listparticipants = "`s!listparticipants` = get the current participants" + c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" c_totalparticipants = "`s!totalparticipants` = get the total number of participants" c_partnerinfo = "`s!partnerinfo` = be DM'd your partner's information" c_start = "`s!start` **(admin only)** = assign Secret Santa partners" From f92cf87366c1dffa7935b06dd5f248d168c69beb Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 22 Nov 2018 18:14:26 -0600 Subject: [PATCH 027/189] a few buf fixes, string alignments --- santa-bot.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 90fa084..937910f 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -104,9 +104,9 @@ def partners_are_valid(usrlist): return result ## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist): - if(user_left_during_pause):# there's probably a better boolean logic way but this is easy - user_left_during_pause = False # acknowledge +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge return True has_changed = True @@ -144,7 +144,7 @@ async def on_message(message): if((message.content[0:2]) == "s!"): print(message.content) with open('./files/chat.log', 'a+') as chat_log: - #chat_log.write('[' + message.author.name + message.author.id + ' in ' + message.channel.name + ' at ' + str(message.timestamp) + ']' + message.content + '\n') + chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) pass #ignore messages from the bot itself @@ -154,7 +154,6 @@ async def on_message(message): #event for a user joining the Secret Santa elif(message_split[0] == "s!join"): #check if message author has already joined - print(len(usr_list)) if user_is_participant(message.author.id, usr_list): await client.send_message(message.channel, '`Error: You have already joined.`') #check if the exchange has already started @@ -169,7 +168,7 @@ async def on_message(message): config.write() #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server))) + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") try: await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' @@ -329,12 +328,13 @@ async def on_message(message): usr_list = copy.deepcopy(potential_list) await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") elif not all_fields_complete: - await client.send_message(message.channel, message.author.mention + " `Error: time for some love through harassment`") + await client.send_message(message.channel, message.author.mention + " `Error: Signups incomplete. Time for some love through harassment.`") else: await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) #command allows you to restart without rematching if no change was made while s!paused elif(message_split[0] == "s!restart"): + is_paused = True if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: #first ensure all users have all info submitted all_fields_complete = True @@ -348,7 +348,7 @@ async def on_message(message): await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) if(list_changed): await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") else: @@ -357,8 +357,12 @@ async def on_message(message): config['programData']['exchange_started'] = True config.write() await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") - else: + elif(message.author.top_role != message.server.role_hierarchy[0]): await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + elif(not is_paused): + await client.send_message(message.channel, "`Error: Secret Santa is not paused`") + else: + await client.send_message(message.channel, "idklol") # allows a way to restart the Secret Santa elif(message_split[0] == "s!pause"): @@ -417,9 +421,9 @@ async def on_message(message): if exchange_started and user_is_participant(message.author.id, usr_list): (usr_index, user) = get_participant_object(message.author.id, usr_list) (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = 'Your partner is ' + partnerobj.name + user.partnerid + '\n' - msgs = msg + 'Their mailing wishlist URL is ' + partnerobj.wishlisturl + '\n' - msg = msg + 'their gift preference is as follows: ' + partnerobj.preferences + '\n' + msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' + msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' + msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" try: await client.send_message(message.author, msg) @@ -442,7 +446,7 @@ async def on_message(message): c_getprefs = "`s!getprefs` = bot will PM you your current preferences" c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" c_totalparticipants = "`s!totalparticipants` = get the total number of participants" - c_partnerinfo = "`s!partnerinfo` = be DM'd your partner's information" + c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" c_start = "`s!start` **(admin only)** = assign Secret Santa partners" c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" From 01c0d2f8fc3a95cbd8c60d35e0ead10c6457cf3a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 13:59:59 -0600 Subject: [PATCH 028/189] message changes, bot now uses token from CONFIG.py no longer saves token in .cfg - safer porting of .cfg --- santa-bot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 937910f..48b065c 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -41,7 +41,7 @@ def pref_is_set(self): os.mkdir('./files/') config = ConfigObj() config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False, 'discord_token': CONFIG.discord_token} + config['programData'] = {'exchange_started': False} config['members'] = {} config.write() @@ -113,7 +113,7 @@ def usr_list_changed_during_pause(usrlist, usr_left): for user in usrlist: has_match = (not (str(user.partnerid) == "")) has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not user_left_during_pause) + has_changed = has_changed & (not usr_left) return (not has_changed) ## if not all users have a match #set up discord connection debug logging @@ -372,7 +372,7 @@ async def on_message(message): config['programData']['exchange_started'] = False config.write() is_paused = True - await client.send_message(message.channel, 'Secret Santa has been paused.') + await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') else: await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) @@ -464,7 +464,7 @@ async def on_message(message): elif(message_split[0] == "s!invite"): link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" - await client.send_message(message.channel, "Bot invite link: {0}".format(link)) + await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) else: await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") @@ -480,4 +480,4 @@ async def on_ready(): #event loop and discord initiation -client.run(config['programData']['discord_token']) \ No newline at end of file +client.run(CONFIG.discord_token) \ No newline at end of file From a7462a3fe8a0aca4577bdc195a9b5c2c94300e35 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 15:41:11 -0600 Subject: [PATCH 029/189] updated error --- BOT_ERROR.py | 11 +++++++++-- santa-bot.py | 21 ++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index 6aa4f51..3ad10a6 100755 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1,6 +1,13 @@ DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange." +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.`" +EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" INVALID_INPUT = "`Error: invalid input`" -EXCHANGE_IN_PROGRESS = "`Error: Too late, the gift exchange is already in progress.`" +EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" NOT_STARTED = "`Error: partners have not been assigned yet.`" NO_PERMISSION = "`Error: you do not have permissions to do this.`" +ALREADY_JOINED = "`Error: You have already joined.`" +SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" +NOT_PAUSED = "`Error: Secret Santa is not paused`" +def HAS_NOT_SUBMITTED(usrname): + return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" + \ No newline at end of file diff --git a/santa-bot.py b/santa-bot.py index 48b065c..772c7cc 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -155,7 +155,7 @@ async def on_message(message): elif(message_split[0] == "s!join"): #check if message author has already joined if user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, '`Error: You have already joined.`') + await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) #check if the exchange has already started elif exchange_started: await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) @@ -288,7 +288,8 @@ async def on_message(message): else: all_fields_complete = False try: - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + #await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) @@ -328,7 +329,7 @@ async def on_message(message): usr_list = copy.deepcopy(potential_list) await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") elif not all_fields_complete: - await client.send_message(message.channel, message.author.mention + " `Error: Signups incomplete. Time for some love through harassment.`") + await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) else: await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) @@ -344,7 +345,7 @@ async def on_message(message): else: all_fields_complete = False try: - await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) @@ -360,7 +361,7 @@ async def on_message(message): elif(message.author.top_role != message.server.role_hierarchy[0]): await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) elif(not is_paused): - await client.send_message(message.channel, "`Error: Secret Santa is not paused`") + await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) else: await client.send_message(message.channel, "idklol") @@ -427,13 +428,15 @@ async def on_message(message): msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" try: await client.send_message(message.author, msg) + await client.send_message(message.channel, "The information has been sent to your DMs.") except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - await client.send_message(message.channel, "The information has been sent to your DMs.") - elif not exchange_started: + elif (not exchange_started) and user_is_participant(message.author.id, usr_list): await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) - elif not user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, '`Error: You are not participating in the gift exchange.`') + elif exchange_started and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.UNJOINED) else: await client.send_message(message.channel, "`Error: this shouldn't happen`") From 5a8e78427145535f076aa8b0ec2881dec79bcebe Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 15:49:49 -0600 Subject: [PATCH 030/189] more error updates --- BOT_ERROR.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index 3ad10a6..3363b18 100755 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1,5 +1,5 @@ DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use `s!join` to join the exchange.`" +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" INVALID_INPUT = "`Error: invalid input`" EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" @@ -10,4 +10,3 @@ NOT_PAUSED = "`Error: Secret Santa is not paused`" def HAS_NOT_SUBMITTED(usrname): return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" - \ No newline at end of file From 1756442d78e7a35b7c07444bd3dd36340570d75d Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 15:50:26 -0600 Subject: [PATCH 031/189] set default wishlist and pref --- santa-bot.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 772c7cc..00b94b5 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -22,14 +22,14 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc def wishlisturl_is_set(self): """returns whether the user has set an wishlisturl""" - if self.wishlisturl == '': + if (self.wishlisturl == "None") or (self.wishlisturl == ""): return False else: return True def pref_is_set(self): """returns whether the user has set gift preferences""" - if self.preferences == '': + if (self.preferences == "None") or (self.preferences == ""): return False else: return True @@ -198,9 +198,9 @@ async def on_message(message): else: await client.delete_message(message) (index, user) = get_participant_object(message.author.id, usr_list) - new_wishlist = "" + new_wishlist = "None" if(message.content == "s!setwishlisturl"): - new_wishlist = "" + new_wishlist = "None" else: new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) try: @@ -241,9 +241,9 @@ async def on_message(message): else: await client.delete_message(message) (index, user) = get_participant_object(message.author.id, usr_list) - new_prefs = "" + new_prefs = "None" if(message.content == "s!setprefs"): - new_prefs = "" + new_prefs = "None" else: new_prefs = message.content.replace("s!setprefs ", "", 1) try: @@ -363,7 +363,7 @@ async def on_message(message): elif(not is_paused): await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) else: - await client.send_message(message.channel, "idklol") + await client.send_message(message.channel, "`Error: this shouldn't happen`") # allows a way to restart the Secret Santa elif(message_split[0] == "s!pause"): From 429dcd631564b04bd82b8939f4756c1e95a0550f Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 16:02:59 -0600 Subject: [PATCH 032/189] completed incomplete function --- santa-bot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 00b94b5..49a4d80 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -219,7 +219,7 @@ async def on_message(message): except: await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: - await client.send_message(message.channel, ) + await client.send_message(message.channel, BOT_ERROR.UNJOINED) # get current wishlist URL(s) elif(message_split[0] == "s!getwishlisturl"): @@ -263,7 +263,6 @@ async def on_message(message): await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) else: await client.send_message(message.channel, BOT_ERROR.UNJOINED) - await client.delete_message(message) #get current preferences elif(message_split[0] == "s!getprefs"): From 3046ac8ec387f9b2101a53292526d4749777ac73 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 16:18:32 -0600 Subject: [PATCH 033/189] more error checking --- BOT_ERROR.py | 2 ++ santa-bot.py | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index 3363b18..6e552a1 100755 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -10,3 +10,5 @@ NOT_PAUSED = "`Error: Secret Santa is not paused`" def HAS_NOT_SUBMITTED(usrname): return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +UNREACHABLE = "`Error: this shouldn't happen`" +NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" diff --git a/santa-bot.py b/santa-bot.py index 49a4d80..9349e9d 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -287,7 +287,6 @@ async def on_message(message): else: all_fields_complete = False try: - #await client.send_message(message.author, '`Error: ' + user.name + ' has not submitted either a mailing wishlist URL or gift preferences.`') await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') except: @@ -329,6 +328,10 @@ async def on_message(message): await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") elif not all_fields_complete: await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not(len(usr_list) > 1): + await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) else: await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) @@ -362,7 +365,7 @@ async def on_message(message): elif(not is_paused): await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) else: - await client.send_message(message.channel, "`Error: this shouldn't happen`") + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) # allows a way to restart the Secret Santa elif(message_split[0] == "s!pause"): @@ -437,7 +440,7 @@ async def on_message(message): elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): await client.send_message(message.channel, BOT_ERROR.UNJOINED) else: - await client.send_message(message.channel, "`Error: this shouldn't happen`") + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" From 9c7ffc5d020a25356f1683e987d60ab42b1ec622 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 16:25:21 -0600 Subject: [PATCH 034/189] I'm being told there were changes here but I don't see them --- BOT_ERROR.py | 0 idx_list.py | 0 requirements.txt | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 BOT_ERROR.py mode change 100755 => 100644 idx_list.py mode change 100755 => 100644 requirements.txt diff --git a/BOT_ERROR.py b/BOT_ERROR.py old mode 100755 new mode 100644 diff --git a/idx_list.py b/idx_list.py old mode 100755 new mode 100644 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 From 5bc4ca570bfbde4b37e9e3b81317d63254239295 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 16:28:17 -0600 Subject: [PATCH 035/189] something stupid with line endings --- BOT_ERROR.py | 28 +++++++++++------------ README.md | 58 ++++++++++++++++++++++++------------------------ idx_list.py | 14 ++++++------ requirements.txt | 4 ++-- 4 files changed, 52 insertions(+), 52 deletions(-) mode change 100644 => 100755 BOT_ERROR.py mode change 100644 => 100755 idx_list.py mode change 100644 => 100755 requirements.txt diff --git a/BOT_ERROR.py b/BOT_ERROR.py old mode 100644 new mode 100755 index 6e552a1..2e4675d --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1,14 +1,14 @@ -DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" -EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" -INVALID_INPUT = "`Error: invalid input`" -EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" -NOT_STARTED = "`Error: partners have not been assigned yet.`" -NO_PERMISSION = "`Error: you do not have permissions to do this.`" -ALREADY_JOINED = "`Error: You have already joined.`" -SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" -NOT_PAUSED = "`Error: Secret Santa is not paused`" -def HAS_NOT_SUBMITTED(usrname): - return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" -UNREACHABLE = "`Error: this shouldn't happen`" -NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" +DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" +EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" +INVALID_INPUT = "`Error: invalid input`" +EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" +NOT_STARTED = "`Error: partners have not been assigned yet.`" +NO_PERMISSION = "`Error: you do not have permissions to do this.`" +ALREADY_JOINED = "`Error: You have already joined.`" +SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" +NOT_PAUSED = "`Error: Secret Santa is not paused`" +def HAS_NOT_SUBMITTED(usrname): + return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +UNREACHABLE = "`Error: this shouldn't happen`" +NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" diff --git a/README.md b/README.md index 8a07e32..c378fd2 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ -# SantaBot - -A Discord bot to organize secret santa gift exchanges using the discord.py Python library - -### Requirements - - python 3.5 or later (can be installed [here](https://www.python.org/downloads/)) - - pip3 - -### Steps to run: -1. Run `pip3 install -r requirements.txt` -2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace `discord_token` in CONFIG.py with your bot token -4. Run `python3 santa-bot.py` - -#### Bot Commands: - -- `s!join` = join the Secret Santa -- `s!leave` = leave the Secret Santa -- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. -- `s!getwishlisturl` = bot will PM you your current wishlist -- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. -- `s!getprefs` = bot will PM you your current preferences -- `s!listparticipants` = get the current participants -- `s!totalparticipants` = get the total number of participants -- `s!partnerinfo` = be DM'd your partner's information -- `s!start` **(admin only)** = assign Secret Santa partners -- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners -- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) -- `s!end` **(admin only)** = end Secret Santa +# SantaBot + +A Discord bot to organize secret santa gift exchanges using the discord.py Python library + +### Requirements + - python 3.5 or later (can be installed [here](https://www.python.org/downloads/)) + - pip3 + +### Steps to run: +1. Run `pip3 install -r requirements.txt` +2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). +3. Replace `discord_token` in CONFIG.py with your bot token +4. Run `python3 santa-bot.py` + +#### Bot Commands: + +- `s!join` = join the Secret Santa +- `s!leave` = leave the Secret Santa +- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. +- `s!getwishlisturl` = bot will PM you your current wishlist +- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. +- `s!getprefs` = bot will PM you your current preferences +- `s!listparticipants` = get the current participants +- `s!totalparticipants` = get the total number of participants +- `s!partnerinfo` = be DM'd your partner's information +- `s!start` **(admin only)** = assign Secret Santa partners +- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners +- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) +- `s!end` **(admin only)** = end Secret Santa - `s!ping` = check if bot is alive \ No newline at end of file diff --git a/idx_list.py b/idx_list.py old mode 100644 new mode 100755 index 36cd7a2..af00342 --- a/idx_list.py +++ b/idx_list.py @@ -1,7 +1,7 @@ -NAME = 0 -DISCRIMINATOR = 1 -IDSTR = 2 -USRNUM = 3 -WISHLISTURL = 4 -PREFERENCES = 5 -PARTNERID = 6 +NAME = 0 +DISCRIMINATOR = 1 +IDSTR = 2 +USRNUM = 3 +WISHLISTURL = 4 +PREFERENCES = 5 +PARTNERID = 6 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 index 7c9c9e5..61d52fc --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==0.16.12 -configobj==5.0.6 +discord.py==0.16.12 +configobj==5.0.6 From 1f0139aaaa2c9af3991bceaf5a6ba8e6a3278564 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 23 Nov 2018 17:03:18 -0600 Subject: [PATCH 036/189] updated README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c378fd2..a87939c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Python library ### Requirements - - python 3.5 or later (can be installed [here](https://www.python.org/downloads/)) - - pip3 +- python 3.5 or later (can be installed [here](https://www.python.org/downloads/)) +- pip3 ### Steps to run: 1. Run `pip3 install -r requirements.txt` @@ -20,7 +20,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - `s!getwishlisturl` = bot will PM you your current wishlist - `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. - `s!getprefs` = bot will PM you your current preferences -- `s!listparticipants` = get the current participants +- `s!listparticipants` **(admin only)** = get the current participants - `s!totalparticipants` = get the total number of participants - `s!partnerinfo` = be DM'd your partner's information - `s!start` **(admin only)** = assign Secret Santa partners From eff7b041bae2dcffcf70e389a4c385affaca5da4 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 2 Dec 2018 20:01:35 -0600 Subject: [PATCH 037/189] better organized file --- santa-bot.py | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 9349e9d..73b9ba1 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -34,30 +34,6 @@ def pref_is_set(self): else: return True -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() - -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) - def user_is_participant(usrid, usrlist): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" @@ -116,6 +92,28 @@ def usr_list_changed_during_pause(usrlist, usr_left): has_changed = has_changed & (not usr_left) return (not has_changed) ## if not all users have a match +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) #set up discord connection debug logging client_log = logging.getLogger('discord') client_log.setLevel(logging.DEBUG) From 0108a6ccd11f5a844fe92d55d406bb1e7e92988e Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 2 Dec 2018 20:03:45 -0600 Subject: [PATCH 038/189] cute ping message --- santa-bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 73b9ba1..6ede7f1 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -463,7 +463,7 @@ async def on_message(message): elif(message_split[0] == "s!ping"): """ Pong! """ - await client.send_message(message.channel, "Pong!") + await client.send_message(message.channel, "Pong! I'm alive!") elif(message_split[0] == "s!invite"): link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" From b696d351bee0dd00ddde8306184c5c93705bb70c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 14 Dec 2018 12:52:33 -0600 Subject: [PATCH 039/189] little message of who to contact --- santa-bot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/santa-bot.py b/santa-bot.py index 6ede7f1..acf6e80 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -459,6 +459,7 @@ async def on_message(message): command_string = '' for command in command_list: command_string = command_string + ("{0}\n".format(command)) + command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." await client.send_message(message.channel, command_string) elif(message_split[0] == "s!ping"): From ca35e118fd68311adaf59c93639d11e6c6569027 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 14 Dec 2018 12:56:54 -0600 Subject: [PATCH 040/189] santa-bot runner. Honestly probably not a good idea to use this. --- run_santa.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 run_santa.sh diff --git a/run_santa.sh b/run_santa.sh new file mode 100644 index 0000000..beedc52 --- /dev/null +++ b/run_santa.sh @@ -0,0 +1,10 @@ +#!/bin/bash +### Honestly don't use this unless you KNOW it's not going to fail. +### This script was written because discord.py does not relaunch bots that run into WebSocket errors but the rewrite does. +### This is to get around that. + +while true +do + python3 santa-bot.py + sleep 10 +done \ No newline at end of file From 26e2231985454ff8eb3396a1ed65b637bb082917 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 14 Dec 2018 13:02:01 -0600 Subject: [PATCH 041/189] Shell script executable. Still probably not a good idea. You'll want to debug errors --- run_santa.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 run_santa.sh diff --git a/run_santa.sh b/run_santa.sh old mode 100644 new mode 100755 From c91240f04acbf62e974c847e545e83c0d344a620 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 17 Dec 2018 15:31:54 -0600 Subject: [PATCH 042/189] run script doesn't need to be in dev branch --- run_santa.sh | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100755 run_santa.sh diff --git a/run_santa.sh b/run_santa.sh deleted file mode 100755 index beedc52..0000000 --- a/run_santa.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -### Honestly don't use this unless you KNOW it's not going to fail. -### This script was written because discord.py does not relaunch bots that run into WebSocket errors but the rewrite does. -### This is to get around that. - -while true -do - python3 santa-bot.py - sleep 10 -done \ No newline at end of file From b5e851eb8329a4e4191cbb55a7f07cd58fca15da Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 15 Jan 2019 14:05:53 -0800 Subject: [PATCH 043/189] reconnect --- santa-bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index acf6e80..5b04cb7 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -484,4 +484,4 @@ async def on_ready(): #event loop and discord initiation -client.run(CONFIG.discord_token) \ No newline at end of file +client.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 62c0c7c1075452a25c78b9352051c964d06c8060 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 15 Jan 2019 14:13:09 -0800 Subject: [PATCH 044/189] Not for use in the dev branch. See comments. May not be necessary due to reconnect=True. --- run_santa.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 run_santa.sh diff --git a/run_santa.sh b/run_santa.sh new file mode 100755 index 0000000..beedc52 --- /dev/null +++ b/run_santa.sh @@ -0,0 +1,10 @@ +#!/bin/bash +### Honestly don't use this unless you KNOW it's not going to fail. +### This script was written because discord.py does not relaunch bots that run into WebSocket errors but the rewrite does. +### This is to get around that. + +while true +do + python3 santa-bot.py + sleep 10 +done \ No newline at end of file From 54365975b5fc4496c2d55366df3ad8c2d1a58ed5 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 15 Jan 2019 14:14:59 -0800 Subject: [PATCH 045/189] Not for use in the dev branch. See comments. May not be necessary due to reconnect=True. --- run_santa.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_santa.sh b/run_santa.sh index beedc52..563ac6b 100755 --- a/run_santa.sh +++ b/run_santa.sh @@ -1,6 +1,6 @@ #!/bin/bash ### Honestly don't use this unless you KNOW it's not going to fail. -### This script was written because discord.py does not relaunch bots that run into WebSocket errors but the rewrite does. +### This script was written because discord.py does not relaunch bots that run into WebSocket errors but the rewrite does. Requires two keyboard interrupts to end. ### This is to get around that. while true From 73ca8afc3ba3863e60d2ebc0d5f85014f0ff9bef Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 15 Jan 2019 17:10:08 -0800 Subject: [PATCH 046/189] user inputs client_id to allow user to invitee bot --- CONFIG.py | 4 ++-- README.md | 2 +- santa-bot.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index e93baae..75ef021 100755 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,2 +1,2 @@ -discord_token = YOUR_TOKEN_HERE -bot_owner = BOT_OWNER_ID_HERE +discord_token = "YOUR_TOKEN_HERE" +client_id = "YOUR_CLIENT_ID" diff --git a/README.md b/README.md index a87939c..2ff973d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Steps to run: 1. Run `pip3 install -r requirements.txt` 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace `discord_token` in CONFIG.py with your bot token +3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token 4. Run `python3 santa-bot.py` #### Bot Commands: diff --git a/santa-bot.py b/santa-bot.py index 5b04cb7..54d0497 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -467,7 +467,7 @@ async def on_message(message): await client.send_message(message.channel, "Pong! I'm alive!") elif(message_split[0] == "s!invite"): - link = "https://discordapp.com/oauth2/authorize?client_id=513141948383756289&scope=bot&permissions=67185664" + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) else: From b9ae2d2de6e6de0545a10a63c051f9a2603c7c7f Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 9 Apr 2019 00:35:34 -0500 Subject: [PATCH 047/189] DELETE FILE after rewrite - makes it easier to rewrite with code side-by-side (initial commit for issue #15) --- santa-bot_old.py | 487 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100755 santa-bot_old.py diff --git a/santa-bot_old.py b/santa-bot_old.py new file mode 100755 index 0000000..54d0497 --- /dev/null +++ b/santa-bot_old.py @@ -0,0 +1,487 @@ +import logging +import asyncio +import os.path +import random +import copy +import discord +import CONFIG +import BOT_ERROR +import idx_list +from configobj import ConfigObj + +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.wishlisturl = wishlisturl #string for user's wishlisturl + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if (self.wishlisturl == "None") or (self.wishlisturl == ""): + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if (self.preferences == "None") or (self.preferences == ""): + return False + else: + return True + +def user_is_participant(usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + result = False + for person in usrlist: + if person.idstr == usrid: + result = True + break + return result + +def get_participant_object(usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if person.idstr == usrid: + return (index, person) + break + +def propose_partner_list(usrlist): + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +## checks if the user list changed during a pause +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match + +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + +#initialize client class instance +client = discord.Client() + +#handler for all on_message events +@client.event +async def on_message(message): + #declare global vars + + global curr_server + global usr_list + global highest_key + global exchange_started + global user_left_during_pause + global is_paused + + message_split = message.content.split() + curr_server = message.server + #write all messages to a chatlog + if((message.content[0:2]) == "s!"): + print(message.content) + with open('./files/chat.log', 'a+') as chat_log: + chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) + pass + + #ignore messages from the bot itself + if message.author == client.user: + return + + #event for a user joining the Secret Santa + elif(message_split[0] == "s!join"): + #check if message author has already joined + if user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) + #check if the exchange has already started + elif exchange_started: + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) + else: + #initialize instance of participant class for the author + highest_key = highest_key + 1 + usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) + #write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] + config.write() + + #prompt user about inputting info + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") + try: + await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' + + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' + + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + + #event for a user to leave the Secret Santa list + elif(message_split[0] == "s!leave"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + user_left_during_pause = True + await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #accept wishlisturl of participants + elif(message_split[0] == "s!setwishlisturl"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) + new_wishlist = "None" + if(message.content == "s!setwishlisturl"): + new_wishlist = "None" + else: + new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + #add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + # get current wishlist URL(s) + elif(message_split[0] == "s!getwishlisturl"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + try: + await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #accept gift preferences of participants + elif(message_split[0] == "s!setprefs"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) + new_prefs = "None" + if(message.content == "s!setprefs"): + new_prefs = "None" + else: + new_prefs = message.content.replace("s!setprefs ", "", 1) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #get current preferences + elif(message_split[0] == "s!getprefs"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + try: + await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #command for admin to begin the Secret Santa partner assignment + elif(message_split[0] == "s!start"): + #only allow people with admin permissions to run + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + try: + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + + #select a random partner for each participant if above loop found no empty values and there are enough people to do it + if all_fields_complete & (len(usr_list) > 1): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid(potential_list)): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + #save to config file + print("list passed") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file + config.write() + #tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' + message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' + message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await client.send_message(this_user, santa_message) + except: + await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + #set exchange_started + assoc. cfg value to True + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not(len(usr_list) > 1): + await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #command allows you to restart without rematching if no change was made while s!paused + elif(message_split[0] == "s!restart"): + is_paused = True + if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + try: + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + if(list_changed): + await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") + else: + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") + elif(message.author.top_role != message.server.role_hierarchy[0]): + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + elif(not is_paused): + await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + + # allows a way to restart the Secret Santa + elif(message_split[0] == "s!pause"): + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #allows a way to end the Secret Santa + elif(message_split[0] == "s!end") and not message.channel.is_private: + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + is_paused = False + config['programData']['exchange_started'] = False + highest_key = 0 + del usr_list[:] + print(len(usr_list)) + config['members'].clear() + config.write() + await client.send_message(message.channel, "Secret Santa ended") + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #lists off all participant names and id's + elif(message_split[0] == "s!listparticipants"): + if (message.author.top_role == message.server.role_hierarchy[0]): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' + msg = msg + 'Use `s!join` to enter the exchange.```' + await client.send_message(message.channel, msg) + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #lists total number of participants + elif(message_split[0] == "s!totalparticipants"): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + elif highest_key == 1: + await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') + else: + await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') + + #allows a user to have the details of their partner restated + elif(message_split[0] == "s!partnerinfo"): + if exchange_started and user_is_participant(message.author.id, usr_list): + (usr_index, user) = get_participant_object(message.author.id, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' + msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' + msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + try: + await client.send_message(message.author, msg) + await client.send_message(message.channel, "The information has been sent to your DMs.") + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + elif (not exchange_started) and user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) + elif exchange_started and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + + elif(message_split[0] == "s!help"): + c_join = "`s!join` = join the Secret Santa" + c_leave = "`s!leave` = leave the Secret Santa" + c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." + c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" + c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." + c_getprefs = "`s!getprefs` = bot will PM you your current preferences" + c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" + c_totalparticipants = "`s!totalparticipants` = get the total number of participants" + c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" + c_start = "`s!start` **(admin only)** = assign Secret Santa partners" + c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" + c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" + c_end = "`s!end` **(admin only)** = end Secret Santa" + c_ping = "`s!ping` = check if bot is alive" + command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] + command_string = '' + for command in command_list: + command_string = command_string + ("{0}\n".format(command)) + command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." + await client.send_message(message.channel, command_string) + + elif(message_split[0] == "s!ping"): + """ Pong! """ + await client.send_message(message.channel, "Pong! I'm alive!") + + elif(message_split[0] == "s!invite"): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) + + else: + await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") + +@client.event +async def on_ready(): + """print message when client is connected""" + print('Logged in as') + print(client.user.name) + print(client.user.id) + await client.change_presence(game = discord.Game(name = "s!help")) + print('------') + + +#event loop and discord initiation +client.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 198603e8b7b7ff3101ca6b6f5535c05ab46afbe8 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 9 Apr 2019 16:25:40 -0500 Subject: [PATCH 048/189] exploring the rewrite --- requirements.txt | 2 +- santa-bot.py | 410 +++-------------------------------------------- 2 files changed, 24 insertions(+), 388 deletions(-) diff --git a/requirements.txt b/requirements.txt index 61d52fc..014e523 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==0.16.12 +discord.py==1.0.0 configobj==5.0.6 diff --git a/santa-bot.py b/santa-bot.py index 54d0497..f0956b9 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -4,6 +4,7 @@ import random import copy import discord +from discord.ext import commands import CONFIG import BOT_ERROR import idx_list @@ -92,396 +93,31 @@ def usr_list_changed_during_pause(usrlist, usr_left): has_changed = has_changed & (not usr_left) return (not has_changed) ## if not all users have a match -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) +bot = commands.Bot(command_prefix = CONFIG.prefix) -#initialize client class instance -client = discord.Client() - -#handler for all on_message events -@client.event -async def on_message(message): - #declare global vars - - global curr_server - global usr_list - global highest_key - global exchange_started - global user_left_during_pause - global is_paused - - message_split = message.content.split() - curr_server = message.server - #write all messages to a chatlog - if((message.content[0:2]) == "s!"): - print(message.content) - with open('./files/chat.log', 'a+') as chat_log: - chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) - pass - - #ignore messages from the bot itself - if message.author == client.user: - return - - #event for a user joining the Secret Santa - elif(message_split[0] == "s!join"): - #check if message author has already joined - if user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) - #check if the exchange has already started - elif exchange_started: - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) - else: - #initialize instance of participant class for the author - highest_key = highest_key + 1 - usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) - #write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] - config.write() - - #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") - try: - await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' - + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' - + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #event for a user to leave the Secret Santa list - elif(message_split[0] == "s!leave"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept wishlisturl of participants - elif(message_split[0] == "s!setwishlisturl"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_wishlist = "None" - if(message.content == "s!setwishlisturl"): - new_wishlist = "None" - else: - new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist - config.write() - #add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - # get current wishlist URL(s) - elif(message_split[0] == "s!getwishlisturl"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept gift preferences of participants - elif(message_split[0] == "s!setprefs"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_prefs = "None" - if(message.content == "s!setprefs"): - new_prefs = "None" - else: - new_prefs = message.content.replace("s!setprefs ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #get current preferences - elif(message_split[0] == "s!getprefs"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #command for admin to begin the Secret Santa partner assignment - elif(message_split[0] == "s!start"): - #only allow people with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #select a random partner for each participant if above loop found no empty values and there are enough people to do it - if all_fields_complete & (len(usr_list) > 1): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - #save to config file - print("list passed") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file - config.write() - #tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' - message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' - message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await client.send_message(this_user, santa_message) - except: - await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - #set exchange_started + assoc. cfg value to True - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not(len(usr_list) > 1): - await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #command allows you to restart without rematching if no change was made while s!paused - elif(message_split[0] == "s!restart"): - is_paused = True - if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") - elif(message.author.top_role != message.server.role_hierarchy[0]): - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - elif(not is_paused): - await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - # allows a way to restart the Secret Santa - elif(message_split[0] == "s!pause"): - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #allows a way to end the Secret Santa - elif(message_split[0] == "s!end") and not message.channel.is_private: - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await client.send_message(message.channel, "Secret Santa ended") - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists off all participant names and id's - elif(message_split[0] == "s!listparticipants"): - if (message.author.top_role == message.server.role_hierarchy[0]): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `s!join` to enter the exchange.```' - await client.send_message(message.channel, msg) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists total number of participants - elif(message_split[0] == "s!totalparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') - else: - await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') - - #allows a user to have the details of their partner restated - elif(message_split[0] == "s!partnerinfo"): - if exchange_started and user_is_participant(message.author.id, usr_list): - (usr_index, user) = get_participant_object(message.author.id, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' - msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' - msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - try: - await client.send_message(message.author, msg) - await client.send_message(message.channel, "The information has been sent to your DMs.") - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - elif (not exchange_started) and user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) - elif exchange_started and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - elif(message_split[0] == "s!help"): - c_join = "`s!join` = join the Secret Santa" - c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." - c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." - c_getprefs = "`s!getprefs` = bot will PM you your current preferences" - c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" - c_totalparticipants = "`s!totalparticipants` = get the total number of participants" - c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" - c_start = "`s!start` **(admin only)** = assign Secret Santa partners" - c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" - c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" - c_end = "`s!end` **(admin only)** = end Secret Santa" - c_ping = "`s!ping` = check if bot is alive" - command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] - command_string = '' - for command in command_list: - command_string = command_string + ("{0}\n".format(command)) - command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." - await client.send_message(message.channel, command_string) - - elif(message_split[0] == "s!ping"): - """ Pong! """ - await client.send_message(message.channel, "Pong! I'm alive!") - - elif(message_split[0] == "s!invite"): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) - - else: - await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") - -@client.event +@bot.event async def on_ready(): """print message when client is connected""" print('Logged in as') - print(client.user.name) - print(client.user.id) - await client.change_presence(game = discord.Game(name = "s!help")) + print(bot.user.name) + print(bot.user.id) + game = discord.Game(name = "s!help") + await bot.change_presence(activity = game) print('------') - -#event loop and discord initiation -client.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file +@bot.command() +async def ping(ctx): + ''' + = Basic ping command + ''' + await ctx.send("pong") + +@bot.command() +async def echo(ctx, content:str, number:int = 1): + ''' + [content] + ''' + for x in range(number): + await ctx.send(content) + +bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 220cbcb0734a21ac6be1283efa15c90dc38e9920 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 17:58:18 -0500 Subject: [PATCH 049/189] working branch --- README.md | 2 +- santa-bot.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ff973d..97835af 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Python library ### Requirements -- python 3.5 or later (can be installed [here](https://www.python.org/downloads/)) +- python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) - pip3 ### Steps to run: diff --git a/santa-bot.py b/santa-bot.py index f0956b9..e0b7e0c 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -113,11 +113,28 @@ async def ping(ctx): await ctx.send("pong") @bot.command() -async def echo(ctx, content:str, number:int = 1): +async def echo(ctx, content:str, number:int = 1, *args): ''' [content] ''' for x in range(number): await ctx.send(content) + await ctx.send("{1}".format(len(args))) + +@bot.command() +async def setwishlisturl(ctx, urls:str): + await ctx.send(urls) + return + +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From e4004124a2c8b40a9e67920fae1b44d28b6817d6 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 18:05:12 -0500 Subject: [PATCH 050/189] startup terminal message adjustment --- README.md | 0 santa-bot.py | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) mode change 100644 => 100755 README.md mode change 100644 => 100755 santa-bot.py diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/santa-bot.py b/santa-bot.py old mode 100644 new mode 100755 index 54d0497..9688d6d --- a/santa-bot.py +++ b/santa-bot.py @@ -7,6 +7,7 @@ import CONFIG import BOT_ERROR import idx_list +import datetime.datetime as DT from configobj import ConfigObj class Participant(object): @@ -476,7 +477,10 @@ async def on_message(message): @client.event async def on_ready(): """print message when client is connected""" - print('Logged in as') + currentDT = DT.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") print(client.user.name) print(client.user.id) await client.change_presence(game = discord.Game(name = "s!help")) From b6a9ef4db045df3280f5869aa6a04eca57d252ff Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 18:23:32 -0500 Subject: [PATCH 051/189] shit --- santa-bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 9688d6d..a3600de 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -7,7 +7,7 @@ import CONFIG import BOT_ERROR import idx_list -import datetime.datetime as DT +import datetime as DT from configobj import ConfigObj class Participant(object): @@ -477,7 +477,7 @@ async def on_message(message): @client.event async def on_ready(): """print message when client is connected""" - currentDT = DT.now() + currentDT = DT.datetime.now() print('------') print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) print("Logged in as") From a99670c6c60ebc9e4fa47182fbbaad6b523b51fe Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 18:39:52 -0500 Subject: [PATCH 052/189] shrug --- README.md | 0 santa-bot.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 README.md mode change 100644 => 100755 santa-bot.py diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/santa-bot.py b/santa-bot.py old mode 100644 new mode 100755 From 939642ea1b95db41f9f687126dd54156de3ee902 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 18:49:16 -0500 Subject: [PATCH 053/189] resolved changes in #23. Changes will be integrated in final rewrite --- santa-bot_old.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/santa-bot_old.py b/santa-bot_old.py index 54d0497..a3600de 100755 --- a/santa-bot_old.py +++ b/santa-bot_old.py @@ -7,6 +7,7 @@ import CONFIG import BOT_ERROR import idx_list +import datetime as DT from configobj import ConfigObj class Participant(object): @@ -476,7 +477,10 @@ async def on_message(message): @client.event async def on_ready(): """print message when client is connected""" - print('Logged in as') + currentDT = DT.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") print(client.user.name) print(client.user.id) await client.change_presence(game = discord.Game(name = "s!help")) From 4806a597fe54f9a21f1382b4ce9d6bd1a5791793 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 23:17:19 -0500 Subject: [PATCH 054/189] added prefix to config --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 014e523..78925d9 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==1.0.0 +discord.py==1.2.0 configobj==5.0.6 From 47ae3309604d14954da09cc21650ea9b450572b1 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 20 May 2019 23:49:22 -0500 Subject: [PATCH 055/189] makes prefix stuff easier for rewrite --- CONFIG.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CONFIG.py b/CONFIG.py index 75ef021..9bb2f33 100755 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,2 +1,3 @@ discord_token = "YOUR_TOKEN_HERE" client_id = "YOUR_CLIENT_ID" +prefix = "YOUR_PREFIX_HERE" From 0c7ef80341d75ac397679c038d858bb7473c97ff Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 21 May 2019 01:02:51 -0500 Subject: [PATCH 056/189] #15 implemented two get/set wishlist URL - might change the command name --- santa-bot.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index e0b7e0c..2b30bd8 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -8,6 +8,7 @@ import CONFIG import BOT_ERROR import idx_list +import datetime as DT from configobj import ConfigObj class Participant(object): @@ -55,6 +56,7 @@ def get_participant_object(usrid, usrlist): break def propose_partner_list(usrlist): + """Generate a proposed partner list""" usr_list_copy = copy.deepcopy(usrlist) partners = copy.deepcopy(usrlist) ## propose partner list @@ -73,6 +75,8 @@ def propose_partner_list(usrlist): ## everybody has a partner, nobody's partnered with themselves def partners_are_valid(usrlist): + """Make sure that everybody has a partner + and nobody is partnered with themselves""" if(not usrlist): return False result = True @@ -98,11 +102,13 @@ def usr_list_changed_during_pause(usrlist, usr_left): @bot.event async def on_ready(): """print message when client is connected""" - print('Logged in as') + currentDT = DT.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") print(bot.user.name) print(bot.user.id) - game = discord.Game(name = "s!help") - await bot.change_presence(activity = game) + await bot.change_presence(activity = discord.Game(name = "s!help")) print('------') @bot.command() @@ -110,20 +116,70 @@ async def ping(ctx): ''' = Basic ping command ''' - await ctx.send("pong") + latency = bot.latency + await ctx.send(latency) + +@bot.command() +async def ding(ctx): + await ctx.send("dong") @bot.command() -async def echo(ctx, content:str, number:int = 1, *args): +async def echo(ctx, *content:str): ''' - [content] + [content] = echos back the [content] ''' - for x in range(number): - await ctx.send(content) - await ctx.send("{1}".format(len(args))) + await ctx.send(' '.join(content)) @bot.command() -async def setwishlisturl(ctx, urls:str): - await ctx.send(urls) +async def setwishlisturl(ctx, *urls:str): + ''' + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" ). + ''' + currAuthor = ctx.author + if user_is_participant(currAuthor.id, usr_list): + if(ctx.message.channel.is_private): + pass + else: + await ctx.messsage.delete() + (index, user) = get_participant_object(currAuthor.id, usr_list) + new_wishlist = "None" + if(len(urls) == 0): + pass + else: + new_wishlist = ", ".join(urls) + try: + # save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + # add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def getwishlisturl(ctx): + ''' + = get return current wishlist url + ''' + currAuthor = ctx.author + if user_is_participant(ctx.author.id, usr_list): + (index, user) = get_participant_object(ctx.author.id, usr_list) + try: + await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) return #initialize config file @@ -136,5 +192,23 @@ async def setwishlisturl(ctx, urls:str): config['programData'] = {'exchange_started': False} config['members'] = {} config.write() +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From f23ea246e2fe26139f5c4062ba1b0d883adb7751 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 21 May 2019 01:28:56 -0500 Subject: [PATCH 057/189] tiny ping fix --- santa-bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 2b30bd8..85c9103 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -117,7 +117,7 @@ async def ping(ctx): = Basic ping command ''' latency = bot.latency - await ctx.send(latency) + await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) @bot.command() async def ding(ctx): From 31cac10f80bbebc7fbb01725e239d6a46f2a7197 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 21 May 2019 17:16:21 -0500 Subject: [PATCH 058/189] #15 added join and invite command --- santa-bot.py | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 85c9103..dbf707e 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -108,7 +108,7 @@ async def on_ready(): print("Logged in as") print(bot.user.name) print(bot.user.id) - await bot.change_presence(activity = discord.Game(name = "s!help")) + await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) print('------') @bot.command() @@ -128,10 +128,13 @@ async def echo(ctx, *content:str): ''' [content] = echos back the [content] ''' - await ctx.send(' '.join(content)) + if(len(content) == 0): + pass + else: + await ctx.send(' '.join(content)) @bot.command() -async def setwishlisturl(ctx, *urls:str): +async def setwishlisturl(ctx, *destination:str): ''' [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" ). ''' @@ -143,10 +146,10 @@ async def setwishlisturl(ctx, *urls:str): await ctx.messsage.delete() (index, user) = get_participant_object(currAuthor.id, usr_list) new_wishlist = "None" - if(len(urls) == 0): + if(len(destination) == 0): pass else: - new_wishlist = ", ".join(urls) + new_wishlist = ", ".join(destination) try: # save to config file config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist @@ -182,6 +185,39 @@ async def getwishlisturl(ctx): await ctx.send(BOT_ERROR.UNJOINED) return +@bot.command() +async def join(ctx): + ''' + Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. + ''' + currAuthor = ctx.author + # check if the exchange has already started + if user_is_participant(currAuthor.id, usr_list): + await ctx.send(BOT_ERROR.ALREADY_JOINED) + else: + # initialize instance of Participant for the author + highest_key = highest_key + 1 + usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) + # write details of the class isntance to config and increment total_users + config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] + config.write() + + # prompt user about inputting info + ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") + try: + userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n + Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n + Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild)) + currAuthor.send(userPrompt) + except: + ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + return + +@bot.command() +async def invite(ctx): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await ctx.send_message("Non-testing bot invite link: {0}".format(link)) + #initialize config file try: config = ConfigObj('./files/botdata.cfg', file_error = True) From a8fd760dec4d36b95e7f39ea405d0853e66b24e3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 21 May 2019 17:36:52 -0500 Subject: [PATCH 059/189] got rid of an annoying warning --- santa-bot_old.py | 1 - 1 file changed, 1 deletion(-) diff --git a/santa-bot_old.py b/santa-bot_old.py index a3600de..69ead6a 100755 --- a/santa-bot_old.py +++ b/santa-bot_old.py @@ -130,7 +130,6 @@ def usr_list_changed_during_pause(usrlist, usr_left): async def on_message(message): #declare global vars - global curr_server global usr_list global highest_key global exchange_started From 58f29e3c2f3e7a64e4f5b5ce4ba070db334f38e9 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 21 May 2019 17:37:33 -0500 Subject: [PATCH 060/189] starting on setprefs -- //TODO: draw the rest of the owl --- santa-bot.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index dbf707e..75e5dd8 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -185,6 +185,16 @@ async def getwishlisturl(ctx): await ctx.send(BOT_ERROR.UNJOINED) return +@bot.command() +async def setprefs(ctx, *preferences:str): + currAuthor = ctx.author + if user_is_participant(currAuthor.id, usr_list): + if(ctx.message.channel.is_private): + pass + else: + ctx.message.delete() + return + @bot.command() async def join(ctx): ''' From e9b554dcf478e8a6bc0dc40a8938f891abe956a5 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 12:57:03 -0500 Subject: [PATCH 061/189] \#15 setprefs --- santa-bot.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index 75e5dd8..299bba7 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -193,6 +193,29 @@ async def setprefs(ctx, *preferences:str): pass else: ctx.message.delete() + (index, user) = get_participant_object(currAuthor, usr_list) + new_prefs = "None" + if(len(preferences) == 0): + pass + else: + new_prefs = ", ".join(preferences) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await currAuthor.send("New preferences: {0}".format(new_prefs)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) return @bot.command() From 1cf5e27458203381038b7d95536c852548554501 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 13:05:28 -0500 Subject: [PATCH 062/189] new separator for preferences and wishlist --- santa-bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 299bba7..db408c7 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -136,7 +136,7 @@ async def echo(ctx, *content:str): @bot.command() async def setwishlisturl(ctx, *destination:str): ''' - [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" ). + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). ''' currAuthor = ctx.author if user_is_participant(currAuthor.id, usr_list): @@ -149,7 +149,7 @@ async def setwishlisturl(ctx, *destination:str): if(len(destination) == 0): pass else: - new_wishlist = ", ".join(destination) + new_wishlist = " | ".join(destination) try: # save to config file config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist @@ -198,7 +198,7 @@ async def setprefs(ctx, *preferences:str): if(len(preferences) == 0): pass else: - new_prefs = ", ".join(preferences) + new_prefs = " | ".join(preferences) try: #save to config file config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) From de717125b58ba82ba586a5c976b6f3f6faa151fd Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 13:09:31 -0500 Subject: [PATCH 063/189] help instruction for setprefs + minor fix-ups --- santa-bot.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index db408c7..7f620e2 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -136,7 +136,7 @@ async def echo(ctx, *content:str): @bot.command() async def setwishlisturl(ctx, *destination:str): ''' - [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). ''' currAuthor = ctx.author if user_is_participant(currAuthor.id, usr_list): @@ -172,7 +172,7 @@ async def setwishlisturl(ctx, *destination:str): @bot.command() async def getwishlisturl(ctx): ''' - = get return current wishlist url + Get current wishlist ''' currAuthor = ctx.author if user_is_participant(ctx.author.id, usr_list): @@ -187,6 +187,9 @@ async def getwishlisturl(ctx): @bot.command() async def setprefs(ctx, *preferences:str): + ''' + Set new preferences + ''' currAuthor = ctx.author if user_is_participant(currAuthor.id, usr_list): if(ctx.message.channel.is_private): @@ -249,7 +252,7 @@ async def join(ctx): @bot.command() async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await ctx.send_message("Non-testing bot invite link: {0}".format(link)) + await ctx.send_message("Bot invite link: {0}".format(link)) #initialize config file try: From e34879241d905a819efa2c6fbdc7716fcec7d137 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 13:11:05 -0500 Subject: [PATCH 064/189] \#15 getprefs --- santa-bot.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index 7f620e2..be63fe3 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -221,6 +221,22 @@ async def setprefs(ctx, *preferences:str): await ctx.send(BOT_ERROR.UNJOINED) return +@bot.command() +async def getprefs(ctx): + ''' + Get current preferences + ''' + currAuthor = ctx.author + if user_is_participant(ctx.author.id, usr_list): + (index, user) = get_participant_object(ctx.author.id, usr_list) + try: + await currAuthor.send("Current preference(s): {0}".format(user.preferences)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + @bot.command() async def join(ctx): ''' From b39f4bf8b30d9bdd508807ca7a8ec57ab359adc6 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 13:38:21 -0500 Subject: [PATCH 065/189] refactor of helper functions --- Helpers.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++ santa-bot.py | 69 +++------------------------------------------------- 2 files changed, 66 insertions(+), 65 deletions(-) create mode 100755 Helpers.py diff --git a/Helpers.py b/Helpers.py new file mode 100755 index 0000000..d763c9f --- /dev/null +++ b/Helpers.py @@ -0,0 +1,62 @@ +import copy +import random + +def user_is_participant(usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + result = False + for person in usrlist: + if person.idstr == usrid: + result = True + break + return result + +def get_participant_object(usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if person.idstr == usrid: + return (index, person) + +def propose_partner_list(usrlist): + """Generate a proposed partner list""" + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + """Make sure that everybody has a partner + and nobody is partnered with themselves""" + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +## checks if the user list changed during a pause +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match diff --git a/santa-bot.py b/santa-bot.py index be63fe3..8b3cd98 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,15 +1,15 @@ import logging import asyncio import os.path -import random -import copy +import datetime as DT +from configobj import ConfigObj import discord from discord.ext import commands + +from Helpers import * import CONFIG import BOT_ERROR import idx_list -import datetime as DT -from configobj import ConfigObj class Participant(object): """class defining a participant and info associated with them""" @@ -36,67 +36,6 @@ def pref_is_set(self): else: return True -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - result = False - for person in usrlist: - if person.idstr == usrid: - result = True - break - return result - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if person.idstr == usrid: - return (index, person) - break - -def propose_partner_list(usrlist): - """Generate a proposed partner list""" - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - """Make sure that everybody has a partner - and nobody is partnered with themselves""" - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - bot = commands.Bot(command_prefix = CONFIG.prefix) @bot.event From 9f51a0a9733d7eb182a6ce852742d85e256ab553 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 15:52:24 -0500 Subject: [PATCH 066/189] refactoring --- Participant.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 Participant.py diff --git a/Participant.py b/Participant.py new file mode 100755 index 0000000..c7d0aff --- /dev/null +++ b/Participant.py @@ -0,0 +1,24 @@ +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.wishlisturl = wishlisturl #string for user's wishlisturl + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if (self.wishlisturl == "None") or (self.wishlisturl == ""): + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if (self.preferences == "None") or (self.preferences == ""): + return False + else: + return True From a41887c31df1fbf66558194c31d7fd5207326670 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 16:10:48 -0500 Subject: [PATCH 067/189] #15 start --- santa-bot.py | 88 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 8b3cd98..f73e5bd 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -7,35 +7,11 @@ from discord.ext import commands from Helpers import * +import Participant import CONFIG import BOT_ERROR import idx_list -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def wishlisturl_is_set(self): - """returns whether the user has set an wishlisturl""" - if (self.wishlisturl == "None") or (self.wishlisturl == ""): - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if (self.preferences == "None") or (self.preferences == ""): - return False - else: - return True - bot = commands.Bot(command_prefix = CONFIG.prefix) @bot.event @@ -176,6 +152,68 @@ async def getprefs(ctx): await ctx.send(BOT_ERROR.UNJOINED) return +@bot.command() +async def start(ctx): + currAuthor = ctx.author + if(currAuthor.top_role == ctx.guild.role_hierarchy[0]): + # first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + + # select a random partner for each participant if all information is complete and there are enough people to do it + if(all_fields_complete and (len(usr_list) > 1)): + print("Proposing a partner list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid(potential_list)): + print("Proposing a partner list") + potential_list = propose_partner_list(usr_list) + # save to config file + print("Partner assignment successful") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid + config.write() + # tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_user = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" + message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" + message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await this_user.send(santa_message) + except: + await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + + # mark the exchange as in-progress + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not (len(usr_list) > 1): + await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION) + return + @bot.command() async def join(ctx): ''' From 0890d067f743cd24d06516f0e6bc56a53e77326b Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 16:11:46 -0500 Subject: [PATCH 068/189] more refactoring --- santa-bot.py | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index f73e5bd..a42c324 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -12,6 +12,35 @@ import BOT_ERROR import idx_list +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + bot = commands.Bot(command_prefix = CONFIG.prefix) @bot.event @@ -247,33 +276,4 @@ async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await ctx.send_message("Bot invite link: {0}".format(link)) -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 2f37ce83d38d31284d8d388631e526f6ae620336 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 16:29:18 -0500 Subject: [PATCH 069/189] #15 restart --- santa-bot.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index a42c324..8da1cf1 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -243,6 +243,40 @@ async def start(ctx): await ctx.send(BOT_ERROR.NO_PERMISSION) return +@bot.command() +async def restart(ctx): + is_paused = True + currAuthor = ctx.author + if((currAuthor.top_role == ctx.guild.role_hierarchy[0]) and is_paused): + # ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + if(list_changed): + ctx.send("User list changed during the pause. Partners must be picked again with `s!start`.") + else: + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") + elif(currAuthor.top_role != ctx.guild.role_hierarchy[0]): + await ctx.send(BOT_ERROR.NO_PERMISSION) + elif(not is_paused): + await ctx.send(BOT_ERROR.NOT_PAUSED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + @bot.command() async def join(ctx): ''' From 31f46aa65a891581d1edcf071f9f96ac796f1b86 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 16:35:07 -0500 Subject: [PATCH 070/189] refactoring --- CONFIG.py | 3 +++ santa-bot.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index 9bb2f33..2e31c78 100755 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,3 +1,6 @@ discord_token = "YOUR_TOKEN_HERE" client_id = "YOUR_CLIENT_ID" prefix = "YOUR_PREFIX_HERE" +bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" +cfg_path = bot_folder + "botdata.cfg" +dbg_path = bot_folder + "debug.log" diff --git a/santa-bot.py b/santa-bot.py index 8da1cf1..b364ff3 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -14,11 +14,11 @@ #initialize config file try: - config = ConfigObj('./files/botdata.cfg', file_error = True) + config = ConfigObj(CONFIG.cfg_path, file_error = True) except: - os.mkdir('./files/') + os.mkdir(CONFIG.bot_folder) config = ConfigObj() - config.filename = './files/botdata.cfg' + config.filename = CONFIG.cfg_path config['programData'] = {'exchange_started': False} config['members'] = {} config.write() @@ -37,7 +37,7 @@ #set up discord connection debug logging client_log = logging.getLogger('discord') client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) client_log.addHandler(client_handler) From 6e93c78ddada3b19932e0109cf6a60dd7768befd Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 16:40:38 -0500 Subject: [PATCH 071/189] runtime prefixes in messages --- santa-bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index b364ff3..48cc855 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -262,7 +262,7 @@ async def restart(ctx): await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) if(list_changed): - ctx.send("User list changed during the pause. Partners must be picked again with `s!start`.") + ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) else: exchange_started = True is_paused = False @@ -298,8 +298,8 @@ async def join(ctx): ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") try: userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n - Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild)) + Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n + Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) currAuthor.send(userPrompt) except: ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) From 925238ee6cb10704b70bbdb2808ce33e1f8742e0 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 19:48:39 -0500 Subject: [PATCH 072/189] #15 end, listparticipants, totalparticipants --- santa-bot.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index 48cc855..8ff4e6f 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -277,6 +277,18 @@ async def restart(ctx): await ctx.send(BOT_ERROR.UNREACHABLE) return +@bot.command() +async def pause(ctx): + if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await ctx.send("Secret Santa has been paused. New people may now join.") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION) + return + @bot.command() async def join(ctx): ''' @@ -305,6 +317,47 @@ async def join(ctx): ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) return +@bot.command() +async def end(ctx): + if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): + exchange_started = False + is_paused = False + config['programData']['exchange_started'] = False + highest_key = 0 + del usr_list[:] + print(len(usr_list)) + config['members'].clear() + config.write() + await ctx.send("Secret Santa ended") + else: + ctx.send(BOT_ERROR.NO_PERMISSION) + return + +@bot.command() +async def listparticipants(ctx): + if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): + if(highest_key == 0): + await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" + msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) + else: + ctx.send(BOT_ERROR.NO_PERMISSION) + return + +@bot.command() +async def totalparticipants(ctx): + if highest_key == 0: + await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + elif highest_key == 1: + await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) + return + @bot.command() async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From 6f26ab150bfaac43954d1dd0393fa57ce4b2a529 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 19:57:10 -0500 Subject: [PATCH 073/189] #15 partnerinfo --- santa-bot.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index 8ff4e6f..dbb3563 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -358,6 +358,32 @@ async def totalparticipants(ctx): await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) return +@bot.command() +async def partnerinfo(ctx): + currAuthor = ctx.author + authorIsParticipant = user_is_participant(currAuthor.id, usr_list) + if(exchange_started and authorIsParticipant): + (usr_index, user) = get_participant_object(currAuthor, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" + msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" + msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" + try: + await currAuthor.send(msg) + await ctx.send("The information has been sent to your DMs.") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + elif((not exchange_started) and authorIsParticipant): + await ctx.send(BOT_ERROR.NOT_STARTED) + elif(exchange_started and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif((not exchange_started) and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.UNJOINED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + @bot.command() async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From f908f4b89671acf8d219c9bf84e4e8b2f6256569 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 29 May 2019 19:58:19 -0500 Subject: [PATCH 074/189] TODOs --- santa-bot.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index dbb3563..bb53cf8 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -183,6 +183,7 @@ async def getprefs(ctx): @bot.command() async def start(ctx): + # TODO: add help menu instruction currAuthor = ctx.author if(currAuthor.top_role == ctx.guild.role_hierarchy[0]): # first ensure all users have all info submitted @@ -245,6 +246,7 @@ async def start(ctx): @bot.command() async def restart(ctx): + # TODO: add help menu instruction is_paused = True currAuthor = ctx.author if((currAuthor.top_role == ctx.guild.role_hierarchy[0]) and is_paused): @@ -279,6 +281,7 @@ async def restart(ctx): @bot.command() async def pause(ctx): + # TODO: add help menu instruction if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): exchange_started = False config['programData']['exchange_started'] = False @@ -319,6 +322,7 @@ async def join(ctx): @bot.command() async def end(ctx): + # TODO: add help menu instruction if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): exchange_started = False is_paused = False @@ -335,6 +339,7 @@ async def end(ctx): @bot.command() async def listparticipants(ctx): + # TODO: add help menu instruction if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): if(highest_key == 0): await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) @@ -350,6 +355,7 @@ async def listparticipants(ctx): @bot.command() async def totalparticipants(ctx): + # TODO: add help menu instruction if highest_key == 0: await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) elif highest_key == 1: @@ -360,6 +366,7 @@ async def totalparticipants(ctx): @bot.command() async def partnerinfo(ctx): + # TODO: add help menu instruction currAuthor = ctx.author authorIsParticipant = user_is_participant(currAuthor.id, usr_list) if(exchange_started and authorIsParticipant): @@ -383,7 +390,7 @@ async def partnerinfo(ctx): else: await ctx.send(BOT_ERROR.UNREACHABLE) return - + @bot.command() async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From 8ab0f4cb00f4e5c1bfb11db47e287580885eb770 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 9 Jun 2019 18:37:54 -0500 Subject: [PATCH 075/189] #15 fixed global variable issues --- Helpers.py | 1 + santa-bot.py | 42 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Helpers.py b/Helpers.py index d763c9f..d75fbd3 100755 --- a/Helpers.py +++ b/Helpers.py @@ -1,5 +1,6 @@ import copy import random +from Participant import Participant def user_is_participant(usrid, usrlist): """Takes a discord user ID string and returns whether diff --git a/santa-bot.py b/santa-bot.py index bb53cf8..718be55 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -7,7 +7,7 @@ from discord.ext import commands from Helpers import * -import Participant +from Participant import Participant import CONFIG import BOT_ERROR import idx_list @@ -16,13 +16,22 @@ try: config = ConfigObj(CONFIG.cfg_path, file_error = True) except: - os.mkdir(CONFIG.bot_folder) + try: + os.mkdir(CONFIG.bot_folder) + except: + pass config = ConfigObj() config.filename = CONFIG.cfg_path config['programData'] = {'exchange_started': False} config['members'] = {} config.write() #initialize data from config file +global usr_list +global highest_key +global exchange_started +global user_left_during_pause +global is_paused + server = '' usr_list = [] highest_key = 0 @@ -83,6 +92,7 @@ async def setwishlisturl(ctx, *destination:str): [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). ''' currAuthor = ctx.author + global usr_list if user_is_participant(currAuthor.id, usr_list): if(ctx.message.channel.is_private): pass @@ -119,6 +129,7 @@ async def getwishlisturl(ctx): Get current wishlist ''' currAuthor = ctx.author + global usr_list if user_is_participant(ctx.author.id, usr_list): (index, user) = get_participant_object(ctx.author.id, usr_list) try: @@ -135,6 +146,7 @@ async def setprefs(ctx, *preferences:str): Set new preferences ''' currAuthor = ctx.author + global usr_list if user_is_participant(currAuthor.id, usr_list): if(ctx.message.channel.is_private): pass @@ -171,6 +183,7 @@ async def getprefs(ctx): Get current preferences ''' currAuthor = ctx.author + global usr_list if user_is_participant(ctx.author.id, usr_list): (index, user) = get_participant_object(ctx.author.id, usr_list) try: @@ -185,6 +198,9 @@ async def getprefs(ctx): async def start(ctx): # TODO: add help menu instruction currAuthor = ctx.author + global usr_list + global exchange_started + global is_paused if(currAuthor.top_role == ctx.guild.role_hierarchy[0]): # first ensure all users have all info submitted all_fields_complete = True @@ -247,8 +263,11 @@ async def start(ctx): @bot.command() async def restart(ctx): # TODO: add help menu instruction - is_paused = True currAuthor = ctx.author + global usr_list + global exchange_started + global is_paused + is_paused = True if((currAuthor.top_role == ctx.guild.role_hierarchy[0]) and is_paused): # ensure all users have all info submitted all_fields_complete = True @@ -282,6 +301,8 @@ async def restart(ctx): @bot.command() async def pause(ctx): # TODO: add help menu instruction + global exchange_started + global is_paused if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): exchange_started = False config['programData']['exchange_started'] = False @@ -298,6 +319,8 @@ async def join(ctx): Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. ''' currAuthor = ctx.author + global usr_list + global highest_key # check if the exchange has already started if user_is_participant(currAuthor.id, usr_list): await ctx.send(BOT_ERROR.ALREADY_JOINED) @@ -310,12 +333,12 @@ async def join(ctx): config.write() # prompt user about inputting info - ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") + await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") try: userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) - currAuthor.send(userPrompt) + await currAuthor.send(userPrompt) except: ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) return @@ -323,6 +346,10 @@ async def join(ctx): @bot.command() async def end(ctx): # TODO: add help menu instruction + global usr_list + global highest_key + global exchange_started + global is_paused if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): exchange_started = False is_paused = False @@ -340,6 +367,8 @@ async def end(ctx): @bot.command() async def listparticipants(ctx): # TODO: add help menu instruction + global usr_list + global highest_key if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): if(highest_key == 0): await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) @@ -356,6 +385,7 @@ async def listparticipants(ctx): @bot.command() async def totalparticipants(ctx): # TODO: add help menu instruction + global highest_key if highest_key == 0: await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) elif highest_key == 1: @@ -368,6 +398,8 @@ async def totalparticipants(ctx): async def partnerinfo(ctx): # TODO: add help menu instruction currAuthor = ctx.author + global usr_list + global exchange_started authorIsParticipant = user_is_participant(currAuthor.id, usr_list) if(exchange_started and authorIsParticipant): (usr_index, user) = get_participant_object(currAuthor, usr_list) From 1872a1e85203a905bb30e471c076c2692433b14a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 9 Jun 2019 19:31:28 -0500 Subject: [PATCH 076/189] updated role hierarchy access to syntax of v1.x, added QoL function in Helpers, closes #27 --- BOT_ERROR.py | 3 ++- Helpers.py | 13 ++++++++----- Participant.py | 6 ++++++ santa-bot.py | 22 +++++++++++----------- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index 2e4675d..a564e9d 100755 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -4,7 +4,8 @@ INVALID_INPUT = "`Error: invalid input`" EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" NOT_STARTED = "`Error: partners have not been assigned yet.`" -NO_PERMISSION = "`Error: you do not have permissions to do this.`" +def NO_PERMISSION(role): + return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) ALREADY_JOINED = "`Error: You have already joined.`" SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" NOT_PAUSED = "`Error: Secret Santa is not paused`" diff --git a/Helpers.py b/Helpers.py index d75fbd3..6986e6a 100755 --- a/Helpers.py +++ b/Helpers.py @@ -2,15 +2,18 @@ import random from Participant import Participant +def isListOfParticipants(usrlist): + for usr in usrlist: + if(not isinstance(usr, Participant)): + return False + def user_is_participant(usrid, usrlist): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" - result = False for person in usrlist: - if person.idstr == usrid: - result = True - break - return result + if int(person.idstr) == usrid: + return True + return False def get_participant_object(usrid, usrlist): """takes a discord user ID string and list of diff --git a/Participant.py b/Participant.py index c7d0aff..97c9c8a 100755 --- a/Participant.py +++ b/Participant.py @@ -9,6 +9,12 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc self.preferences = preferences #string for user's gift preferences self.partnerid = partnerid #string for id of partner + def __repr__(self): + return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + + def __str__(self): + return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + def wishlisturl_is_set(self): """returns whether the user has set an wishlisturl""" if (self.wishlisturl == "None") or (self.wishlisturl == ""): diff --git a/santa-bot.py b/santa-bot.py index 718be55..12dbb65 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -201,7 +201,7 @@ async def start(ctx): global usr_list global exchange_started global is_paused - if(currAuthor.top_role == ctx.guild.role_hierarchy[0]): + if(currAuthor.top_role == ctx.guild.roles[-1]): # first ensure all users have all info submitted all_fields_complete = True for user in usr_list: @@ -257,7 +257,7 @@ async def start(ctx): else: await ctx.send(BOT_ERROR.UNREACHABLE) else: - await ctx.send(BOT_ERROR.NO_PERMISSION) + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return @bot.command() @@ -268,7 +268,7 @@ async def restart(ctx): global exchange_started global is_paused is_paused = True - if((currAuthor.top_role == ctx.guild.role_hierarchy[0]) and is_paused): + if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): # ensure all users have all info submitted all_fields_complete = True for user in usr_list: @@ -290,8 +290,8 @@ async def restart(ctx): config['programData']['exchange_started'] = True config.write() await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") - elif(currAuthor.top_role != ctx.guild.role_hierarchy[0]): - await ctx.send(BOT_ERROR.NO_PERMISSION) + elif(currAuthor.top_role != ctx.guild.roles[-1]): + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) elif(not is_paused): await ctx.send(BOT_ERROR.NOT_PAUSED) else: @@ -303,14 +303,14 @@ async def pause(ctx): # TODO: add help menu instruction global exchange_started global is_paused - if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): + if(ctx.author.top_role == ctx.guild.roles[-1]): exchange_started = False config['programData']['exchange_started'] = False config.write() is_paused = True await ctx.send("Secret Santa has been paused. New people may now join.") else: - await ctx.send(BOT_ERROR.NO_PERMISSION) + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return @bot.command() @@ -350,7 +350,7 @@ async def end(ctx): global highest_key global exchange_started global is_paused - if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): + if(ctx.author.top_role == ctx.guild.roles[-1]): exchange_started = False is_paused = False config['programData']['exchange_started'] = False @@ -361,7 +361,7 @@ async def end(ctx): config.write() await ctx.send("Secret Santa ended") else: - ctx.send(BOT_ERROR.NO_PERMISSION) + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return @bot.command() @@ -369,7 +369,7 @@ async def listparticipants(ctx): # TODO: add help menu instruction global usr_list global highest_key - if(ctx.author.top_role == ctx.guild.role_hierarchy[0]): + if(ctx.author.top_role == ctx.guild.roles[-1]): if(highest_key == 0): await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) else: @@ -379,7 +379,7 @@ async def listparticipants(ctx): msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) else: - ctx.send(BOT_ERROR.NO_PERMISSION) + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return @bot.command() From 0d386a3b68d2572b819fbdad568afd37e5576c84 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 9 Jun 2019 19:54:13 -0500 Subject: [PATCH 077/189] #15 added leave --- Helpers.py | 4 ++-- santa-bot.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Helpers.py b/Helpers.py index 6986e6a..65d86b3 100755 --- a/Helpers.py +++ b/Helpers.py @@ -11,7 +11,7 @@ def user_is_participant(usrid, usrlist): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" for person in usrlist: - if int(person.idstr) == usrid: + if person.idstr == str(usrid): return True return False @@ -20,7 +20,7 @@ def get_participant_object(usrid, usrlist): participant objects, and returns the first participant object with matching id.""" for (index, person) in enumerate(usrlist): - if person.idstr == usrid: + if person.idstr == str(usrid): return (index, person) def propose_partner_list(usrlist): diff --git a/santa-bot.py b/santa-bot.py index 12dbb65..1bb263e 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -343,6 +343,24 @@ async def join(ctx): ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) return +@bot.command() +async def leave(ctx): + currAuthor = ctx.author + global usr_list + global is_paused + global user_left_during_pause + if(user_is_participant(currAuthor.id, usr_list)): + (index, user) = get_participant_object(currAuthor.id, usr_list) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + user_left_during_pause = True + await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + @bot.command() async def end(ctx): # TODO: add help menu instruction From f1703573f70a3cecfe6031d569f9ca163a228243 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 9 Jun 2019 20:10:49 -0500 Subject: [PATCH 078/189] fixed leave, fixed multiple leave/join bug --- Helpers.py | 5 +++-- santa-bot.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Helpers.py b/Helpers.py index 65d86b3..dd05748 100755 --- a/Helpers.py +++ b/Helpers.py @@ -10,8 +10,9 @@ def isListOfParticipants(usrlist): def user_is_participant(usrid, usrlist): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" + print(usrlist) for person in usrlist: - if person.idstr == str(usrid): + if int(person.idstr) == usrid: return True return False @@ -20,7 +21,7 @@ def get_participant_object(usrid, usrlist): participant objects, and returns the first participant object with matching id.""" for (index, person) in enumerate(usrlist): - if person.idstr == str(usrid): + if person.idstr == int(usrid): return (index, person) def propose_partner_list(usrlist): diff --git a/santa-bot.py b/santa-bot.py index 1bb263e..5cb39be 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -356,7 +356,7 @@ async def leave(ctx): config.write() if(is_paused): user_left_during_pause = True - await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) else: await ctx.send(BOT_ERROR.UNJOINED) return From 6744f62be19363061a45742cbbfab225213c8af8 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 9 Jun 2019 20:12:06 -0500 Subject: [PATCH 079/189] one todo, minor comment fix --- santa-bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 5cb39be..133e0b2 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -328,7 +328,7 @@ async def join(ctx): # initialize instance of Participant for the author highest_key = highest_key + 1 usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) - # write details of the class isntance to config and increment total_users + # write details of the class instance to config and increment total_users config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] config.write() @@ -345,6 +345,7 @@ async def join(ctx): @bot.command() async def leave(ctx): + # TODO: add help menu instruction currAuthor = ctx.author global usr_list global is_paused From 8aea26d1ee3a6f204facd3d533a6386d3b529960 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 9 Jun 2019 20:57:40 -0500 Subject: [PATCH 080/189] wishlisturl get/set works --- Helpers.py | 6 +++++- santa-bot.py | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Helpers.py b/Helpers.py index dd05748..323331d 100755 --- a/Helpers.py +++ b/Helpers.py @@ -1,5 +1,6 @@ import copy import random +from discord.abc import PrivateChannel from Participant import Participant def isListOfParticipants(usrlist): @@ -21,7 +22,7 @@ def get_participant_object(usrid, usrlist): participant objects, and returns the first participant object with matching id.""" for (index, person) in enumerate(usrlist): - if person.idstr == int(usrid): + if(int(person.idstr) == usrid): return (index, person) def propose_partner_list(usrlist): @@ -65,3 +66,6 @@ def usr_list_changed_during_pause(usrlist, usr_left): has_changed = has_changed & has_match # figures out if all users have a match has_changed = has_changed & (not usr_left) return (not has_changed) ## if not all users have a match + +def channelIsPrivate(channel): + return isinstance(channel, PrivateChannel) diff --git a/santa-bot.py b/santa-bot.py index 133e0b2..c8fff76 100755 --- a/santa-bot.py +++ b/santa-bot.py @@ -94,10 +94,10 @@ async def setwishlisturl(ctx, *destination:str): currAuthor = ctx.author global usr_list if user_is_participant(currAuthor.id, usr_list): - if(ctx.message.channel.is_private): + if(channelIsPrivate(ctx.channel)): pass else: - await ctx.messsage.delete() + await ctx.message.delete() (index, user) = get_participant_object(currAuthor.id, usr_list) new_wishlist = "None" if(len(destination) == 0): @@ -148,10 +148,10 @@ async def setprefs(ctx, *preferences:str): currAuthor = ctx.author global usr_list if user_is_participant(currAuthor.id, usr_list): - if(ctx.message.channel.is_private): + if(channelIsPrivate(ctx.channel)): pass else: - ctx.message.delete() + await ctx.message.delete() (index, user) = get_participant_object(currAuthor, usr_list) new_prefs = "None" if(len(preferences) == 0): From f3ef7379eb88b3f32524bfea304f18af1f91ffb1 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 9 Sep 2019 21:44:43 -0700 Subject: [PATCH 081/189] Line endings --- BOT_ERROR.py | 30 +- Helpers.py | 142 +++---- Participant.py | 60 +-- README.md | 58 +-- idx_list.py | 14 +- requirements.txt | 4 +- run_santa.sh | 0 santa-bot.py | 898 +++++++++++++++++++++---------------------- santa-bot_old.py | 978 +++++++++++++++++++++++------------------------ 9 files changed, 1092 insertions(+), 1092 deletions(-) mode change 100755 => 100644 BOT_ERROR.py mode change 100755 => 100644 Helpers.py mode change 100755 => 100644 Participant.py mode change 100755 => 100644 README.md mode change 100755 => 100644 idx_list.py mode change 100755 => 100644 requirements.txt mode change 100755 => 100644 run_santa.sh mode change 100755 => 100644 santa-bot.py mode change 100755 => 100644 santa-bot_old.py diff --git a/BOT_ERROR.py b/BOT_ERROR.py old mode 100755 new mode 100644 index a564e9d..f13afcd --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1,15 +1,15 @@ -DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" -EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" -INVALID_INPUT = "`Error: invalid input`" -EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" -NOT_STARTED = "`Error: partners have not been assigned yet.`" -def NO_PERMISSION(role): - return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) -ALREADY_JOINED = "`Error: You have already joined.`" -SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" -NOT_PAUSED = "`Error: Secret Santa is not paused`" -def HAS_NOT_SUBMITTED(usrname): - return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" -UNREACHABLE = "`Error: this shouldn't happen`" -NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" +DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" +EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" +INVALID_INPUT = "`Error: invalid input`" +EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" +NOT_STARTED = "`Error: partners have not been assigned yet.`" +def NO_PERMISSION(role): + return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) +ALREADY_JOINED = "`Error: You have already joined.`" +SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" +NOT_PAUSED = "`Error: Secret Santa is not paused`" +def HAS_NOT_SUBMITTED(usrname): + return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +UNREACHABLE = "`Error: this shouldn't happen`" +NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" diff --git a/Helpers.py b/Helpers.py old mode 100755 new mode 100644 index 323331d..e5774ff --- a/Helpers.py +++ b/Helpers.py @@ -1,71 +1,71 @@ -import copy -import random -from discord.abc import PrivateChannel -from Participant import Participant - -def isListOfParticipants(usrlist): - for usr in usrlist: - if(not isinstance(usr, Participant)): - return False - -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - print(usrlist) - for person in usrlist: - if int(person.idstr) == usrid: - return True - return False - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if(int(person.idstr) == usrid): - return (index, person) - -def propose_partner_list(usrlist): - """Generate a proposed partner list""" - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - """Make sure that everybody has a partner - and nobody is partnered with themselves""" - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - -def channelIsPrivate(channel): - return isinstance(channel, PrivateChannel) +import copy +import random +from discord.abc import PrivateChannel +from Participant import Participant + +def isListOfParticipants(usrlist): + for usr in usrlist: + if(not isinstance(usr, Participant)): + return False + +def user_is_participant(usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + print(usrlist) + for person in usrlist: + if int(person.idstr) == usrid: + return True + return False + +def get_participant_object(usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if(int(person.idstr) == usrid): + return (index, person) + +def propose_partner_list(usrlist): + """Generate a proposed partner list""" + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + """Make sure that everybody has a partner + and nobody is partnered with themselves""" + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +## checks if the user list changed during a pause +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match + +def channelIsPrivate(channel): + return isinstance(channel, PrivateChannel) diff --git a/Participant.py b/Participant.py old mode 100755 new mode 100644 index 97c9c8a..371bd43 --- a/Participant.py +++ b/Participant.py @@ -1,30 +1,30 @@ -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def __repr__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" - - def __str__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" - - def wishlisturl_is_set(self): - """returns whether the user has set an wishlisturl""" - if (self.wishlisturl == "None") or (self.wishlisturl == ""): - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if (self.preferences == "None") or (self.preferences == ""): - return False - else: - return True +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.wishlisturl = wishlisturl #string for user's wishlisturl + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def __repr__(self): + return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + + def __str__(self): + return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if (self.wishlisturl == "None") or (self.wishlisturl == ""): + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if (self.preferences == "None") or (self.preferences == ""): + return False + else: + return True diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 97835af..ea2df23 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ -# SantaBot - -A Discord bot to organize secret santa gift exchanges using the discord.py Python library - -### Requirements -- python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) -- pip3 - -### Steps to run: -1. Run `pip3 install -r requirements.txt` -2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token -4. Run `python3 santa-bot.py` - -#### Bot Commands: - -- `s!join` = join the Secret Santa -- `s!leave` = leave the Secret Santa -- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. -- `s!getwishlisturl` = bot will PM you your current wishlist -- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. -- `s!getprefs` = bot will PM you your current preferences -- `s!listparticipants` **(admin only)** = get the current participants -- `s!totalparticipants` = get the total number of participants -- `s!partnerinfo` = be DM'd your partner's information -- `s!start` **(admin only)** = assign Secret Santa partners -- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners -- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) -- `s!end` **(admin only)** = end Secret Santa +# SantaBot + +A Discord bot to organize secret santa gift exchanges using the discord.py Python library + +### Requirements +- python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) +- pip3 + +### Steps to run: +1. Run `pip3 install -r requirements.txt` +2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). +3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token +4. Run `python3 santa-bot.py` + +#### Bot Commands: + +- `s!join` = join the Secret Santa +- `s!leave` = leave the Secret Santa +- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. +- `s!getwishlisturl` = bot will PM you your current wishlist +- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. +- `s!getprefs` = bot will PM you your current preferences +- `s!listparticipants` **(admin only)** = get the current participants +- `s!totalparticipants` = get the total number of participants +- `s!partnerinfo` = be DM'd your partner's information +- `s!start` **(admin only)** = assign Secret Santa partners +- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners +- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) +- `s!end` **(admin only)** = end Secret Santa - `s!ping` = check if bot is alive \ No newline at end of file diff --git a/idx_list.py b/idx_list.py old mode 100755 new mode 100644 index af00342..36cd7a2 --- a/idx_list.py +++ b/idx_list.py @@ -1,7 +1,7 @@ -NAME = 0 -DISCRIMINATOR = 1 -IDSTR = 2 -USRNUM = 3 -WISHLISTURL = 4 -PREFERENCES = 5 -PARTNERID = 6 +NAME = 0 +DISCRIMINATOR = 1 +IDSTR = 2 +USRNUM = 3 +WISHLISTURL = 4 +PREFERENCES = 5 +PARTNERID = 6 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 index 78925d9..8934eda --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==1.2.0 -configobj==5.0.6 +discord.py==1.2.0 +configobj==5.0.6 diff --git a/run_santa.sh b/run_santa.sh old mode 100755 new mode 100644 diff --git a/santa-bot.py b/santa-bot.py old mode 100755 new mode 100644 index c8fff76..31e96ee --- a/santa-bot.py +++ b/santa-bot.py @@ -1,450 +1,450 @@ -import logging -import asyncio -import os.path -import datetime as DT -from configobj import ConfigObj -import discord -from discord.ext import commands - -from Helpers import * -from Participant import Participant -import CONFIG -import BOT_ERROR -import idx_list - -#initialize config file -try: - config = ConfigObj(CONFIG.cfg_path, file_error = True) -except: - try: - os.mkdir(CONFIG.bot_folder) - except: - pass - config = ConfigObj() - config.filename = CONFIG.cfg_path - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -global usr_list -global highest_key -global exchange_started -global user_left_during_pause -global is_paused - -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - -bot = commands.Bot(command_prefix = CONFIG.prefix) - -@bot.event -async def on_ready(): - """print message when client is connected""" - currentDT = DT.datetime.now() - print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) - print("Logged in as") - print(bot.user.name) - print(bot.user.id) - await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) - print('------') - -@bot.command() -async def ping(ctx): - ''' - = Basic ping command - ''' - latency = bot.latency - await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) - -@bot.command() -async def ding(ctx): - await ctx.send("dong") - -@bot.command() -async def echo(ctx, *content:str): - ''' - [content] = echos back the [content] - ''' - if(len(content) == 0): - pass - else: - await ctx.send(' '.join(content)) - -@bot.command() -async def setwishlisturl(ctx, *destination:str): - ''' - [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(currAuthor.id, usr_list): - if(channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() - (index, user) = get_participant_object(currAuthor.id, usr_list) - new_wishlist = "None" - if(len(destination) == 0): - pass - else: - new_wishlist = " | ".join(destination) - try: - # save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist - config.write() - # add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def getwishlisturl(ctx): - ''' - Get current wishlist - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(ctx.author.id, usr_list): - (index, user) = get_participant_object(ctx.author.id, usr_list) - try: - await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def setprefs(ctx, *preferences:str): - ''' - Set new preferences - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(currAuthor.id, usr_list): - if(channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() - (index, user) = get_participant_object(currAuthor, usr_list) - new_prefs = "None" - if(len(preferences) == 0): - pass - else: - new_prefs = " | ".join(preferences) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await currAuthor.send("New preferences: {0}".format(new_prefs)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def getprefs(ctx): - ''' - Get current preferences - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(ctx.author.id, usr_list): - (index, user) = get_participant_object(ctx.author.id, usr_list) - try: - await currAuthor.send("Current preference(s): {0}".format(user.preferences)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def start(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - global is_paused - if(currAuthor.top_role == ctx.guild.roles[-1]): - # first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if(user.wishlisturl_is_set() and user.pref_is_set()): - pass - else: - all_fields_complete = False - try: - await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - - # select a random partner for each participant if all information is complete and there are enough people to do it - if(all_fields_complete and (len(usr_list) > 1)): - print("Proposing a partner list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): - print("Proposing a partner list") - potential_list = propose_partner_list(usr_list) - # save to config file - print("Partner assignment successful") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid - config.write() - # tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_user = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" - message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" - message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await this_user.send(santa_message) - except: - await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - - # mark the exchange as in-progress - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not (len(usr_list) > 1): - await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def restart(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - global is_paused - is_paused = True - if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): - # ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if(user.wishlisturl_is_set() and user.pref_is_set()): - pass - else: - all_fields_complete = False - try: - await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") - elif(currAuthor.top_role != ctx.guild.roles[-1]): - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - elif(not is_paused): - await ctx.send(BOT_ERROR.NOT_PAUSED) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - return - -@bot.command() -async def pause(ctx): - # TODO: add help menu instruction - global exchange_started - global is_paused - if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await ctx.send("Secret Santa has been paused. New people may now join.") - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def join(ctx): - ''' - Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. - ''' - currAuthor = ctx.author - global usr_list - global highest_key - # check if the exchange has already started - if user_is_participant(currAuthor.id, usr_list): - await ctx.send(BOT_ERROR.ALREADY_JOINED) - else: - # initialize instance of Participant for the author - highest_key = highest_key + 1 - usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) - # write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] - config.write() - - # prompt user about inputting info - await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") - try: - userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n - Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) - await currAuthor.send(userPrompt) - except: - ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - return - -@bot.command() -async def leave(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global is_paused - global user_left_during_pause - if(user_is_participant(currAuthor.id, usr_list)): - (index, user) = get_participant_object(currAuthor.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def end(ctx): - # TODO: add help menu instruction - global usr_list - global highest_key - global exchange_started - global is_paused - if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await ctx.send("Secret Santa ended") - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def listparticipants(ctx): - # TODO: add help menu instruction - global usr_list - global highest_key - if(ctx.author.top_role == ctx.guild.roles[-1]): - if(highest_key == 0): - await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" - msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def totalparticipants(ctx): - # TODO: add help menu instruction - global highest_key - if highest_key == 0: - await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - elif highest_key == 1: - await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - else: - await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) - return - -@bot.command() -async def partnerinfo(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - authorIsParticipant = user_is_participant(currAuthor.id, usr_list) - if(exchange_started and authorIsParticipant): - (usr_index, user) = get_participant_object(currAuthor, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" - msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" - msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" - try: - await currAuthor.send(msg) - await ctx.send("The information has been sent to your DMs.") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - elif((not exchange_started) and authorIsParticipant): - await ctx.send(BOT_ERROR.NOT_STARTED) - elif(exchange_started and (not authorIsParticipant)): - await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif((not exchange_started) and (not authorIsParticipant)): - await ctx.send(BOT_ERROR.UNJOINED) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - return - -@bot.command() -async def invite(ctx): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await ctx.send_message("Bot invite link: {0}".format(link)) - +import logging +import asyncio +import os.path +import datetime as DT +from configobj import ConfigObj +import discord +from discord.ext import commands + +from Helpers import * +from Participant import Participant +import CONFIG +import BOT_ERROR +import idx_list + +#initialize config file +try: + config = ConfigObj(CONFIG.cfg_path, file_error = True) +except: + try: + os.mkdir(CONFIG.bot_folder) + except: + pass + config = ConfigObj() + config.filename = CONFIG.cfg_path + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +global usr_list +global highest_key +global exchange_started +global user_left_during_pause +global is_paused + +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + +bot = commands.Bot(command_prefix = CONFIG.prefix) + +@bot.event +async def on_ready(): + """print message when client is connected""" + currentDT = DT.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") + print(bot.user.name) + print(bot.user.id) + await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) + print('------') + +@bot.command() +async def ping(ctx): + ''' + = Basic ping command + ''' + latency = bot.latency + await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) + +@bot.command() +async def ding(ctx): + await ctx.send("dong") + +@bot.command() +async def echo(ctx, *content:str): + ''' + [content] = echos back the [content] + ''' + if(len(content) == 0): + pass + else: + await ctx.send(' '.join(content)) + +@bot.command() +async def setwishlisturl(ctx, *destination:str): + ''' + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(currAuthor.id, usr_list): + if(channelIsPrivate(ctx.channel)): + pass + else: + await ctx.message.delete() + (index, user) = get_participant_object(currAuthor.id, usr_list) + new_wishlist = "None" + if(len(destination) == 0): + pass + else: + new_wishlist = " | ".join(destination) + try: + # save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + # add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def getwishlisturl(ctx): + ''' + Get current wishlist + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(ctx.author.id, usr_list): + (index, user) = get_participant_object(ctx.author.id, usr_list) + try: + await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def setprefs(ctx, *preferences:str): + ''' + Set new preferences + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(currAuthor.id, usr_list): + if(channelIsPrivate(ctx.channel)): + pass + else: + await ctx.message.delete() + (index, user) = get_participant_object(currAuthor, usr_list) + new_prefs = "None" + if(len(preferences) == 0): + pass + else: + new_prefs = " | ".join(preferences) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await currAuthor.send("New preferences: {0}".format(new_prefs)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def getprefs(ctx): + ''' + Get current preferences + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(ctx.author.id, usr_list): + (index, user) = get_participant_object(ctx.author.id, usr_list) + try: + await currAuthor.send("Current preference(s): {0}".format(user.preferences)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def start(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global exchange_started + global is_paused + if(currAuthor.top_role == ctx.guild.roles[-1]): + # first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + + # select a random partner for each participant if all information is complete and there are enough people to do it + if(all_fields_complete and (len(usr_list) > 1)): + print("Proposing a partner list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid(potential_list)): + print("Proposing a partner list") + potential_list = propose_partner_list(usr_list) + # save to config file + print("Partner assignment successful") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid + config.write() + # tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_user = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" + message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" + message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await this_user.send(santa_message) + except: + await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + + # mark the exchange as in-progress + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not (len(usr_list) > 1): + await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def restart(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global exchange_started + global is_paused + is_paused = True + if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): + # ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + if(list_changed): + ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) + else: + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") + elif(currAuthor.top_role != ctx.guild.roles[-1]): + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + elif(not is_paused): + await ctx.send(BOT_ERROR.NOT_PAUSED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + +@bot.command() +async def pause(ctx): + # TODO: add help menu instruction + global exchange_started + global is_paused + if(ctx.author.top_role == ctx.guild.roles[-1]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await ctx.send("Secret Santa has been paused. New people may now join.") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def join(ctx): + ''' + Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. + ''' + currAuthor = ctx.author + global usr_list + global highest_key + # check if the exchange has already started + if user_is_participant(currAuthor.id, usr_list): + await ctx.send(BOT_ERROR.ALREADY_JOINED) + else: + # initialize instance of Participant for the author + highest_key = highest_key + 1 + usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) + # write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] + config.write() + + # prompt user about inputting info + await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") + try: + userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n + Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n + Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) + await currAuthor.send(userPrompt) + except: + ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + return + +@bot.command() +async def leave(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global is_paused + global user_left_during_pause + if(user_is_participant(currAuthor.id, usr_list)): + (index, user) = get_participant_object(currAuthor.id, usr_list) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + user_left_during_pause = True + await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def end(ctx): + # TODO: add help menu instruction + global usr_list + global highest_key + global exchange_started + global is_paused + if(ctx.author.top_role == ctx.guild.roles[-1]): + exchange_started = False + is_paused = False + config['programData']['exchange_started'] = False + highest_key = 0 + del usr_list[:] + print(len(usr_list)) + config['members'].clear() + config.write() + await ctx.send("Secret Santa ended") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def listparticipants(ctx): + # TODO: add help menu instruction + global usr_list + global highest_key + if(ctx.author.top_role == ctx.guild.roles[-1]): + if(highest_key == 0): + await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" + msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def totalparticipants(ctx): + # TODO: add help menu instruction + global highest_key + if highest_key == 0: + await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + elif highest_key == 1: + await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) + return + +@bot.command() +async def partnerinfo(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global exchange_started + authorIsParticipant = user_is_participant(currAuthor.id, usr_list) + if(exchange_started and authorIsParticipant): + (usr_index, user) = get_participant_object(currAuthor, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" + msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" + msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" + try: + await currAuthor.send(msg) + await ctx.send("The information has been sent to your DMs.") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + elif((not exchange_started) and authorIsParticipant): + await ctx.send(BOT_ERROR.NOT_STARTED) + elif(exchange_started and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif((not exchange_started) and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.UNJOINED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + +@bot.command() +async def invite(ctx): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await ctx.send_message("Bot invite link: {0}".format(link)) + bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file diff --git a/santa-bot_old.py b/santa-bot_old.py old mode 100755 new mode 100644 index 69ead6a..cef729d --- a/santa-bot_old.py +++ b/santa-bot_old.py @@ -1,490 +1,490 @@ -import logging -import asyncio -import os.path -import random -import copy -import discord -import CONFIG -import BOT_ERROR -import idx_list -import datetime as DT -from configobj import ConfigObj - -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def wishlisturl_is_set(self): - """returns whether the user has set an wishlisturl""" - if (self.wishlisturl == "None") or (self.wishlisturl == ""): - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if (self.preferences == "None") or (self.preferences == ""): - return False - else: - return True - -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - result = False - for person in usrlist: - if person.idstr == usrid: - result = True - break - return result - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if person.idstr == usrid: - return (index, person) - break - -def propose_partner_list(usrlist): - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - -#initialize client class instance -client = discord.Client() - -#handler for all on_message events -@client.event -async def on_message(message): - #declare global vars - - global usr_list - global highest_key - global exchange_started - global user_left_during_pause - global is_paused - - message_split = message.content.split() - curr_server = message.server - #write all messages to a chatlog - if((message.content[0:2]) == "s!"): - print(message.content) - with open('./files/chat.log', 'a+') as chat_log: - chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) - pass - - #ignore messages from the bot itself - if message.author == client.user: - return - - #event for a user joining the Secret Santa - elif(message_split[0] == "s!join"): - #check if message author has already joined - if user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) - #check if the exchange has already started - elif exchange_started: - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) - else: - #initialize instance of participant class for the author - highest_key = highest_key + 1 - usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) - #write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] - config.write() - - #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") - try: - await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' - + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' - + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #event for a user to leave the Secret Santa list - elif(message_split[0] == "s!leave"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept wishlisturl of participants - elif(message_split[0] == "s!setwishlisturl"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_wishlist = "None" - if(message.content == "s!setwishlisturl"): - new_wishlist = "None" - else: - new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist - config.write() - #add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - # get current wishlist URL(s) - elif(message_split[0] == "s!getwishlisturl"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept gift preferences of participants - elif(message_split[0] == "s!setprefs"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_prefs = "None" - if(message.content == "s!setprefs"): - new_prefs = "None" - else: - new_prefs = message.content.replace("s!setprefs ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #get current preferences - elif(message_split[0] == "s!getprefs"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #command for admin to begin the Secret Santa partner assignment - elif(message_split[0] == "s!start"): - #only allow people with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #select a random partner for each participant if above loop found no empty values and there are enough people to do it - if all_fields_complete & (len(usr_list) > 1): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - #save to config file - print("list passed") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file - config.write() - #tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' - message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' - message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await client.send_message(this_user, santa_message) - except: - await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - #set exchange_started + assoc. cfg value to True - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not(len(usr_list) > 1): - await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #command allows you to restart without rematching if no change was made while s!paused - elif(message_split[0] == "s!restart"): - is_paused = True - if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") - elif(message.author.top_role != message.server.role_hierarchy[0]): - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - elif(not is_paused): - await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - # allows a way to restart the Secret Santa - elif(message_split[0] == "s!pause"): - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #allows a way to end the Secret Santa - elif(message_split[0] == "s!end") and not message.channel.is_private: - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await client.send_message(message.channel, "Secret Santa ended") - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists off all participant names and id's - elif(message_split[0] == "s!listparticipants"): - if (message.author.top_role == message.server.role_hierarchy[0]): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `s!join` to enter the exchange.```' - await client.send_message(message.channel, msg) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists total number of participants - elif(message_split[0] == "s!totalparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') - else: - await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') - - #allows a user to have the details of their partner restated - elif(message_split[0] == "s!partnerinfo"): - if exchange_started and user_is_participant(message.author.id, usr_list): - (usr_index, user) = get_participant_object(message.author.id, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' - msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' - msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - try: - await client.send_message(message.author, msg) - await client.send_message(message.channel, "The information has been sent to your DMs.") - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - elif (not exchange_started) and user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) - elif exchange_started and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - elif(message_split[0] == "s!help"): - c_join = "`s!join` = join the Secret Santa" - c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." - c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." - c_getprefs = "`s!getprefs` = bot will PM you your current preferences" - c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" - c_totalparticipants = "`s!totalparticipants` = get the total number of participants" - c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" - c_start = "`s!start` **(admin only)** = assign Secret Santa partners" - c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" - c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" - c_end = "`s!end` **(admin only)** = end Secret Santa" - c_ping = "`s!ping` = check if bot is alive" - command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] - command_string = '' - for command in command_list: - command_string = command_string + ("{0}\n".format(command)) - command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." - await client.send_message(message.channel, command_string) - - elif(message_split[0] == "s!ping"): - """ Pong! """ - await client.send_message(message.channel, "Pong! I'm alive!") - - elif(message_split[0] == "s!invite"): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) - - else: - await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") - -@client.event -async def on_ready(): - """print message when client is connected""" - currentDT = DT.datetime.now() - print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) - print("Logged in as") - print(client.user.name) - print(client.user.id) - await client.change_presence(game = discord.Game(name = "s!help")) - print('------') - - -#event loop and discord initiation +import logging +import asyncio +import os.path +import random +import copy +import discord +import CONFIG +import BOT_ERROR +import idx_list +import datetime as DT +from configobj import ConfigObj + +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.wishlisturl = wishlisturl #string for user's wishlisturl + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if (self.wishlisturl == "None") or (self.wishlisturl == ""): + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if (self.preferences == "None") or (self.preferences == ""): + return False + else: + return True + +def user_is_participant(usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + result = False + for person in usrlist: + if person.idstr == usrid: + result = True + break + return result + +def get_participant_object(usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if person.idstr == usrid: + return (index, person) + break + +def propose_partner_list(usrlist): + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +## checks if the user list changed during a pause +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match + +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + +#initialize client class instance +client = discord.Client() + +#handler for all on_message events +@client.event +async def on_message(message): + #declare global vars + + global usr_list + global highest_key + global exchange_started + global user_left_during_pause + global is_paused + + message_split = message.content.split() + curr_server = message.server + #write all messages to a chatlog + if((message.content[0:2]) == "s!"): + print(message.content) + with open('./files/chat.log', 'a+') as chat_log: + chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) + pass + + #ignore messages from the bot itself + if message.author == client.user: + return + + #event for a user joining the Secret Santa + elif(message_split[0] == "s!join"): + #check if message author has already joined + if user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) + #check if the exchange has already started + elif exchange_started: + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) + else: + #initialize instance of participant class for the author + highest_key = highest_key + 1 + usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) + #write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] + config.write() + + #prompt user about inputting info + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") + try: + await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' + + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' + + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + + #event for a user to leave the Secret Santa list + elif(message_split[0] == "s!leave"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + user_left_during_pause = True + await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #accept wishlisturl of participants + elif(message_split[0] == "s!setwishlisturl"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) + new_wishlist = "None" + if(message.content == "s!setwishlisturl"): + new_wishlist = "None" + else: + new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + #add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + # get current wishlist URL(s) + elif(message_split[0] == "s!getwishlisturl"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + try: + await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #accept gift preferences of participants + elif(message_split[0] == "s!setprefs"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) + new_prefs = "None" + if(message.content == "s!setprefs"): + new_prefs = "None" + else: + new_prefs = message.content.replace("s!setprefs ", "", 1) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #get current preferences + elif(message_split[0] == "s!getprefs"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + try: + await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #command for admin to begin the Secret Santa partner assignment + elif(message_split[0] == "s!start"): + #only allow people with admin permissions to run + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + try: + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + + #select a random partner for each participant if above loop found no empty values and there are enough people to do it + if all_fields_complete & (len(usr_list) > 1): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid(potential_list)): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + #save to config file + print("list passed") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file + config.write() + #tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' + message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' + message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await client.send_message(this_user, santa_message) + except: + await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + #set exchange_started + assoc. cfg value to True + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not(len(usr_list) > 1): + await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #command allows you to restart without rematching if no change was made while s!paused + elif(message_split[0] == "s!restart"): + is_paused = True + if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + try: + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + if(list_changed): + await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") + else: + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") + elif(message.author.top_role != message.server.role_hierarchy[0]): + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + elif(not is_paused): + await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + + # allows a way to restart the Secret Santa + elif(message_split[0] == "s!pause"): + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #allows a way to end the Secret Santa + elif(message_split[0] == "s!end") and not message.channel.is_private: + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + is_paused = False + config['programData']['exchange_started'] = False + highest_key = 0 + del usr_list[:] + print(len(usr_list)) + config['members'].clear() + config.write() + await client.send_message(message.channel, "Secret Santa ended") + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #lists off all participant names and id's + elif(message_split[0] == "s!listparticipants"): + if (message.author.top_role == message.server.role_hierarchy[0]): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' + msg = msg + 'Use `s!join` to enter the exchange.```' + await client.send_message(message.channel, msg) + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #lists total number of participants + elif(message_split[0] == "s!totalparticipants"): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + elif highest_key == 1: + await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') + else: + await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') + + #allows a user to have the details of their partner restated + elif(message_split[0] == "s!partnerinfo"): + if exchange_started and user_is_participant(message.author.id, usr_list): + (usr_index, user) = get_participant_object(message.author.id, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' + msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' + msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + try: + await client.send_message(message.author, msg) + await client.send_message(message.channel, "The information has been sent to your DMs.") + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + elif (not exchange_started) and user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) + elif exchange_started and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + + elif(message_split[0] == "s!help"): + c_join = "`s!join` = join the Secret Santa" + c_leave = "`s!leave` = leave the Secret Santa" + c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." + c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" + c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." + c_getprefs = "`s!getprefs` = bot will PM you your current preferences" + c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" + c_totalparticipants = "`s!totalparticipants` = get the total number of participants" + c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" + c_start = "`s!start` **(admin only)** = assign Secret Santa partners" + c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" + c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" + c_end = "`s!end` **(admin only)** = end Secret Santa" + c_ping = "`s!ping` = check if bot is alive" + command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] + command_string = '' + for command in command_list: + command_string = command_string + ("{0}\n".format(command)) + command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." + await client.send_message(message.channel, command_string) + + elif(message_split[0] == "s!ping"): + """ Pong! """ + await client.send_message(message.channel, "Pong! I'm alive!") + + elif(message_split[0] == "s!invite"): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) + + else: + await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") + +@client.event +async def on_ready(): + """print message when client is connected""" + currentDT = DT.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") + print(client.user.name) + print(client.user.id) + await client.change_presence(game = discord.Game(name = "s!help")) + print('------') + + +#event loop and discord initiation client.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 75d91bd808f2f2c2558933b14748c0a3875316df Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 9 Sep 2019 21:44:43 -0700 Subject: [PATCH 082/189] Line endings --- BOT_ERROR.py | 30 +- CONFIG.py | 12 +- Helpers.py | 142 +++---- Participant.py | 60 +-- README.md | 58 +-- idx_list.py | 14 +- requirements.txt | 4 +- run_santa.sh | 0 santa-bot.py | 898 +++++++++++++++++++++---------------------- santa-bot_old.py | 978 +++++++++++++++++++++++------------------------ 10 files changed, 1098 insertions(+), 1098 deletions(-) mode change 100755 => 100644 BOT_ERROR.py mode change 100755 => 100644 CONFIG.py mode change 100755 => 100644 Helpers.py mode change 100755 => 100644 Participant.py mode change 100755 => 100644 README.md mode change 100755 => 100644 idx_list.py mode change 100755 => 100644 requirements.txt mode change 100755 => 100644 run_santa.sh mode change 100755 => 100644 santa-bot.py mode change 100755 => 100644 santa-bot_old.py diff --git a/BOT_ERROR.py b/BOT_ERROR.py old mode 100755 new mode 100644 index a564e9d..f13afcd --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -1,15 +1,15 @@ -DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" -EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" -INVALID_INPUT = "`Error: invalid input`" -EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" -NOT_STARTED = "`Error: partners have not been assigned yet.`" -def NO_PERMISSION(role): - return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) -ALREADY_JOINED = "`Error: You have already joined.`" -SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" -NOT_PAUSED = "`Error: Secret Santa is not paused`" -def HAS_NOT_SUBMITTED(usrname): - return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" -UNREACHABLE = "`Error: this shouldn't happen`" -NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" +DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" +EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" +INVALID_INPUT = "`Error: invalid input`" +EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" +NOT_STARTED = "`Error: partners have not been assigned yet.`" +def NO_PERMISSION(role): + return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) +ALREADY_JOINED = "`Error: You have already joined.`" +SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" +NOT_PAUSED = "`Error: Secret Santa is not paused`" +def HAS_NOT_SUBMITTED(usrname): + return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +UNREACHABLE = "`Error: this shouldn't happen`" +NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" diff --git a/CONFIG.py b/CONFIG.py old mode 100755 new mode 100644 index 2e31c78..f8b9c36 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,6 +1,6 @@ -discord_token = "YOUR_TOKEN_HERE" -client_id = "YOUR_CLIENT_ID" -prefix = "YOUR_PREFIX_HERE" -bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" -cfg_path = bot_folder + "botdata.cfg" -dbg_path = bot_folder + "debug.log" +discord_token = "YOUR_TOKEN_HERE" +client_id = "YOUR_CLIENT_ID" +prefix = "YOUR_PREFIX_HERE" +bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" +cfg_path = bot_folder + "botdata.cfg" +dbg_path = bot_folder + "debug.log" diff --git a/Helpers.py b/Helpers.py old mode 100755 new mode 100644 index 323331d..e5774ff --- a/Helpers.py +++ b/Helpers.py @@ -1,71 +1,71 @@ -import copy -import random -from discord.abc import PrivateChannel -from Participant import Participant - -def isListOfParticipants(usrlist): - for usr in usrlist: - if(not isinstance(usr, Participant)): - return False - -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - print(usrlist) - for person in usrlist: - if int(person.idstr) == usrid: - return True - return False - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if(int(person.idstr) == usrid): - return (index, person) - -def propose_partner_list(usrlist): - """Generate a proposed partner list""" - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - """Make sure that everybody has a partner - and nobody is partnered with themselves""" - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - -def channelIsPrivate(channel): - return isinstance(channel, PrivateChannel) +import copy +import random +from discord.abc import PrivateChannel +from Participant import Participant + +def isListOfParticipants(usrlist): + for usr in usrlist: + if(not isinstance(usr, Participant)): + return False + +def user_is_participant(usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + print(usrlist) + for person in usrlist: + if int(person.idstr) == usrid: + return True + return False + +def get_participant_object(usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if(int(person.idstr) == usrid): + return (index, person) + +def propose_partner_list(usrlist): + """Generate a proposed partner list""" + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + """Make sure that everybody has a partner + and nobody is partnered with themselves""" + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +## checks if the user list changed during a pause +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match + +def channelIsPrivate(channel): + return isinstance(channel, PrivateChannel) diff --git a/Participant.py b/Participant.py old mode 100755 new mode 100644 index 97c9c8a..371bd43 --- a/Participant.py +++ b/Participant.py @@ -1,30 +1,30 @@ -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def __repr__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" - - def __str__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" - - def wishlisturl_is_set(self): - """returns whether the user has set an wishlisturl""" - if (self.wishlisturl == "None") or (self.wishlisturl == ""): - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if (self.preferences == "None") or (self.preferences == ""): - return False - else: - return True +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.wishlisturl = wishlisturl #string for user's wishlisturl + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def __repr__(self): + return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + + def __str__(self): + return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if (self.wishlisturl == "None") or (self.wishlisturl == ""): + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if (self.preferences == "None") or (self.preferences == ""): + return False + else: + return True diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 97835af..ea2df23 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ -# SantaBot - -A Discord bot to organize secret santa gift exchanges using the discord.py Python library - -### Requirements -- python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) -- pip3 - -### Steps to run: -1. Run `pip3 install -r requirements.txt` -2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token -4. Run `python3 santa-bot.py` - -#### Bot Commands: - -- `s!join` = join the Secret Santa -- `s!leave` = leave the Secret Santa -- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. -- `s!getwishlisturl` = bot will PM you your current wishlist -- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. -- `s!getprefs` = bot will PM you your current preferences -- `s!listparticipants` **(admin only)** = get the current participants -- `s!totalparticipants` = get the total number of participants -- `s!partnerinfo` = be DM'd your partner's information -- `s!start` **(admin only)** = assign Secret Santa partners -- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners -- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) -- `s!end` **(admin only)** = end Secret Santa +# SantaBot + +A Discord bot to organize secret santa gift exchanges using the discord.py Python library + +### Requirements +- python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) +- pip3 + +### Steps to run: +1. Run `pip3 install -r requirements.txt` +2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). +3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token +4. Run `python3 santa-bot.py` + +#### Bot Commands: + +- `s!join` = join the Secret Santa +- `s!leave` = leave the Secret Santa +- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. +- `s!getwishlisturl` = bot will PM you your current wishlist +- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. +- `s!getprefs` = bot will PM you your current preferences +- `s!listparticipants` **(admin only)** = get the current participants +- `s!totalparticipants` = get the total number of participants +- `s!partnerinfo` = be DM'd your partner's information +- `s!start` **(admin only)** = assign Secret Santa partners +- `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners +- `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) +- `s!end` **(admin only)** = end Secret Santa - `s!ping` = check if bot is alive \ No newline at end of file diff --git a/idx_list.py b/idx_list.py old mode 100755 new mode 100644 index af00342..36cd7a2 --- a/idx_list.py +++ b/idx_list.py @@ -1,7 +1,7 @@ -NAME = 0 -DISCRIMINATOR = 1 -IDSTR = 2 -USRNUM = 3 -WISHLISTURL = 4 -PREFERENCES = 5 -PARTNERID = 6 +NAME = 0 +DISCRIMINATOR = 1 +IDSTR = 2 +USRNUM = 3 +WISHLISTURL = 4 +PREFERENCES = 5 +PARTNERID = 6 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 index 78925d9..8934eda --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==1.2.0 -configobj==5.0.6 +discord.py==1.2.0 +configobj==5.0.6 diff --git a/run_santa.sh b/run_santa.sh old mode 100755 new mode 100644 diff --git a/santa-bot.py b/santa-bot.py old mode 100755 new mode 100644 index c8fff76..31e96ee --- a/santa-bot.py +++ b/santa-bot.py @@ -1,450 +1,450 @@ -import logging -import asyncio -import os.path -import datetime as DT -from configobj import ConfigObj -import discord -from discord.ext import commands - -from Helpers import * -from Participant import Participant -import CONFIG -import BOT_ERROR -import idx_list - -#initialize config file -try: - config = ConfigObj(CONFIG.cfg_path, file_error = True) -except: - try: - os.mkdir(CONFIG.bot_folder) - except: - pass - config = ConfigObj() - config.filename = CONFIG.cfg_path - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -global usr_list -global highest_key -global exchange_started -global user_left_during_pause -global is_paused - -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - -bot = commands.Bot(command_prefix = CONFIG.prefix) - -@bot.event -async def on_ready(): - """print message when client is connected""" - currentDT = DT.datetime.now() - print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) - print("Logged in as") - print(bot.user.name) - print(bot.user.id) - await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) - print('------') - -@bot.command() -async def ping(ctx): - ''' - = Basic ping command - ''' - latency = bot.latency - await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) - -@bot.command() -async def ding(ctx): - await ctx.send("dong") - -@bot.command() -async def echo(ctx, *content:str): - ''' - [content] = echos back the [content] - ''' - if(len(content) == 0): - pass - else: - await ctx.send(' '.join(content)) - -@bot.command() -async def setwishlisturl(ctx, *destination:str): - ''' - [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(currAuthor.id, usr_list): - if(channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() - (index, user) = get_participant_object(currAuthor.id, usr_list) - new_wishlist = "None" - if(len(destination) == 0): - pass - else: - new_wishlist = " | ".join(destination) - try: - # save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist - config.write() - # add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def getwishlisturl(ctx): - ''' - Get current wishlist - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(ctx.author.id, usr_list): - (index, user) = get_participant_object(ctx.author.id, usr_list) - try: - await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def setprefs(ctx, *preferences:str): - ''' - Set new preferences - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(currAuthor.id, usr_list): - if(channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() - (index, user) = get_participant_object(currAuthor, usr_list) - new_prefs = "None" - if(len(preferences) == 0): - pass - else: - new_prefs = " | ".join(preferences) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await currAuthor.send("New preferences: {0}".format(new_prefs)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def getprefs(ctx): - ''' - Get current preferences - ''' - currAuthor = ctx.author - global usr_list - if user_is_participant(ctx.author.id, usr_list): - (index, user) = get_participant_object(ctx.author.id, usr_list) - try: - await currAuthor.send("Current preference(s): {0}".format(user.preferences)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def start(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - global is_paused - if(currAuthor.top_role == ctx.guild.roles[-1]): - # first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if(user.wishlisturl_is_set() and user.pref_is_set()): - pass - else: - all_fields_complete = False - try: - await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - - # select a random partner for each participant if all information is complete and there are enough people to do it - if(all_fields_complete and (len(usr_list) > 1)): - print("Proposing a partner list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): - print("Proposing a partner list") - potential_list = propose_partner_list(usr_list) - # save to config file - print("Partner assignment successful") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid - config.write() - # tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_user = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" - message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" - message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await this_user.send(santa_message) - except: - await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - - # mark the exchange as in-progress - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not (len(usr_list) > 1): - await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def restart(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - global is_paused - is_paused = True - if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): - # ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if(user.wishlisturl_is_set() and user.pref_is_set()): - pass - else: - all_fields_complete = False - try: - await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") - elif(currAuthor.top_role != ctx.guild.roles[-1]): - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - elif(not is_paused): - await ctx.send(BOT_ERROR.NOT_PAUSED) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - return - -@bot.command() -async def pause(ctx): - # TODO: add help menu instruction - global exchange_started - global is_paused - if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await ctx.send("Secret Santa has been paused. New people may now join.") - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def join(ctx): - ''' - Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. - ''' - currAuthor = ctx.author - global usr_list - global highest_key - # check if the exchange has already started - if user_is_participant(currAuthor.id, usr_list): - await ctx.send(BOT_ERROR.ALREADY_JOINED) - else: - # initialize instance of Participant for the author - highest_key = highest_key + 1 - usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) - # write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] - config.write() - - # prompt user about inputting info - await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") - try: - userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n - Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) - await currAuthor.send(userPrompt) - except: - ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - return - -@bot.command() -async def leave(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global is_paused - global user_left_during_pause - if(user_is_participant(currAuthor.id, usr_list)): - (index, user) = get_participant_object(currAuthor.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def end(ctx): - # TODO: add help menu instruction - global usr_list - global highest_key - global exchange_started - global is_paused - if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await ctx.send("Secret Santa ended") - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def listparticipants(ctx): - # TODO: add help menu instruction - global usr_list - global highest_key - if(ctx.author.top_role == ctx.guild.roles[-1]): - if(highest_key == 0): - await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" - msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def totalparticipants(ctx): - # TODO: add help menu instruction - global highest_key - if highest_key == 0: - await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - elif highest_key == 1: - await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - else: - await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) - return - -@bot.command() -async def partnerinfo(ctx): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - authorIsParticipant = user_is_participant(currAuthor.id, usr_list) - if(exchange_started and authorIsParticipant): - (usr_index, user) = get_participant_object(currAuthor, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" - msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" - msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" - try: - await currAuthor.send(msg) - await ctx.send("The information has been sent to your DMs.") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - elif((not exchange_started) and authorIsParticipant): - await ctx.send(BOT_ERROR.NOT_STARTED) - elif(exchange_started and (not authorIsParticipant)): - await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif((not exchange_started) and (not authorIsParticipant)): - await ctx.send(BOT_ERROR.UNJOINED) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - return - -@bot.command() -async def invite(ctx): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await ctx.send_message("Bot invite link: {0}".format(link)) - +import logging +import asyncio +import os.path +import datetime as DT +from configobj import ConfigObj +import discord +from discord.ext import commands + +from Helpers import * +from Participant import Participant +import CONFIG +import BOT_ERROR +import idx_list + +#initialize config file +try: + config = ConfigObj(CONFIG.cfg_path, file_error = True) +except: + try: + os.mkdir(CONFIG.bot_folder) + except: + pass + config = ConfigObj() + config.filename = CONFIG.cfg_path + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +global usr_list +global highest_key +global exchange_started +global user_left_during_pause +global is_paused + +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + +bot = commands.Bot(command_prefix = CONFIG.prefix) + +@bot.event +async def on_ready(): + """print message when client is connected""" + currentDT = DT.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") + print(bot.user.name) + print(bot.user.id) + await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) + print('------') + +@bot.command() +async def ping(ctx): + ''' + = Basic ping command + ''' + latency = bot.latency + await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) + +@bot.command() +async def ding(ctx): + await ctx.send("dong") + +@bot.command() +async def echo(ctx, *content:str): + ''' + [content] = echos back the [content] + ''' + if(len(content) == 0): + pass + else: + await ctx.send(' '.join(content)) + +@bot.command() +async def setwishlisturl(ctx, *destination:str): + ''' + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(currAuthor.id, usr_list): + if(channelIsPrivate(ctx.channel)): + pass + else: + await ctx.message.delete() + (index, user) = get_participant_object(currAuthor.id, usr_list) + new_wishlist = "None" + if(len(destination) == 0): + pass + else: + new_wishlist = " | ".join(destination) + try: + # save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + # add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def getwishlisturl(ctx): + ''' + Get current wishlist + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(ctx.author.id, usr_list): + (index, user) = get_participant_object(ctx.author.id, usr_list) + try: + await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def setprefs(ctx, *preferences:str): + ''' + Set new preferences + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(currAuthor.id, usr_list): + if(channelIsPrivate(ctx.channel)): + pass + else: + await ctx.message.delete() + (index, user) = get_participant_object(currAuthor, usr_list) + new_prefs = "None" + if(len(preferences) == 0): + pass + else: + new_prefs = " | ".join(preferences) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await currAuthor.send("New preferences: {0}".format(new_prefs)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def getprefs(ctx): + ''' + Get current preferences + ''' + currAuthor = ctx.author + global usr_list + if user_is_participant(ctx.author.id, usr_list): + (index, user) = get_participant_object(ctx.author.id, usr_list) + try: + await currAuthor.send("Current preference(s): {0}".format(user.preferences)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def start(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global exchange_started + global is_paused + if(currAuthor.top_role == ctx.guild.roles[-1]): + # first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + + # select a random partner for each participant if all information is complete and there are enough people to do it + if(all_fields_complete and (len(usr_list) > 1)): + print("Proposing a partner list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid(potential_list)): + print("Proposing a partner list") + potential_list = propose_partner_list(usr_list) + # save to config file + print("Partner assignment successful") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid + config.write() + # tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_user = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" + message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" + message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await this_user.send(santa_message) + except: + await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + + # mark the exchange as in-progress + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not (len(usr_list) > 1): + await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def restart(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global exchange_started + global is_paused + is_paused = True + if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): + # ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + if(list_changed): + ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) + else: + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") + elif(currAuthor.top_role != ctx.guild.roles[-1]): + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + elif(not is_paused): + await ctx.send(BOT_ERROR.NOT_PAUSED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + +@bot.command() +async def pause(ctx): + # TODO: add help menu instruction + global exchange_started + global is_paused + if(ctx.author.top_role == ctx.guild.roles[-1]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await ctx.send("Secret Santa has been paused. New people may now join.") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def join(ctx): + ''' + Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. + ''' + currAuthor = ctx.author + global usr_list + global highest_key + # check if the exchange has already started + if user_is_participant(currAuthor.id, usr_list): + await ctx.send(BOT_ERROR.ALREADY_JOINED) + else: + # initialize instance of Participant for the author + highest_key = highest_key + 1 + usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) + # write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] + config.write() + + # prompt user about inputting info + await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") + try: + userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n + Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n + Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) + await currAuthor.send(userPrompt) + except: + ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + return + +@bot.command() +async def leave(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global is_paused + global user_left_during_pause + if(user_is_participant(currAuthor.id, usr_list)): + (index, user) = get_participant_object(currAuthor.id, usr_list) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + user_left_during_pause = True + await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + +@bot.command() +async def end(ctx): + # TODO: add help menu instruction + global usr_list + global highest_key + global exchange_started + global is_paused + if(ctx.author.top_role == ctx.guild.roles[-1]): + exchange_started = False + is_paused = False + config['programData']['exchange_started'] = False + highest_key = 0 + del usr_list[:] + print(len(usr_list)) + config['members'].clear() + config.write() + await ctx.send("Secret Santa ended") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def listparticipants(ctx): + # TODO: add help menu instruction + global usr_list + global highest_key + if(ctx.author.top_role == ctx.guild.roles[-1]): + if(highest_key == 0): + await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" + msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + +@bot.command() +async def totalparticipants(ctx): + # TODO: add help menu instruction + global highest_key + if highest_key == 0: + await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + elif highest_key == 1: + await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) + return + +@bot.command() +async def partnerinfo(ctx): + # TODO: add help menu instruction + currAuthor = ctx.author + global usr_list + global exchange_started + authorIsParticipant = user_is_participant(currAuthor.id, usr_list) + if(exchange_started and authorIsParticipant): + (usr_index, user) = get_participant_object(currAuthor, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" + msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" + msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" + try: + await currAuthor.send(msg) + await ctx.send("The information has been sent to your DMs.") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + elif((not exchange_started) and authorIsParticipant): + await ctx.send(BOT_ERROR.NOT_STARTED) + elif(exchange_started and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif((not exchange_started) and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.UNJOINED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + +@bot.command() +async def invite(ctx): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await ctx.send_message("Bot invite link: {0}".format(link)) + bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file diff --git a/santa-bot_old.py b/santa-bot_old.py old mode 100755 new mode 100644 index 69ead6a..cef729d --- a/santa-bot_old.py +++ b/santa-bot_old.py @@ -1,490 +1,490 @@ -import logging -import asyncio -import os.path -import random -import copy -import discord -import CONFIG -import BOT_ERROR -import idx_list -import datetime as DT -from configobj import ConfigObj - -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def wishlisturl_is_set(self): - """returns whether the user has set an wishlisturl""" - if (self.wishlisturl == "None") or (self.wishlisturl == ""): - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if (self.preferences == "None") or (self.preferences == ""): - return False - else: - return True - -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - result = False - for person in usrlist: - if person.idstr == usrid: - result = True - break - return result - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if person.idstr == usrid: - return (index, person) - break - -def propose_partner_list(usrlist): - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - -#initialize client class instance -client = discord.Client() - -#handler for all on_message events -@client.event -async def on_message(message): - #declare global vars - - global usr_list - global highest_key - global exchange_started - global user_left_during_pause - global is_paused - - message_split = message.content.split() - curr_server = message.server - #write all messages to a chatlog - if((message.content[0:2]) == "s!"): - print(message.content) - with open('./files/chat.log', 'a+') as chat_log: - chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) - pass - - #ignore messages from the bot itself - if message.author == client.user: - return - - #event for a user joining the Secret Santa - elif(message_split[0] == "s!join"): - #check if message author has already joined - if user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) - #check if the exchange has already started - elif exchange_started: - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) - else: - #initialize instance of participant class for the author - highest_key = highest_key + 1 - usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) - #write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] - config.write() - - #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") - try: - await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' - + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' - + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #event for a user to leave the Secret Santa list - elif(message_split[0] == "s!leave"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept wishlisturl of participants - elif(message_split[0] == "s!setwishlisturl"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_wishlist = "None" - if(message.content == "s!setwishlisturl"): - new_wishlist = "None" - else: - new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist - config.write() - #add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - # get current wishlist URL(s) - elif(message_split[0] == "s!getwishlisturl"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept gift preferences of participants - elif(message_split[0] == "s!setprefs"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_prefs = "None" - if(message.content == "s!setprefs"): - new_prefs = "None" - else: - new_prefs = message.content.replace("s!setprefs ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #get current preferences - elif(message_split[0] == "s!getprefs"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #command for admin to begin the Secret Santa partner assignment - elif(message_split[0] == "s!start"): - #only allow people with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #select a random partner for each participant if above loop found no empty values and there are enough people to do it - if all_fields_complete & (len(usr_list) > 1): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - #save to config file - print("list passed") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file - config.write() - #tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' - message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' - message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await client.send_message(this_user, santa_message) - except: - await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - #set exchange_started + assoc. cfg value to True - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not(len(usr_list) > 1): - await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #command allows you to restart without rematching if no change was made while s!paused - elif(message_split[0] == "s!restart"): - is_paused = True - if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") - elif(message.author.top_role != message.server.role_hierarchy[0]): - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - elif(not is_paused): - await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - # allows a way to restart the Secret Santa - elif(message_split[0] == "s!pause"): - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #allows a way to end the Secret Santa - elif(message_split[0] == "s!end") and not message.channel.is_private: - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await client.send_message(message.channel, "Secret Santa ended") - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists off all participant names and id's - elif(message_split[0] == "s!listparticipants"): - if (message.author.top_role == message.server.role_hierarchy[0]): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `s!join` to enter the exchange.```' - await client.send_message(message.channel, msg) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists total number of participants - elif(message_split[0] == "s!totalparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') - else: - await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') - - #allows a user to have the details of their partner restated - elif(message_split[0] == "s!partnerinfo"): - if exchange_started and user_is_participant(message.author.id, usr_list): - (usr_index, user) = get_participant_object(message.author.id, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' - msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' - msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - try: - await client.send_message(message.author, msg) - await client.send_message(message.channel, "The information has been sent to your DMs.") - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - elif (not exchange_started) and user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) - elif exchange_started and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - elif(message_split[0] == "s!help"): - c_join = "`s!join` = join the Secret Santa" - c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." - c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." - c_getprefs = "`s!getprefs` = bot will PM you your current preferences" - c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" - c_totalparticipants = "`s!totalparticipants` = get the total number of participants" - c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" - c_start = "`s!start` **(admin only)** = assign Secret Santa partners" - c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" - c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" - c_end = "`s!end` **(admin only)** = end Secret Santa" - c_ping = "`s!ping` = check if bot is alive" - command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] - command_string = '' - for command in command_list: - command_string = command_string + ("{0}\n".format(command)) - command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." - await client.send_message(message.channel, command_string) - - elif(message_split[0] == "s!ping"): - """ Pong! """ - await client.send_message(message.channel, "Pong! I'm alive!") - - elif(message_split[0] == "s!invite"): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) - - else: - await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") - -@client.event -async def on_ready(): - """print message when client is connected""" - currentDT = DT.datetime.now() - print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) - print("Logged in as") - print(client.user.name) - print(client.user.id) - await client.change_presence(game = discord.Game(name = "s!help")) - print('------') - - -#event loop and discord initiation +import logging +import asyncio +import os.path +import random +import copy +import discord +import CONFIG +import BOT_ERROR +import idx_list +import datetime as DT +from configobj import ConfigObj + +class Participant(object): + """class defining a participant and info associated with them""" + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + self.name = name #string containing name of user + self.discriminator = discriminator #string containing discriminant of user + self.idstr = idstr #string containing id of user + self.usrnum = usrnum #int value referencing the instance's location in usr_list + self.wishlisturl = wishlisturl #string for user's wishlisturl + self.preferences = preferences #string for user's gift preferences + self.partnerid = partnerid #string for id of partner + + def wishlisturl_is_set(self): + """returns whether the user has set an wishlisturl""" + if (self.wishlisturl == "None") or (self.wishlisturl == ""): + return False + else: + return True + + def pref_is_set(self): + """returns whether the user has set gift preferences""" + if (self.preferences == "None") or (self.preferences == ""): + return False + else: + return True + +def user_is_participant(usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + result = False + for person in usrlist: + if person.idstr == usrid: + result = True + break + return result + +def get_participant_object(usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if person.idstr == usrid: + return (index, person) + break + +def propose_partner_list(usrlist): + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + +## everybody has a partner, nobody's partnered with themselves +def partners_are_valid(usrlist): + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + +## checks if the user list changed during a pause +def usr_list_changed_during_pause(usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match + +#initialize config file +try: + config = ConfigObj('./files/botdata.cfg', file_error = True) +except: + os.mkdir('./files/') + config = ConfigObj() + config.filename = './files/botdata.cfg' + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() +#initialize data from config file +server = '' +usr_list = [] +highest_key = 0 +user_left_during_pause = False +is_paused = False +exchange_started = config['programData'].as_bool('exchange_started') +for key in config['members']: + data = config['members'][str(key)] + usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr_list.append(usr) + highest_key = int(key) +#set up discord connection debug logging +client_log = logging.getLogger('discord') +client_log.setLevel(logging.DEBUG) +client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') +client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) +client_log.addHandler(client_handler) + +#initialize client class instance +client = discord.Client() + +#handler for all on_message events +@client.event +async def on_message(message): + #declare global vars + + global usr_list + global highest_key + global exchange_started + global user_left_during_pause + global is_paused + + message_split = message.content.split() + curr_server = message.server + #write all messages to a chatlog + if((message.content[0:2]) == "s!"): + print(message.content) + with open('./files/chat.log', 'a+') as chat_log: + chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) + pass + + #ignore messages from the bot itself + if message.author == client.user: + return + + #event for a user joining the Secret Santa + elif(message_split[0] == "s!join"): + #check if message author has already joined + if user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) + #check if the exchange has already started + elif exchange_started: + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) + else: + #initialize instance of participant class for the author + highest_key = highest_key + 1 + usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) + #write details of the class instance to config and increment total_users + config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] + config.write() + + #prompt user about inputting info + await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") + try: + await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' + + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' + + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + + #event for a user to leave the Secret Santa list + elif(message_split[0] == "s!leave"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + usr_list.remove(user) + popped_user = config['members'].pop(str(user.usrnum)) + config.write() + if(is_paused): + user_left_during_pause = True + await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #accept wishlisturl of participants + elif(message_split[0] == "s!setwishlisturl"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) + new_wishlist = "None" + if(message.content == "s!setwishlisturl"): + new_wishlist = "None" + else: + new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config.write() + #add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + # get current wishlist URL(s) + elif(message_split[0] == "s!getwishlisturl"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + try: + await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #accept gift preferences of participants + elif(message_split[0] == "s!setprefs"): + #check if author has joined the exchange yet + if user_is_participant(message.author.id, usr_list): + if(message.channel.is_private): + pass + else: + await client.delete_message(message) + (index, user) = get_participant_object(message.author.id, usr_list) + new_prefs = "None" + if(message.content == "s!setprefs"): + new_prefs = "None" + else: + new_prefs = message.content.replace("s!setprefs ", "", 1) + try: + #save to config file + config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + except: + try: + await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #get current preferences + elif(message_split[0] == "s!getprefs"): + if user_is_participant(message.author.id, usr_list): + (index, user) = get_participant_object(message.author.id, usr_list) + try: + await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + else: + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + + #command for admin to begin the Secret Santa partner assignment + elif(message_split[0] == "s!start"): + #only allow people with admin permissions to run + if message.author.top_role == message.server.role_hierarchy[0]: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + try: + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + + #select a random partner for each participant if above loop found no empty values and there are enough people to do it + if all_fields_complete & (len(usr_list) > 1): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + while(not partners_are_valid(potential_list)): + print("proposing a list") + potential_list = propose_partner_list(usr_list) + #save to config file + print("list passed") + for user in potential_list: + (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) + (index, partner) = get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file + config.write() + #tell participants who their partner is + this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) + this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' + message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' + message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await client.send_message(this_user, santa_message) + except: + await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + #set exchange_started + assoc. cfg value to True + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + usr_list = copy.deepcopy(potential_list) + await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not(len(usr_list) > 1): + await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #command allows you to restart without rematching if no change was made while s!paused + elif(message_split[0] == "s!restart"): + is_paused = True + if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: + #first ensure all users have all info submitted + all_fields_complete = True + for user in usr_list: + if user.wishlisturl_is_set() and user.pref_is_set(): + pass + else: + all_fields_complete = False + try: + await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + if(list_changed): + await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") + else: + exchange_started = True + is_paused = False + config['programData']['exchange_started'] = True + config.write() + await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") + elif(message.author.top_role != message.server.role_hierarchy[0]): + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + elif(not is_paused): + await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + + # allows a way to restart the Secret Santa + elif(message_split[0] == "s!pause"): + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + config['programData']['exchange_started'] = False + config.write() + is_paused = True + await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #allows a way to end the Secret Santa + elif(message_split[0] == "s!end") and not message.channel.is_private: + #only allow ppl with admin permissions to run + if (message.author.top_role == message.server.role_hierarchy[0]): + exchange_started = False + is_paused = False + config['programData']['exchange_started'] = False + highest_key = 0 + del usr_list[:] + print(len(usr_list)) + config['members'].clear() + config.write() + await client.send_message(message.channel, "Secret Santa ended") + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #lists off all participant names and id's + elif(message_split[0] == "s!listparticipants"): + if (message.author.top_role == message.server.role_hierarchy[0]): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in usr_list: + this_user = discord.User(user = user.name, id = user.idstr) + msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' + msg = msg + 'Use `s!join` to enter the exchange.```' + await client.send_message(message.channel, msg) + else: + await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) + + #lists total number of participants + elif(message_split[0] == "s!totalparticipants"): + if highest_key == 0: + await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') + elif highest_key == 1: + await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') + else: + await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') + + #allows a user to have the details of their partner restated + elif(message_split[0] == "s!partnerinfo"): + if exchange_started and user_is_participant(message.author.id, usr_list): + (usr_index, user) = get_participant_object(message.author.id, usr_list) + (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' + msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' + msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + try: + await client.send_message(message.author, msg) + await client.send_message(message.channel, "The information has been sent to your DMs.") + except: + await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) + elif (not exchange_started) and user_is_participant(message.author.id, usr_list): + await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) + elif exchange_started and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): + await client.send_message(message.channel, BOT_ERROR.UNJOINED) + else: + await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) + + elif(message_split[0] == "s!help"): + c_join = "`s!join` = join the Secret Santa" + c_leave = "`s!leave` = leave the Secret Santa" + c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." + c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" + c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." + c_getprefs = "`s!getprefs` = bot will PM you your current preferences" + c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" + c_totalparticipants = "`s!totalparticipants` = get the total number of participants" + c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" + c_start = "`s!start` **(admin only)** = assign Secret Santa partners" + c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" + c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" + c_end = "`s!end` **(admin only)** = end Secret Santa" + c_ping = "`s!ping` = check if bot is alive" + command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] + command_string = '' + for command in command_list: + command_string = command_string + ("{0}\n".format(command)) + command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." + await client.send_message(message.channel, command_string) + + elif(message_split[0] == "s!ping"): + """ Pong! """ + await client.send_message(message.channel, "Pong! I'm alive!") + + elif(message_split[0] == "s!invite"): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) + + else: + await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") + +@client.event +async def on_ready(): + """print message when client is connected""" + currentDT = DT.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") + print(client.user.name) + print(client.user.id) + await client.change_presence(game = discord.Game(name = "s!help")) + print('------') + + +#event loop and discord initiation client.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 95eb0cd0a84df4894601c2ec8b04133c3a19b288 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 10 Sep 2019 00:34:04 -0700 Subject: [PATCH 083/189] Quick and dirty mute function -- delete in later commit --- BOT_ERROR.py | 0 README.md | 0 idx_list.py | 0 requirements.txt | 0 run_santa.sh | 0 santa-bot.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 62 insertions(+) mode change 100755 => 100644 BOT_ERROR.py mode change 100755 => 100644 README.md mode change 100755 => 100644 idx_list.py mode change 100755 => 100644 requirements.txt mode change 100755 => 100644 run_santa.sh mode change 100755 => 100644 santa-bot.py diff --git a/BOT_ERROR.py b/BOT_ERROR.py old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/idx_list.py b/idx_list.py old mode 100755 new mode 100644 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 diff --git a/run_santa.sh b/run_santa.sh old mode 100755 new mode 100644 diff --git a/santa-bot.py b/santa-bot.py old mode 100755 new mode 100644 index a3600de..619dcc9 --- a/santa-bot.py +++ b/santa-bot.py @@ -93,6 +93,24 @@ def usr_list_changed_during_pause(usrlist, usr_left): has_changed = has_changed & (not usr_left) return (not has_changed) ## if not all users have a match +def member_is_mod(person, mod_list): + if(isinstance(person, discord.Member)): + person_roles = person.roles + person_roles_ids = [] + for person_role in person_roles: + person_roles_ids.append(person_role.id) + lst3 = [value for value in person_roles_ids if value in mod_list] + if(lst3): + return True + return False + +def is_role_in_server(p_role, server_role_hierarchy): + if(isinstance(p_role, str)): + for server_role in server_role_hierarchy: + if((str(server_role) == p_role) or (str(server_role.mention) == p_role)): + return server_role + return False + #initialize config file try: config = ConfigObj('./files/botdata.cfg', file_error = True) @@ -110,6 +128,8 @@ def usr_list_changed_during_pause(usrlist, usr_left): user_left_during_pause = False is_paused = False exchange_started = config['programData'].as_bool('exchange_started') +mods = [] +mute_role = False for key in config['members']: data = config['members'][str(key)] usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) @@ -136,6 +156,9 @@ async def on_message(message): global exchange_started global user_left_during_pause global is_paused + global mods + global mute_role + global client message_split = message.content.split() curr_server = message.server @@ -441,6 +464,45 @@ async def on_message(message): else: await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) +############################################################## +############## DELETE BELOW IN FUTURE COMMIT ################ +############################################################## + elif(message_split[0] == "s!setmods"): + if(message.author.top_role == message.server.role_hierarchy[0]): + message_split = list(message_split) + message_split.pop(0) + for mod_role in message_split: + for server_role in message.server.role_hierarchy: + if(mod_role == server_role.mention): + mods.append(server_role.id) + await client.send_message(message.channel, mods) + else: + await client.send_message(message.channel, "Only the admin can do this") + + elif(message_split[0] == "s!setmute"): + temp_mute_role = is_role_in_server(message_split[1], message.server.role_hierarchy) + print(temp_mute_role) + if(member_is_mod(message.author, mods) and temp_mute_role): + mute_role = temp_mute_role + await client.send_message(message.channel, mute_role) + + + elif(message_split[0] == "s!mute"): + mute_subject = message.mentions[0].id + mute_subject_name = message.mentions[0].name + mute_member = message.server.get_member(mute_subject) + + print(mute_subject_name) + if(not mute_role): + await client.send_message(message.channel, "No mute role set") + else: + await client.add_roles(message.mentions[0], mute_role) + await client.send_message(message.channel, f"**@{mute_subject_name} muted** ") + +############################################################## +############## DELETE ABOVE IN FUTURE COMMIT ################ +############################################################## + elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" c_leave = "`s!leave` = leave the Secret Santa" From c9ebbef6bb72489b3af0a9a8d208a6e932464d9c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Tue, 10 Sep 2019 00:34:04 -0700 Subject: [PATCH 084/189] Quick and dirty mute function -- delete in later commit --- BOT_ERROR.py | 0 CONFIG.py | 0 README.md | 0 idx_list.py | 0 requirements.txt | 0 run_santa.sh | 0 santa-bot.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 62 insertions(+) mode change 100755 => 100644 BOT_ERROR.py mode change 100755 => 100644 CONFIG.py mode change 100755 => 100644 README.md mode change 100755 => 100644 idx_list.py mode change 100755 => 100644 requirements.txt mode change 100755 => 100644 run_santa.sh mode change 100755 => 100644 santa-bot.py diff --git a/BOT_ERROR.py b/BOT_ERROR.py old mode 100755 new mode 100644 diff --git a/CONFIG.py b/CONFIG.py old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/idx_list.py b/idx_list.py old mode 100755 new mode 100644 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 diff --git a/run_santa.sh b/run_santa.sh old mode 100755 new mode 100644 diff --git a/santa-bot.py b/santa-bot.py old mode 100755 new mode 100644 index a3600de..619dcc9 --- a/santa-bot.py +++ b/santa-bot.py @@ -93,6 +93,24 @@ def usr_list_changed_during_pause(usrlist, usr_left): has_changed = has_changed & (not usr_left) return (not has_changed) ## if not all users have a match +def member_is_mod(person, mod_list): + if(isinstance(person, discord.Member)): + person_roles = person.roles + person_roles_ids = [] + for person_role in person_roles: + person_roles_ids.append(person_role.id) + lst3 = [value for value in person_roles_ids if value in mod_list] + if(lst3): + return True + return False + +def is_role_in_server(p_role, server_role_hierarchy): + if(isinstance(p_role, str)): + for server_role in server_role_hierarchy: + if((str(server_role) == p_role) or (str(server_role.mention) == p_role)): + return server_role + return False + #initialize config file try: config = ConfigObj('./files/botdata.cfg', file_error = True) @@ -110,6 +128,8 @@ def usr_list_changed_during_pause(usrlist, usr_left): user_left_during_pause = False is_paused = False exchange_started = config['programData'].as_bool('exchange_started') +mods = [] +mute_role = False for key in config['members']: data = config['members'][str(key)] usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) @@ -136,6 +156,9 @@ async def on_message(message): global exchange_started global user_left_during_pause global is_paused + global mods + global mute_role + global client message_split = message.content.split() curr_server = message.server @@ -441,6 +464,45 @@ async def on_message(message): else: await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) +############################################################## +############## DELETE BELOW IN FUTURE COMMIT ################ +############################################################## + elif(message_split[0] == "s!setmods"): + if(message.author.top_role == message.server.role_hierarchy[0]): + message_split = list(message_split) + message_split.pop(0) + for mod_role in message_split: + for server_role in message.server.role_hierarchy: + if(mod_role == server_role.mention): + mods.append(server_role.id) + await client.send_message(message.channel, mods) + else: + await client.send_message(message.channel, "Only the admin can do this") + + elif(message_split[0] == "s!setmute"): + temp_mute_role = is_role_in_server(message_split[1], message.server.role_hierarchy) + print(temp_mute_role) + if(member_is_mod(message.author, mods) and temp_mute_role): + mute_role = temp_mute_role + await client.send_message(message.channel, mute_role) + + + elif(message_split[0] == "s!mute"): + mute_subject = message.mentions[0].id + mute_subject_name = message.mentions[0].name + mute_member = message.server.get_member(mute_subject) + + print(mute_subject_name) + if(not mute_role): + await client.send_message(message.channel, "No mute role set") + else: + await client.add_roles(message.mentions[0], mute_role) + await client.send_message(message.channel, f"**@{mute_subject_name} muted** ") + +############################################################## +############## DELETE ABOVE IN FUTURE COMMIT ################ +############################################################## + elif(message_split[0] == "s!help"): c_join = "`s!join` = join the Secret Santa" c_leave = "`s!leave` = leave the Secret Santa" From 6bb6bcfd4432ccbd5b92d48e04b9cb4bfca1477c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 22 Sep 2019 22:25:36 -0700 Subject: [PATCH 085/189] #28 refactored into classes // TODO: make usr_list a class variable of SantaBotHelpers --- .gitignore | 1 + Helpers.py | 71 ---------------------------------- Participant.py | 2 +- SantaBotConstants.py | 8 ++++ SantaBotHelpers.py | 92 ++++++++++++++++++++++++++++++++++++++++++++ idx_list.py | 7 ---- requirements.txt | 2 +- santa-bot.py | 77 ++++++++++++++++++------------------ 8 files changed, 143 insertions(+), 117 deletions(-) delete mode 100644 Helpers.py create mode 100644 SantaBotConstants.py create mode 100644 SantaBotHelpers.py delete mode 100644 idx_list.py diff --git a/.gitignore b/.gitignore index 30d7612..5dbfb0a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ CONFIG.py /__pycache__ Procfile +*.code-workspace diff --git a/Helpers.py b/Helpers.py deleted file mode 100644 index e5774ff..0000000 --- a/Helpers.py +++ /dev/null @@ -1,71 +0,0 @@ -import copy -import random -from discord.abc import PrivateChannel -from Participant import Participant - -def isListOfParticipants(usrlist): - for usr in usrlist: - if(not isinstance(usr, Participant)): - return False - -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - print(usrlist) - for person in usrlist: - if int(person.idstr) == usrid: - return True - return False - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if(int(person.idstr) == usrid): - return (index, person) - -def propose_partner_list(usrlist): - """Generate a proposed partner list""" - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - """Make sure that everybody has a partner - and nobody is partnered with themselves""" - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - -def channelIsPrivate(channel): - return isinstance(channel, PrivateChannel) diff --git a/Participant.py b/Participant.py index 371bd43..624c06b 100644 --- a/Participant.py +++ b/Participant.py @@ -5,7 +5,7 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc self.discriminator = discriminator #string containing discriminant of user self.idstr = idstr #string containing id of user self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl + self.wishlisturl = wishlisturl #string for user's wishlisturl self.preferences = preferences #string for user's gift preferences self.partnerid = partnerid #string for id of partner diff --git a/SantaBotConstants.py b/SantaBotConstants.py new file mode 100644 index 0000000..8a3468a --- /dev/null +++ b/SantaBotConstants.py @@ -0,0 +1,8 @@ +class SantaBotConstants(): + NAME = 0 + DISCRIMINATOR = 1 + IDSTR = 2 + USRNUM = 3 + WISHLISTURL = 4 + PREFERENCES = 5 + PARTNERID = 6 diff --git a/SantaBotHelpers.py b/SantaBotHelpers.py new file mode 100644 index 0000000..43a793e --- /dev/null +++ b/SantaBotHelpers.py @@ -0,0 +1,92 @@ +import copy +import random +from discord import Member +from discord.abc import PrivateChannel +from Participant import Participant + +class SantaBotHelpers(): + + def isListOfParticipants(self, usrlist): + for usr in usrlist: + if(not isinstance(usr, Participant)): + return False + + def user_is_participant(self, usrid, usrlist): + """Takes a discord user ID string and returns whether + a user with that ID is in usr_list""" + print(usrlist) + for person in usrlist: + if int(person.idstr) == usrid: + return True + return False + + def get_participant_object(self, usrid, usrlist): + """takes a discord user ID string and list of + participant objects, and returns the first + participant object with matching id.""" + for (index, person) in enumerate(usrlist): + if(int(person.idstr) == usrid): + return (index, person) + + def propose_partner_list(self, usrlist): + """Generate a proposed partner list""" + usr_list_copy = copy.deepcopy(usrlist) + partners = copy.deepcopy(usrlist) + ## propose partner list + for user in usr_list_copy: + candidates = partners + partner = candidates[random.randint(0, len(candidates) - 1)] + while(partner.idstr == user.idstr): + partner = candidates[random.randint(0, len(candidates) - 1)] + if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): + break # no choice but to pick yourself (this will be declared invalid later) + #remove user's partner from list of possible partners + partners.remove(partner) + #save the partner id to the participant's class instance + user.partnerid = partner.idstr + return usr_list_copy + + ## everybody has a partner, nobody's partnered with themselves + def partners_are_valid(self, usrlist): + """Make sure that everybody has a partner + and nobody is partnered with themselves""" + if(not usrlist): + return False + result = True + for user in usrlist: + result = result & (user.partnerid != '') & (user.partnerid != user.idstr) + return result + + ## checks if the user list changed during a pause + def usr_list_changed_during_pause(self, usrlist, usr_left): + if(usr_left):# there's probably a better boolean logic way but this is easy + usr_left = False # acknowledge + return True + + has_changed = True + for user in usrlist: + has_match = (not (str(user.partnerid) == "")) + has_changed = has_changed & has_match # figures out if all users have a match + has_changed = has_changed & (not usr_left) + return (not has_changed) ## if not all users have a match + + def channelIsPrivate(self, channel): + return isinstance(channel, PrivateChannel) + + def member_is_mod(self, person, mod_list): + if(isinstance(person, Member)): + person_roles = person.roles + person_roles_ids = [] + for person_role in person_roles: + person_roles_ids.append(person_role.id) + lst3 = [value for value in person_roles_ids if value in mod_list] + if(lst3): + return True + return False + + def is_role_in_server(self, p_role, server_role_hierarchy): + if(isinstance(p_role, str)): + for server_role in server_role_hierarchy: + if((str(server_role) == p_role) or (str(server_role.mention) == p_role)): + return server_role + return False diff --git a/idx_list.py b/idx_list.py deleted file mode 100644 index 36cd7a2..0000000 --- a/idx_list.py +++ /dev/null @@ -1,7 +0,0 @@ -NAME = 0 -DISCRIMINATOR = 1 -IDSTR = 2 -USRNUM = 3 -WISHLISTURL = 4 -PREFERENCES = 5 -PARTNERID = 6 diff --git a/requirements.txt b/requirements.txt index 8934eda..3182b3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==1.2.0 +discord.py==1.2.3 configobj==5.0.6 diff --git a/santa-bot.py b/santa-bot.py index 31e96ee..3a17500 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,16 +1,18 @@ -import logging import asyncio -import os.path import datetime as DT -from configobj import ConfigObj +import logging +import os.path +import copy + import discord +from configobj import ConfigObj from discord.ext import commands -from Helpers import * -from Participant import Participant -import CONFIG import BOT_ERROR -import idx_list +import CONFIG +from SantaBotConstants import SantaBotConstants +from SantaBotHelpers import SantaBotHelpers +from Participant import Participant #initialize config file try: @@ -37,10 +39,12 @@ highest_key = 0 user_left_during_pause = False is_paused = False +SantaBotHelper = SantaBotHelpers() + exchange_started = config['programData'].as_bool('exchange_started') for key in config['members']: data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) + usr = Participant(data[SantaBotConstants.NAME], data[SantaBotConstants.DISCRIMINATOR], data[SantaBotConstants.IDSTR], data[SantaBotConstants.USRNUM], data[SantaBotConstants.WISHLISTURL], data[SantaBotConstants.PREFERENCES], data[SantaBotConstants.PARTNERID]) usr_list.append(usr) highest_key = int(key) #set up discord connection debug logging @@ -93,12 +97,12 @@ async def setwishlisturl(ctx, *destination:str): ''' currAuthor = ctx.author global usr_list - if user_is_participant(currAuthor.id, usr_list): - if(channelIsPrivate(ctx.channel)): + if SantaBotHelper.user_is_participant(currAuthor.id, usr_list): + if(SantaBotHelper.channelIsPrivate(ctx.channel)): pass else: await ctx.message.delete() - (index, user) = get_participant_object(currAuthor.id, usr_list) + (index, user) = SantaBotHelper.get_participant_object(currAuthor.id, usr_list) new_wishlist = "None" if(len(destination) == 0): pass @@ -106,7 +110,7 @@ async def setwishlisturl(ctx, *destination:str): new_wishlist = " | ".join(destination) try: # save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist + config['members'][str(user.usrnum)][SantaBotConstants.WISHLISTURL] = new_wishlist config.write() # add the input to the value in the user's class instance user.wishlisturl = new_wishlist @@ -130,8 +134,8 @@ async def getwishlisturl(ctx): ''' currAuthor = ctx.author global usr_list - if user_is_participant(ctx.author.id, usr_list): - (index, user) = get_participant_object(ctx.author.id, usr_list) + if SantaBotHelper.user_is_participant(ctx.author.id, usr_list): + (index, user) = SantaBotHelper.get_participant_object(ctx.author.id, usr_list) try: await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) except: @@ -147,12 +151,12 @@ async def setprefs(ctx, *preferences:str): ''' currAuthor = ctx.author global usr_list - if user_is_participant(currAuthor.id, usr_list): - if(channelIsPrivate(ctx.channel)): + if SantaBotHelper.user_is_participant(currAuthor.id, usr_list): + if(SantaBotHelper.channelIsPrivate(ctx.channel)): pass else: await ctx.message.delete() - (index, user) = get_participant_object(currAuthor, usr_list) + (index, user) = SantaBotHelper.get_participant_object(currAuthor, usr_list) new_prefs = "None" if(len(preferences) == 0): pass @@ -160,7 +164,7 @@ async def setprefs(ctx, *preferences:str): new_prefs = " | ".join(preferences) try: #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) + config['members'][str(user.usrnum)][SantaBotConstants.PREFERENCES] = str(new_prefs) config.write() #add the input to the value in the user's class instance user.preferences = new_prefs @@ -184,8 +188,8 @@ async def getprefs(ctx): ''' currAuthor = ctx.author global usr_list - if user_is_participant(ctx.author.id, usr_list): - (index, user) = get_participant_object(ctx.author.id, usr_list) + if SantaBotHelper.user_is_participant(ctx.author.id, usr_list): + (index, user) = SantaBotHelper.get_participant_object(ctx.author.id, usr_list) try: await currAuthor.send("Current preference(s): {0}".format(user.preferences)) except: @@ -218,21 +222,20 @@ async def start(ctx): # select a random partner for each participant if all information is complete and there are enough people to do it if(all_fields_complete and (len(usr_list) > 1)): print("Proposing a partner list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): + potential_list = SantaBotHelper.propose_partner_list(usr_list) + while(not SantaBotHelper.partners_are_valid(potential_list)): print("Proposing a partner list") - potential_list = propose_partner_list(usr_list) + potential_list = SantaBotHelper.propose_partner_list(usr_list) # save to config file print("Partner assignment successful") for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) + (temp_index, temp_user) = SantaBotHelper.get_participant_object(user.idstr, usr_list) + (index, partner) = SantaBotHelper.get_participant_object(user.partnerid, potential_list) temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid + config['members'][str(user.usrnum)][SantaBotConstants.PARTNERID] = user.partnerid config.write() # tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_user = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) + this_user = ctx.guild.get_member(int(user.idstr)) message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" @@ -281,7 +284,7 @@ async def restart(ctx): await ctx.send("`Partner assignment cancelled: participant info incomplete.`") except: await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) + list_changed = SantaBotHelper.usr_list_changed_during_pause(usr_list, user_left_during_pause) if(list_changed): ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) else: @@ -322,7 +325,7 @@ async def join(ctx): global usr_list global highest_key # check if the exchange has already started - if user_is_participant(currAuthor.id, usr_list): + if SantaBotHelper.user_is_participant(currAuthor.id, usr_list): await ctx.send(BOT_ERROR.ALREADY_JOINED) else: # initialize instance of Participant for the author @@ -350,8 +353,8 @@ async def leave(ctx): global usr_list global is_paused global user_left_during_pause - if(user_is_participant(currAuthor.id, usr_list)): - (index, user) = get_participant_object(currAuthor.id, usr_list) + if(SantaBotHelper.user_is_participant(currAuthor.id, usr_list)): + (index, user) = SantaBotHelper.get_participant_object(currAuthor.id, usr_list) usr_list.remove(user) popped_user = config['members'].pop(str(user.usrnum)) config.write() @@ -394,7 +397,7 @@ async def listparticipants(ctx): else: msg = '```The following people are signed up for the Secret Santa exchange:\n' for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) + this_user = ctx.guild.get_member(user.idstr) msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) else: @@ -419,10 +422,10 @@ async def partnerinfo(ctx): currAuthor = ctx.author global usr_list global exchange_started - authorIsParticipant = user_is_participant(currAuthor.id, usr_list) + authorIsParticipant = SantaBotHelper.user_is_participant(currAuthor.id, usr_list) if(exchange_started and authorIsParticipant): - (usr_index, user) = get_participant_object(currAuthor, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) + (usr_index, user) = SantaBotHelper.get_participant_object(currAuthor, usr_list) + (partner_index, partnerobj) = SantaBotHelper.get_participant_object(user.partnerid, usr_list) msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" @@ -447,4 +450,4 @@ async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await ctx.send_message("Bot invite link: {0}".format(link)) -bot.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file +bot.run(CONFIG.discord_token, reconnect = True) From 4a58a0db34548160bbbe47d7afc2324e458b85d3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 5 Oct 2019 11:19:24 -0700 Subject: [PATCH 086/189] Added commit message template Added commit message template Makes commits easier --- .gitmessage | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .gitmessage diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 0000000..76be237 --- /dev/null +++ b/.gitmessage @@ -0,0 +1,10 @@ +[commit title] + +#: [commit title] + +Closes #: [commit title] + + + description + reasoning + \ No newline at end of file From 23da805c8173c89e900f2d20f5780d10e6c9a9b6 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 5 Oct 2019 11:31:13 -0700 Subject: [PATCH 087/189] Closes #29: full string representation of Participant object Added the rest of the variables to the representation Closing issue --- Participant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Participant.py b/Participant.py index 624c06b..2f37fe3 100644 --- a/Participant.py +++ b/Participant.py @@ -10,7 +10,7 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc self.partnerid = partnerid #string for id of partner def __repr__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + return f"Participant(User: {self.name}#{self.discriminator}, Key: {self.usrnum}, wishlisturl={self.wishlisturl}, preferences={self.preferences}, partnerid={self.partnerid})" def __str__(self): return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" From e375a6767c2287ed9302734185c1a4001f5a2065 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 5 Oct 2019 11:31:13 -0700 Subject: [PATCH 088/189] Close #29: full string representation of Participant object Added the rest of the variables to the representation Closing issue --- Participant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Participant.py b/Participant.py index 624c06b..2f37fe3 100644 --- a/Participant.py +++ b/Participant.py @@ -10,7 +10,7 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc self.partnerid = partnerid #string for id of partner def __repr__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + return f"Participant(User: {self.name}#{self.discriminator}, Key: {self.usrnum}, wishlisturl={self.wishlisturl}, preferences={self.preferences}, partnerid={self.partnerid})" def __str__(self): return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" From 111c727528969f80caf930f29b72c953ed277b1e Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 3 Nov 2019 01:47:32 -0700 Subject: [PATCH 089/189] A few house-keeping updates A few house-keeping updates N/A --- .gitmessage | 2 +- Participant.py | 10 ++++++++-- santa-bot.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.gitmessage b/.gitmessage index 76be237..582cb55 100644 --- a/.gitmessage +++ b/.gitmessage @@ -7,4 +7,4 @@ Closes #: [commit title] description reasoning - \ No newline at end of file + diff --git a/Participant.py b/Participant.py index 2f37fe3..0a8ab25 100644 --- a/Participant.py +++ b/Participant.py @@ -8,13 +8,19 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc self.wishlisturl = wishlisturl #string for user's wishlisturl self.preferences = preferences #string for user's gift preferences self.partnerid = partnerid #string for id of partner - + def __repr__(self): return f"Participant(User: {self.name}#{self.discriminator}, Key: {self.usrnum}, wishlisturl={self.wishlisturl}, preferences={self.preferences}, partnerid={self.partnerid})" def __str__(self): return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" - + + def __hash__(self): + return hash(self.idstr) + + def __eq__(self, other): + return self.idstr == other.idstr + def wishlisturl_is_set(self): """returns whether the user has set an wishlisturl""" if (self.wishlisturl == "None") or (self.wishlisturl == ""): diff --git a/santa-bot.py b/santa-bot.py index 3a17500..1cfba98 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -339,8 +339,8 @@ async def join(ctx): await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") try: userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{1}setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n - Use `{2}setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) + Use `{1}setwishlisturl [wishlist urls separated by a space ]` to set your wishlist URL (you may also add your mailing address).\n + Use `{2}setprefs [preferences separated by a space ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) await currAuthor.send(userPrompt) except: ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) From 7b1a73ab2e3e2aa9a840cc6ddf1431a9a2b5448c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 3 Nov 2019 01:58:43 -0800 Subject: [PATCH 090/189] Reaction role assignment Reaction role assignment Our bot keeps dying --- santa-bot.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/santa-bot.py b/santa-bot.py index 1cfba98..7f9480f 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -450,4 +450,38 @@ async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await ctx.send_message("Bot invite link: {0}".format(link)) +ROLE_CHANNEL = 640410764007440404 +async def manage_reactions(payload, add): + message_id = payload.message_id + channel_id = payload.channel_id + emoji = payload.emoji + guild = bot.get_guild(payload.guild_id) + user = guild.get_member(payload.user_id) + + if channel_id == ROLE_CHANNEL: + channel = discord.utils.get(bot.get_all_channels(), id=channel_id) + async for message in channel.history(limit=200): + if message.id == message_id: + break + + content = message.content.split("\n") + for line in content: + l = line.split(" ") + for c, word in enumerate(l): + if word == "for" and c > 0 and l[c-1] == str(emoji): + role_id = l[c+1][3:-1] + role = guild.get_role(int(role_id)) + if add: + await user.add_roles(role) + else: + await user.remove_roles(role) + +@bot.event +async def on_raw_reaction_add(payload): + await manage_reactions(payload, True) + +@bot.event +async def on_raw_reaction_remove(payload): + await manage_reactions(payload, False) + bot.run(CONFIG.discord_token, reconnect = True) From 7d65f68d2b790f5fad667e7342a3036fc01accdf Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 3 Nov 2019 02:16:24 -0800 Subject: [PATCH 091/189] some shit --- Participant.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Participant.py b/Participant.py index 0a8ab25..7d0f360 100644 --- a/Participant.py +++ b/Participant.py @@ -9,11 +9,11 @@ def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferenc self.preferences = preferences #string for user's gift preferences self.partnerid = partnerid #string for id of partner - def __repr__(self): - return f"Participant(User: {self.name}#{self.discriminator}, Key: {self.usrnum}, wishlisturl={self.wishlisturl}, preferences={self.preferences}, partnerid={self.partnerid})" + #def __repr__(self): + # return f"Participant(User: {self.name}#{self.discriminator}, Key: {self.usrnum}, wishlisturl={self.wishlisturl}, preferences={self.preferences}, partnerid={self.partnerid})" - def __str__(self): - return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" + #def __str__(self): + # return f"User: {self.name}#{self.discriminator}, Key: {self.usrnum}" def __hash__(self): return hash(self.idstr) From 74c494bb73eb450a7c0d6493005b0597d0d60790 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 3 Nov 2019 02:20:46 -0800 Subject: [PATCH 092/189] wrong channel ID --- santa-bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 7f9480f..af217d8 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -450,7 +450,7 @@ async def invite(ctx): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await ctx.send_message("Bot invite link: {0}".format(link)) -ROLE_CHANNEL = 640410764007440404 +ROLE_CHANNEL = 461740709062377473 async def manage_reactions(payload, add): message_id = payload.message_id channel_id = payload.channel_id From bbdc1991bd08e4764a33457adbf07c378663e577 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 18 Nov 2019 21:46:29 -0800 Subject: [PATCH 093/189] requirements.txt update --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3182b3c..92ba4ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -discord.py==1.2.3 -configobj==5.0.6 +discord.py +configobj From 06dc19d5e405d4982b083b2ea7ca1de85b76c1e3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 8 Jan 2020 23:32:02 -0800 Subject: [PATCH 094/189] Function typing Function typing Helpful for hints --- CONFIG.py | 8 ++++---- SantaBotHelpers.py | 20 ++++++++++++-------- santa-bot.py | 34 +++++++++++++++++----------------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index f8b9c36..77a82a6 100644 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,6 +1,6 @@ -discord_token = "YOUR_TOKEN_HERE" -client_id = "YOUR_CLIENT_ID" -prefix = "YOUR_PREFIX_HERE" -bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" +discord_token = "NTEzMTQxOTQ4MzgzNzU2Mjg5.XXcvGA.fHD7ZlywnOxZZ4nB0DsqUpjGqgY" +client_id = "513141948383756289" +prefix = "st!" +bot_folder = "./files/" cfg_path = bot_folder + "botdata.cfg" dbg_path = bot_folder + "debug.log" diff --git a/SantaBotHelpers.py b/SantaBotHelpers.py index 43a793e..0ba2456 100644 --- a/SantaBotHelpers.py +++ b/SantaBotHelpers.py @@ -2,16 +2,17 @@ import random from discord import Member from discord.abc import PrivateChannel +import discord from Participant import Participant class SantaBotHelpers(): - def isListOfParticipants(self, usrlist): + def isListOfParticipants(self, usrlist: list): for usr in usrlist: if(not isinstance(usr, Participant)): return False - def user_is_participant(self, usrid, usrlist): + def user_is_participant(self, usrid: discord.User.id, usrlist: list): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" print(usrlist) @@ -20,7 +21,7 @@ def user_is_participant(self, usrid, usrlist): return True return False - def get_participant_object(self, usrid, usrlist): + def get_participant_object(self, usrid: discord.User.id, usrlist: list): """takes a discord user ID string and list of participant objects, and returns the first participant object with matching id.""" @@ -28,7 +29,7 @@ def get_participant_object(self, usrid, usrlist): if(int(person.idstr) == usrid): return (index, person) - def propose_partner_list(self, usrlist): + def propose_partner_list(self, usrlist: list): """Generate a proposed partner list""" usr_list_copy = copy.deepcopy(usrlist) partners = copy.deepcopy(usrlist) @@ -47,7 +48,7 @@ def propose_partner_list(self, usrlist): return usr_list_copy ## everybody has a partner, nobody's partnered with themselves - def partners_are_valid(self, usrlist): + def partners_are_valid(self, usrlist: list): """Make sure that everybody has a partner and nobody is partnered with themselves""" if(not usrlist): @@ -58,7 +59,8 @@ def partners_are_valid(self, usrlist): return result ## checks if the user list changed during a pause - def usr_list_changed_during_pause(self, usrlist, usr_left): + def usr_list_changed_during_pause(self, usrlist: list, usr_left: bool): + """checks if the user list changed during a pause""" if(usr_left):# there's probably a better boolean logic way but this is easy usr_left = False # acknowledge return True @@ -73,7 +75,9 @@ def usr_list_changed_during_pause(self, usrlist, usr_left): def channelIsPrivate(self, channel): return isinstance(channel, PrivateChannel) - def member_is_mod(self, person, mod_list): + def member_is_mod(self, person: discord.Member, mod_list: list): + """Checks that a given member is in the mod list + @param person the Member in question""" if(isinstance(person, Member)): person_roles = person.roles person_roles_ids = [] @@ -84,7 +88,7 @@ def member_is_mod(self, person, mod_list): return True return False - def is_role_in_server(self, p_role, server_role_hierarchy): + def is_role_in_server(self, p_role: str, server_role_hierarchy: list): if(isinstance(p_role, str)): for server_role in server_role_hierarchy: if((str(server_role) == p_role) or (str(server_role.mention) == p_role)): diff --git a/santa-bot.py b/santa-bot.py index af217d8..4414769 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -69,7 +69,7 @@ async def on_ready(): print('------') @bot.command() -async def ping(ctx): +async def ping(ctx: commands.Context): ''' = Basic ping command ''' @@ -77,11 +77,11 @@ async def ping(ctx): await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) @bot.command() -async def ding(ctx): +async def ding(ctx: commands.Context): await ctx.send("dong") @bot.command() -async def echo(ctx, *content:str): +async def echo(ctx: commands.Context, *content:str): ''' [content] = echos back the [content] ''' @@ -91,7 +91,7 @@ async def echo(ctx, *content:str): await ctx.send(' '.join(content)) @bot.command() -async def setwishlisturl(ctx, *destination:str): +async def setwishlisturl(ctx: commands.Context, *destination:str): ''' [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). ''' @@ -128,7 +128,7 @@ async def setwishlisturl(ctx, *destination:str): return @bot.command() -async def getwishlisturl(ctx): +async def getwishlisturl(ctx: commands.Context): ''' Get current wishlist ''' @@ -145,7 +145,7 @@ async def getwishlisturl(ctx): return @bot.command() -async def setprefs(ctx, *preferences:str): +async def setprefs(ctx: commands.Context, *preferences:str): ''' Set new preferences ''' @@ -182,7 +182,7 @@ async def setprefs(ctx, *preferences:str): return @bot.command() -async def getprefs(ctx): +async def getprefs(ctx: commands.Context): ''' Get current preferences ''' @@ -199,7 +199,7 @@ async def getprefs(ctx): return @bot.command() -async def start(ctx): +async def start(ctx: commands.Context): # TODO: add help menu instruction currAuthor = ctx.author global usr_list @@ -264,7 +264,7 @@ async def start(ctx): return @bot.command() -async def restart(ctx): +async def restart(ctx: commands.Context): # TODO: add help menu instruction currAuthor = ctx.author global usr_list @@ -302,7 +302,7 @@ async def restart(ctx): return @bot.command() -async def pause(ctx): +async def pause(ctx: commands.Context): # TODO: add help menu instruction global exchange_started global is_paused @@ -317,7 +317,7 @@ async def pause(ctx): return @bot.command() -async def join(ctx): +async def join(ctx: commands.Context): ''' Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. ''' @@ -347,7 +347,7 @@ async def join(ctx): return @bot.command() -async def leave(ctx): +async def leave(ctx: commands.Context): # TODO: add help menu instruction currAuthor = ctx.author global usr_list @@ -366,7 +366,7 @@ async def leave(ctx): return @bot.command() -async def end(ctx): +async def end(ctx: commands.Context): # TODO: add help menu instruction global usr_list global highest_key @@ -387,7 +387,7 @@ async def end(ctx): return @bot.command() -async def listparticipants(ctx): +async def listparticipants(ctx: commands.Context): # TODO: add help menu instruction global usr_list global highest_key @@ -405,7 +405,7 @@ async def listparticipants(ctx): return @bot.command() -async def totalparticipants(ctx): +async def totalparticipants(ctx: commands.Context): # TODO: add help menu instruction global highest_key if highest_key == 0: @@ -417,7 +417,7 @@ async def totalparticipants(ctx): return @bot.command() -async def partnerinfo(ctx): +async def partnerinfo(ctx: commands.Context): # TODO: add help menu instruction currAuthor = ctx.author global usr_list @@ -446,7 +446,7 @@ async def partnerinfo(ctx): return @bot.command() -async def invite(ctx): +async def invite(ctx: commands.Context): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await ctx.send_message("Bot invite link: {0}".format(link)) From 18990c8841ad5d0ae5394ae1daa5552e9601ca18 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 8 Jan 2020 23:51:06 -0800 Subject: [PATCH 095/189] revert CONFIG.py token deactivated --- CONFIG.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index 77a82a6..f8b9c36 100644 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,6 +1,6 @@ -discord_token = "NTEzMTQxOTQ4MzgzNzU2Mjg5.XXcvGA.fHD7ZlywnOxZZ4nB0DsqUpjGqgY" -client_id = "513141948383756289" -prefix = "st!" -bot_folder = "./files/" +discord_token = "YOUR_TOKEN_HERE" +client_id = "YOUR_CLIENT_ID" +prefix = "YOUR_PREFIX_HERE" +bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" cfg_path = bot_folder + "botdata.cfg" dbg_path = bot_folder + "debug.log" From 33a668d537aaa97a137931e989c828f624545720 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 9 Jan 2020 00:13:08 -0800 Subject: [PATCH 096/189] Closes #15: Removed santa-bot_old.py due to rewrite completion see title see title --- santa-bot_old.py | 490 ----------------------------------------------- 1 file changed, 490 deletions(-) delete mode 100644 santa-bot_old.py diff --git a/santa-bot_old.py b/santa-bot_old.py deleted file mode 100644 index cef729d..0000000 --- a/santa-bot_old.py +++ /dev/null @@ -1,490 +0,0 @@ -import logging -import asyncio -import os.path -import random -import copy -import discord -import CONFIG -import BOT_ERROR -import idx_list -import datetime as DT -from configobj import ConfigObj - -class Participant(object): - """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): - self.name = name #string containing name of user - self.discriminator = discriminator #string containing discriminant of user - self.idstr = idstr #string containing id of user - self.usrnum = usrnum #int value referencing the instance's location in usr_list - self.wishlisturl = wishlisturl #string for user's wishlisturl - self.preferences = preferences #string for user's gift preferences - self.partnerid = partnerid #string for id of partner - - def wishlisturl_is_set(self): - """returns whether the user has set an wishlisturl""" - if (self.wishlisturl == "None") or (self.wishlisturl == ""): - return False - else: - return True - - def pref_is_set(self): - """returns whether the user has set gift preferences""" - if (self.preferences == "None") or (self.preferences == ""): - return False - else: - return True - -def user_is_participant(usrid, usrlist): - """Takes a discord user ID string and returns whether - a user with that ID is in usr_list""" - result = False - for person in usrlist: - if person.idstr == usrid: - result = True - break - return result - -def get_participant_object(usrid, usrlist): - """takes a discord user ID string and list of - participant objects, and returns the first - participant object with matching id.""" - for (index, person) in enumerate(usrlist): - if person.idstr == usrid: - return (index, person) - break - -def propose_partner_list(usrlist): - usr_list_copy = copy.deepcopy(usrlist) - partners = copy.deepcopy(usrlist) - ## propose partner list - for user in usr_list_copy: - candidates = partners - partner = candidates[random.randint(0, len(candidates) - 1)] - while(partner.idstr == user.idstr): - partner = candidates[random.randint(0, len(candidates) - 1)] - if((len(candidates) == 1) & (candidates[0].idstr == user.idstr)): - break # no choice but to pick yourself (this will be declared invalid later) - #remove user's partner from list of possible partners - partners.remove(partner) - #save the partner id to the participant's class instance - user.partnerid = partner.idstr - return usr_list_copy - -## everybody has a partner, nobody's partnered with themselves -def partners_are_valid(usrlist): - if(not usrlist): - return False - result = True - for user in usrlist: - result = result & (user.partnerid != '') & (user.partnerid != user.idstr) - return result - -## checks if the user list changed during a pause -def usr_list_changed_during_pause(usrlist, usr_left): - if(usr_left):# there's probably a better boolean logic way but this is easy - usr_left = False # acknowledge - return True - - has_changed = True - for user in usrlist: - has_match = (not (str(user.partnerid) == "")) - has_changed = has_changed & has_match # figures out if all users have a match - has_changed = has_changed & (not usr_left) - return (not has_changed) ## if not all users have a match - -#initialize config file -try: - config = ConfigObj('./files/botdata.cfg', file_error = True) -except: - os.mkdir('./files/') - config = ConfigObj() - config.filename = './files/botdata.cfg' - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() -#initialize data from config file -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[idx_list.NAME], data[idx_list.DISCRIMINATOR], data[idx_list.IDSTR], data[idx_list.USRNUM], data[idx_list.WISHLISTURL], data[idx_list.PREFERENCES], data[idx_list.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename='./files/debug.log', encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) - -#initialize client class instance -client = discord.Client() - -#handler for all on_message events -@client.event -async def on_message(message): - #declare global vars - - global usr_list - global highest_key - global exchange_started - global user_left_during_pause - global is_paused - - message_split = message.content.split() - curr_server = message.server - #write all messages to a chatlog - if((message.content[0:2]) == "s!"): - print(message.content) - with open('./files/chat.log', 'a+') as chat_log: - chat_log.write("[{0}#{1} ({2}) at {3}] {4}\n".format(message.author.name, message.author.discriminator, message.author.id, str(message.timestamp), message.content)) - pass - - #ignore messages from the bot itself - if message.author == client.user: - return - - #event for a user joining the Secret Santa - elif(message_split[0] == "s!join"): - #check if message author has already joined - if user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.ALREADY_JOINED) - #check if the exchange has already started - elif exchange_started: - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_IN_PROGRESS) - else: - #initialize instance of participant class for the author - highest_key = highest_key + 1 - usr_list.append(Participant(message.author.name, message.author.discriminator, message.author.id, highest_key)) - #write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [message.author.name, message.author.discriminator, message.author.id, highest_key, '', '', ''] - config.write() - - #prompt user about inputting info - await client.send_message(message.channel, message.author.mention + " has been added to the {0} Secret Santa exchange!".format(str(curr_server)) + "\nMore instructions have been DMd to you.") - try: - await client.send_message(message.author, 'Welcome to the __' + str(curr_server) + '__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n' - + 'Use `s!setwishlisturl [wishlist urls separated by | ]` to set your wishlist URL (you may also add your mailing address).\n' - + 'Use `s!setprefs [preferences separated by | ]` to set gift preferences for your Secret Santa. Put N/A if none.') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #event for a user to leave the Secret Santa list - elif(message_split[0] == "s!leave"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await client.send_message(message.channel, message.author.mention + " has left the {0} Secret Santa exchange".format(str(curr_server))) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept wishlisturl of participants - elif(message_split[0] == "s!setwishlisturl"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_wishlist = "None" - if(message.content == "s!setwishlisturl"): - new_wishlist = "None" - else: - new_wishlist = message.content.replace("s!setwishlisturl ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.WISHLISTURL] = new_wishlist - config.write() - #add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await client.send_message(message.author, "New wishlist URL: {0}".format(new_wishlist)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - # get current wishlist URL(s) - elif(message_split[0] == "s!getwishlisturl"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current wishlist URL(s): {0}".format(user.wishlisturl)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #accept gift preferences of participants - elif(message_split[0] == "s!setprefs"): - #check if author has joined the exchange yet - if user_is_participant(message.author.id, usr_list): - if(message.channel.is_private): - pass - else: - await client.delete_message(message) - (index, user) = get_participant_object(message.author.id, usr_list) - new_prefs = "None" - if(message.content == "s!setprefs"): - new_prefs = "None" - else: - new_prefs = message.content.replace("s!setprefs ", "", 1) - try: - #save to config file - config['members'][str(user.usrnum)][idx_list.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await client.send_message(message.author, "New preferences: {0}".format(new_prefs)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - except: - try: - await client.send_message(message.author, BOT_ERROR.INVALID_INPUT) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #get current preferences - elif(message_split[0] == "s!getprefs"): - if user_is_participant(message.author.id, usr_list): - (index, user) = get_participant_object(message.author.id, usr_list) - try: - await client.send_message(message.author, "Current preference(s): {0}".format(user.preferences)) - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - else: - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - - #command for admin to begin the Secret Santa partner assignment - elif(message_split[0] == "s!start"): - #only allow people with admin permissions to run - if message.author.top_role == message.server.role_hierarchy[0]: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - - #select a random partner for each participant if above loop found no empty values and there are enough people to do it - if all_fields_complete & (len(usr_list) > 1): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - while(not partners_are_valid(potential_list)): - print("proposing a list") - potential_list = propose_partner_list(usr_list) - #save to config file - print("list passed") - for user in potential_list: - (temp_index, temp_user) = get_participant_object(user.idstr, usr_list) - (index, partner) = get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][idx_list.PARTNERID] = user.partnerid # update config file - config.write() - #tell participants who their partner is - this_user = discord.User(name = user.name, discriminator = user.discriminator, id = user.idstr) - this_partner = discord.User(name = partner.name, discriminator = partner.discriminator, id = partner.idstr) - message_pt1 = str(partner.name) + '#' + str(partner.discriminator) + ' is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n' - message_pt2 = 'Their wishlist(s) can be found here: ' + partner.wishlisturl + '\n' - message_pt3 = 'And their gift preferences can be found here: ' + partner.preferences + '\n' - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await client.send_message(this_user, santa_message) - except: - await client.send_message(message.author, "Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - #set exchange_started + assoc. cfg value to True - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await client.send_message(message.channel, "Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not(len(usr_list) > 1): - await client.send_message(message.channel, BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #command allows you to restart without rematching if no change was made while s!paused - elif(message_split[0] == "s!restart"): - is_paused = True - if (message.author.top_role == message.server.role_hierarchy[0]) and is_paused: - #first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if user.wishlisturl_is_set() and user.pref_is_set(): - pass - else: - all_fields_complete = False - try: - await client.send_message(message.author, BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await client.send_message(message.author, '`Partner assignment canceled: participant info incomplete.`') - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - list_changed = usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - await client.send_message(message.channel, "User list changed during the pause. Partners must be picked again with `s!start`.") - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await client.send_message(message.channel, "No change was made during the pause. Secret Santa resumed with the same partners.") - elif(message.author.top_role != message.server.role_hierarchy[0]): - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - elif(not is_paused): - await client.send_message(message.channel, BOT_ERROR.NOT_PAUSED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - # allows a way to restart the Secret Santa - elif(message_split[0] == "s!pause"): - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await client.send_message(message.channel, 'Secret Santa has been paused. New people may now join.') - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #allows a way to end the Secret Santa - elif(message_split[0] == "s!end") and not message.channel.is_private: - #only allow ppl with admin permissions to run - if (message.author.top_role == message.server.role_hierarchy[0]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await client.send_message(message.channel, "Secret Santa ended") - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists off all participant names and id's - elif(message_split[0] == "s!listparticipants"): - if (message.author.top_role == message.server.role_hierarchy[0]): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = discord.User(user = user.name, id = user.idstr) - msg = msg + str(user.name) + '#' + str(user.discriminator) + '\n' - msg = msg + 'Use `s!join` to enter the exchange.```' - await client.send_message(message.channel, msg) - else: - await client.send_message(message.channel, BOT_ERROR.NO_PERMISSION) - - #lists total number of participants - elif(message_split[0] == "s!totalparticipants"): - if highest_key == 0: - await client.send_message(message.channel, 'Nobody has signed up for the Secret Santa exchange yet. Use `s!join` to enter the exchange.') - elif highest_key == 1: - await client.send_message(message.channel, '1 person has signed up for the Secret Santa exchange. Use `s!join` to enter the exchange.') - else: - await client.send_message(message.channel, 'A total of ' + str(len(usr_list)) + ' users have joined the Secret Santa exchange so far. Use `s!join` to enter the exchange.') - - #allows a user to have the details of their partner restated - elif(message_split[0] == "s!partnerinfo"): - if exchange_started and user_is_participant(message.author.id, usr_list): - (usr_index, user) = get_participant_object(message.author.id, usr_list) - (partner_index, partnerobj) = get_participant_object(user.partnerid, usr_list) - msg = 'Your partner is ' + partnerobj.name + "#" + partnerobj.discriminator + '\n' - msg = msg + 'Their wishlist(s) can be found here: ' + partnerobj.wishlisturl + '\n' - msg = msg + 'And their gift preferences can be found here: ' + partnerobj.preferences + '\n' - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - try: - await client.send_message(message.author, msg) - await client.send_message(message.channel, "The information has been sent to your DMs.") - except: - await client.send_message(message.channel, message.author.mention + BOT_ERROR.DM_FAILED) - elif (not exchange_started) and user_is_participant(message.author.id, usr_list): - await client.send_message(message.channel, BOT_ERROR.NOT_STARTED) - elif exchange_started and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif (not exchange_started) and (not user_is_participant(message.author.id, usr_list)): - await client.send_message(message.channel, BOT_ERROR.UNJOINED) - else: - await client.send_message(message.channel, BOT_ERROR.UNREACHABLE) - - elif(message_split[0] == "s!help"): - c_join = "`s!join` = join the Secret Santa" - c_leave = "`s!leave` = leave the Secret Santa" - c_setwishlisturl = "`s!setwishlisturl [wishlist urls separated by | ]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__." - c_getwishlisturl = "`s!getwishlisturl` = bot will PM you your current wishlist" - c_setprefs = "`s!setprefs [preferences separated by | ]` = set preferences (replaces current). Put N/A if none. __This field required__." - c_getprefs = "`s!getprefs` = bot will PM you your current preferences" - c_listparticipants = "`s!listparticipants` **(admin only)** = get the current participants" - c_totalparticipants = "`s!totalparticipants` = get the total number of participants" - c_partnerinfo = "`s!partnerinfo` = be DMd your partner's information" - c_start = "`s!start` **(admin only)** = assign Secret Santa partners" - c_restart = "`s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners" - c_pause = "`s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners)" - c_end = "`s!end` **(admin only)** = end Secret Santa" - c_ping = "`s!ping` = check if bot is alive" - command_list = [c_join, c_leave, c_setwishlisturl, c_getwishlisturl, c_setprefs, c_getprefs, c_listparticipants, c_totalparticipants, c_partnerinfo, c_start, c_pause, c_restart, c_end, c_ping] - command_string = '' - for command in command_list: - command_string = command_string + ("{0}\n".format(command)) - command_string += "\nContact your server's SantaBot administrator or <@224949031514800128> if you need any more help." - await client.send_message(message.channel, command_string) - - elif(message_split[0] == "s!ping"): - """ Pong! """ - await client.send_message(message.channel, "Pong! I'm alive!") - - elif(message_split[0] == "s!invite"): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await client.send_message(message.channel, "Non-testing bot invite link: {0}".format(link)) - - else: - await client.send_message(message.channel, "Command not found. Please use `s!help` if you need help with the commands.") - -@client.event -async def on_ready(): - """print message when client is connected""" - currentDT = DT.datetime.now() - print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) - print("Logged in as") - print(client.user.name) - print(client.user.id) - await client.change_presence(game = discord.Game(name = "s!help")) - print('------') - - -#event loop and discord initiation -client.run(CONFIG.discord_token, reconnect = True) \ No newline at end of file From 0cad3b56d4b5471db4c4e601c67599a963a89d9c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 15:30:50 -0700 Subject: [PATCH 097/189] [commit title] #: [commit title] Closes #: [commit title] description reasoning --- santa-bot.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 4414769..bdbccf6 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,12 +1,13 @@ import asyncio import datetime as DT import logging -import os.path +import os import copy import discord from configobj import ConfigObj from discord.ext import commands +from discord.ext.commands import has_permissions, MissingPermissions import BOT_ERROR import CONFIG @@ -445,6 +446,35 @@ async def partnerinfo(ctx: commands.Context): await ctx.send(BOT_ERROR.UNREACHABLE) return +@bot.command() +@has_permissions(manage_roles=True, ban_members=True) +async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_to_message: int): + src_id = channel_to_archive + dest_id = channel_to_message + src_channel = bot.get_channel(src_id) + dest_channel = bot.get_channel(dest_id) + ctx.send(content=f"Attempting to archive <#{src_id}> in <#{dest_id}>") + pins_to_archive = await src_channel.pins() + pins_to_archive.reverse() + + for pin in pins_to_archive: + attachments = pin.attachments + attachment_str = "" + for attachment in attachments: + attachment_str += f"{attachment.url}\n" # add link to attachments + + pin_author = pin.author.name + pin_datetime = pin.created_at.strftime("%B %d, %Y") + pin_url = pin.jump_url + pin_content = pin.content + output_str = ( + f"-**(from `{pin_author}` on {pin_datetime})** {pin_content}\n" + f"QotD link: {pin_url}" + f"Attachment links: {attachment_str}" + ) + await dest_channel.send(content=output_str) + + @bot.command() async def invite(ctx: commands.Context): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From 758d9ab62cc264efb800c7934f1a6c7528073bb9 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 15:30:50 -0700 Subject: [PATCH 098/189] [commit title] #: [commit title] Closes #: [commit title] description reasoning --- santa-bot.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 4414769..bdbccf6 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,12 +1,13 @@ import asyncio import datetime as DT import logging -import os.path +import os import copy import discord from configobj import ConfigObj from discord.ext import commands +from discord.ext.commands import has_permissions, MissingPermissions import BOT_ERROR import CONFIG @@ -445,6 +446,35 @@ async def partnerinfo(ctx: commands.Context): await ctx.send(BOT_ERROR.UNREACHABLE) return +@bot.command() +@has_permissions(manage_roles=True, ban_members=True) +async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_to_message: int): + src_id = channel_to_archive + dest_id = channel_to_message + src_channel = bot.get_channel(src_id) + dest_channel = bot.get_channel(dest_id) + ctx.send(content=f"Attempting to archive <#{src_id}> in <#{dest_id}>") + pins_to_archive = await src_channel.pins() + pins_to_archive.reverse() + + for pin in pins_to_archive: + attachments = pin.attachments + attachment_str = "" + for attachment in attachments: + attachment_str += f"{attachment.url}\n" # add link to attachments + + pin_author = pin.author.name + pin_datetime = pin.created_at.strftime("%B %d, %Y") + pin_url = pin.jump_url + pin_content = pin.content + output_str = ( + f"-**(from `{pin_author}` on {pin_datetime})** {pin_content}\n" + f"QotD link: {pin_url}" + f"Attachment links: {attachment_str}" + ) + await dest_channel.send(content=output_str) + + @bot.command() async def invite(ctx: commands.Context): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From 760b0a0bd3ecc36f7b06f11f5a0bc7deb0c0130e Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 15:36:16 -0700 Subject: [PATCH 099/189] Revert "[commit title]" This reverts commit 0cad3b56d4b5471db4c4e601c67599a963a89d9c. --- santa-bot.py | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index bdbccf6..4414769 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,13 +1,12 @@ import asyncio import datetime as DT import logging -import os +import os.path import copy import discord from configobj import ConfigObj from discord.ext import commands -from discord.ext.commands import has_permissions, MissingPermissions import BOT_ERROR import CONFIG @@ -446,35 +445,6 @@ async def partnerinfo(ctx: commands.Context): await ctx.send(BOT_ERROR.UNREACHABLE) return -@bot.command() -@has_permissions(manage_roles=True, ban_members=True) -async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_to_message: int): - src_id = channel_to_archive - dest_id = channel_to_message - src_channel = bot.get_channel(src_id) - dest_channel = bot.get_channel(dest_id) - ctx.send(content=f"Attempting to archive <#{src_id}> in <#{dest_id}>") - pins_to_archive = await src_channel.pins() - pins_to_archive.reverse() - - for pin in pins_to_archive: - attachments = pin.attachments - attachment_str = "" - for attachment in attachments: - attachment_str += f"{attachment.url}\n" # add link to attachments - - pin_author = pin.author.name - pin_datetime = pin.created_at.strftime("%B %d, %Y") - pin_url = pin.jump_url - pin_content = pin.content - output_str = ( - f"-**(from `{pin_author}` on {pin_datetime})** {pin_content}\n" - f"QotD link: {pin_url}" - f"Attachment links: {attachment_str}" - ) - await dest_channel.send(content=output_str) - - @bot.command() async def invite(ctx: commands.Context): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From 1f132a688cb8316ded98bf9dd2ec14f330694cd2 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 15:41:44 -0700 Subject: [PATCH 100/189] #31: Add command to archive pins Add command to archive pins Needed the utility for one of the servers this bot runs in --- santa-bot.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 4414769..19e60aa 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,12 +1,13 @@ import asyncio import datetime as DT import logging -import os.path +import os import copy import discord from configobj import ConfigObj from discord.ext import commands +from discord.ext.commands import has_permissions, MissingPermissions import BOT_ERROR import CONFIG @@ -445,6 +446,39 @@ async def partnerinfo(ctx: commands.Context): await ctx.send(BOT_ERROR.UNREACHABLE) return +@bot.command() +@has_permissions(manage_roles=True, ban_members=True) +async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_to_message: int): + ''' + Archive the pins in one channel to another channel as messages + ''' + src_id = channel_to_archive + dest_id = channel_to_message + src_channel = bot.get_channel(src_id) + dest_channel = bot.get_channel(dest_id) + ctx.send(content=f"Attempting to archive <#{src_id}> in <#{dest_id}>") + pins_to_archive = await src_channel.pins() + pins_to_archive.reverse() + + for pin in pins_to_archive: + attachments = pin.attachments + attachment_str = "" + for attachment in attachments: + attachment_str += f"{attachment.url}\n" # add link to attachments + + pin_author = pin.author.name + pin_datetime = pin.created_at.strftime("%B %d, %Y") + pin_url = pin.jump_url + pin_content = pin.content + output_str = ( + f"-**(from `{pin_author}` on {pin_datetime})** {pin_content}\n" + f"QotD link: {pin_url}" + f"Attachment links: {attachment_str}" + ) + await dest_channel.send(content=output_str) + #await pin.unpin() + + @bot.command() async def invite(ctx: commands.Context): link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) From 72ed40e819a835848ae455d2a6392f21ef4638a8 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 15:54:31 -0700 Subject: [PATCH 101/189] #31: Separate archiving and unpinning commands Separate archiving and unpinning Allow user to check archive output before unpinning all --- santa-bot.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 19e60aa..fcf9973 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -456,7 +456,7 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t dest_id = channel_to_message src_channel = bot.get_channel(src_id) dest_channel = bot.get_channel(dest_id) - ctx.send(content=f"Attempting to archive <#{src_id}> in <#{dest_id}>") + await ctx.send(content=f"Attempting to archive pinned messages in <#{src_id}> in <#{dest_id}>") pins_to_archive = await src_channel.pins() pins_to_archive.reverse() @@ -472,12 +472,24 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t pin_content = pin.content output_str = ( f"-**(from `{pin_author}` on {pin_datetime})** {pin_content}\n" - f"QotD link: {pin_url}" + f"QotD link: <{pin_url}>\n" f"Attachment links: {attachment_str}" ) await dest_channel.send(content=output_str) - #await pin.unpin() + + await ctx.send(content=f"Pinned message are archived in <#{dest_id}>. Please use {CONFIG.prefix}unpin_all to remove the pins in <#{src_id}>") +@bot.command() +@has_permissions(manage_roles=True, ban_members=True) +async def unpin_all(ctx: commands.Context): + ''' + Unpins all the pinned messages in the channel. Called to clean up after archive_pins. + ''' + await ctx.send(content=f"Attempting to remove all pinned messages.") + pins_to_remove = await ctx.pins() + for pin in pins_to_remove: + await pin.unpin() + await ctx.send(content=f"All pinned messages removed.") @bot.command() async def invite(ctx: commands.Context): From 0033b3090a6f90d4f04f327acee1ef78cb52cd9a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 15:56:46 -0700 Subject: [PATCH 102/189] #31: quick edit quick edit --- santa-bot.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index fcf9973..da15c1a 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -456,7 +456,8 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t dest_id = channel_to_message src_channel = bot.get_channel(src_id) dest_channel = bot.get_channel(dest_id) - await ctx.send(content=f"Attempting to archive pinned messages in <#{src_id}> in <#{dest_id}>") + start_message = f"Attempting to archive pinned messages in <#{src_id}> in <#{dest_id}>" + await ctx.send(content=start_message) pins_to_archive = await src_channel.pins() pins_to_archive.reverse() @@ -477,7 +478,8 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t ) await dest_channel.send(content=output_str) - await ctx.send(content=f"Pinned message are archived in <#{dest_id}>. Please use {CONFIG.prefix}unpin_all to remove the pins in <#{src_id}>") + end_message = f"Pinned message are archived in <#{dest_id}>. Please use {CONFIG.prefix}unpin_all to remove the pins in <#{src_id}>" + await ctx.send(content=end_message) @bot.command() @has_permissions(manage_roles=True, ban_members=True) @@ -485,11 +487,13 @@ async def unpin_all(ctx: commands.Context): ''' Unpins all the pinned messages in the channel. Called to clean up after archive_pins. ''' - await ctx.send(content=f"Attempting to remove all pinned messages.") + start_message = f"Attempting to remove all pinned messages." + await ctx.send(content=start_message) pins_to_remove = await ctx.pins() for pin in pins_to_remove: await pin.unpin() - await ctx.send(content=f"All pinned messages removed.") + end_message = f"All pinned messages removed." + await ctx.send(content=end_message) @bot.command() async def invite(ctx: commands.Context): From 0314cff94a4426b181f06c96586ecba4d9613957 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 16:41:05 -0700 Subject: [PATCH 103/189] #31: error shit error shit --- BOT_ERROR.py | 1 + santa-bot.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index f13afcd..d210db8 100644 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -13,3 +13,4 @@ def HAS_NOT_SUBMITTED(usrname): return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" UNREACHABLE = "`Error: this shouldn't happen`" NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" +MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" diff --git a/santa-bot.py b/santa-bot.py index da15c1a..5bb01f1 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -7,7 +7,7 @@ import discord from configobj import ConfigObj from discord.ext import commands -from discord.ext.commands import has_permissions, MissingPermissions +from discord.ext.commands import has_permissions, MissingPermissions, MissingRequiredArgument import BOT_ERROR import CONFIG @@ -526,6 +526,18 @@ async def manage_reactions(payload, add): else: await user.remove_roles(role) +@unpin_all.error +@archive_pins.error +async def pin_archive_error(error, ctx): + if isinstance(error, MissingPermissions): + text = BOT_ERROR.NO_PERMISSION("mod") + await ctx.send(content=text) + elif isinstance(error, MissingRequiredArgument): + await ctx.send(content=BOT_ERROR.MISSING_ARGUMENTS) + else: + await ctx.send(content="Error: undetermined please contact <@224949031514800128>") + + @bot.event async def on_raw_reaction_add(payload): await manage_reactions(payload, True) From 242f2741af624c22af2923507587481213bade87 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 17:27:25 -0700 Subject: [PATCH 104/189] #31: remove f-strings to work on raspberry pi remove f-strings to work on raspberry pi --- santa-bot.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 5bb01f1..f40d1b4 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -456,7 +456,7 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t dest_id = channel_to_message src_channel = bot.get_channel(src_id) dest_channel = bot.get_channel(dest_id) - start_message = f"Attempting to archive pinned messages in <#{src_id}> in <#{dest_id}>" + start_message = "Attempting to archive pinned messages in <#{0}> in <#{1}>".format(src_id, dest_id) await ctx.send(content=start_message) pins_to_archive = await src_channel.pins() pins_to_archive.reverse() @@ -465,20 +465,18 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t attachments = pin.attachments attachment_str = "" for attachment in attachments: - attachment_str += f"{attachment.url}\n" # add link to attachments + attachment_str += "{0}\n".format(attachment.url) # add link to attachments pin_author = pin.author.name pin_datetime = pin.created_at.strftime("%B %d, %Y") pin_url = pin.jump_url pin_content = pin.content - output_str = ( - f"-**(from `{pin_author}` on {pin_datetime})** {pin_content}\n" - f"QotD link: <{pin_url}>\n" - f"Attachment links: {attachment_str}" - ) + output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_datetime, pin_content) + output_str += "QotD link: <{0}>\n".format(pin_url) + output_str += "Attachment links: {0}".format(attachment_str) await dest_channel.send(content=output_str) - end_message = f"Pinned message are archived in <#{dest_id}>. Please use {CONFIG.prefix}unpin_all to remove the pins in <#{src_id}>" + end_message = "Pinned message are archived in <#{0}>. Please use {1}unpin_all to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) await ctx.send(content=end_message) @bot.command() @@ -487,12 +485,12 @@ async def unpin_all(ctx: commands.Context): ''' Unpins all the pinned messages in the channel. Called to clean up after archive_pins. ''' - start_message = f"Attempting to remove all pinned messages." + start_message = "Attempting to remove all pinned messages." await ctx.send(content=start_message) pins_to_remove = await ctx.pins() for pin in pins_to_remove: await pin.unpin() - end_message = f"All pinned messages removed." + end_message = "All pinned messages removed." await ctx.send(content=end_message) @bot.command() From c4834d0b33361de9a01ddfd006e526172370e628 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 18:04:53 -0700 Subject: [PATCH 105/189] #31: last error stuff for archiving last error stuff for archiving --- BOT_ERROR.py | 2 ++ santa-bot.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/BOT_ERROR.py b/BOT_ERROR.py index d210db8..987f5a2 100644 --- a/BOT_ERROR.py +++ b/BOT_ERROR.py @@ -14,3 +14,5 @@ def HAS_NOT_SUBMITTED(usrname): UNREACHABLE = "`Error: this shouldn't happen`" NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" +def ARCHIVE_ERROR_LENGTH(msg_url): + return "`ERROR: output message is too long pinned message {0} was not archived.`" diff --git a/santa-bot.py b/santa-bot.py index f40d1b4..eba33ac 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -456,7 +456,7 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t dest_id = channel_to_message src_channel = bot.get_channel(src_id) dest_channel = bot.get_channel(dest_id) - start_message = "Attempting to archive pinned messages in <#{0}> in <#{1}>".format(src_id, dest_id) + start_message = "Attempting to archive pinned messages from <#{0}> to <#{1}>".format(src_id, dest_id) await ctx.send(content=start_message) pins_to_archive = await src_channel.pins() pins_to_archive.reverse() @@ -472,11 +472,15 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t pin_url = pin.jump_url pin_content = pin.content output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_datetime, pin_content) - output_str += "QotD link: <{0}>\n".format(pin_url) - output_str += "Attachment links: {0}".format(attachment_str) - await dest_channel.send(content=output_str) + output_str += "Message link: <{0}>\n".format(pin_url) + if not attachment_str: + output_str += "Attachment links: {0}".format(attachment_str) + if len(output_str) > 2000: + await ctx.send(content=BOT_ERROR.ARCHIVE_ERROR_LENGTH(pin_url)) + else: + await dest_channel.send(content=output_str) - end_message = "Pinned message are archived in <#{0}>. Please use {1}unpin_all to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) + end_message = "Pinned message are archived in <#{0}>. If the archive messages look good, use __{1}unpin_all__ to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) await ctx.send(content=end_message) @bot.command() From c2661d1ea76e87151ad4364e46cdc28314e66ce6 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 18:13:37 -0700 Subject: [PATCH 106/189] #31: unpin last thing unpin last thing --- santa-bot.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index eba33ac..8e5971c 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -485,13 +485,17 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t @bot.command() @has_permissions(manage_roles=True, ban_members=True) -async def unpin_all(ctx: commands.Context): +async def unpin_all(ctx: commands.Context, channel_id_to_unpin: int = -1): ''' Unpins all the pinned messages in the channel. Called to clean up after archive_pins. ''' + if channel_id_to_unpin == -1: + channel_id_to_unpin = ctx.channel.id + + remove_channel = await bot.get_channel(channel_id_to_unpin) start_message = "Attempting to remove all pinned messages." await ctx.send(content=start_message) - pins_to_remove = await ctx.pins() + pins_to_remove = await remove_channel.pins() for pin in pins_to_remove: await pin.unpin() end_message = "All pinned messages removed." From 062a37f2276895ac2155b488eed0fb45bd5bbb54 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 18:14:46 -0700 Subject: [PATCH 107/189] misc --- santa-bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index 8e5971c..6cd28ad 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -480,7 +480,7 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t else: await dest_channel.send(content=output_str) - end_message = "Pinned message are archived in <#{0}>. If the archive messages look good, use __{1}unpin_all__ to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) + end_message = "Pinned message are archived in <#{0}>. If the archive messages look good, use **{1}unpin_all** to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) await ctx.send(content=end_message) @bot.command() From d3683cc1a476a99a6cd40ab4f2cee9e84a54a447 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 19:23:05 -0700 Subject: [PATCH 108/189] #31: Update unpin description Update unpin description --- santa-bot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/santa-bot.py b/santa-bot.py index 6cd28ad..8456b1b 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -488,6 +488,7 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t async def unpin_all(ctx: commands.Context, channel_id_to_unpin: int = -1): ''' Unpins all the pinned messages in the channel. Called to clean up after archive_pins. + Defaults to the channel in which it's called. ''' if channel_id_to_unpin == -1: channel_id_to_unpin = ctx.channel.id From 941c93b4bd61d3f7fa739b22e11b735218e8ea7b Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 19:54:40 -0700 Subject: [PATCH 109/189] #31: Attachment link bug Attachment link bug 'Attachment link' logic was flipped so it was only showing up when string was empty. Changed to always appear either for attachment links or for spacing purposes. --- santa-bot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index 8456b1b..adec6a5 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -473,8 +473,7 @@ async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_t pin_content = pin.content output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_datetime, pin_content) output_str += "Message link: <{0}>\n".format(pin_url) - if not attachment_str: - output_str += "Attachment links: {0}".format(attachment_str) + output_str += "Attachment links: {0}".format(attachment_str) if len(output_str) > 2000: await ctx.send(content=BOT_ERROR.ARCHIVE_ERROR_LENGTH(pin_url)) else: From 9857aad4f277f5ac7c8b655d0c09f2dfa68fb05a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 20:17:29 -0700 Subject: [PATCH 110/189] #31: remove bad await from unpin #31: remove bad await from unpin reasoning --- santa-bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/santa-bot.py b/santa-bot.py index adec6a5..dda825f 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -489,10 +489,11 @@ async def unpin_all(ctx: commands.Context, channel_id_to_unpin: int = -1): Unpins all the pinned messages in the channel. Called to clean up after archive_pins. Defaults to the channel in which it's called. ''' + print(channel_id_to_unpin) if channel_id_to_unpin == -1: channel_id_to_unpin = ctx.channel.id - remove_channel = await bot.get_channel(channel_id_to_unpin) + remove_channel = bot.get_channel(channel_id_to_unpin) start_message = "Attempting to remove all pinned messages." await ctx.send(content=start_message) pins_to_remove = await remove_channel.pins() From 3c84fded5b53cccba0bfde1fc27093eba4f7eb23 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 22 Apr 2020 20:18:51 -0700 Subject: [PATCH 111/189] #31: last messaging tune-up last messaging tune-up --- santa-bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index dda825f..04b2110 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -494,12 +494,12 @@ async def unpin_all(ctx: commands.Context, channel_id_to_unpin: int = -1): channel_id_to_unpin = ctx.channel.id remove_channel = bot.get_channel(channel_id_to_unpin) - start_message = "Attempting to remove all pinned messages." + start_message = "Attempting to remove all pinned messages from <#channel_id_to_unpin>." await ctx.send(content=start_message) pins_to_remove = await remove_channel.pins() for pin in pins_to_remove: await pin.unpin() - end_message = "All pinned messages removed." + end_message = "All pinned messages removed from <#channel_id_to_unpin>." await ctx.send(content=end_message) @bot.command() From fb0cc94696f280054ba1913a9b757317ddad3e8b Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 3 May 2020 19:02:46 -0700 Subject: [PATCH 112/189] #33: Begin cog re-org: set/getwishlisturl, setprefs #33: Begin cog re-org: set/getwishlisturl, setprefs --- cogs/SecretSanta.py | 137 ++++++++++++++++++ BOT_ERROR.py => helpers/BOT_ERROR.py | 36 ++--- .../SecretSantaConstants.py | 2 +- .../SecretSantaHelpers.py | 6 +- .../SecretSantaParticipant.py | 2 +- santa-bot.py | 4 +- 6 files changed, 162 insertions(+), 25 deletions(-) create mode 100644 cogs/SecretSanta.py rename BOT_ERROR.py => helpers/BOT_ERROR.py (98%) rename SantaBotConstants.py => helpers/SecretSantaConstants.py (80%) rename SantaBotHelpers.py => helpers/SecretSantaHelpers.py (96%) rename Participant.py => helpers/SecretSantaParticipant.py (97%) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py new file mode 100644 index 0000000..603b97d --- /dev/null +++ b/cogs/SecretSanta.py @@ -0,0 +1,137 @@ +import CONFIG +from configobj import ConfigObj +import os +import copy + +from discord.ext import commands + +import helpers.BOT_ERROR as BOT_ERROR +from helpers.SecretSantaConstants import SecretSantaConstants +from helpers.SecretSantaHelpers import SecretSantaHelpers +from helpers.SecretSantaParticipant import SecretSantaParticipant + +class SecretSanta(commands.Cog): + + def __init__(self, bot): + self.bot = bot + self.exchange_started = False + self.server = '' + self.usr_list = [] + self.highest_key = 0 + self.user_left_during_pause = False + self.is_paused = False + self.SecretSantaHelper = SecretSantaHelpers() + self.config = None + + + #initialize config file + try: + self.config = ConfigObj(CONFIG.cfg_path, file_error = True) + except: + try: + os.mkdir(CONFIG.bot_folder) + except: + pass + self.config = ConfigObj() + self.config.filename = CONFIG.cfg_path + self.config['programData'] = {'exchange_started': False} + self.config['members'] = {} + self.config.write() + + # retrieve data from config file + self.exchange_started = self.config['programData'].as_bool('exchange_started') + for key in self.config['members']: + data = self.config['members'][str(key)] + usr = SecretSantaParticipant(data[SecretSantaConstants.NAME], data[SecretSantaConstants.DISCRIMINATOR], data[SecretSantaConstants.IDSTR], data[SecretSantaConstants.USRNUM], data[SecretSantaConstants.WISHLISTURL], data[SecretSantaConstants.PREFERENCES], data[SecretSantaConstants.PARTNERID]) + self.usr_list.append(usr) + self.highest_key = int(key) + + @bot.command() + async def setwishlisturl(self, ctx: commands.Context, *destination:str): + ''' + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). + ''' + currAuthor = ctx.author + if self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): + if(self.SecretSantaHelper.channelIsPrivate(ctx.channel)): + pass + else: + await ctx.message.delete() + (index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) + new_wishlist = "None" + if(len(destination) == 0): + pass + else: + new_wishlist = " | ".join(destination) + try: + # save to config file + self.config['members'][str(user.usrnum)][SecretSantaConstants.WISHLISTURL] = new_wishlist + self.config.write() + # add the input to the value in the user's class instance + user.wishlisturl = new_wishlist + try: + await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + + @commands.command() + async def getwishlisturl(self, ctx: commands.Context): + ''' + Get current wishlist + ''' + currAuthor = ctx.author + if self.SecretSantaHelper.user_is_participant(ctx.author.id, self.usr_list): + (index, user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) + try: + await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + + @commands.command() + async def setprefs(self, ctx: commands.Context, *preferences:str): + ''' + Set new preferences + ''' + currAuthor = ctx.author + if self.SantaBotHelper.user_is_participant(currAuthor.id, self.usr_list): + if(self.SantaBotHelper.channelIsPrivate(ctx.channel)): + pass + else: + await ctx.message.delete() + (index, user) = self.SantaBotHelper.get_participant_object(currAuthor, self.usr_list) + new_prefs = "None" + if(len(preferences) == 0): + pass + else: + new_prefs = " | ".join(preferences) + try: + #save to config file + self.config['members'][str(user.usrnum)][SecretSantaConstants.PREFERENCES] = str(new_prefs) + self.config.write() + #add the input to the value in the user's class instance + user.preferences = new_prefs + try: + await currAuthor.send("New preferences: {0}".format(new_prefs)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except: + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + + \ No newline at end of file diff --git a/BOT_ERROR.py b/helpers/BOT_ERROR.py similarity index 98% rename from BOT_ERROR.py rename to helpers/BOT_ERROR.py index 987f5a2..7868adf 100644 --- a/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,18 +1,18 @@ -DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" -EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" -INVALID_INPUT = "`Error: invalid input`" -EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" -NOT_STARTED = "`Error: partners have not been assigned yet.`" -def NO_PERMISSION(role): - return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) -ALREADY_JOINED = "`Error: You have already joined.`" -SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" -NOT_PAUSED = "`Error: Secret Santa is not paused`" -def HAS_NOT_SUBMITTED(usrname): - return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" -UNREACHABLE = "`Error: this shouldn't happen`" -NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" -MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" -def ARCHIVE_ERROR_LENGTH(msg_url): - return "`ERROR: output message is too long pinned message {0} was not archived.`" +DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" +EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" +INVALID_INPUT = "`Error: invalid input`" +EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" +NOT_STARTED = "`Error: partners have not been assigned yet.`" +def NO_PERMISSION(role): + return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) +ALREADY_JOINED = "`Error: You have already joined.`" +SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" +NOT_PAUSED = "`Error: Secret Santa is not paused`" +def HAS_NOT_SUBMITTED(usrname): + return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +UNREACHABLE = "`Error: this shouldn't happen`" +NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" +MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" +def ARCHIVE_ERROR_LENGTH(msg_url): + return "`ERROR: output message is too long pinned message {0} was not archived.`" diff --git a/SantaBotConstants.py b/helpers/SecretSantaConstants.py similarity index 80% rename from SantaBotConstants.py rename to helpers/SecretSantaConstants.py index 8a3468a..e49975c 100644 --- a/SantaBotConstants.py +++ b/helpers/SecretSantaConstants.py @@ -1,4 +1,4 @@ -class SantaBotConstants(): +class SecretSantaConstants(): NAME = 0 DISCRIMINATOR = 1 IDSTR = 2 diff --git a/SantaBotHelpers.py b/helpers/SecretSantaHelpers.py similarity index 96% rename from SantaBotHelpers.py rename to helpers/SecretSantaHelpers.py index 0ba2456..ec15cbd 100644 --- a/SantaBotHelpers.py +++ b/helpers/SecretSantaHelpers.py @@ -3,13 +3,13 @@ from discord import Member from discord.abc import PrivateChannel import discord -from Participant import Participant +from helpers.SecretSantaParticipant import SecretSantaParticipant -class SantaBotHelpers(): +class SecretSantaHelpers(): def isListOfParticipants(self, usrlist: list): for usr in usrlist: - if(not isinstance(usr, Participant)): + if(not isinstance(usr, SecretSantaParticipant)): return False def user_is_participant(self, usrid: discord.User.id, usrlist: list): diff --git a/Participant.py b/helpers/SecretSantaParticipant.py similarity index 97% rename from Participant.py rename to helpers/SecretSantaParticipant.py index 7d0f360..c65e6d9 100644 --- a/Participant.py +++ b/helpers/SecretSantaParticipant.py @@ -1,4 +1,4 @@ -class Participant(object): +class SecretSantaParticipant(object): """class defining a participant and info associated with them""" def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): self.name = name #string containing name of user diff --git a/santa-bot.py b/santa-bot.py index 04b2110..e87a60c 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -494,12 +494,12 @@ async def unpin_all(ctx: commands.Context, channel_id_to_unpin: int = -1): channel_id_to_unpin = ctx.channel.id remove_channel = bot.get_channel(channel_id_to_unpin) - start_message = "Attempting to remove all pinned messages from <#channel_id_to_unpin>." + start_message = "Attempting to remove all pinned messages from <#{0}>.".format(channel_id_to_unpin) await ctx.send(content=start_message) pins_to_remove = await remove_channel.pins() for pin in pins_to_remove: await pin.unpin() - end_message = "All pinned messages removed from <#channel_id_to_unpin>." + end_message = "All pinned messages removed from <#{0}>.".format(channel_id_to_unpin) await ctx.send(content=end_message) @bot.command() From 7f1bfb0df15d330a7b5fce8498a4d7bf8b26fdac Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 3 May 2020 20:42:59 -0700 Subject: [PATCH 113/189] [commit title] #33: Move the rest of the Secret Santa commands Closes #: [commit title] #33: Move the rest of the Secret Santa commands, move config back to main file config should be read from main bot file then passed to SantaBot --- cogs/SecretSanta.py | 266 ++++++++++++++++++++++-- helpers/SecretSantaHelpers.py | 1 + santa-bot.py | 374 ---------------------------------- 3 files changed, 246 insertions(+), 395 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 603b97d..95c13b0 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -12,7 +12,7 @@ class SecretSanta(commands.Cog): - def __init__(self, bot): + def __init__(self, bot : commands.Bot, config: ConfigObj): self.bot = bot self.exchange_started = False self.server = '' @@ -21,22 +21,7 @@ def __init__(self, bot): self.user_left_during_pause = False self.is_paused = False self.SecretSantaHelper = SecretSantaHelpers() - self.config = None - - - #initialize config file - try: - self.config = ConfigObj(CONFIG.cfg_path, file_error = True) - except: - try: - os.mkdir(CONFIG.bot_folder) - except: - pass - self.config = ConfigObj() - self.config.filename = CONFIG.cfg_path - self.config['programData'] = {'exchange_started': False} - self.config['members'] = {} - self.config.write() + self.config = config # retrieve data from config file self.exchange_started = self.config['programData'].as_bool('exchange_started') @@ -104,12 +89,12 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): Set new preferences ''' currAuthor = ctx.author - if self.SantaBotHelper.user_is_participant(currAuthor.id, self.usr_list): - if(self.SantaBotHelper.channelIsPrivate(ctx.channel)): + if self.self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): + if(self.self.SecretSantaHelper.channelIsPrivate(ctx.channel)): pass else: await ctx.message.delete() - (index, user) = self.SantaBotHelper.get_participant_object(currAuthor, self.usr_list) + (index, user) = self.self.SecretSantaHelper.get_participant_object(currAuthor, self.usr_list) new_prefs = "None" if(len(preferences) == 0): pass @@ -134,4 +119,243 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): await ctx.send(BOT_ERROR.UNJOINED) return - \ No newline at end of file + @commands.command() + async def getprefs(self, ctx: commands.Context): + ''' + Get current preferences + ''' + currAuthor = ctx.author + if self.SecretSantaHelper.user_is_participant(ctx.author.id, self.usr_list): + (index, user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) + try: + await currAuthor.send("Current preference(s): {0}".format(user.preferences)) + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + + @commands.command() + async def start(self, ctx: commands.Context): + # TODO: add help menu instruction + currAuthor = ctx.author + if(currAuthor.top_role == ctx.guild.roles[-1]): + # first ensure all users have all info submitted + all_fields_complete = True + for user in self.usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + + # select a random partner for each participant if all information is complete and there are enough people to do it + if(all_fields_complete and (len(self.usr_list) > 1)): + print("Proposing a partner list") + potential_list = self.SecretSantaHelper.propose_partner_list(self.usr_list) + while(not self.SecretSantaHelper.partners_are_valid(potential_list)): + print("Proposing a partner list") + potential_list = self.SecretSantaHelper.propose_partner_list(self.usr_list) + # save to config file + print("Partner assignment successful") + for user in potential_list: + (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(user.idstr, self.usr_list) + (index, partner) = self.SecretSantaHelper.get_participant_object(user.partnerid, potential_list) + temp_user.partnerid = user.partnerid + self.config['members'][str(user.usrnum)][SecretSantaConstants.PARTNERID] = user.partnerid + self.config.write() + # tell participants who their partner is + this_user = ctx.guild.get_member(int(user.idstr)) + message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" + message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" + message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" + message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 + try: + await this_user.send(santa_message) + except: + await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + + # mark the exchange as in-progress + exchange_started = True + is_paused = False + self.config['programData']['exchange_started'] = True + self.config.write() + usr_list = copy.deepcopy(potential_list) + await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") + elif not all_fields_complete: + await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) + elif not (len(self.usr_list) > 1): + await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + + @commands.command() + async def restart(self, ctx: commands.Context): + # TODO: add help menu instruction + currAuthor = ctx.author + is_paused = True + if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): + # ensure all users have all info submitted + all_fields_complete = True + for user in self.usr_list: + if(user.wishlisturl_is_set() and user.pref_is_set()): + pass + else: + all_fields_complete = False + try: + await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) + await ctx.send("`Partner assignment cancelled: participant info incomplete.`") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + list_changed = self.SecretSantaHelper.usr_list_changed_during_pause(self.usr_list, self.user_left_during_pause) + if(list_changed): + ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) + else: + exchange_started = True + is_paused = False + self.config['programData']['exchange_started'] = True + self.config.write() + await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") + elif(currAuthor.top_role != ctx.guild.roles[-1]): + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + elif(not is_paused): + await ctx.send(BOT_ERROR.NOT_PAUSED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return + + @commands.command() + async def pause(self, ctx: commands.Context): + # TODO: add help menu instruction + if(ctx.author.top_role == ctx.guild.roles[-1]): + exchange_started = False + self.config['programData']['exchange_started'] = False + self.config.write() + is_paused = True + await ctx.send("Secret Santa has been paused. New people may now join.") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + + @commands.command() + async def join(self, ctx: commands.Context): + ''' + Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. + ''' + currAuthor = ctx.author + # check if the exchange has already started + if self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): + await ctx.send(BOT_ERROR.ALREADY_JOINED) + else: + # initialize instance of Participant for the author + self.highest_key = self.highest_key + 1 + self.usr_list.append(SecretSantaParticipant(currAuthor.name, currAuthor.discriminator, currAuthor.id, self.highest_key)) + # write details of the class instance to config and increment total_users + self.config['members'][str(self.highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, self.highest_key, "", "", ""] + self.config.write() + + # prompt user about inputting info + await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") + try: + userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n + Use `{1}setwishlisturl [wishlist urls separated by a space ]` to set your wishlist URL (you may also add your mailing address).\n + Use `{2}setprefs [preferences separated by a space ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) + await currAuthor.send(userPrompt) + except: + ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + return + + @commands.command() + async def leave(self, ctx: commands.Context): + # TODO: add help menu instruction + currAuthor = ctx.author + if(self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list)): + (index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) + self.usr_list.remove(user) + popped_user = self.config['members'].pop(str(user.usrnum)) + self.config.write() + if(self.is_paused): + user_left_during_pause = True + await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) + else: + await ctx.send(BOT_ERROR.UNJOINED) + return + + @commands.command() + async def end(self, ctx: commands.Context): + # TODO: add help menu instruction + if(ctx.author.top_role == ctx.guild.roles[-1]): + exchange_started = False + is_paused = False + self.config['programData']['exchange_started'] = False + highest_key = 0 + del self.usr_list[:] + print(len(self.usr_list)) + self.config['members'].clear() + self.config.write() + await ctx.send("Secret Santa ended") + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + + @commands.command() + async def listparticipants(self, ctx: commands.Context): + # TODO: add help menu instruction + if(ctx.author.top_role == ctx.guild.roles[-1]): + if(self.highest_key == 0): + await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in self.usr_list: + this_user = ctx.guild.get_member(user.idstr) + msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" + msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) + else: + await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + return + + @commands.command() + async def totalparticipants(self, ctx: commands.Context): + # TODO: add help menu instruction + if self.highest_key == 0: + await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + elif self.highest_key == 1: + await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + else: + await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(self.usr_list)), CONFIG.prefix)) + return + + @commands.command() + async def partnerinfo(self, ctx: commands.Context): + # TODO: add help menu instruction + currAuthor = ctx.author + authorIsParticipant = self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list) + if(self.exchange_started and authorIsParticipant): + (usr_index, user) = self.SecretSantaHelper.get_participant_object(currAuthor, self.usr_list) + (partner_index, partnerobj) = self.SecretSantaHelper.get_participant_object(user.partnerid, self.usr_list) + msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" + msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" + msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" + msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" + try: + await currAuthor.send(msg) + await ctx.send("The information has been sent to your DMs.") + except: + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + elif((not self.exchange_started) and authorIsParticipant): + await ctx.send(BOT_ERROR.NOT_STARTED) + elif(self.exchange_started and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) + elif((not self.exchange_started) and (not authorIsParticipant)): + await ctx.send(BOT_ERROR.UNJOINED) + else: + await ctx.send(BOT_ERROR.UNREACHABLE) + return \ No newline at end of file diff --git a/helpers/SecretSantaHelpers.py b/helpers/SecretSantaHelpers.py index ec15cbd..056e44c 100644 --- a/helpers/SecretSantaHelpers.py +++ b/helpers/SecretSantaHelpers.py @@ -28,6 +28,7 @@ def get_participant_object(self, usrid: discord.User.id, usrlist: list): for (index, person) in enumerate(usrlist): if(int(person.idstr) == usrid): return (index, person) + return (-1, None) def propose_partner_list(self, usrlist: list): """Generate a proposed partner list""" diff --git a/santa-bot.py b/santa-bot.py index e87a60c..04846f0 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -28,26 +28,7 @@ config['programData'] = {'exchange_started': False} config['members'] = {} config.write() -#initialize data from config file -global usr_list -global highest_key -global exchange_started -global user_left_during_pause -global is_paused -server = '' -usr_list = [] -highest_key = 0 -user_left_during_pause = False -is_paused = False -SantaBotHelper = SantaBotHelpers() - -exchange_started = config['programData'].as_bool('exchange_started') -for key in config['members']: - data = config['members'][str(key)] - usr = Participant(data[SantaBotConstants.NAME], data[SantaBotConstants.DISCRIMINATOR], data[SantaBotConstants.IDSTR], data[SantaBotConstants.USRNUM], data[SantaBotConstants.WISHLISTURL], data[SantaBotConstants.PREFERENCES], data[SantaBotConstants.PARTNERID]) - usr_list.append(usr) - highest_key = int(key) #set up discord connection debug logging client_log = logging.getLogger('discord') client_log.setLevel(logging.DEBUG) @@ -91,361 +72,6 @@ async def echo(ctx: commands.Context, *content:str): else: await ctx.send(' '.join(content)) -@bot.command() -async def setwishlisturl(ctx: commands.Context, *destination:str): - ''' - [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). - ''' - currAuthor = ctx.author - global usr_list - if SantaBotHelper.user_is_participant(currAuthor.id, usr_list): - if(SantaBotHelper.channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() - (index, user) = SantaBotHelper.get_participant_object(currAuthor.id, usr_list) - new_wishlist = "None" - if(len(destination) == 0): - pass - else: - new_wishlist = " | ".join(destination) - try: - # save to config file - config['members'][str(user.usrnum)][SantaBotConstants.WISHLISTURL] = new_wishlist - config.write() - # add the input to the value in the user's class instance - user.wishlisturl = new_wishlist - try: - await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def getwishlisturl(ctx: commands.Context): - ''' - Get current wishlist - ''' - currAuthor = ctx.author - global usr_list - if SantaBotHelper.user_is_participant(ctx.author.id, usr_list): - (index, user) = SantaBotHelper.get_participant_object(ctx.author.id, usr_list) - try: - await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def setprefs(ctx: commands.Context, *preferences:str): - ''' - Set new preferences - ''' - currAuthor = ctx.author - global usr_list - if SantaBotHelper.user_is_participant(currAuthor.id, usr_list): - if(SantaBotHelper.channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() - (index, user) = SantaBotHelper.get_participant_object(currAuthor, usr_list) - new_prefs = "None" - if(len(preferences) == 0): - pass - else: - new_prefs = " | ".join(preferences) - try: - #save to config file - config['members'][str(user.usrnum)][SantaBotConstants.PREFERENCES] = str(new_prefs) - config.write() - #add the input to the value in the user's class instance - user.preferences = new_prefs - try: - await currAuthor.send("New preferences: {0}".format(new_prefs)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def getprefs(ctx: commands.Context): - ''' - Get current preferences - ''' - currAuthor = ctx.author - global usr_list - if SantaBotHelper.user_is_participant(ctx.author.id, usr_list): - (index, user) = SantaBotHelper.get_participant_object(ctx.author.id, usr_list) - try: - await currAuthor.send("Current preference(s): {0}".format(user.preferences)) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def start(ctx: commands.Context): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - global is_paused - if(currAuthor.top_role == ctx.guild.roles[-1]): - # first ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if(user.wishlisturl_is_set() and user.pref_is_set()): - pass - else: - all_fields_complete = False - try: - await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - - # select a random partner for each participant if all information is complete and there are enough people to do it - if(all_fields_complete and (len(usr_list) > 1)): - print("Proposing a partner list") - potential_list = SantaBotHelper.propose_partner_list(usr_list) - while(not SantaBotHelper.partners_are_valid(potential_list)): - print("Proposing a partner list") - potential_list = SantaBotHelper.propose_partner_list(usr_list) - # save to config file - print("Partner assignment successful") - for user in potential_list: - (temp_index, temp_user) = SantaBotHelper.get_participant_object(user.idstr, usr_list) - (index, partner) = SantaBotHelper.get_participant_object(user.partnerid, potential_list) - temp_user.partnerid = user.partnerid - config['members'][str(user.usrnum)][SantaBotConstants.PARTNERID] = user.partnerid - config.write() - # tell participants who their partner is - this_user = ctx.guild.get_member(int(user.idstr)) - message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" - message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" - message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" - santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 - try: - await this_user.send(santa_message) - except: - await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) - - # mark the exchange as in-progress - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - usr_list = copy.deepcopy(potential_list) - await ctx.send("Secret Santa pairs have been picked! Check your PMs and remember not to let your partner know. Have fun!") - elif not all_fields_complete: - await ctx.send(currAuthor.mention + BOT_ERROR.SIGNUPS_INCOMPLETE) - elif not (len(usr_list) > 1): - await ctx.send(BOT_ERROR.NOT_ENOUGH_SIGNUPS) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def restart(ctx: commands.Context): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - global is_paused - is_paused = True - if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): - # ensure all users have all info submitted - all_fields_complete = True - for user in usr_list: - if(user.wishlisturl_is_set() and user.pref_is_set()): - pass - else: - all_fields_complete = False - try: - await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) - await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - list_changed = SantaBotHelper.usr_list_changed_during_pause(usr_list, user_left_during_pause) - if(list_changed): - ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) - else: - exchange_started = True - is_paused = False - config['programData']['exchange_started'] = True - config.write() - await ctx.send("No change was made during the pause. Secret Santa resumed with the same partners.") - elif(currAuthor.top_role != ctx.guild.roles[-1]): - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - elif(not is_paused): - await ctx.send(BOT_ERROR.NOT_PAUSED) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - return - -@bot.command() -async def pause(ctx: commands.Context): - # TODO: add help menu instruction - global exchange_started - global is_paused - if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - config['programData']['exchange_started'] = False - config.write() - is_paused = True - await ctx.send("Secret Santa has been paused. New people may now join.") - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def join(ctx: commands.Context): - ''' - Join Secret Santa if it has not started. Contact the Secret Santa admin if you wish to join. - ''' - currAuthor = ctx.author - global usr_list - global highest_key - # check if the exchange has already started - if SantaBotHelper.user_is_participant(currAuthor.id, usr_list): - await ctx.send(BOT_ERROR.ALREADY_JOINED) - else: - # initialize instance of Participant for the author - highest_key = highest_key + 1 - usr_list.append(Participant(currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key)) - # write details of the class instance to config and increment total_users - config['members'][str(highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, highest_key, "", "", ""] - config.write() - - # prompt user about inputting info - await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") - try: - userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{1}setwishlisturl [wishlist urls separated by a space ]` to set your wishlist URL (you may also add your mailing address).\n - Use `{2}setprefs [preferences separated by a space ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) - await currAuthor.send(userPrompt) - except: - ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - return - -@bot.command() -async def leave(ctx: commands.Context): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global is_paused - global user_left_during_pause - if(SantaBotHelper.user_is_participant(currAuthor.id, usr_list)): - (index, user) = SantaBotHelper.get_participant_object(currAuthor.id, usr_list) - usr_list.remove(user) - popped_user = config['members'].pop(str(user.usrnum)) - config.write() - if(is_paused): - user_left_during_pause = True - await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) - else: - await ctx.send(BOT_ERROR.UNJOINED) - return - -@bot.command() -async def end(ctx: commands.Context): - # TODO: add help menu instruction - global usr_list - global highest_key - global exchange_started - global is_paused - if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - is_paused = False - config['programData']['exchange_started'] = False - highest_key = 0 - del usr_list[:] - print(len(usr_list)) - config['members'].clear() - config.write() - await ctx.send("Secret Santa ended") - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def listparticipants(ctx: commands.Context): - # TODO: add help menu instruction - global usr_list - global highest_key - if(ctx.author.top_role == ctx.guild.roles[-1]): - if(highest_key == 0): - await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in usr_list: - this_user = ctx.guild.get_member(user.idstr) - msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" - msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) - else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) - return - -@bot.command() -async def totalparticipants(ctx: commands.Context): - # TODO: add help menu instruction - global highest_key - if highest_key == 0: - await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - elif highest_key == 1: - await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) - else: - await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(usr_list)), CONFIG.prefix)) - return - -@bot.command() -async def partnerinfo(ctx: commands.Context): - # TODO: add help menu instruction - currAuthor = ctx.author - global usr_list - global exchange_started - authorIsParticipant = SantaBotHelper.user_is_participant(currAuthor.id, usr_list) - if(exchange_started and authorIsParticipant): - (usr_index, user) = SantaBotHelper.get_participant_object(currAuthor, usr_list) - (partner_index, partnerobj) = SantaBotHelper.get_participant_object(user.partnerid, usr_list) - msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" - msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" - msg = msg + "And their gift preferences can be found here: " + partnerobj.preferences + "\n" - msg = msg + "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *Secret* Santa, after all!" - try: - await currAuthor.send(msg) - await ctx.send("The information has been sent to your DMs.") - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - elif((not exchange_started) and authorIsParticipant): - await ctx.send(BOT_ERROR.NOT_STARTED) - elif(exchange_started and (not authorIsParticipant)): - await ctx.send(BOT_ERROR.EXCHANGE_STARTED_UNJOINED) - elif((not exchange_started) and (not authorIsParticipant)): - await ctx.send(BOT_ERROR.UNJOINED) - else: - await ctx.send(BOT_ERROR.UNREACHABLE) - return - @bot.command() @has_permissions(manage_roles=True, ban_members=True) async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_to_message: int): From 48ac51de691f2ef4f4e34a93ff0b42665b3c354a Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 00:40:21 -0700 Subject: [PATCH 114/189] #33: Moved all other commands (pins, reaction roles, ping, etc.) to cogs #33: Moved all other commands (pins, reaction roles, ping, etc.) to cogs General code cleanup --- .gitignore | 140 +++++++++++++++++++++++++++++++++ cogs/SantaAdministrative.py | 122 +++++++++++++++++++++++++++++ cogs/SantaMiscellaneous.py | 33 ++++++++ cogs/SecretSanta.py | 7 +- helpers/BOT_ERROR.py | 3 +- santa-bot.py | 151 +++--------------------------------- 6 files changed, 312 insertions(+), 144 deletions(-) create mode 100644 cogs/SantaAdministrative.py create mode 100644 cogs/SantaMiscellaneous.py diff --git a/.gitignore b/.gitignore index 5dbfb0a..38ce345 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,143 @@ CONFIG.py /__pycache__ Procfile *.code-workspace + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py new file mode 100644 index 0000000..1233601 --- /dev/null +++ b/cogs/SantaAdministrative.py @@ -0,0 +1,122 @@ +import CONFIG + +from discord.ext import commands +from discord.ext.commands import has_permissions, MissingPermissions, MissingRequiredArgument +from discord import RawReactionActionEvent + +import helpers.BOT_ERROR as BOT_ERROR + +class SantaAdministrative(commands.Cog, name='Administrative'): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.ROLE_CHANNEL = -1 + + @commands.command() + async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel: int): + dest_channel = self.bot.get_channel(reaction_role_channel) + self.ROLE_CHANNEL = reaction_role_channel + if dest_channel != None: + await ctx.send(content="Reaction role channel assigned to <#{0}>".format(self.ROLE_CHANNEL)) + else: + await ctx.send(content=BOT_ERROR.REACTION_ROLE_UNASSIGNED) + + @commands.command() + @has_permissions(manage_roles=True, ban_members=True) + async def archive_pins(self, ctx: commands.Context, channel_to_archive: int, channel_to_message: int): + ''' + Archive the pins in one channel to another channel as messages + ''' + src_id = channel_to_archive + dest_id = channel_to_message + src_channel = self.bot.get_channel(src_id) + dest_channel = self.bot.get_channel(dest_id) + start_message = "Attempting to archive pinned messages from <#{0}> to <#{1}>".format(src_id, dest_id) + await ctx.send(content=start_message) + pins_to_archive = await src_channel.pins() + pins_to_archive.reverse() + + for pin in pins_to_archive: + attachments = pin.attachments + attachment_str = "" + for attachment in attachments: + attachment_str += "{0}\n".format(attachment.url) # add link to attachments + + pin_author = pin.author.name + pin_datetime = pin.created_at.strftime("%B %d, %Y") + pin_url = pin.jump_url + pin_content = pin.content + output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_datetime, pin_content) + output_str += "Message link: <{0}>\n".format(pin_url) + output_str += "Attachment links: {0}".format(attachment_str) + if len(output_str) > 2000: + await ctx.send(content=BOT_ERROR.ARCHIVE_ERROR_LENGTH(pin_url)) + else: + await dest_channel.send(content=output_str) + + end_message = "Pinned message are archived in <#{0}>. If the archive messages look good, use **{1}unpin_all** to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) + await ctx.send(content=end_message) + + @commands.command() + @has_permissions(manage_roles=True, ban_members=True) + async def unpin_all(self, ctx: commands.Context, channel_id_to_unpin: int = -1): + ''' + Unpins all the pinned messages in the channel. Called to clean up after archive_pins. + Defaults to the channel in which it's called. + ''' + print(channel_id_to_unpin) + if channel_id_to_unpin == -1: + channel_id_to_unpin = ctx.channel.id + + remove_channel = self.bot.get_channel(channel_id_to_unpin) + start_message = "Attempting to remove all pinned messages from <#{0}>.".format(channel_id_to_unpin) + await ctx.send(content=start_message) + pins_to_remove = await remove_channel.pins() + for pin in pins_to_remove: + await pin.unpin() + end_message = "All pinned messages removed from <#{0}>.".format(channel_id_to_unpin) + await ctx.send(content=end_message) + + @unpin_all.error + @archive_pins.error + async def pin_archive_error(self, error, ctx): + if isinstance(error, MissingPermissions): + text = BOT_ERROR.NO_PERMISSION("mod") + await ctx.send(content=text) + elif isinstance(error, MissingRequiredArgument): + await ctx.send(content=BOT_ERROR.MISSING_ARGUMENTS) + else: + await ctx.send(content="Error: undetermined please contact <@224949031514800128>") + + @commands.Cog.listener(name='on_raw_reaction_add') + @commands.Cog.listener(name='on_raw_reaction_remove') + async def manage_reactions(self, payload: RawReactionActionEvent): + if(self.ROLE_CHANNEL == -1): + print(BOT_ERROR.REACTION_ROLE_UNASSIGNED) + return + + message_id = payload.message_id + channel_id = payload.channel_id + emoji = payload.emoji + guild = self.bot.get_guild(payload.guild_id) + user = guild.get_member(payload.user_id) + + if channel_id == self.ROLE_CHANNEL: + channel = self.bot.get_channel(self.ROLE_CHANNEL) + message = None + async for message in channel.history(limit=200): + if message.id == message_id: + break + + if message != None: + content = message.content.split("\n") + for line in content: + l = line.split(" ") + for c, word in enumerate(l): + if word == "for" and c > 0 and l[c-1] == str(emoji): + role_id = l[c+1][3:-1] + role = guild.get_role(int(role_id)) + print(payload.event_type) + if payload.event_type == "REACTION_ADD": + await user.add_roles(role) + else: + await user.remove_roles(role) diff --git a/cogs/SantaMiscellaneous.py b/cogs/SantaMiscellaneous.py new file mode 100644 index 0000000..9a277e0 --- /dev/null +++ b/cogs/SantaMiscellaneous.py @@ -0,0 +1,33 @@ +from discord.ext import commands +import CONFIG + +class SantaMiscellaneous(commands.Cog, name='Miscellaneous'): + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command() + async def invite(self, ctx: commands.Context): + link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) + await ctx.send_message("Bot invite link: {0}".format(link)) + + @commands.command() + async def echo(self, ctx: commands.Context, *content:str): + ''' + [content] = echos back the [content] + ''' + if(len(content) == 0): + pass + else: + await ctx.send(' '.join(content)) + + @commands.command() + async def ding(self, ctx: commands.Context): + await ctx.send("dong") + + @commands.command() + async def ping(self, ctx: commands.Context): + ''' + = Basic ping command + ''' + latency = self.bot.latency + await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) \ No newline at end of file diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 95c13b0..8a11970 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -1,6 +1,5 @@ import CONFIG from configobj import ConfigObj -import os import copy from discord.ext import commands @@ -10,9 +9,9 @@ from helpers.SecretSantaHelpers import SecretSantaHelpers from helpers.SecretSantaParticipant import SecretSantaParticipant -class SecretSanta(commands.Cog): +class SecretSanta(commands.Cog, name='Secret Santa'): - def __init__(self, bot : commands.Bot, config: ConfigObj): + def __init__(self, bot: commands.Bot, config: ConfigObj): self.bot = bot self.exchange_started = False self.server = '' @@ -31,7 +30,7 @@ def __init__(self, bot : commands.Bot, config: ConfigObj): self.usr_list.append(usr) self.highest_key = int(key) - @bot.command() + @commands.command() async def setwishlisturl(self, ctx: commands.Context, *destination:str): ''' [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 7868adf..28d3d90 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -15,4 +15,5 @@ def HAS_NOT_SUBMITTED(usrname): NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" def ARCHIVE_ERROR_LENGTH(msg_url): - return "`ERROR: output message is too long pinned message {0} was not archived.`" + return "`ERROR: output message is too long pinned message {0} was not archived.`".format(msg_url) +REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned" diff --git a/santa-bot.py b/santa-bot.py index 04846f0..d21859e 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,19 +1,15 @@ -import asyncio -import datetime as DT +import datetime import logging import os -import copy import discord from configobj import ConfigObj from discord.ext import commands -from discord.ext.commands import has_permissions, MissingPermissions, MissingRequiredArgument -import BOT_ERROR import CONFIG -from SantaBotConstants import SantaBotConstants -from SantaBotHelpers import SantaBotHelpers -from Participant import Participant +from cogs.SecretSanta import SecretSanta +from cogs.SantaAdministrative import SantaAdministrative +from cogs.SantaMiscellaneous import SantaMiscellaneous #initialize config file try: @@ -35,13 +31,12 @@ client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) client_log.addHandler(client_handler) - bot = commands.Bot(command_prefix = CONFIG.prefix) @bot.event async def on_ready(): """print message when client is connected""" - currentDT = DT.datetime.now() + currentDT = datetime.datetime.now() print('------') print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) print("Logged in as") @@ -50,133 +45,11 @@ async def on_ready(): await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) print('------') -@bot.command() -async def ping(ctx: commands.Context): - ''' - = Basic ping command - ''' - latency = bot.latency - await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) - -@bot.command() -async def ding(ctx: commands.Context): - await ctx.send("dong") - -@bot.command() -async def echo(ctx: commands.Context, *content:str): - ''' - [content] = echos back the [content] - ''' - if(len(content) == 0): - pass - else: - await ctx.send(' '.join(content)) - -@bot.command() -@has_permissions(manage_roles=True, ban_members=True) -async def archive_pins(ctx: commands.Context, channel_to_archive: int, channel_to_message: int): - ''' - Archive the pins in one channel to another channel as messages - ''' - src_id = channel_to_archive - dest_id = channel_to_message - src_channel = bot.get_channel(src_id) - dest_channel = bot.get_channel(dest_id) - start_message = "Attempting to archive pinned messages from <#{0}> to <#{1}>".format(src_id, dest_id) - await ctx.send(content=start_message) - pins_to_archive = await src_channel.pins() - pins_to_archive.reverse() - - for pin in pins_to_archive: - attachments = pin.attachments - attachment_str = "" - for attachment in attachments: - attachment_str += "{0}\n".format(attachment.url) # add link to attachments - - pin_author = pin.author.name - pin_datetime = pin.created_at.strftime("%B %d, %Y") - pin_url = pin.jump_url - pin_content = pin.content - output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_datetime, pin_content) - output_str += "Message link: <{0}>\n".format(pin_url) - output_str += "Attachment links: {0}".format(attachment_str) - if len(output_str) > 2000: - await ctx.send(content=BOT_ERROR.ARCHIVE_ERROR_LENGTH(pin_url)) - else: - await dest_channel.send(content=output_str) - - end_message = "Pinned message are archived in <#{0}>. If the archive messages look good, use **{1}unpin_all** to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) - await ctx.send(content=end_message) - -@bot.command() -@has_permissions(manage_roles=True, ban_members=True) -async def unpin_all(ctx: commands.Context, channel_id_to_unpin: int = -1): - ''' - Unpins all the pinned messages in the channel. Called to clean up after archive_pins. - Defaults to the channel in which it's called. - ''' - print(channel_id_to_unpin) - if channel_id_to_unpin == -1: - channel_id_to_unpin = ctx.channel.id - - remove_channel = bot.get_channel(channel_id_to_unpin) - start_message = "Attempting to remove all pinned messages from <#{0}>.".format(channel_id_to_unpin) - await ctx.send(content=start_message) - pins_to_remove = await remove_channel.pins() - for pin in pins_to_remove: - await pin.unpin() - end_message = "All pinned messages removed from <#{0}>.".format(channel_id_to_unpin) - await ctx.send(content=end_message) - -@bot.command() -async def invite(ctx: commands.Context): - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await ctx.send_message("Bot invite link: {0}".format(link)) - -ROLE_CHANNEL = 461740709062377473 -async def manage_reactions(payload, add): - message_id = payload.message_id - channel_id = payload.channel_id - emoji = payload.emoji - guild = bot.get_guild(payload.guild_id) - user = guild.get_member(payload.user_id) - - if channel_id == ROLE_CHANNEL: - channel = discord.utils.get(bot.get_all_channels(), id=channel_id) - async for message in channel.history(limit=200): - if message.id == message_id: - break - - content = message.content.split("\n") - for line in content: - l = line.split(" ") - for c, word in enumerate(l): - if word == "for" and c > 0 and l[c-1] == str(emoji): - role_id = l[c+1][3:-1] - role = guild.get_role(int(role_id)) - if add: - await user.add_roles(role) - else: - await user.remove_roles(role) - -@unpin_all.error -@archive_pins.error -async def pin_archive_error(error, ctx): - if isinstance(error, MissingPermissions): - text = BOT_ERROR.NO_PERMISSION("mod") - await ctx.send(content=text) - elif isinstance(error, MissingRequiredArgument): - await ctx.send(content=BOT_ERROR.MISSING_ARGUMENTS) - else: - await ctx.send(content="Error: undetermined please contact <@224949031514800128>") - - -@bot.event -async def on_raw_reaction_add(payload): - await manage_reactions(payload, True) - -@bot.event -async def on_raw_reaction_remove(payload): - await manage_reactions(payload, False) +def start_santa_bot(): + bot.add_cog(SecretSanta(bot, config)) + bot.add_cog(SantaAdministrative(bot)) + bot.add_cog(SantaMiscellaneous(bot)) + bot.run(CONFIG.discord_token, reconnect = True) -bot.run(CONFIG.discord_token, reconnect = True) +if __name__ == '__main__': + start_santa_bot() From d5153c08a2124beb54a2145b3f7e65d3daf2f1f3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 00:44:06 -0700 Subject: [PATCH 115/189] Sort bot errors alphabetically Sort bot errors alphabetically Code cleanup --- helpers/BOT_ERROR.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 28d3d90..7cf6282 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,19 +1,20 @@ +ALREADY_JOINED = "`Error: You have already joined.`" DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" +EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" INVALID_INPUT = "`Error: invalid input`" -EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" +MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" +NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" +NOT_PAUSED = "`Error: Secret Santa is not paused`" NOT_STARTED = "`Error: partners have not been assigned yet.`" -def NO_PERMISSION(role): - return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) -ALREADY_JOINED = "`Error: You have already joined.`" +REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned" SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" -NOT_PAUSED = "`Error: Secret Santa is not paused`" -def HAS_NOT_SUBMITTED(usrname): - return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" UNREACHABLE = "`Error: this shouldn't happen`" -NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" -MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" + def ARCHIVE_ERROR_LENGTH(msg_url): return "`ERROR: output message is too long pinned message {0} was not archived.`".format(msg_url) -REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned" +def HAS_NOT_SUBMITTED(usrname): + return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" +def NO_PERMISSION(role): + return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) From 38e34a25a4e0aa736478629ad2218445c4d7d632 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 01:04:28 -0700 Subject: [PATCH 116/189] Add role_channel to config --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 38ce345..6a67a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ *.cfg /.vscode .pylintrc -CONFIG.py /__pycache__ Procfile *.code-workspace From 784f6f928234c716e300d4810753ba71631735f2 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 01:07:15 -0700 Subject: [PATCH 117/189] Designate reaction role channel in CONFIG Designate reaction role channel in CONFIG Requires as little tinkering with the guts of the code as possible --- CONFIG.py | 1 + cogs/SantaAdministrative.py | 7 ++++++- cogs/SantaMiscellaneous.py | 7 +++++-- santa-bot.py | 28 ++++++++++++++-------------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index f8b9c36..2410065 100644 --- a/CONFIG.py +++ b/CONFIG.py @@ -4,3 +4,4 @@ bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" cfg_path = bot_folder + "botdata.cfg" dbg_path = bot_folder + "debug.log" +role_channel = -1 diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 1233601..c687b25 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -9,10 +9,15 @@ class SantaAdministrative(commands.Cog, name='Administrative'): def __init__(self, bot: commands.Bot): self.bot = bot - self.ROLE_CHANNEL = -1 + self.role_channel = CONFIG.role_channel @commands.command() + @has_permissions(manage_roles=True, ban_members=True) async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel: int): + ''' + Not recommended. Allows a reaction role channel to be assigned. + The recommended route is to set the role_channel variable in the bot's config file to the channel ID you want. + ''' dest_channel = self.bot.get_channel(reaction_role_channel) self.ROLE_CHANNEL = reaction_role_channel if dest_channel != None: diff --git a/cogs/SantaMiscellaneous.py b/cogs/SantaMiscellaneous.py index 9a277e0..ffc0ebb 100644 --- a/cogs/SantaMiscellaneous.py +++ b/cogs/SantaMiscellaneous.py @@ -5,8 +5,11 @@ class SantaMiscellaneous(commands.Cog, name='Miscellaneous'): def __init__(self, bot: commands.Bot): self.bot = bot - @commands.command() + # @commands.command() ### normally commented out because this bot is not meant to be sharded async def invite(self, ctx: commands.Context): + ''' + Creates an invite for this bot + ''' link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) await ctx.send_message("Bot invite link: {0}".format(link)) @@ -30,4 +33,4 @@ async def ping(self, ctx: commands.Context): = Basic ping command ''' latency = self.bot.latency - await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) \ No newline at end of file + await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) diff --git a/santa-bot.py b/santa-bot.py index d21859e..5d19564 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -11,20 +11,6 @@ from cogs.SantaAdministrative import SantaAdministrative from cogs.SantaMiscellaneous import SantaMiscellaneous -#initialize config file -try: - config = ConfigObj(CONFIG.cfg_path, file_error = True) -except: - try: - os.mkdir(CONFIG.bot_folder) - except: - pass - config = ConfigObj() - config.filename = CONFIG.cfg_path - config['programData'] = {'exchange_started': False} - config['members'] = {} - config.write() - #set up discord connection debug logging client_log = logging.getLogger('discord') client_log.setLevel(logging.DEBUG) @@ -46,6 +32,20 @@ async def on_ready(): print('------') def start_santa_bot(): + #initialize config file + try: + config = ConfigObj(CONFIG.cfg_path, file_error = True) + except: + try: + os.mkdir(CONFIG.bot_folder) + except: + pass + config = ConfigObj() + config.filename = CONFIG.cfg_path + config['programData'] = {'exchange_started': False} + config['members'] = {} + config.write() + bot.add_cog(SecretSanta(bot, config)) bot.add_cog(SantaAdministrative(bot)) bot.add_cog(SantaMiscellaneous(bot)) From ce8ee0617e73e46c6565aa7b7ed54b0bee524813 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 01:27:07 -0700 Subject: [PATCH 118/189] Whoops role channel needed to be lowercased everywhere else --- cogs/SantaAdministrative.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index c687b25..1a28cca 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -19,9 +19,9 @@ async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel The recommended route is to set the role_channel variable in the bot's config file to the channel ID you want. ''' dest_channel = self.bot.get_channel(reaction_role_channel) - self.ROLE_CHANNEL = reaction_role_channel + self.role_channel = reaction_role_channel if dest_channel != None: - await ctx.send(content="Reaction role channel assigned to <#{0}>".format(self.ROLE_CHANNEL)) + await ctx.send(content="Reaction role channel assigned to <#{0}>".format(self.role_channel)) else: await ctx.send(content=BOT_ERROR.REACTION_ROLE_UNASSIGNED) @@ -95,7 +95,7 @@ async def pin_archive_error(self, error, ctx): @commands.Cog.listener(name='on_raw_reaction_add') @commands.Cog.listener(name='on_raw_reaction_remove') async def manage_reactions(self, payload: RawReactionActionEvent): - if(self.ROLE_CHANNEL == -1): + if(self.role_channel == -1): print(BOT_ERROR.REACTION_ROLE_UNASSIGNED) return @@ -105,8 +105,8 @@ async def manage_reactions(self, payload: RawReactionActionEvent): guild = self.bot.get_guild(payload.guild_id) user = guild.get_member(payload.user_id) - if channel_id == self.ROLE_CHANNEL: - channel = self.bot.get_channel(self.ROLE_CHANNEL) + if channel_id == self.role_channel: + channel = self.bot.get_channel(self.role_channel) message = None async for message in channel.history(limit=200): if message.id == message_id: From 64f1eaf192fe39f28dfd3eecd90761a80703a5e1 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 01:32:28 -0700 Subject: [PATCH 119/189] Clean up debug print --- cogs/SantaAdministrative.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 1a28cca..5e9db24 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -120,7 +120,6 @@ async def manage_reactions(self, payload: RawReactionActionEvent): if word == "for" and c > 0 and l[c-1] == str(emoji): role_id = l[c+1][3:-1] role = guild.get_role(int(role_id)) - print(payload.event_type) if payload.event_type == "REACTION_ADD": await user.add_roles(role) else: From 2dc8e149ee0b7e50b4486bd869488d292b1f6ca4 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 01:37:07 -0700 Subject: [PATCH 120/189] make run_santa.sh executable --- run_santa.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 run_santa.sh diff --git a/run_santa.sh b/run_santa.sh old mode 100644 new mode 100755 From f1007545f6661514c2031fda43cbbffa2fc9773b Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 4 May 2020 12:37:46 -0700 Subject: [PATCH 121/189] Update README.md --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea2df23..6ffa7ca 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,15 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Steps to run: 1. Run `pip3 install -r requirements.txt` 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token +3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start + 3a. Replace other variables in CONFIG.py as you want + - `role_channel` is necessary for using reaction roles + - `bot_folder` this is where the .cfg file for the Secret Santa participants and the debug log are stored + - `prefix` change it to whatever you want otherwise commands default to YOUR_PREFIX_HEREjoin, YOUR_PREFIX_HEREunpin_all, etc. + - `cfg_path` and `dbg_path` don't need to do anything here 4. Run `python3 santa-bot.py` -#### Bot Commands: +#### Secret Santa Commands: - `s!join` = join the Secret Santa - `s!leave` = leave the Secret Santa @@ -27,4 +32,14 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners - `s!pause` **(admin only)** = pause Secret Santa (will require `s!start` and will reshuffle partners) - `s!end` **(admin only)** = end Secret Santa -- `s!ping` = check if bot is alive \ No newline at end of file + +#### Administrative Commands: +- `s!assign_role_channel CHANNEL_ID` **(admin only)** = change the channel the bot looks at for reaction roles +- `s!archive pins SRC_CHANNEL_ID DEST_CHANNEL_ID` **(admin only)** = archive all pins from the source channel to the destination channel as messages +- `s!unpin_all CHANNEL_ID` **(admin only)** = unpin all messages in the indicated channel (defaults to the channel the command is called in) + +#### Miscellaneous Commands: + +- `s!ping` = check if bot is alive +- `s!echo` = make the bot say stuff +- `s!ding` = dong From 0dd753f07999920d3bf75e2419e500bf3d82fd52 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 4 May 2020 13:50:00 -0700 Subject: [PATCH 122/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ffa7ca..ecf75c6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho #### Administrative Commands: - `s!assign_role_channel CHANNEL_ID` **(admin only)** = change the channel the bot looks at for reaction roles - `s!archive pins SRC_CHANNEL_ID DEST_CHANNEL_ID` **(admin only)** = archive all pins from the source channel to the destination channel as messages -- `s!unpin_all CHANNEL_ID` **(admin only)** = unpin all messages in the indicated channel (defaults to the channel the command is called in) +- `s!unpin_all [CHANNEL_ID]` **(admin only)** = unpin all messages in the indicated channel (defaults to the channel the command is called in) #### Miscellaneous Commands: From b535a80aa47c44b9a415ced88ff08169edc000aa Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Mon, 4 May 2020 22:05:34 -0700 Subject: [PATCH 123/189] Move on_ready listener to Administrative class Move on_ready listener to Administrative class + align error messages Streamline main bot file even further --- cogs/SantaAdministrative.py | 19 ++++++++++++++++--- helpers/BOT_ERROR.py | 29 +++++++++++++++-------------- santa-bot.py | 14 -------------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 5e9db24..7ed1e53 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -1,9 +1,10 @@ -import CONFIG - +import datetime from discord.ext import commands from discord.ext.commands import has_permissions, MissingPermissions, MissingRequiredArgument from discord import RawReactionActionEvent +from discord import Game as discordGame +import CONFIG import helpers.BOT_ERROR as BOT_ERROR class SantaAdministrative(commands.Cog, name='Administrative'): @@ -90,7 +91,7 @@ async def pin_archive_error(self, error, ctx): elif isinstance(error, MissingRequiredArgument): await ctx.send(content=BOT_ERROR.MISSING_ARGUMENTS) else: - await ctx.send(content="Error: undetermined please contact <@224949031514800128>") + await ctx.send(content=BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) @commands.Cog.listener(name='on_raw_reaction_add') @commands.Cog.listener(name='on_raw_reaction_remove') @@ -124,3 +125,15 @@ async def manage_reactions(self, payload: RawReactionActionEvent): await user.add_roles(role) else: await user.remove_roles(role) + + @commands.Cog.listener(name='on_ready') + async def nice_ready_print(self): + """print message when client is connected""" + currentDT = datetime.datetime.now() + print('------') + print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print("Logged in as") + print(self.bot.user.name) + print(self.bot.user.id) + await self.bot.change_presence(activity = discordGame(name = CONFIG.prefix + "help")) + print('------') diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 7cf6282..35c8921 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,20 +1,21 @@ -ALREADY_JOINED = "`Error: You have already joined.`" -DM_FAILED = " `Error: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -EXCHANGE_IN_PROGRESS = "`Error: The gift exchange is already in progress.`" -EXCHANGE_STARTED_UNJOINED = "`Error: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" -INVALID_INPUT = "`Error: invalid input`" -MISSING_ARGUMENTS = "`Error: command usage is missing arguments.`" -NOT_ENOUGH_SIGNUPS = "`Error: Secret santa not started. Need more people.`" -NOT_PAUSED = "`Error: Secret Santa is not paused`" -NOT_STARTED = "`Error: partners have not been assigned yet.`" +ALREADY_JOINED = "`ERROR: You have already joined.`" +DM_FAILED = " `ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" +EXCHANGE_IN_PROGRESS = "`ERROR: The gift exchange is already in progress.`" +EXCHANGE_STARTED_UNJOINED = "`ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" +INVALID_INPUT = "`ERROR: invalid input`" +MISSING_ARGUMENTS = "`ERROR: command usage is missing arguments.`" +NOT_ENOUGH_SIGNUPS = "`ERROR: Secret santa not started. Need more people.`" +NOT_PAUSED = "`ERROR: Secret Santa is not paused`" +NOT_STARTED = "`ERROR: partners have not been assigned yet.`" REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned" -SIGNUPS_INCOMPLETE = "`Error: Signups incomplete. Time for some love through harassment.`" -UNJOINED = "`Error: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" -UNREACHABLE = "`Error: this shouldn't happen`" +SIGNUPS_INCOMPLETE = "`ERROR: Signups incomplete. Time for some love through harassment.`" +UNDETERMINED_CONTACT_CODE_OWNER = "ERROR: undetermined please contact <@224949031514800128> for more information" # @mentions me, the maintainer of SantaBot +UNJOINED = "`ERROR: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" +UNREACHABLE = "`ERROR: this shouldn't happen`" def ARCHIVE_ERROR_LENGTH(msg_url): return "`ERROR: output message is too long pinned message {0} was not archived.`".format(msg_url) def HAS_NOT_SUBMITTED(usrname): - return "`Error: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" + return "`ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" def NO_PERMISSION(role): - return "`Error: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) + return "`ERROR: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) diff --git a/santa-bot.py b/santa-bot.py index 5d19564..a024fc2 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,8 +1,6 @@ -import datetime import logging import os -import discord from configobj import ConfigObj from discord.ext import commands @@ -19,18 +17,6 @@ client_log.addHandler(client_handler) bot = commands.Bot(command_prefix = CONFIG.prefix) -@bot.event -async def on_ready(): - """print message when client is connected""" - currentDT = datetime.datetime.now() - print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) - print("Logged in as") - print(bot.user.name) - print(bot.user.id) - await bot.change_presence(activity = discord.Game(name = CONFIG.prefix + "help")) - print('------') - def start_santa_bot(): #initialize config file try: From 92bdc32ea28d4c2af8daefaa4973cd0890036a1a Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 4 May 2020 22:36:22 -0700 Subject: [PATCH 124/189] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ecf75c6..8ed4899 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # SantaBot -A Discord bot to organize secret santa gift exchanges using the discord.py Python library +A Discord bot to organize secret santa gift exchanges using the discord.py Python library + some other admin-type stuff for my server + +- This bot code must be forked/pulled and run locally +- If you don't want the admin-type stuff, just go in and comment out the add_cog(SantaAdministrative(...)) line ### Requirements - python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) From eac061da405f3acda66a8d30aa8cdd1592786ec7 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 4 May 2020 23:26:42 -0700 Subject: [PATCH 125/189] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ed4899..a1a6589 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Steps to run: 1. Run `pip3 install -r requirements.txt` 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start - 3a. Replace other variables in CONFIG.py as you want +3. Replace variables in CONFIG.py + 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start + 3b. Replace other variables as you want - `role_channel` is necessary for using reaction roles - `bot_folder` this is where the .cfg file for the Secret Santa participants and the debug log are stored - `prefix` change it to whatever you want otherwise commands default to YOUR_PREFIX_HEREjoin, YOUR_PREFIX_HEREunpin_all, etc. From 68dac0b3e43cfdf018f4d613b0624f52ac824a70 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 13 May 2020 01:06:28 -0700 Subject: [PATCH 126/189] Closes #25: Fill in all help commands in SecretSanta cog Closes #25: Fill in all help commands in SecretSanta cog. Also fixes class variable bugs related to migration to SecretSanta class. --- cogs/SecretSanta.py | 54 ++++++++++++++++++++++++++++++-------------- helpers/BOT_ERROR.py | 2 ++ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 8a11970..6ae12f3 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -136,7 +136,9 @@ async def getprefs(self, ctx: commands.Context): @commands.command() async def start(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + Begin the Secret Santa + ''' currAuthor = ctx.author if(currAuthor.top_role == ctx.guild.roles[-1]): # first ensure all users have all info submitted @@ -180,8 +182,8 @@ async def start(self, ctx: commands.Context): await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) # mark the exchange as in-progress - exchange_started = True - is_paused = False + self.exchange_started = True + self.is_paused = False self.config['programData']['exchange_started'] = True self.config.write() usr_list = copy.deepcopy(potential_list) @@ -198,7 +200,9 @@ async def start(self, ctx: commands.Context): @commands.command() async def restart(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + Restart the Secret Santa after pause + ''' currAuthor = ctx.author is_paused = True if((currAuthor.top_role == ctx.guild.roles[-1]) and is_paused): @@ -218,7 +222,7 @@ async def restart(self, ctx: commands.Context): if(list_changed): ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) else: - exchange_started = True + self.exchange_started = True is_paused = False self.config['programData']['exchange_started'] = True self.config.write() @@ -233,12 +237,14 @@ async def restart(self, ctx: commands.Context): @commands.command() async def pause(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + Pause for people that want to join last-minute (reshuffles and matches upon restart) + ''' if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False + self.exchange_started = False self.config['programData']['exchange_started'] = False self.config.write() - is_paused = True + self.is_paused = True await ctx.send("Secret Santa has been paused. New people may now join.") else: await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) @@ -274,7 +280,13 @@ async def join(self, ctx: commands.Context): @commands.command() async def leave(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + Leave the Secret Santa + ''' + if(self.exchange_started): + await ctx.send(BOT_ERROR.EXCHANGE_IN_PROGRESS_LEAVE("a mod")) + return + currAuthor = ctx.author if(self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list)): (index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) @@ -282,7 +294,7 @@ async def leave(self, ctx: commands.Context): popped_user = self.config['members'].pop(str(user.usrnum)) self.config.write() if(self.is_paused): - user_left_during_pause = True + self.user_left_during_pause = True await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) else: await ctx.send(BOT_ERROR.UNJOINED) @@ -290,12 +302,14 @@ async def leave(self, ctx: commands.Context): @commands.command() async def end(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + End the Secret Santa + ''' if(ctx.author.top_role == ctx.guild.roles[-1]): - exchange_started = False - is_paused = False + self.exchange_started = False + self.is_paused = False self.config['programData']['exchange_started'] = False - highest_key = 0 + self.highest_key = 0 del self.usr_list[:] print(len(self.usr_list)) self.config['members'].clear() @@ -307,7 +321,9 @@ async def end(self, ctx: commands.Context): @commands.command() async def listparticipants(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + List Secret Santa participants + ''' if(ctx.author.top_role == ctx.guild.roles[-1]): if(self.highest_key == 0): await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) @@ -323,7 +339,9 @@ async def listparticipants(self, ctx: commands.Context): @commands.command() async def totalparticipants(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + Find out how many people have joined the Secret Santa + ''' if self.highest_key == 0: await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) elif self.highest_key == 1: @@ -334,7 +352,9 @@ async def totalparticipants(self, ctx: commands.Context): @commands.command() async def partnerinfo(self, ctx: commands.Context): - # TODO: add help menu instruction + ''' + Get your partner information via DM (partner name, wishlist, gift preference) + ''' currAuthor = ctx.author authorIsParticipant = self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list) if(self.exchange_started and authorIsParticipant): diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 35c8921..6b1fc0f 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -15,6 +15,8 @@ def ARCHIVE_ERROR_LENGTH(msg_url): return "`ERROR: output message is too long pinned message {0} was not archived.`".format(msg_url) +def EXCHANGE_IN_PROGRESS_LEAVE(role): + return "`ERROR: The gift exchange is already in progress. Please contact <@224949031514800128> or {0} about leaving.`".format(role) def HAS_NOT_SUBMITTED(usrname): return "`ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" def NO_PERMISSION(role): From cd5a749a3da3511df859c4bdb50aba85a81a33b7 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 15 May 2020 01:22:30 -0700 Subject: [PATCH 127/189] Modify CONFIG.py to work on any system Programatically insert file separators to find the cfg, sqlite, dbg path. Create sqlite path. Allows users to run SantaBot on whatever OS they want --- CONFIG.py | 18 +++++++++++++++--- README.md | 6 +++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index 2410065..5312d03 100644 --- a/CONFIG.py +++ b/CONFIG.py @@ -1,7 +1,19 @@ +import os +import pathlib + discord_token = "YOUR_TOKEN_HERE" client_id = "YOUR_CLIENT_ID" prefix = "YOUR_PREFIX_HERE" -bot_folder = "./YOUR_STORAGE_FOLDER_HERE/" -cfg_path = bot_folder + "botdata.cfg" -dbg_path = bot_folder + "debug.log" +bot_folder = "YOUR_STORAGE_FOLDER_NAME_HERE" +cfg_name = "botdata.cfg" +sqlite_name = "santabotdb.sqlite" +dbg_name = "debug.log" role_channel = -1 + + +############################################### +### DO NOT CHANGE BELOW ### +############################################### +cfg_path = os.path.join(pathlib.Path(__file__).parent, bot_folder, cfg_name) +sqlite_path = os.path.join(pathlib.Path(__file__).parent, bot_folder, sqlite_name) +dbg_path = os.path.join(pathlib.Path(__file__).parent, bot_folder, dbg_name) diff --git a/README.md b/README.md index a1a6589..02e4931 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho 3. Replace variables in CONFIG.py 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start 3b. Replace other variables as you want - - `role_channel` is necessary for using reaction roles - - `bot_folder` this is where the .cfg file for the Secret Santa participants and the debug log are stored + - `role_channel` is REQUIRED for using reaction roles - but will throw an error if unassigned + - `bot_folder` this is where the .cfg for the Secret Santa participants, the debug log, and the SQLite database files are stored - `prefix` change it to whatever you want otherwise commands default to YOUR_PREFIX_HEREjoin, YOUR_PREFIX_HEREunpin_all, etc. - - `cfg_path` and `dbg_path` don't need to do anything here + - `cfg_name`, `dbg_name`, `sqlite_name` don't *need* to do anything here unless you want to 4. Run `python3 santa-bot.py` #### Secret Santa Commands: From 9449f58eea6318169d08e9212f9c8db304ccfde0 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 16 May 2020 17:24:37 -0700 Subject: [PATCH 128/189] #41: Implement countdown timer command Also begin work on #38 by implementing SQLiteHelper. Removed standard datetime -> pendulum. requirements.txt changed accordingly. Removed datetime in favor of the more powerful pendulum datetime package. --- .gitignore | 1 + cogs/SantaAdministrative.py | 105 +++++++++++++++++++++++++++++-- cogs/SantaMiscellaneous.py | 7 +-- helpers/BOT_ERROR.py | 41 ++++++------ helpers/SQLiteHelper.py | 120 ++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + santa-bot.py | 8 ++- 7 files changed, 253 insertions(+), 30 deletions(-) create mode 100644 helpers/SQLiteHelper.py diff --git a/.gitignore b/.gitignore index 6a67a4b..6b034a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.log *.cfg +*.sqlite /.vscode .pylintrc /__pycache__ diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 7ed1e53..d581a77 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -1,4 +1,5 @@ -import datetime +import pendulum + from discord.ext import commands from discord.ext.commands import has_permissions, MissingPermissions, MissingRequiredArgument from discord import RawReactionActionEvent @@ -6,11 +7,14 @@ import CONFIG import helpers.BOT_ERROR as BOT_ERROR +from helpers.SQLiteHelper import SQLiteHelper class SantaAdministrative(commands.Cog, name='Administrative'): - def __init__(self, bot: commands.Bot): + def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): self.bot = bot self.role_channel = CONFIG.role_channel + self.sqlhelp = sqlitehelper + self.sqlhelp.create_table("Countdowns", "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") @commands.command() @has_permissions(manage_roles=True, ban_members=True) @@ -48,10 +52,12 @@ async def archive_pins(self, ctx: commands.Context, channel_to_archive: int, cha attachment_str += "{0}\n".format(attachment.url) # add link to attachments pin_author = pin.author.name - pin_datetime = pin.created_at.strftime("%B %d, %Y") + pin_pendulum = pendulum.instance(pin.created_at) + pin_dt_str = pin_pendulum.format("MMMM DD, YYYY") + pin_url = pin.jump_url pin_content = pin.content - output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_datetime, pin_content) + output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_dt_str, pin_content) output_str += "Message link: <{0}>\n".format(pin_url) output_str += "Attachment links: {0}".format(attachment_str) if len(output_str) > 2000: @@ -82,6 +88,93 @@ async def unpin_all(self, ctx: commands.Context, channel_id_to_unpin: int = -1): end_message = "All pinned messages removed from <#{0}>.".format(channel_id_to_unpin) await ctx.send(content=end_message) + @commands.command(aliases=['cd']) + async def countdown(self, ctx: commands.Context, command: str, *, arg=""): + expected_pend_format = "MM/D/YY [@] h:m A Z" + cd_table_name = "Countdowns" + args = arg.split(sep=" | ") + countdown_name = args[0] + countdown_time = "" + if(len(args) > 1): + countdown_time = args[1] + if(command == "set"): + try: + pend_test_convert = pendulum.from_format(countdown_time, expected_pend_format) # check that the format is correct + if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(countdown_name, countdown_time, ctx.author.id)])): + await ctx.send("{0} countdown set for {1} ({2})".format(countdown_name, countdown_time, pend_test_convert.diff_for_humans(pendulum.now()))) + except ValueError as error: + expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" + error_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" + print(error_str) + await ctx.send(error_str) + return + elif(command == "change"): + try: + pend_test_convert = pendulum.from_format(countdown_time, expected_pend_format) # check that the format is correct + except ValueError as error: + expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" + error_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" + print(error_str) + await ctx.send(error_str) + return + + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + if(len(query_result) > 0): + (query_id, query_name, query_time, query_user_id) = query_result[0] + if(ctx.author.id == query_user_id): + if(self.sqlhelp.execute_update_query(cd_table_name, "time=\'{0}\'".format(countdown_time), "id={0}".format(query_id))): + await ctx.send("Updated countdown for {0}".format(countdown_name)) + else: + await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + else: + cd_owner = ctx.guild.get_member(query_user_id) + await ctx.send(BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name)) + else: + await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + elif(command == "check"): + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + (query_id, query_name, query_time, query_user_id) = query_result[0] + cd_pend = pendulum.from_format(query_time, expected_pend_format) + now_dt = pendulum.now() + cd_diff = cd_pend.diff(now_dt) + output = "Time until {0}: {1} days, {2} hours, {3} minutes".format(countdown_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + await ctx.send(output) + else: + await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + elif(command == "list"): + query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) + output = "Name | Time | Time Until\n" + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + cd_pend = pendulum.from_format(query_time, expected_pend_format) + output += "{0} | {1} | {2} now\n".format(query_name, query_time, cd_pend.diff_for_humans(pendulum.now())) + await ctx.send(output) + elif(command == "remove"): + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + (query_id, query_name, query_time, query_user_id) = query_result[0] + if(self.sqlhelp.execute_delete_query(cd_table_name, "id=query_user_id")): + await ctx.send("Countdown timer removed.") + else: + await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + else: + await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + elif(command == "clean"): + query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + if(not pendulum.from_format(query_time, expected_pend_format).is_future()): # if the countdown as passed, delete + self.sqlhelp.execute_delete_query(cd_table_name, "id = {0}".format(query_id)) + else: + await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_COMMAND) + @unpin_all.error @archive_pins.error async def pin_archive_error(self, error, ctx): @@ -129,9 +222,9 @@ async def manage_reactions(self, payload: RawReactionActionEvent): @commands.Cog.listener(name='on_ready') async def nice_ready_print(self): """print message when client is connected""" - currentDT = datetime.datetime.now() + currentDT = pendulum.now() print('------') - print (currentDT.strftime("%Y-%m-%d %H:%M:%S")) + print (currentDT.format("YYYY-MM-D H:mm:ss")) print("Logged in as") print(self.bot.user.name) print(self.bot.user.id) diff --git a/cogs/SantaMiscellaneous.py b/cogs/SantaMiscellaneous.py index ffc0ebb..8060ecc 100644 --- a/cogs/SantaMiscellaneous.py +++ b/cogs/SantaMiscellaneous.py @@ -14,14 +14,11 @@ async def invite(self, ctx: commands.Context): await ctx.send_message("Bot invite link: {0}".format(link)) @commands.command() - async def echo(self, ctx: commands.Context, *content:str): + async def echo(self, ctx: commands.Context, *, content:str): ''' [content] = echos back the [content] ''' - if(len(content) == 0): - pass - else: - await ctx.send(' '.join(content)) + await ctx.send(content) @commands.command() async def ding(self, ctx: commands.Context): diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 6b1fc0f..e997536 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,23 +1,28 @@ -ALREADY_JOINED = "`ERROR: You have already joined.`" -DM_FAILED = " `ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages.`" -EXCHANGE_IN_PROGRESS = "`ERROR: The gift exchange is already in progress.`" -EXCHANGE_STARTED_UNJOINED = "`ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join.`" -INVALID_INPUT = "`ERROR: invalid input`" -MISSING_ARGUMENTS = "`ERROR: command usage is missing arguments.`" -NOT_ENOUGH_SIGNUPS = "`ERROR: Secret santa not started. Need more people.`" -NOT_PAUSED = "`ERROR: Secret Santa is not paused`" -NOT_STARTED = "`ERROR: partners have not been assigned yet.`" -REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned" -SIGNUPS_INCOMPLETE = "`ERROR: Signups incomplete. Time for some love through harassment.`" -UNDETERMINED_CONTACT_CODE_OWNER = "ERROR: undetermined please contact <@224949031514800128> for more information" # @mentions me, the maintainer of SantaBot -UNJOINED = "`ERROR: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange.`" -UNREACHABLE = "`ERROR: this shouldn't happen`" +ALREADY_JOINED = "ERROR: You have already joined." +DM_FAILED = "ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages." +EXCHANGE_IN_PROGRESS = "ERROR: The gift exchange is already in progress." +EXCHANGE_STARTED_UNJOINED = "ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join." +INVALID_INPUT = "ERROR: invalid input." +INVALID_COUNTDOWN_COMMAND = "ERROR: invalid countdown option." +MISSING_ARGUMENTS = "ERROR: command usage is missing arguments." +NOT_ENOUGH_SIGNUPS = "ERROR: Secret santa not started. Need more people." +NOT_PAUSED = "ERROR: Secret Santa is not paused" +NOT_STARTED = "ERROR: partners have not been assigned yet." +REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned." +SIGNUPS_INCOMPLETE = "ERROR: Signups incomplete. Time for some love through harassment." +UNDETERMINED_CONTACT_CODE_OWNER = "ERROR: undetermined please contact <@224949031514800128> for more information." # @mentions me, the maintainer of SantaBot +UNJOINED = "ERROR: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange." +UNREACHABLE = "ERROR: this shouldn't happen." def ARCHIVE_ERROR_LENGTH(msg_url): - return "`ERROR: output message is too long pinned message {0} was not archived.`".format(msg_url) + return "ERROR: output message is too long pinned message {0} was not archived.".format(msg_url) def EXCHANGE_IN_PROGRESS_LEAVE(role): - return "`ERROR: The gift exchange is already in progress. Please contact <@224949031514800128> or {0} about leaving.`".format(role) + return "ERROR: The gift exchange is already in progress. Please contact <@224949031514800128> or {0} about leaving.".format(role) def HAS_NOT_SUBMITTED(usrname): - return "`ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences.`" + return "ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences." +def INVALID_COUNTDOWN_NAME(cd_name): + return "ERROR: countdown timer {0} does not exist. Use `s!countdown list` to list all countdown timers.".format(cd_name) +def CANNOT_CHANGE_COUNTDOWN(author_name): + return "ERROR: you do not have permission to change that countdown timer. Please contact {0}".format(author_name) def NO_PERMISSION(role): - return "`ERROR: you do not have permissions to do this.\nYou need the {0} role for that.`".format(role) + return "ERROR: you do not have permissions to do this.\nYou need the {0} role for that.".format(role) diff --git a/helpers/SQLiteHelper.py b/helpers/SQLiteHelper.py new file mode 100644 index 0000000..121e238 --- /dev/null +++ b/helpers/SQLiteHelper.py @@ -0,0 +1,120 @@ +import sqlite3 +from sqlite3 import Error + +class SQLiteHelper(): + def __init__(self, connection_path: str): + self.__connection_path = connection_path + self.__connection = None + + def create_connection(self): + try: + if(self.__connection == None): + self.__connection = sqlite3.connect(self.__connection_path) + print("Connection to SQLite DB successful") + except Error as e: + print("The error '{0}' occurred".format(e)) + + def execute_query(self, query): + if(self.__connection == None): + print("Connection has not been created") + + cursor = self.__connection.cursor() + try: + cursor.execute(query) + self.__connection.commit() + print("Query executed successfully") + return True + except Error as e: + print("The error '{0}' occurred".format(e.with_traceback)) + return False + + def create_table(self, table_name: str, table_format: str): + ''' + example_usage --> + + ex_table_name = "users" + + ex_table_format = "( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + age INTEGER, + gender TEXT, + nationality TEXT + )" + + ~~~ + + CREATE TABLE IF NOT EXISTS users ( + + id INTEGER PRIMARY KEY AUTOINCREMENT, + + name TEXT NOT NULL, + + age INTEGER, + + gender TEXT, + + nationality TEXT + + ); + ''' + table_query = """ + CREATE TABLE IF NOT EXISTS {0} {1}; + """.format(table_name, table_format) + return self.execute_query(table_query) + + + def insert_records(self, table_name: str, column_list: str, values: list): + ''' + example usage --> + + ex_table_name = "users", + + ex_column_list = "(name, age, gender, nationality)", + + ex_values = ["('James', 25, 'male', 'USA')", "('Leila', 32, 'female', 'France')"] + + ~~~ + + INSERT INTO users (name, age, gender, nationality) + + VALUES + + ('James', 25, 'male', 'USA'), + + ('Leila', 32, 'female', 'France'); + ''' + value_str = ",".join(values) + table_query = """ + INSERT INTO + {0} {1} + VALUES + {2}; + """.format(table_name, column_list, value_str) + + return self.execute_query(table_query) + + def execute_read_query(self, query): + cursor = self.__connection.cursor() + result = None + try: + cursor.execute(query) + result = cursor.fetchall() + return result + except Error as e: + print("Problem reading - the error '{0}' occurred".format(e)) + + def execute_delete_query(self, table_name: str, conditions: str): + delete_query = "DELETE FROM {0} WHERE {1}".format(table_name, conditions) + return self.execute_query(delete_query) + + def execute_update_query(self, table_name: str, new_column_data: str, conditions: str): + update_query = """ + UPDATE + {0} + SET + {1} + WHERE + {2} + """.format(table_name, new_column_data, conditions) + return self.execute_query(update_query) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 92ba4ec..a6c5758 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ discord.py configobj +pendulum diff --git a/santa-bot.py b/santa-bot.py index a024fc2..276add4 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -8,6 +8,7 @@ from cogs.SecretSanta import SecretSanta from cogs.SantaAdministrative import SantaAdministrative from cogs.SantaMiscellaneous import SantaMiscellaneous +from helpers.SQLiteHelper import SQLiteHelper #set up discord connection debug logging client_log = logging.getLogger('discord') @@ -19,6 +20,7 @@ def start_santa_bot(): #initialize config file + config = None try: config = ConfigObj(CONFIG.cfg_path, file_error = True) except: @@ -32,8 +34,12 @@ def start_santa_bot(): config['members'] = {} config.write() + # initialize the SQLite db + sqlitehelper = SQLiteHelper(CONFIG.sqlite_path) + sqlitehelper.create_connection() + bot.add_cog(SecretSanta(bot, config)) - bot.add_cog(SantaAdministrative(bot)) + bot.add_cog(SantaAdministrative(bot, sqlitehelper)) bot.add_cog(SantaMiscellaneous(bot)) bot.run(CONFIG.discord_token, reconnect = True) From 559729b33fec83205cfc93ffeda3bd6d92cb5bf3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 17 May 2020 13:19:18 -0700 Subject: [PATCH 129/189] Bound pin archive channel lookup by the context Bound pin archive channel lookup by the context, error checking Error-checking to make sure it doesn't error without letting the user know --- cogs/SantaAdministrative.py | 15 +++++++++++---- helpers/BOT_ERROR.py | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index d581a77..a2c3777 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -23,7 +23,7 @@ async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel Not recommended. Allows a reaction role channel to be assigned. The recommended route is to set the role_channel variable in the bot's config file to the channel ID you want. ''' - dest_channel = self.bot.get_channel(reaction_role_channel) + dest_channel = ctx.guild.get_channel(reaction_role_channel) self.role_channel = reaction_role_channel if dest_channel != None: await ctx.send(content="Reaction role channel assigned to <#{0}>".format(self.role_channel)) @@ -38,10 +38,14 @@ async def archive_pins(self, ctx: commands.Context, channel_to_archive: int, cha ''' src_id = channel_to_archive dest_id = channel_to_message - src_channel = self.bot.get_channel(src_id) - dest_channel = self.bot.get_channel(dest_id) + src_channel = ctx.guild.get_channel(src_id) + dest_channel = ctx.guild.get_channel(dest_id) + start_message = "Attempting to archive pinned messages from <#{0}> to <#{1}>".format(src_id, dest_id) await ctx.send(content=start_message) + if((src_channel == None) or (dest_channel == None)): + await ctx.send(BOT_ERROR.INACCESSIBLE_CHANNEL) + return pins_to_archive = await src_channel.pins() pins_to_archive.reverse() @@ -79,9 +83,12 @@ async def unpin_all(self, ctx: commands.Context, channel_id_to_unpin: int = -1): if channel_id_to_unpin == -1: channel_id_to_unpin = ctx.channel.id - remove_channel = self.bot.get_channel(channel_id_to_unpin) + remove_channel = ctx.guild.get_channel(channel_id_to_unpin) start_message = "Attempting to remove all pinned messages from <#{0}>.".format(channel_id_to_unpin) await ctx.send(content=start_message) + if(remove_channel == None): + await ctx.send(BOT_ERROR.INACCESSIBLE_CHANNEL) + return pins_to_remove = await remove_channel.pins() for pin in pins_to_remove: await pin.unpin() diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index e997536..707f04f 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -2,6 +2,7 @@ DM_FAILED = "ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages." EXCHANGE_IN_PROGRESS = "ERROR: The gift exchange is already in progress." EXCHANGE_STARTED_UNJOINED = "ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join." +INACCESSIBLE_CHANNEL = "ERROR: the channel specified is not accessible to this bot." INVALID_INPUT = "ERROR: invalid input." INVALID_COUNTDOWN_COMMAND = "ERROR: invalid countdown option." MISSING_ARGUMENTS = "ERROR: command usage is missing arguments." From 42c4cbdab66dad4821bfe95f0156dcadba413a9f Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sun, 17 May 2020 16:34:31 -0700 Subject: [PATCH 130/189] Add countdown argument help Add countdown argument help Give the user some guidance on what to do since this is a multi-stage command --- cogs/SantaAdministrative.py | 63 ++++++++++++++++++++++++++++++++----- helpers/BOT_ERROR.py | 2 +- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index a2c3777..231f06c 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -96,14 +96,31 @@ async def unpin_all(self, ctx: commands.Context, channel_id_to_unpin: int = -1): await ctx.send(content=end_message) @commands.command(aliases=['cd']) - async def countdown(self, ctx: commands.Context, command: str, *, arg=""): + async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): + ''' + Add a countdown timer + ''' + if(command == ""): + usage_guide = "Proper usage: {0} \n".format(CONFIG.prefix) + usage_guide += "Use each sub-command for more information on the necessary arguments" + await ctx.send(usage_guide) + return + expected_pend_format = "MM/D/YY [@] h:m A Z" cd_table_name = "Countdowns" args = arg.split(sep=" | ") - countdown_name = args[0] + countdown_name = "" countdown_time = "" + if(len(args) > 0): + countdown_name = args[0] if(len(args) > 1): countdown_time = args[1] + + cd_hints = self.find_countdown_hints(command, countdown_name, countdown_time) + if(cd_hints != ""): + await ctx.send(cd_hints) + return + if(command == "set"): try: pend_test_convert = pendulum.from_format(countdown_time, expected_pend_format) # check that the format is correct @@ -111,7 +128,7 @@ async def countdown(self, ctx: commands.Context, command: str, *, arg=""): await ctx.send("{0} countdown set for {1} ({2})".format(countdown_name, countdown_time, pend_test_convert.diff_for_humans(pendulum.now()))) except ValueError as error: expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" - error_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" + error_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" print(error_str) await ctx.send(error_str) return @@ -146,10 +163,9 @@ async def countdown(self, ctx: commands.Context, command: str, *, arg=""): if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] cd_pend = pendulum.from_format(query_time, expected_pend_format) - now_dt = pendulum.now() - cd_diff = cd_pend.diff(now_dt) - output = "Time until {0}: {1} days, {2} hours, {3} minutes".format(countdown_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) - await ctx.send(output) + cd_diff = cd_pend.diff(pendulum.now()) + time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(countdown_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + await ctx.send(time_until_str) else: await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) elif(command == "list"): @@ -159,7 +175,9 @@ async def countdown(self, ctx: commands.Context, command: str, *, arg=""): if(query_results != None): for (query_id, query_name, query_time, query_user_id) in query_results: cd_pend = pendulum.from_format(query_time, expected_pend_format) - output += "{0} | {1} | {2} now\n".format(query_name, query_time, cd_pend.diff_for_humans(pendulum.now())) + cd_diff = cd_pend.diff(pendulum.now()) + time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(countdown_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + output += "{0} | {1} | {2}\n".format(query_name, query_time, time_until_str) await ctx.send(output) elif(command == "remove"): query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) @@ -182,6 +200,35 @@ async def countdown(self, ctx: commands.Context, command: str, *, arg=""): else: await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_COMMAND) + def find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): + ''' + Get argument hints based on the command input and user input - only called from SantaAdministrative.countdown() + ''' + argument_help = "" + if(cd_command == "set"): + if(cd_name == ""): + argument_help += "Missing arguments: | \n" + argument_help += "formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + elif(cd_time == ""): + argument_help += "Missing formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + elif(cd_command == "change"): + if(cd_name == ""): + argument_help += "Missing arguments: | \n" + argument_help += "formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + elif(cd_time == ""): + argument_help += "Missing formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + elif(cd_command == "check"): + if(cd_name == ""): + argument_help += "Missing argument: \n" + elif(cd_command == "list"): + pass + elif(cd_command == "remove"): + if(cd_name == ""): + argument_help += "Missing arguments: " + elif(cd_command == "clean"): + pass + return argument_help + @unpin_all.error @archive_pins.error async def pin_archive_error(self, error, ctx): diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 707f04f..4cb6b1c 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -22,7 +22,7 @@ def EXCHANGE_IN_PROGRESS_LEAVE(role): def HAS_NOT_SUBMITTED(usrname): return "ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences." def INVALID_COUNTDOWN_NAME(cd_name): - return "ERROR: countdown timer {0} does not exist. Use `s!countdown list` to list all countdown timers.".format(cd_name) + return "ERROR: countdown timer `{0}` does not exist. Use `s!countdown list` to list all countdown timers.".format(cd_name) def CANNOT_CHANGE_COUNTDOWN(author_name): return "ERROR: you do not have permission to change that countdown timer. Please contact {0}".format(author_name) def NO_PERMISSION(role): From 17eae79dc6c45f7709793b8a025f0704968cfe49 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Wed, 20 May 2020 11:27:36 -0700 Subject: [PATCH 131/189] Clean up countdown, use TextChannelConverters Clean up countdown, use TextChannelConverters Cleaner code --- cogs/SantaAdministrative.py | 219 ++++++++++++++++++++---------------- 1 file changed, 121 insertions(+), 98 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 231f06c..39e32c3 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -1,8 +1,8 @@ import pendulum +import discord from discord.ext import commands from discord.ext.commands import has_permissions, MissingPermissions, MissingRequiredArgument -from discord import RawReactionActionEvent from discord import Game as discordGame import CONFIG @@ -12,41 +12,34 @@ class SantaAdministrative(commands.Cog, name='Administrative'): def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): self.bot = bot - self.role_channel = CONFIG.role_channel + self.role_channel = bot.get_channel(CONFIG.role_channel) if CONFIG.role_channel != -1 else None self.sqlhelp = sqlitehelper self.sqlhelp.create_table("Countdowns", "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") @commands.command() @has_permissions(manage_roles=True, ban_members=True) - async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel: int): + async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel: discord.TextChannel=None): ''' Not recommended. Allows a reaction role channel to be assigned. The recommended route is to set the role_channel variable in the bot's config file to the channel ID you want. ''' - dest_channel = ctx.guild.get_channel(reaction_role_channel) - self.role_channel = reaction_role_channel - if dest_channel != None: + if reaction_role_channel != None: + self.role_channel = reaction_role_channel await ctx.send(content="Reaction role channel assigned to <#{0}>".format(self.role_channel)) else: await ctx.send(content=BOT_ERROR.REACTION_ROLE_UNASSIGNED) @commands.command() @has_permissions(manage_roles=True, ban_members=True) - async def archive_pins(self, ctx: commands.Context, channel_to_archive: int, channel_to_message: int): + async def archive_pins(self, ctx: commands.Context, channel_to_archive: discord.TextChannel, channel_to_message: discord.TextChannel): ''' Archive the pins in one channel to another channel as messages ''' - src_id = channel_to_archive - dest_id = channel_to_message - src_channel = ctx.guild.get_channel(src_id) - dest_channel = ctx.guild.get_channel(dest_id) - start_message = "Attempting to archive pinned messages from <#{0}> to <#{1}>".format(src_id, dest_id) + start_message = "Attempting to archive pinned messages from {0} to {1}".format(channel_to_archive.mention, channel_to_message.mention) await ctx.send(content=start_message) - if((src_channel == None) or (dest_channel == None)): - await ctx.send(BOT_ERROR.INACCESSIBLE_CHANNEL) - return - pins_to_archive = await src_channel.pins() + + pins_to_archive = await channel_to_archive.pins() pins_to_archive.reverse() for pin in pins_to_archive: @@ -67,32 +60,30 @@ async def archive_pins(self, ctx: commands.Context, channel_to_archive: int, cha if len(output_str) > 2000: await ctx.send(content=BOT_ERROR.ARCHIVE_ERROR_LENGTH(pin_url)) else: - await dest_channel.send(content=output_str) + await channel_to_message.send(content=output_str) - end_message = "Pinned message are archived in <#{0}>. If the archive messages look good, use **{1}unpin_all** to remove the pins in <#{2}>".format(dest_id, CONFIG.prefix, src_id) + end_message = "Pinned message are archived in {0}. If the archive messages look good, use **{1}unpin_all** to remove the pins in {2}".format(channel_to_message.mention, CONFIG.prefix, channel_to_archive.mention) await ctx.send(content=end_message) @commands.command() @has_permissions(manage_roles=True, ban_members=True) - async def unpin_all(self, ctx: commands.Context, channel_id_to_unpin: int = -1): + async def unpin_all(self, ctx: commands.Context, channel_to_unpin: discord.TextChannel = None): ''' Unpins all the pinned messages in the channel. Called to clean up after archive_pins. Defaults to the channel in which it's called. ''' - print(channel_id_to_unpin) - if channel_id_to_unpin == -1: - channel_id_to_unpin = ctx.channel.id + if(channel_to_unpin == None): + channel_to_unpin = ctx.channel() - remove_channel = ctx.guild.get_channel(channel_id_to_unpin) - start_message = "Attempting to remove all pinned messages from <#{0}>.".format(channel_id_to_unpin) + start_message = "Attempting to remove all pinned messages from {0}.".format(channel_to_unpin.mention) await ctx.send(content=start_message) - if(remove_channel == None): + if(channel_to_unpin == None): await ctx.send(BOT_ERROR.INACCESSIBLE_CHANNEL) return - pins_to_remove = await remove_channel.pins() + pins_to_remove = await channel_to_unpin.pins() for pin in pins_to_remove: await pin.unpin() - end_message = "All pinned messages removed from <#{0}>.".format(channel_id_to_unpin) + end_message = "All pinned messages removed from {0}.".format(channel_to_unpin.mention) await ctx.send(content=end_message) @commands.command(aliases=['cd']) @@ -121,84 +112,114 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): await ctx.send(cd_hints) return + relay_message = "" if(command == "set"): - try: - pend_test_convert = pendulum.from_format(countdown_time, expected_pend_format) # check that the format is correct - if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(countdown_name, countdown_time, ctx.author.id)])): - await ctx.send("{0} countdown set for {1} ({2})".format(countdown_name, countdown_time, pend_test_convert.diff_for_humans(pendulum.now()))) - except ValueError as error: - expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" - error_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" - print(error_str) - await ctx.send(error_str) - return + relay_message = self.countdown_cmd_set(ctx, expected_pend_format, cd_table_name, countdown_name, countdown_time) elif(command == "change"): - try: - pend_test_convert = pendulum.from_format(countdown_time, expected_pend_format) # check that the format is correct - except ValueError as error: - expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" - error_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" - print(error_str) - await ctx.send(error_str) - return - - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) - query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) - if(query_result != None): - if(len(query_result) > 0): - (query_id, query_name, query_time, query_user_id) = query_result[0] - if(ctx.author.id == query_user_id): - if(self.sqlhelp.execute_update_query(cd_table_name, "time=\'{0}\'".format(countdown_time), "id={0}".format(query_id))): - await ctx.send("Updated countdown for {0}".format(countdown_name)) - else: - await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) - else: - cd_owner = ctx.guild.get_member(query_user_id) - await ctx.send(BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name)) - else: - await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + relay_message = self.countdown_cmd_change(ctx, expected_pend_format, cd_table_name, countdown_name, countdown_time) elif(command == "check"): - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) - query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) - if(query_result != None): - (query_id, query_name, query_time, query_user_id) = query_result[0] - cd_pend = pendulum.from_format(query_time, expected_pend_format) - cd_diff = cd_pend.diff(pendulum.now()) - time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(countdown_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) - await ctx.send(time_until_str) - else: - await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + relay_message = self.countdown_cmd_check(expected_pend_format, cd_table_name, countdown_name) elif(command == "list"): - query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) - query_results = self.sqlhelp.execute_read_query(query_get_all_timers) - output = "Name | Time | Time Until\n" - if(query_results != None): - for (query_id, query_name, query_time, query_user_id) in query_results: - cd_pend = pendulum.from_format(query_time, expected_pend_format) - cd_diff = cd_pend.diff(pendulum.now()) - time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(countdown_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) - output += "{0} | {1} | {2}\n".format(query_name, query_time, time_until_str) - await ctx.send(output) + relay_message = self.countdown_cmd_list(expected_pend_format, cd_table_name, countdown_name) elif(command == "remove"): - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, countdown_name) - query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) - if(query_result != None): + relay_message = self.countdown_cmd_remove(expected_pend_format, cd_table_name, countdown_name) + elif(command == "clean"): + relay_message = self.countdown_cmd_clean(expected_pend_format, cd_table_name) + else: + relay_message = BOT_ERROR.INVALID_COUNTDOWN_COMMAND + + await ctx.send(content=relay_message) + + def countdown_cmd_set(self, ctx: commands.Context, pend_format: str, cd_table_name: str, cd_name: str, cd_time: str): + result_str = "" + try: + pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct + if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): + result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, pend_test_convert.diff_for_humans(pendulum.now())) + except ValueError as error: + expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" + result_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" + print(result_str) + finally: + return result_str + + def countdown_cmd_change(self, ctx: commands.Context, pend_format: str, cd_table_name: str, cd_name: str, cd_time: str): + result_str = "" + try: + pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct + except ValueError as error: + expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" + result_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" + print(result_str) + return result_str + + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + if(len(query_result) > 0): (query_id, query_name, query_time, query_user_id) = query_result[0] - if(self.sqlhelp.execute_delete_query(cd_table_name, "id=query_user_id")): - await ctx.send("Countdown timer removed.") + if(ctx.author.id == query_user_id): + if(self.sqlhelp.execute_update_query(cd_table_name, "time=\'{0}\'".format(cd_time), "id={0}".format(query_id))): + result_str = "Updated countdown for {0}".format(cd_name) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) else: - await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) + cd_owner = ctx.guild.get_member(query_user_id) + result_str = BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + + return result_str + + def countdown_cmd_check(self, pend_format: str, cd_table_name: str, cd_name: str): + result_str = "" + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + (query_id, query_name, query_time, query_user_id) = query_result[0] + cd_pend = pendulum.from_format(query_time, pend_format) + cd_diff = cd_pend.diff(pendulum.now()) + result_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(cd_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + return result_str + + def countdown_cmd_list(self, pend_format: str, cd_table_name: str, cd_name:str): + result_str = "" + query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) + result_str = "Name | Time | Time Until\n" + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + cd_pend = pendulum.from_format(query_time, pend_format) + cd_diff = cd_pend.diff(pendulum.now()) + time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(cd_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + result_str += "{0} | {1} | {2}\n".format(query_name, query_time, time_until_str) + return result_str + + def countdown_cmd_remove(self, pend_format: str, cd_table_name: str, cd_name: str): + result_str = "" + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + (query_id, query_name, query_time, query_user_id) = query_result[0] + if(self.sqlhelp.execute_delete_query(cd_table_name, "id=query_user_id")): + result_str = "Countdown timer removed." else: - await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_NAME(countdown_name)) - elif(command == "clean"): - query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) - query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns - if(query_results != None): - for (query_id, query_name, query_time, query_user_id) in query_results: - if(not pendulum.from_format(query_time, expected_pend_format).is_future()): # if the countdown as passed, delete - self.sqlhelp.execute_delete_query(cd_table_name, "id = {0}".format(query_id)) + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) else: - await ctx.send(BOT_ERROR.INVALID_COUNTDOWN_COMMAND) + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + return result_str + + def countdown_cmd_clean(self, pend_format: str, cd_table_name: str): + result_str = "" + query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + if(not pendulum.from_format(query_time, pend_format).is_future()): # if the countdown has passed, delete + result_str += "{0} has passed. Deleting {1} countdown.\n".format(query_time, query_name) + self.sqlhelp.execute_delete_query(cd_table_name, "id = {0}".format(query_id)) def find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): ''' @@ -237,13 +258,15 @@ async def pin_archive_error(self, error, ctx): await ctx.send(content=text) elif isinstance(error, MissingRequiredArgument): await ctx.send(content=BOT_ERROR.MISSING_ARGUMENTS) + elif isinstance(error, commands.CommandError): + await ctx.send(content=error) else: await ctx.send(content=BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) @commands.Cog.listener(name='on_raw_reaction_add') @commands.Cog.listener(name='on_raw_reaction_remove') - async def manage_reactions(self, payload: RawReactionActionEvent): - if(self.role_channel == -1): + async def manage_reactions(self, payload: discord.RawReactionActionEvent): + if(self.role_channel == None): print(BOT_ERROR.REACTION_ROLE_UNASSIGNED) return From 533751dc6fb26637ca6fc7fc3f3cb02da9b96814 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 20 May 2020 12:59:52 -0700 Subject: [PATCH 132/189] Fix up role channel assignment Default role channel assignment command to current channel --- cogs/SantaAdministrative.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 39e32c3..de0f4b1 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -18,16 +18,21 @@ def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): @commands.command() @has_permissions(manage_roles=True, ban_members=True) - async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel: discord.TextChannel=None): + async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel: discord.TextChannel = None): ''' Not recommended. Allows a reaction role channel to be assigned. The recommended route is to set the role_channel variable in the bot's config file to the channel ID you want. ''' if reaction_role_channel != None: - self.role_channel = reaction_role_channel - await ctx.send(content="Reaction role channel assigned to <#{0}>".format(self.role_channel)) + self.role_channel = reaction_role_channel # use given channel else: - await ctx.send(content=BOT_ERROR.REACTION_ROLE_UNASSIGNED) + if(isinstance(ctx.channel, discord.PrivateChannel)): + await ctx.send(content=BOT_ERROR.REACTION_ROLE_UNASSIGNED) + else: + self.role_channel = ctx.channel # use current channel + + if(self.role_channel != None): + await ctx.send(content="Reaction role channel assigned to {0}".format(self.role_channel.mention)) @commands.command() @has_permissions(manage_roles=True, ban_members=True) From 18dbc6f082fd5e5c3582e86eac4a9926b8da723c Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 00:25:08 -0700 Subject: [PATCH 133/189] #41 reduce the number of re-written check strings #41 reduce the number of rewritten check strings, add complete command to help message Lets user see the full thing they should write, less rewritten code --- cogs/SantaAdministrative.py | 65 +++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index de0f4b1..e99b1f7 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -104,13 +104,13 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): expected_pend_format = "MM/D/YY [@] h:m A Z" cd_table_name = "Countdowns" - args = arg.split(sep=" | ") + args = arg.split(sep="|") # minimum separator if the user misses a space somewhere (spaces stripped in next few lines) countdown_name = "" countdown_time = "" if(len(args) > 0): - countdown_name = args[0] + countdown_name = args[0].strip() if(len(args) > 1): - countdown_time = args[1] + countdown_time = args[1].strip() cd_hints = self.find_countdown_hints(command, countdown_name, countdown_time) if(cd_hints != ""): @@ -124,10 +124,10 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): relay_message = self.countdown_cmd_change(ctx, expected_pend_format, cd_table_name, countdown_name, countdown_time) elif(command == "check"): relay_message = self.countdown_cmd_check(expected_pend_format, cd_table_name, countdown_name) - elif(command == "list"): - relay_message = self.countdown_cmd_list(expected_pend_format, cd_table_name, countdown_name) elif(command == "remove"): relay_message = self.countdown_cmd_remove(expected_pend_format, cd_table_name, countdown_name) + elif(command == "list"): + relay_message = self.countdown_cmd_list(expected_pend_format, cd_table_name) elif(command == "clean"): relay_message = self.countdown_cmd_clean(expected_pend_format, cd_table_name) else: @@ -189,19 +189,6 @@ def countdown_cmd_check(self, pend_format: str, cd_table_name: str, cd_name: str result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str - def countdown_cmd_list(self, pend_format: str, cd_table_name: str, cd_name:str): - result_str = "" - query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) - query_results = self.sqlhelp.execute_read_query(query_get_all_timers) - result_str = "Name | Time | Time Until\n" - if(query_results != None): - for (query_id, query_name, query_time, query_user_id) in query_results: - cd_pend = pendulum.from_format(query_time, pend_format) - cd_diff = cd_pend.diff(pendulum.now()) - time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(cd_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) - result_str += "{0} | {1} | {2}\n".format(query_name, query_time, time_until_str) - return result_str - def countdown_cmd_remove(self, pend_format: str, cd_table_name: str, cd_name: str): result_str = "" query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) @@ -216,6 +203,19 @@ def countdown_cmd_remove(self, pend_format: str, cd_table_name: str, cd_name: st result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str + def countdown_cmd_list(self, pend_format: str, cd_table_name: str): + result_str = "" + query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) + result_str = "Countdown Name | Time | Time Until\n" + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + cd_pend = pendulum.from_format(query_time, pend_format) + cd_diff = cd_pend.diff(pendulum.now()) + time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(query_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + result_str += "{0} | {1} | {2}\n".format(query_name, query_time, time_until_str) + return result_str + def countdown_cmd_clean(self, pend_format: str, cd_table_name: str): result_str = "" query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) @@ -230,27 +230,36 @@ def find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): ''' Get argument hints based on the command input and user input - only called from SantaAdministrative.countdown() ''' + missing_args_str = "Missing argument(s):" + missing_name_str = "" + missing_time_str = "" + missing_time_hint = "Formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + complete_command_str = "Complete command: `{0}countdown {1}".format(CONFIG.prefix, cd_command) argument_help = "" if(cd_command == "set"): if(cd_name == ""): - argument_help += "Missing arguments: | \n" - argument_help += "formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) elif(cd_time == ""): - argument_help += "Missing formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + argument_help = "{0} {1}\n{2}".format(missing_args_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) elif(cd_command == "change"): if(cd_name == ""): - argument_help += "Missing arguments: | \n" - argument_help += "formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) elif(cd_time == ""): - argument_help += "Missing formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + argument_help = "{0} {1}\n{2}".format(missing_args_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) elif(cd_command == "check"): if(cd_name == ""): - argument_help += "Missing argument: \n" - elif(cd_command == "list"): - pass + argument_help = "{0} {1}".format(missing_args_str, missing_name_str) + argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) elif(cd_command == "remove"): if(cd_name == ""): - argument_help += "Missing arguments: " + argument_help = "{0} {1}".format(missing_args_str, missing_name_str) + argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) + elif(cd_command == "list"): + pass elif(cd_command == "clean"): pass return argument_help From cfa8225f8e2a5c04c7161f0ae935db2fe3bbe0f3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 00:32:32 -0700 Subject: [PATCH 134/189] #41 add safety to countdown remove and set add safety to countdown remove and set don't want people removing other peoples' countdowns, let the user know they need a unique countdown name --- cogs/SantaAdministrative.py | 16 +++++++++++----- helpers/BOT_ERROR.py | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index e99b1f7..7e9c3de 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -125,7 +125,7 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): elif(command == "check"): relay_message = self.countdown_cmd_check(expected_pend_format, cd_table_name, countdown_name) elif(command == "remove"): - relay_message = self.countdown_cmd_remove(expected_pend_format, cd_table_name, countdown_name) + relay_message = self.countdown_cmd_remove(ctx, expected_pend_format, cd_table_name, countdown_name) elif(command == "list"): relay_message = self.countdown_cmd_list(expected_pend_format, cd_table_name) elif(command == "clean"): @@ -141,6 +141,8 @@ def countdown_cmd_set(self, ctx: commands.Context, pend_format: str, cd_table_na pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, pend_test_convert.diff_for_humans(pendulum.now())) + else: + result_str = BOT_ERROR.COUNTDOWN_NAME_TAKEN except ValueError as error: expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" result_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" @@ -189,16 +191,20 @@ def countdown_cmd_check(self, pend_format: str, cd_table_name: str, cd_name: str result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str - def countdown_cmd_remove(self, pend_format: str, cd_table_name: str, cd_name: str): + def countdown_cmd_remove(self, ctx: commands.Context, pend_format: str, cd_table_name: str, cd_name: str): result_str = "" query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] - if(self.sqlhelp.execute_delete_query(cd_table_name, "id=query_user_id")): - result_str = "Countdown timer removed." + if(query_user_id == ctx.author.id): + if(self.sqlhelp.execute_delete_query(cd_table_name, "id={0}".format(query_id))): + result_str = "Countdown timer `{0}` removed.".format(query_name) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) else: - result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + cd_owner = ctx.guild.get_member(query_user_id) + result_str = BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name) else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 4cb6b1c..2b78ce4 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,4 +1,5 @@ ALREADY_JOINED = "ERROR: You have already joined." +COUNTDOWN_NAME_TAKEN = "ERROR: That countdown name is already in use." DM_FAILED = "ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages." EXCHANGE_IN_PROGRESS = "ERROR: The gift exchange is already in progress." EXCHANGE_STARTED_UNJOINED = "ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join." From 64a5dddc8584d23ccf835cafe1e3d63dfb8cbe43 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 00:53:05 -0700 Subject: [PATCH 135/189] #41 last usage guide help --- cogs/SantaAdministrative.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 7e9c3de..5058b62 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -97,8 +97,8 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): Add a countdown timer ''' if(command == ""): - usage_guide = "Proper usage: {0} \n".format(CONFIG.prefix) - usage_guide += "Use each sub-command for more information on the necessary arguments" + usage_guide = "Proper usage: `{0} `\n".format(CONFIG.prefix) + usage_guide += "Use each sub-command (`{0} `) for more information on the necessary arguments".format(CONFIG.prefix) await ctx.send(usage_guide) return From bb12c1c75e33431d06e5c74adeddf2788ee1adfd Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 00:56:55 -0700 Subject: [PATCH 136/189] #41 clarify help countdown help command --- cogs/SantaAdministrative.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 5058b62..06fcf7b 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -95,6 +95,7 @@ async def unpin_all(self, ctx: commands.Context, channel_to_unpin: discord.TextC async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): ''' Add a countdown timer + Commands: set, change, check, list, remove, clean ''' if(command == ""): usage_guide = "Proper usage: `{0} `\n".format(CONFIG.prefix) From 246bc383a0de14892fd4d489fab8971827a8a3b2 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 01:04:44 -0700 Subject: [PATCH 137/189] #41 modify countdown set feedback message --- cogs/SantaAdministrative.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 06fcf7b..85993cc 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -141,7 +141,9 @@ def countdown_cmd_set(self, ctx: commands.Context, pend_format: str, cd_table_na try: pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): - result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, pend_test_convert.diff_for_humans(pendulum.now())) + cd_diff = pend_test_convert.diff(pendulum.now()) + diff_str = "{1} days, {2} hours, {3} minutes from now".format(cd_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, diff_str) else: result_str = BOT_ERROR.COUNTDOWN_NAME_TAKEN except ValueError as error: From 231acd5c9fa8981e7bc94c83db80e5d85c2e27c6 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 10:01:45 -0700 Subject: [PATCH 138/189] #41 use common function for the diff string --- cogs/SantaAdministrative.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 85993cc..3e583f6 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -128,7 +128,7 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): elif(command == "remove"): relay_message = self.countdown_cmd_remove(ctx, expected_pend_format, cd_table_name, countdown_name) elif(command == "list"): - relay_message = self.countdown_cmd_list(expected_pend_format, cd_table_name) + relay_message = self.countdown_cmd_list(ctx, expected_pend_format, cd_table_name) elif(command == "clean"): relay_message = self.countdown_cmd_clean(expected_pend_format, cd_table_name) else: @@ -141,8 +141,7 @@ def countdown_cmd_set(self, ctx: commands.Context, pend_format: str, cd_table_na try: pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): - cd_diff = pend_test_convert.diff(pendulum.now()) - diff_str = "{1} days, {2} hours, {3} minutes from now".format(cd_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + diff_str = self.find_pend_diff_str(pend_test_convert) result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, diff_str) else: result_str = BOT_ERROR.COUNTDOWN_NAME_TAKEN @@ -170,7 +169,8 @@ def countdown_cmd_change(self, ctx: commands.Context, pend_format: str, cd_table (query_id, query_name, query_time, query_user_id) = query_result[0] if(ctx.author.id == query_user_id): if(self.sqlhelp.execute_update_query(cd_table_name, "time=\'{0}\'".format(cd_time), "id={0}".format(query_id))): - result_str = "Updated countdown for {0}".format(cd_name) + diff_str = self.find_pend_diff_str(pend_test_convert) + result_str = "Updated countdown for {0}. Now set for {1}".format(cd_name, diff_str) else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) else: @@ -188,8 +188,8 @@ def countdown_cmd_check(self, pend_format: str, cd_table_name: str, cd_name: str if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] cd_pend = pendulum.from_format(query_time, pend_format) - cd_diff = cd_pend.diff(pendulum.now()) - result_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(cd_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) + diff_str = self.find_pend_diff_str(cd_pend) + result_str = "Time until {0}: {1}".format(cd_name, diff_str) else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str @@ -212,17 +212,18 @@ def countdown_cmd_remove(self, ctx: commands.Context, pend_format: str, cd_table result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str - def countdown_cmd_list(self, pend_format: str, cd_table_name: str): + def countdown_cmd_list(self, ctx: commands.Context, pend_format: str, cd_table_name: str): result_str = "" query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) query_results = self.sqlhelp.execute_read_query(query_get_all_timers) - result_str = "Countdown Name | Time | Time Until\n" + result_str = "Countdown Name | Owner | Time | Time Until\n" if(query_results != None): for (query_id, query_name, query_time, query_user_id) in query_results: - cd_pend = pendulum.from_format(query_time, pend_format) - cd_diff = cd_pend.diff(pendulum.now()) - time_until_str = "Time until {0}: {1} days, {2} hours, {3} minutes".format(query_name, cd_diff.days, cd_diff.hours, cd_diff.minutes) - result_str += "{0} | {1} | {2}\n".format(query_name, query_time, time_until_str) + cd_pend = pendulum.from_format(query_time, pend_format) # convert to pendulum + diff_str = self.find_pend_diff_str(cd_pend) + time_until_str = "Time until {0}: {1}".format(query_name, diff_str) + cd_owner = ctx.guild.get_member(query_user_id).name + result_str += "{0} | {1} | {2} | {3}\n".format(query_name, cd_owner, query_time, time_until_str) return result_str def countdown_cmd_clean(self, pend_format: str, cd_table_name: str): @@ -273,6 +274,14 @@ def find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): pass return argument_help + def find_pend_diff_str(self, pend: pendulum.DateTime): + cd_diff = pend.diff(pendulum.now()) + (diff_days, diff_hours, diff_minutes) = (cd_diff.days, cd_diff.hours, cd_diff.minutes) + if(not pend.is_future()): + (diff_days, diff_hours, diff_minutes) = (-diff_days, -diff_hours, -diff_minutes) + diff_str = "{0} days, {1} hours, {2} minutes from now".format(diff_days, diff_hours, diff_minutes) + return diff_str + @unpin_all.error @archive_pins.error async def pin_archive_error(self, error, ctx): From 6e3daa1a0ddc513afde03eaef1a5b07208731dcf Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 10:17:53 -0700 Subject: [PATCH 139/189] borked the role channel when I used the converter for assign_role_channel --- cogs/SantaAdministrative.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 3e583f6..61b9322 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -12,7 +12,7 @@ class SantaAdministrative(commands.Cog, name='Administrative'): def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): self.bot = bot - self.role_channel = bot.get_channel(CONFIG.role_channel) if CONFIG.role_channel != -1 else None + self.role_channel = bot.get_channel(CONFIG.role_channel) if (CONFIG.role_channel != -1) else None self.sqlhelp = sqlitehelper self.sqlhelp.create_table("Countdowns", "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") @@ -298,9 +298,15 @@ async def pin_archive_error(self, error, ctx): @commands.Cog.listener(name='on_raw_reaction_add') @commands.Cog.listener(name='on_raw_reaction_remove') async def manage_reactions(self, payload: discord.RawReactionActionEvent): - if(self.role_channel == None): - print(BOT_ERROR.REACTION_ROLE_UNASSIGNED) - return + if(self.role_channel == None): # if no role channel assigned, + self.role_channel = self.bot.get_channel(CONFIG.role_channel) if (CONFIG.role_channel != -1) else None # attempt to get role channel + if(self.role_channel == None): # if that fails (CONFIG.role_channel == -1 or get_channel fails) + print(BOT_ERROR.REACTION_ROLE_UNASSIGNED) # throw failure message + return # end command + else: + pass # reassignment of role_channel worked + else: + pass # role_channel is assigned message_id = payload.message_id channel_id = payload.channel_id @@ -308,7 +314,7 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): guild = self.bot.get_guild(payload.guild_id) user = guild.get_member(payload.user_id) - if channel_id == self.role_channel: + if channel_id == self.role_channel.id: channel = self.bot.get_channel(self.role_channel) message = None async for message in channel.history(limit=200): From f42a472ddfa6979e755ba0565d3fc32604e42a45 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 21 May 2020 10:23:11 -0700 Subject: [PATCH 140/189] removed unnecessary get_channel call --- cogs/SantaAdministrative.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 61b9322..a871b8f 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -315,9 +315,8 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): user = guild.get_member(payload.user_id) if channel_id == self.role_channel.id: - channel = self.bot.get_channel(self.role_channel) message = None - async for message in channel.history(limit=200): + async for message in self.role_channel.history(limit=200): if message.id == message_id: break From cbe64258597ba76b2419a7a547873b3f41329e7d Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Sat, 23 May 2020 01:31:53 -0700 Subject: [PATCH 141/189] Keep back pendulum Keep back pendulum Would prefer to keep this project platform-agnostic and pendulum 2.0.4+ fails to build for some reason (see link in requirements.txt) --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a6c5758..647ae24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ discord.py configobj -pendulum +# keep back due to pendulum 2.0.4+ failing to build on Windows (2.1.0 installs on Linux) - see https://github.com/sdispater/pendulum/issues/457 +pendulum==2.0.3 From 11d26959d7c99082e9f189c34836eabc32ceba45 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 27 May 2020 14:26:29 -0700 Subject: [PATCH 142/189] Update SantaAdministrative.py --- cogs/SantaAdministrative.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index a871b8f..cfe0ede 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -235,6 +235,7 @@ def countdown_cmd_clean(self, pend_format: str, cd_table_name: str): if(not pendulum.from_format(query_time, pend_format).is_future()): # if the countdown has passed, delete result_str += "{0} has passed. Deleting {1} countdown.\n".format(query_time, query_name) self.sqlhelp.execute_delete_query(cd_table_name, "id = {0}".format(query_id)) + return result_str def find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): ''' From a6d3b9df0f9a7df7a80ccf2159aa1252053bbaaf Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 28 May 2020 22:41:52 -0700 Subject: [PATCH 143/189] Switch constants to enum Switch constants to enum --- helpers/SecretSantaConstants.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helpers/SecretSantaConstants.py b/helpers/SecretSantaConstants.py index e49975c..8b2b96f 100644 --- a/helpers/SecretSantaConstants.py +++ b/helpers/SecretSantaConstants.py @@ -1,4 +1,6 @@ -class SecretSantaConstants(): +from enum import Enum + +class SecretSantaConstants(Enum): NAME = 0 DISCRIMINATOR = 1 IDSTR = 2 From 0e58a49cdc07ae2f83f848f9dd7ec8c1c14cdce3 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Thu, 28 May 2020 23:25:57 -0700 Subject: [PATCH 144/189] Create Utilities cog, slim cogs to just commands.stuff Create Utilities cog, slim cogs to just commands.stuff Streamlines cogs --- cogs/SantaAdministrative.py | 197 +------------------------------- cogs/SantaUtilities.py | 35 ++++++ helpers/SantaCountdownHelper.py | 181 +++++++++++++++++++++++++++++ santa-bot.py | 10 +- 4 files changed, 225 insertions(+), 198 deletions(-) create mode 100644 cogs/SantaUtilities.py create mode 100644 helpers/SantaCountdownHelper.py diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index cfe0ede..acda1fc 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -7,14 +7,11 @@ import CONFIG import helpers.BOT_ERROR as BOT_ERROR -from helpers.SQLiteHelper import SQLiteHelper class SantaAdministrative(commands.Cog, name='Administrative'): - def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): + def __init__(self, bot: commands.Bot): self.bot = bot self.role_channel = bot.get_channel(CONFIG.role_channel) if (CONFIG.role_channel != -1) else None - self.sqlhelp = sqlitehelper - self.sqlhelp.create_table("Countdowns", "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") @commands.command() @has_permissions(manage_roles=True, ban_members=True) @@ -91,198 +88,6 @@ async def unpin_all(self, ctx: commands.Context, channel_to_unpin: discord.TextC end_message = "All pinned messages removed from {0}.".format(channel_to_unpin.mention) await ctx.send(content=end_message) - @commands.command(aliases=['cd']) - async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): - ''' - Add a countdown timer - Commands: set, change, check, list, remove, clean - ''' - if(command == ""): - usage_guide = "Proper usage: `{0} `\n".format(CONFIG.prefix) - usage_guide += "Use each sub-command (`{0} `) for more information on the necessary arguments".format(CONFIG.prefix) - await ctx.send(usage_guide) - return - - expected_pend_format = "MM/D/YY [@] h:m A Z" - cd_table_name = "Countdowns" - args = arg.split(sep="|") # minimum separator if the user misses a space somewhere (spaces stripped in next few lines) - countdown_name = "" - countdown_time = "" - if(len(args) > 0): - countdown_name = args[0].strip() - if(len(args) > 1): - countdown_time = args[1].strip() - - cd_hints = self.find_countdown_hints(command, countdown_name, countdown_time) - if(cd_hints != ""): - await ctx.send(cd_hints) - return - - relay_message = "" - if(command == "set"): - relay_message = self.countdown_cmd_set(ctx, expected_pend_format, cd_table_name, countdown_name, countdown_time) - elif(command == "change"): - relay_message = self.countdown_cmd_change(ctx, expected_pend_format, cd_table_name, countdown_name, countdown_time) - elif(command == "check"): - relay_message = self.countdown_cmd_check(expected_pend_format, cd_table_name, countdown_name) - elif(command == "remove"): - relay_message = self.countdown_cmd_remove(ctx, expected_pend_format, cd_table_name, countdown_name) - elif(command == "list"): - relay_message = self.countdown_cmd_list(ctx, expected_pend_format, cd_table_name) - elif(command == "clean"): - relay_message = self.countdown_cmd_clean(expected_pend_format, cd_table_name) - else: - relay_message = BOT_ERROR.INVALID_COUNTDOWN_COMMAND - - await ctx.send(content=relay_message) - - def countdown_cmd_set(self, ctx: commands.Context, pend_format: str, cd_table_name: str, cd_name: str, cd_time: str): - result_str = "" - try: - pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct - if(self.sqlhelp.insert_records(cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): - diff_str = self.find_pend_diff_str(pend_test_convert) - result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, diff_str) - else: - result_str = BOT_ERROR.COUNTDOWN_NAME_TAKEN - except ValueError as error: - expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" - result_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" - print(result_str) - finally: - return result_str - - def countdown_cmd_change(self, ctx: commands.Context, pend_format: str, cd_table_name: str, cd_name: str, cd_time: str): - result_str = "" - try: - pend_test_convert = pendulum.from_format(cd_time, pend_format) # check that the format is correct - except ValueError as error: - expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" - result_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" - print(result_str) - return result_str - - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) - query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) - if(query_result != None): - if(len(query_result) > 0): - (query_id, query_name, query_time, query_user_id) = query_result[0] - if(ctx.author.id == query_user_id): - if(self.sqlhelp.execute_update_query(cd_table_name, "time=\'{0}\'".format(cd_time), "id={0}".format(query_id))): - diff_str = self.find_pend_diff_str(pend_test_convert) - result_str = "Updated countdown for {0}. Now set for {1}".format(cd_name, diff_str) - else: - result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) - else: - cd_owner = ctx.guild.get_member(query_user_id) - result_str = BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name) - else: - result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) - - return result_str - - def countdown_cmd_check(self, pend_format: str, cd_table_name: str, cd_name: str): - result_str = "" - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) - query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) - if(query_result != None): - (query_id, query_name, query_time, query_user_id) = query_result[0] - cd_pend = pendulum.from_format(query_time, pend_format) - diff_str = self.find_pend_diff_str(cd_pend) - result_str = "Time until {0}: {1}".format(cd_name, diff_str) - else: - result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) - return result_str - - def countdown_cmd_remove(self, ctx: commands.Context, pend_format: str, cd_table_name: str, cd_name: str): - result_str = "" - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(cd_table_name, cd_name) - query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) - if(query_result != None): - (query_id, query_name, query_time, query_user_id) = query_result[0] - if(query_user_id == ctx.author.id): - if(self.sqlhelp.execute_delete_query(cd_table_name, "id={0}".format(query_id))): - result_str = "Countdown timer `{0}` removed.".format(query_name) - else: - result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) - else: - cd_owner = ctx.guild.get_member(query_user_id) - result_str = BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name) - else: - result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) - return result_str - - def countdown_cmd_list(self, ctx: commands.Context, pend_format: str, cd_table_name: str): - result_str = "" - query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) - query_results = self.sqlhelp.execute_read_query(query_get_all_timers) - result_str = "Countdown Name | Owner | Time | Time Until\n" - if(query_results != None): - for (query_id, query_name, query_time, query_user_id) in query_results: - cd_pend = pendulum.from_format(query_time, pend_format) # convert to pendulum - diff_str = self.find_pend_diff_str(cd_pend) - time_until_str = "Time until {0}: {1}".format(query_name, diff_str) - cd_owner = ctx.guild.get_member(query_user_id).name - result_str += "{0} | {1} | {2} | {3}\n".format(query_name, cd_owner, query_time, time_until_str) - return result_str - - def countdown_cmd_clean(self, pend_format: str, cd_table_name: str): - result_str = "" - query_get_all_timers = "SELECT * FROM {0};".format(cd_table_name) - query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns - if(query_results != None): - for (query_id, query_name, query_time, query_user_id) in query_results: - if(not pendulum.from_format(query_time, pend_format).is_future()): # if the countdown has passed, delete - result_str += "{0} has passed. Deleting {1} countdown.\n".format(query_time, query_name) - self.sqlhelp.execute_delete_query(cd_table_name, "id = {0}".format(query_id)) - return result_str - - def find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): - ''' - Get argument hints based on the command input and user input - only called from SantaAdministrative.countdown() - ''' - missing_args_str = "Missing argument(s):" - missing_name_str = "" - missing_time_str = "" - missing_time_hint = "Formatted time ex. `5/17/20 @ 1:00 PM -06:00`" - complete_command_str = "Complete command: `{0}countdown {1}".format(CONFIG.prefix, cd_command) - argument_help = "" - if(cd_command == "set"): - if(cd_name == ""): - argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) - elif(cd_time == ""): - argument_help = "{0} {1}\n{2}".format(missing_args_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) - elif(cd_command == "change"): - if(cd_name == ""): - argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) - elif(cd_time == ""): - argument_help = "{0} {1}\n{2}".format(missing_args_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) - elif(cd_command == "check"): - if(cd_name == ""): - argument_help = "{0} {1}".format(missing_args_str, missing_name_str) - argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) - elif(cd_command == "remove"): - if(cd_name == ""): - argument_help = "{0} {1}".format(missing_args_str, missing_name_str) - argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) - elif(cd_command == "list"): - pass - elif(cd_command == "clean"): - pass - return argument_help - - def find_pend_diff_str(self, pend: pendulum.DateTime): - cd_diff = pend.diff(pendulum.now()) - (diff_days, diff_hours, diff_minutes) = (cd_diff.days, cd_diff.hours, cd_diff.minutes) - if(not pend.is_future()): - (diff_days, diff_hours, diff_minutes) = (-diff_days, -diff_hours, -diff_minutes) - diff_str = "{0} days, {1} hours, {2} minutes from now".format(diff_days, diff_hours, diff_minutes) - return diff_str - @unpin_all.error @archive_pins.error async def pin_archive_error(self, error, ctx): diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py new file mode 100644 index 0000000..13c10fa --- /dev/null +++ b/cogs/SantaUtilities.py @@ -0,0 +1,35 @@ +from discord.ext import commands + +import CONFIG +from helpers.SantaCountdownHelper import SantaCountdownHelper +from helpers.SQLiteHelper import SQLiteHelper + +class SantaUtilities(commands.Cog, name='Utilities'): + def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): + self.bot = bot + self.cd_helper = SantaCountdownHelper(sqlitehelper) + + @commands.command(aliases=['cd']) + async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): + ''' + Add a countdown timer + Commands: set, change, check, list, remove, clean + ''' + if(command == ""): + usage_guide = "Proper usage: `{0} `\n".format(CONFIG.prefix) + usage_guide += "Use each sub-command (`{0} `) for more information on the necessary arguments".format(CONFIG.prefix) + await ctx.send(usage_guide) + return + + args = arg.split(sep="|") # minimum separator if the user misses a space somewhere (spaces stripped in next few lines) + countdown_name = "" + countdown_time = "" + if(len(args) > 0): + countdown_name = args[0].strip() + if(len(args) > 1): + countdown_time = args[1].strip() + + relay_message = self.cd_helper.run_countdown_command(ctx, command, countdown_name, countdown_time) + + await ctx.send(content=relay_message) + return \ No newline at end of file diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py new file mode 100644 index 0000000..051efca --- /dev/null +++ b/helpers/SantaCountdownHelper.py @@ -0,0 +1,181 @@ +import pendulum +from discord.ext import commands + +import helpers.BOT_ERROR as BOT_ERROR +from helpers.SQLiteHelper import SQLiteHelper +import CONFIG + +class SantaCountdownHelper(): + def __init__(self, sqlitehelper: SQLiteHelper): + self.pend_format = "MM/D/YY [@] h:m A Z" + self.cd_table_name = "Countdowns" + self.sqlhelp = sqlitehelper + self.sqlhelp.create_table(self.cd_table_name, "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") + + def __countdown_cmd_set(self, ctx: commands.Context, cd_name: str, cd_time: str): + result_str = "" + try: + pend_test_convert = pendulum.from_format(cd_time, self.pend_format) # check that the format is correct + if(self.sqlhelp.insert_records(self.cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): + diff_str = self.__find_pend_diff_str(pend_test_convert) + result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, diff_str) + else: + result_str = BOT_ERROR.COUNTDOWN_NAME_TAKEN + except ValueError as error: + expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" + result_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" + print(result_str) + finally: + return result_str + + def __countdown_cmd_change(self, ctx: commands.Context, cd_name: str, cd_time: str): + result_str = "" + try: + pend_test_convert = pendulum.from_format(cd_time, self.pend_format) # check that the format is correct + except ValueError as error: + expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" + result_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" + print(result_str) + return result_str + + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(self.cd_table_name, cd_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + if(len(query_result) > 0): + (query_id, query_name, query_time, query_user_id) = query_result[0] + if(ctx.author.id == query_user_id): + if(self.sqlhelp.execute_update_query(self.cd_table_name, "time=\'{0}\'".format(cd_time), "id={0}".format(query_id))): + diff_str = self.__find_pend_diff_str(pend_test_convert) + result_str = "Updated countdown for {0}. Now set for {1}".format(cd_name, diff_str) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + else: + cd_owner = ctx.guild.get_member(query_user_id) + result_str = BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + + return result_str + + def __countdown_cmd_check(self, cd_name: str): + result_str = "" + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(self.cd_table_name, cd_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + (query_id, query_name, query_time, query_user_id) = query_result[0] + cd_pend = pendulum.from_format(query_time, self.pend_format) + diff_str = self.__find_pend_diff_str(cd_pend) + result_str = "Time until {0}: {1}".format(cd_name, diff_str) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + return result_str + + def __countdown_cmd_remove(self, ctx: commands.Context, cd_name: str): + result_str = "" + query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(self.cd_table_name, cd_name) + query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) + if(query_result != None): + (query_id, query_name, query_time, query_user_id) = query_result[0] + if(query_user_id == ctx.author.id): + if(self.sqlhelp.execute_delete_query(self.cd_table_name, "id={0}".format(query_id))): + result_str = "Countdown timer `{0}` removed.".format(query_name) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + else: + cd_owner = ctx.guild.get_member(query_user_id) + result_str = BOT_ERROR.CANNOT_CHANGE_COUNTDOWN(cd_owner.name) + else: + result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) + return result_str + + def __countdown_cmd_list(self, ctx: commands.Context, ): + result_str = "" + query_get_all_timers = "SELECT * FROM {0};".format(self.cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) + result_str = "Countdown Name | Owner | Time | Time Until\n" + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + cd_pend = pendulum.from_format(query_time, self.pend_format) # convert to pendulum + diff_str = self.__find_pend_diff_str(cd_pend) + time_until_str = "Time until {0}: {1}".format(query_name, diff_str) + cd_owner = ctx.guild.get_member(query_user_id).name + result_str += "{0} | {1} | {2} | {3}\n".format(query_name, cd_owner, query_time, time_until_str) + return result_str + + def __countdown_cmd_clean(self): + result_str = "" + query_get_all_timers = "SELECT * FROM {0};".format(self.cd_table_name) + query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns + if(query_results != None): + for (query_id, query_name, query_time, query_user_id) in query_results: + if(not pendulum.from_format(query_time, self.pend_format).is_future()): # if the countdown has passed, delete + result_str += "{0} has passed. Deleting {1} countdown.\n".format(query_time, query_name) + self.sqlhelp.execute_delete_query(self.cd_table_name, "id = {0}".format(query_id)) + return result_str + + def __find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): + ''' + Get argument hints based on the command input and user input - only called from SantaAdministrative.countdown() + ''' + missing_args_str = "Missing argument(s):" + missing_name_str = "" + missing_time_str = "" + missing_time_hint = "Formatted time ex. `5/17/20 @ 1:00 PM -06:00`" + complete_command_str = "Complete command: `{0}countdown {1}".format(CONFIG.prefix, cd_command) + argument_help = "" + if(cd_command == "set"): + if(cd_name == ""): + argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) + elif(cd_time == ""): + argument_help = "{0} {1}\n{2}\n".format(missing_args_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) + elif(cd_command == "change"): + if(cd_name == ""): + argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) + elif(cd_time == ""): + argument_help = "{0} {1}\n{2}\n".format(missing_args_str, missing_time_str, missing_time_hint) + argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) + elif(cd_command == "check"): + if(cd_name == ""): + argument_help = "{0} {1}\n".format(missing_args_str, missing_name_str) + argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) + elif(cd_command == "remove"): + if(cd_name == ""): + argument_help = "{0} {1}\n".format(missing_args_str, missing_name_str) + argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) + elif(cd_command == "list"): + pass + elif(cd_command == "clean"): + pass + return argument_help + + def __find_pend_diff_str(self, pend: pendulum.DateTime): + cd_diff = pend.diff(pendulum.now()) + (diff_days, diff_hours, diff_minutes) = (cd_diff.days, cd_diff.hours, cd_diff.minutes) + if(not pend.is_future()): + (diff_days, diff_hours, diff_minutes) = (-diff_days, -diff_hours, -diff_minutes) + diff_str = "{0} days, {1} hours, {2} minutes from now".format(diff_days, diff_hours, diff_minutes) + return diff_str + + def run_countdown_command(self, ctx: commands.Context, cd_command: str, cd_name: str, cd_time: str): + output = self.__find_countdown_hints(cd_command, cd_name, cd_time) + if(output == ""): # no hints were needed + if(cd_command == "set"): + output = self.__countdown_cmd_set(ctx, cd_name, cd_time) + elif(cd_command == "change"): + output = self.__countdown_cmd_change(ctx, cd_name, cd_time) + elif(cd_command == "check"): + output = self.__countdown_cmd_check(cd_name) + elif(cd_command == "remove"): + output = self.__countdown_cmd_remove(ctx, cd_name) + elif(cd_command == "list"): + output = self.__countdown_cmd_list(ctx) + elif(cd_command == "clean"): + output = self.__countdown_cmd_clean() + else: + output = BOT_ERROR.INVALID_COUNTDOWN_COMMAND + output += "\nCountdown options/sub-commands: set, change, check , remove, list, clean." + + return output \ No newline at end of file diff --git a/santa-bot.py b/santa-bot.py index 276add4..f02ac1d 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -8,7 +8,9 @@ from cogs.SecretSanta import SecretSanta from cogs.SantaAdministrative import SantaAdministrative from cogs.SantaMiscellaneous import SantaMiscellaneous +from cogs.SantaUtilities import SantaUtilities from helpers.SQLiteHelper import SQLiteHelper +from helpers.SantaCountdownHelper import SantaCountdownHelper #set up discord connection debug logging client_log = logging.getLogger('discord') @@ -38,9 +40,13 @@ def start_santa_bot(): sqlitehelper = SQLiteHelper(CONFIG.sqlite_path) sqlitehelper.create_connection() - bot.add_cog(SecretSanta(bot, config)) - bot.add_cog(SantaAdministrative(bot, sqlitehelper)) + # add the cogs + bot.add_cog(SantaAdministrative(bot)) bot.add_cog(SantaMiscellaneous(bot)) + bot.add_cog(SantaUtilities(bot, sqlitehelper)) + bot.add_cog(SecretSanta(bot, config)) + + # kick off the bot bot.run(CONFIG.discord_token, reconnect = True) if __name__ == '__main__': From 2967e133ea93f4cb935976cc27fea61acf195c2e Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 29 May 2020 00:02:49 -0700 Subject: [PATCH 145/189] pathlib on unix returns a PosixPath for some reason instead of str --- CONFIG.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONFIG.py b/CONFIG.py index 5312d03..0caa100 100644 --- a/CONFIG.py +++ b/CONFIG.py @@ -14,6 +14,6 @@ ############################################### ### DO NOT CHANGE BELOW ### ############################################### -cfg_path = os.path.join(pathlib.Path(__file__).parent, bot_folder, cfg_name) -sqlite_path = os.path.join(pathlib.Path(__file__).parent, bot_folder, sqlite_name) -dbg_path = os.path.join(pathlib.Path(__file__).parent, bot_folder, dbg_name) +cfg_path = os.path.join(str(pathlib.Path(__file__).parent), bot_folder, cfg_name) +sqlite_path = os.path.join(str(pathlib.Path(__file__).parent), bot_folder, sqlite_name) +dbg_path = os.path.join(str(pathlib.Path(__file__).parent), bot_folder, dbg_name) From 2f69ecdf7c321677740ce5765d4cf53f903ff2a1 Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 29 May 2020 00:43:05 -0700 Subject: [PATCH 146/189] Tell the user their countdown options Tell the user their countdown options The user may not try s!help cd --- cogs/SantaUtilities.py | 2 +- helpers/BOT_ERROR.py | 3 ++- helpers/SantaCountdownHelper.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 13c10fa..65831ee 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -32,4 +32,4 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): relay_message = self.cd_helper.run_countdown_command(ctx, command, countdown_name, countdown_time) await ctx.send(content=relay_message) - return \ No newline at end of file + return diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 2b78ce4..8093dd5 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -5,7 +5,6 @@ EXCHANGE_STARTED_UNJOINED = "ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join." INACCESSIBLE_CHANNEL = "ERROR: the channel specified is not accessible to this bot." INVALID_INPUT = "ERROR: invalid input." -INVALID_COUNTDOWN_COMMAND = "ERROR: invalid countdown option." MISSING_ARGUMENTS = "ERROR: command usage is missing arguments." NOT_ENOUGH_SIGNUPS = "ERROR: Secret santa not started. Need more people." NOT_PAUSED = "ERROR: Secret Santa is not paused" @@ -22,6 +21,8 @@ def EXCHANGE_IN_PROGRESS_LEAVE(role): return "ERROR: The gift exchange is already in progress. Please contact <@224949031514800128> or {0} about leaving.".format(role) def HAS_NOT_SUBMITTED(usrname): return "ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences." +def INVALID_COUNTDOWN_COMMAND(attempted_command): + return "ERROR: invalid countdown option `{0}`.".format(attempted_command) def INVALID_COUNTDOWN_NAME(cd_name): return "ERROR: countdown timer `{0}` does not exist. Use `s!countdown list` to list all countdown timers.".format(cd_name) def CANNOT_CHANGE_COUNTDOWN(author_name): diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py index 051efca..ac59dad 100644 --- a/helpers/SantaCountdownHelper.py +++ b/helpers/SantaCountdownHelper.py @@ -175,7 +175,7 @@ def run_countdown_command(self, ctx: commands.Context, cd_command: str, cd_name: elif(cd_command == "clean"): output = self.__countdown_cmd_clean() else: - output = BOT_ERROR.INVALID_COUNTDOWN_COMMAND - output += "\nCountdown options/sub-commands: set, change, check , remove, list, clean." + output = BOT_ERROR.INVALID_COUNTDOWN_COMMAND(cd_command) + output += "\nCountdown options/sub-commands: `set`, `change`, `check` , `remove`, `list`, `clean`." return output \ No newline at end of file From 29f80e053cd4950ad3f367d74c1a2cbb201876be Mon Sep 17 00:00:00 2001 From: Akaash Chikarmane Date: Fri, 29 May 2020 15:01:49 -0700 Subject: [PATCH 147/189] Command to generate emote URLs for ~~stealing~~ inspiration Command to generate emote URLs for ~~stealing~~ inspiration Don't need to be on desktop to do it --- cogs/SantaUtilities.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 65831ee..4be24d9 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -33,3 +33,18 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): await ctx.send(content=relay_message) return + + @commands.command() + async def emote(self, ctx: commands.Context, *emotes: str): + ''' + Get the URL to emotes without needing to open the link. + ''' + output = "" + for passed_emote in emotes: + emote_parts = passed_emote.split(sep=":") + emote_name = emote_parts[1] + emote_id = (emote_parts[2])[:-1] + print("Name={0}, ID={1}".format(emote_name, emote_id)) + output = "https://cdn.discordapp.com/emojis/{0}.png\n".format(str(emote_id)) + await ctx.send(content=output) + return From 4199cd74c78cd4ccd678eebb008c63194efcefa0 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sat, 30 May 2020 21:21:55 -0700 Subject: [PATCH 148/189] Move logger to santabot main function Move logger to santabot main function Ensure nothing runs outside of main function and in fact nothing runs in the entire bot unless santa-bot.py is run --- santa-bot.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/santa-bot.py b/santa-bot.py index f02ac1d..bc41ee2 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -12,14 +12,6 @@ from helpers.SQLiteHelper import SQLiteHelper from helpers.SantaCountdownHelper import SantaCountdownHelper -#set up discord connection debug logging -client_log = logging.getLogger('discord') -client_log.setLevel(logging.DEBUG) -client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') -client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) -client_log.addHandler(client_handler) -bot = commands.Bot(command_prefix = CONFIG.prefix) - def start_santa_bot(): #initialize config file config = None @@ -40,7 +32,15 @@ def start_santa_bot(): sqlitehelper = SQLiteHelper(CONFIG.sqlite_path) sqlitehelper.create_connection() + #set up discord connection debug logging + client_log = logging.getLogger('discord') + client_log.setLevel(logging.DEBUG) + client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') + client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) + client_log.addHandler(client_handler) + # add the cogs + bot = commands.Bot(command_prefix = CONFIG.prefix) bot.add_cog(SantaAdministrative(bot)) bot.add_cog(SantaMiscellaneous(bot)) bot.add_cog(SantaUtilities(bot, sqlitehelper)) @@ -51,3 +51,6 @@ def start_santa_bot(): if __name__ == '__main__': start_santa_bot() +else: + print("santa-bot.py does nothing unless it is run as the main program") + quit() \ No newline at end of file From 25658837199e717b3f453ab6f8703764c7845c4b Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Tue, 2 Jun 2020 19:41:02 -0700 Subject: [PATCH 149/189] Make variable names a little clearer Make variable names a little clearer Quality of life --- cogs/SantaAdministrative.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index acda1fc..1646a53 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -129,10 +129,11 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): if message != None: content = message.content.split("\n") for line in content: - l = line.split(" ") - for c, word in enumerate(l): - if word == "for" and c > 0 and l[c-1] == str(emoji): - role_id = l[c+1][3:-1] + line_tokens = line.split(" ") + line_tokens = [i for i in line_tokens if i] # remove empty strings for quality of life (doesn't break with extra spaces) + for (idx, token) in enumerate(line_tokens): + if token == "for" and idx > 0 and line_tokens[idx-1] == str(emoji): + role_id = line_tokens[idx+1][3:-1] role = guild.get_role(int(role_id)) if payload.event_type == "REACTION_ADD": await user.add_roles(role) From a83ae174709d5adc7798aae5a9e21c912dcbe9b4 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 3 Jun 2020 03:00:19 -0700 Subject: [PATCH 150/189] Save a few computations in possible with rxn roles Save a few computations in possible with rxn roles Save half a picosecond (maybe) --- cogs/SantaAdministrative.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 1646a53..8066b9a 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -139,6 +139,7 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): await user.add_roles(role) else: await user.remove_roles(role) + return @commands.Cog.listener(name='on_ready') async def nice_ready_print(self): From 779d82148c17b07a19599e35098b7fb78d531473 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 5 Jun 2020 11:38:47 -0700 Subject: [PATCH 151/189] Closes #45: Open the url to find the appropriate emote extension Open the url to find the appropriate emote extension Allow the user to get animated emotes without having to change the URL themselves --- cogs/SantaUtilities.py | 24 ++++++++++++++++++------ requirements.txt | 1 + 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 4be24d9..964026e 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -1,3 +1,6 @@ +import urllib3 +import bs4 + from discord.ext import commands import CONFIG @@ -39,12 +42,21 @@ async def emote(self, ctx: commands.Context, *emotes: str): ''' Get the URL to emotes without needing to open the link. ''' - output = "" + http = urllib3.PoolManager() for passed_emote in emotes: + # Create the base URL of the emote emote_parts = passed_emote.split(sep=":") - emote_name = emote_parts[1] - emote_id = (emote_parts[2])[:-1] - print("Name={0}, ID={1}".format(emote_name, emote_id)) - output = "https://cdn.discordapp.com/emojis/{0}.png\n".format(str(emote_id)) - await ctx.send(content=output) + (emote_name, emote_id) = (emote_parts[1], (emote_parts[2])[:-1]) + base_url = "https://cdn.discordapp.com/emojis/{0}".format(str(emote_id)) + + # http request to find the appropriate extension + response = http.urlopen('GET', url=base_url) + img_type = response.info()['Content-Type'] + img_ext = img_type.split(sep="/")[1] + http.clear() + + # output + emote_url = "{0}.{1}".format(base_url, img_ext) + print("Name={0}, ID={1}, IMG_TYPE={2}".format(emote_name, emote_id, img_type)) + await ctx.send(content=emote_url) return diff --git a/requirements.txt b/requirements.txt index 647ae24..9d6dab5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ discord.py configobj # keep back due to pendulum 2.0.4+ failing to build on Windows (2.1.0 installs on Linux) - see https://github.com/sdispater/pendulum/issues/457 pendulum==2.0.3 +urllib3 From fc1fc673bb4b44f570483afe0e280b4522638fec Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 5 Jun 2020 11:42:00 -0700 Subject: [PATCH 152/189] Remove beautifulsoup import from Utilities Remove beautifulsoup import from Utilities Accidentally committed, didn't need it for emote --- cogs/SantaUtilities.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 964026e..265586b 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -1,5 +1,4 @@ import urllib3 -import bs4 from discord.ext import commands From 13a38d73d917fd33995df8f04109bc17f02cfb37 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 8 Jun 2020 18:22:33 -0700 Subject: [PATCH 153/189] Make the CD date format a bit easier Make the CD date format a bit easier Easier use of command --- helpers/SantaCountdownHelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py index ac59dad..eee8b8f 100644 --- a/helpers/SantaCountdownHelper.py +++ b/helpers/SantaCountdownHelper.py @@ -7,7 +7,7 @@ class SantaCountdownHelper(): def __init__(self, sqlitehelper: SQLiteHelper): - self.pend_format = "MM/D/YY [@] h:m A Z" + self.pend_format = "M/D/YY [@] h:m A Z" self.cd_table_name = "Countdowns" self.sqlhelp = sqlitehelper self.sqlhelp.create_table(self.cd_table_name, "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") From d376fc6837bbe6caf6d5c9b9a54ede6fcf5cb453 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 11 Jun 2020 11:56:59 -0700 Subject: [PATCH 154/189] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02e4931..fb4e5d9 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,14 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - `s!end` **(admin only)** = end Secret Santa #### Administrative Commands: -- `s!assign_role_channel CHANNEL_ID` **(admin only)** = change the channel the bot looks at for reaction roles -- `s!archive pins SRC_CHANNEL_ID DEST_CHANNEL_ID` **(admin only)** = archive all pins from the source channel to the destination channel as messages +- `s!assign_role_channel CHANNEL` **(admin only)** = change the channel the bot looks at for reaction roles +- `s!archive pins SRC_CHANNEL DEST_CHANNEL` **(admin only)** = archive all pins from the source channel to the destination channel as messages - `s!unpin_all [CHANNEL_ID]` **(admin only)** = unpin all messages in the indicated channel (defaults to the channel the command is called in) +#### Utility Commands: +- `s!emote [any number of emotes]` = returns the URL of the emote image/gif for convenience +- `s![countdown|cd] = set/check a countdown (global for the server, e.g. time until a Manga Club event) - help text is returns as needed + #### Miscellaneous Commands: - `s!ping` = check if bot is alive From af2018aa55a65b8253f9c29fe927e47202cf881f Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 11 Jun 2020 22:06:04 -0700 Subject: [PATCH 155/189] Closes #26: Use f-strings since raspberry pi has python 3.7.3 now Use f-strings since raspberry pi has python 3.7.3 now More concise code --- cogs/SantaAdministrative.py | 18 +++++----- cogs/SantaMiscellaneous.py | 6 ++-- cogs/SantaUtilities.py | 10 +++--- cogs/SecretSanta.py | 32 +++++++++--------- helpers/BOT_ERROR.py | 14 ++++---- helpers/SQLiteHelper.py | 32 +++++++++--------- helpers/SantaCountdownHelper.py | 60 ++++++++++++++++----------------- 7 files changed, 86 insertions(+), 86 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 8066b9a..78c2d1f 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -29,7 +29,7 @@ async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel self.role_channel = ctx.channel # use current channel if(self.role_channel != None): - await ctx.send(content="Reaction role channel assigned to {0}".format(self.role_channel.mention)) + await ctx.send(content=f"Reaction role channel assigned to {reaction_role_channel.name}") @commands.command() @has_permissions(manage_roles=True, ban_members=True) @@ -38,7 +38,7 @@ async def archive_pins(self, ctx: commands.Context, channel_to_archive: discord. Archive the pins in one channel to another channel as messages ''' - start_message = "Attempting to archive pinned messages from {0} to {1}".format(channel_to_archive.mention, channel_to_message.mention) + start_message = f"Attempting to archive pinned messages from {channel_to_archive.mention} to {channel_to_message.mention}" await ctx.send(content=start_message) pins_to_archive = await channel_to_archive.pins() @@ -48,7 +48,7 @@ async def archive_pins(self, ctx: commands.Context, channel_to_archive: discord. attachments = pin.attachments attachment_str = "" for attachment in attachments: - attachment_str += "{0}\n".format(attachment.url) # add link to attachments + attachment_str += f"{attachment.url}\n" # add link to attachments pin_author = pin.author.name pin_pendulum = pendulum.instance(pin.created_at) @@ -56,15 +56,15 @@ async def archive_pins(self, ctx: commands.Context, channel_to_archive: discord. pin_url = pin.jump_url pin_content = pin.content - output_str = "-**(from `{0}` on {1})** {2}\n".format(pin_author, pin_dt_str, pin_content) - output_str += "Message link: <{0}>\n".format(pin_url) - output_str += "Attachment links: {0}".format(attachment_str) + output_str = f"-**(from `{pin_author}` on {pin_dt_str})** {pin_content}\n" + output_str += f"Message link: <{pin_url}>\n" + output_str += f"Attachment links: {attachment_str}" if len(output_str) > 2000: await ctx.send(content=BOT_ERROR.ARCHIVE_ERROR_LENGTH(pin_url)) else: await channel_to_message.send(content=output_str) - end_message = "Pinned message are archived in {0}. If the archive messages look good, use **{1}unpin_all** to remove the pins in {2}".format(channel_to_message.mention, CONFIG.prefix, channel_to_archive.mention) + end_message = f"Pinned message are archived in {channel_to_message.mention}. If the archive messages look good, use **{CONFIG.prefix}unpin_all** to remove the pins in {channel_to_archive.mention}" await ctx.send(content=end_message) @commands.command() @@ -77,7 +77,7 @@ async def unpin_all(self, ctx: commands.Context, channel_to_unpin: discord.TextC if(channel_to_unpin == None): channel_to_unpin = ctx.channel() - start_message = "Attempting to remove all pinned messages from {0}.".format(channel_to_unpin.mention) + start_message = f"Attempting to remove all pinned messages from {channel_to_unpin.mention}." await ctx.send(content=start_message) if(channel_to_unpin == None): await ctx.send(BOT_ERROR.INACCESSIBLE_CHANNEL) @@ -85,7 +85,7 @@ async def unpin_all(self, ctx: commands.Context, channel_to_unpin: discord.TextC pins_to_remove = await channel_to_unpin.pins() for pin in pins_to_remove: await pin.unpin() - end_message = "All pinned messages removed from {0}.".format(channel_to_unpin.mention) + end_message = f"All pinned messages removed from {channel_to_unpin.mention}." await ctx.send(content=end_message) @unpin_all.error diff --git a/cogs/SantaMiscellaneous.py b/cogs/SantaMiscellaneous.py index 8060ecc..9972612 100644 --- a/cogs/SantaMiscellaneous.py +++ b/cogs/SantaMiscellaneous.py @@ -10,8 +10,8 @@ async def invite(self, ctx: commands.Context): ''' Creates an invite for this bot ''' - link = "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=67185664".format(CONFIG.client_id) - await ctx.send_message("Bot invite link: {0}".format(link)) + link = f"https://discordapp.com/oauth2/authorize?client_id={CONFIG.client_id}&scope=bot&permissions=67185664" + await ctx.send_message(f"Bot invite link: {link}") @commands.command() async def echo(self, ctx: commands.Context, *, content:str): @@ -30,4 +30,4 @@ async def ping(self, ctx: commands.Context): = Basic ping command ''' latency = self.bot.latency - await ctx.send("{0} milliseconds".format(round(latency, 4)*1000)) + await ctx.send(f"{str(round(latency, 4)*1000)} milliseconds") diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 265586b..5032cc4 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -18,8 +18,8 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): Commands: set, change, check, list, remove, clean ''' if(command == ""): - usage_guide = "Proper usage: `{0} `\n".format(CONFIG.prefix) - usage_guide += "Use each sub-command (`{0} `) for more information on the necessary arguments".format(CONFIG.prefix) + usage_guide = f"Proper usage: `{CONFIG.prefix} `\n" + usage_guide += f"Use each sub-command (`{CONFIG.prefix} `) for more information on the necessary arguments" await ctx.send(usage_guide) return @@ -46,7 +46,7 @@ async def emote(self, ctx: commands.Context, *emotes: str): # Create the base URL of the emote emote_parts = passed_emote.split(sep=":") (emote_name, emote_id) = (emote_parts[1], (emote_parts[2])[:-1]) - base_url = "https://cdn.discordapp.com/emojis/{0}".format(str(emote_id)) + base_url = f"https://cdn.discordapp.com/emojis/{str(emote_id)}" # http request to find the appropriate extension response = http.urlopen('GET', url=base_url) @@ -55,7 +55,7 @@ async def emote(self, ctx: commands.Context, *emotes: str): http.clear() # output - emote_url = "{0}.{1}".format(base_url, img_ext) - print("Name={0}, ID={1}, IMG_TYPE={2}".format(emote_name, emote_id, img_type)) + emote_url = f"{base_url}.{img_ext}" + print(f"Name={emote_name}, ID={emote_id}, IMG_TYPE={img_type}") await ctx.send(content=emote_url) return diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 6ae12f3..dbeb7fc 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -54,7 +54,7 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): # add the input to the value in the user's class instance user.wishlisturl = new_wishlist try: - await currAuthor.send("New wishlist URL: {0}".format(new_wishlist)) + await currAuthor.send(f"New wishlist URL: {new_wishlist}") except: await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) except: @@ -75,7 +75,7 @@ async def getwishlisturl(self, ctx: commands.Context): if self.SecretSantaHelper.user_is_participant(ctx.author.id, self.usr_list): (index, user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) try: - await currAuthor.send("Current wishlist destination(s): {0}".format(user.wishlisturl)) + await currAuthor.send(f"Current wishlist destination(s): {user.wishlisturl}") except: await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) else: @@ -106,7 +106,7 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): #add the input to the value in the user's class instance user.preferences = new_prefs try: - await currAuthor.send("New preferences: {0}".format(new_prefs)) + await currAuthor.send(f"New preferences: {new_prefs}") except: await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) except: @@ -127,7 +127,7 @@ async def getprefs(self, ctx: commands.Context): if self.SecretSantaHelper.user_is_participant(ctx.author.id, self.usr_list): (index, user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) try: - await currAuthor.send("Current preference(s): {0}".format(user.preferences)) + await currAuthor.send(f"Current preference(s): {user.preferences}") except: await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) else: @@ -179,7 +179,7 @@ async def start(self, ctx: commands.Context): try: await this_user.send(santa_message) except: - await currAuthor.send("Failed to send message to {0}#{1} about their partner. Harass them to turn on server DMs for Secret Santa stuff.".format(this_user.name, this_user.discriminator)) + await currAuthor.send(f"Failed to send message to {this_user.name}#{this_user.discriminator} about their partner. Harass them to turn on server DMs for Secret Santa stuff.") # mark the exchange as in-progress self.exchange_started = True @@ -220,7 +220,7 @@ async def restart(self, ctx: commands.Context): await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) list_changed = self.SecretSantaHelper.usr_list_changed_during_pause(self.usr_list, self.user_left_during_pause) if(list_changed): - ctx.send("User list changed during the pause. Partners must be picked again with `{0}start`.".format(CONFIG.prefix)) + ctx.send(f"User list changed during the pause. Partners must be picked again with `{CONFIG.prefix}start`.") else: self.exchange_started = True is_paused = False @@ -268,11 +268,11 @@ async def join(self, ctx: commands.Context): self.config.write() # prompt user about inputting info - await ctx.send(currAuthor.mention + " has been added to the {0} Secret Santa exchange!".format(str(ctx.guild)) + "\nMore instructions have been DMd to you.") + await ctx.send(currAuthor.mention + f" has been added to the {str(ctx.guild)} Secret Santa exchange!" + "\nMore instructions have been DMd to you.") try: - userPrompt = """Welcome to the __{0}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{1}setwishlisturl [wishlist urls separated by a space ]` to set your wishlist URL (you may also add your mailing address).\n - Use `{2}setprefs [preferences separated by a space ]` to set gift preferences for your Secret Santa. Put N/A if none.""".format(str(ctx.guild), CONFIG.prefix, CONFIG.prefix) + userPrompt = f"""Welcome to the __{str(ctx.guild)}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n + Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space ]` to set your wishlist URL (you may also add your mailing address).\n + Use `{CONFIG.prefix}setprefs [preferences separated by a space ]` to set gift preferences for your Secret Santa. Put N/A if none.""" await currAuthor.send(userPrompt) except: ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) @@ -295,7 +295,7 @@ async def leave(self, ctx: commands.Context): self.config.write() if(self.is_paused): self.user_left_during_pause = True - await ctx.send(currAuthor.mention + " has left the {0} Secret Santa exchange".format(str(ctx.guild))) + await ctx.send(currAuthor.mention + f" has left the {str(ctx.guild)} Secret Santa exchange") else: await ctx.send(BOT_ERROR.UNJOINED) return @@ -326,13 +326,13 @@ async def listparticipants(self, ctx: commands.Context): ''' if(ctx.author.top_role == ctx.guild.roles[-1]): if(self.highest_key == 0): - await ctx.send("Nobody has signed up for the secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + await ctx.send(f"Nobody has signed up for the secret Santa exchange yet. Use `{CONFIG.prefix}join` to enter the exchange.") else: msg = '```The following people are signed up for the Secret Santa exchange:\n' for user in self.usr_list: this_user = ctx.guild.get_member(user.idstr) msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" - msg = msg + "\nUse `{0}join` to enter the exchange.```".format(CONFIG.prefix) + msg = msg + f"\nUse `{CONFIG.prefix}join` to enter the exchange.```" else: await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return @@ -343,11 +343,11 @@ async def totalparticipants(self, ctx: commands.Context): Find out how many people have joined the Secret Santa ''' if self.highest_key == 0: - await ctx.send("Nobody has signed up for the Secret Santa exchange yet. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + await ctx.send(f"Nobody has signed up for the Secret Santa exchange yet. Use `{CONFIG.prefix}join` to enter the exchange.") elif self.highest_key == 1: - await ctx.send("1 person has signed up for the Secret Santa exchange. Use `{0}join` to enter the exchange.".format(CONFIG.prefix)) + await ctx.send(f"1 person has signed up for the Secret Santa exchange. Use `{CONFIG.prefix}join` to enter the exchange.") else: - await ctx.send("{0} people have joined the Secret Santa exchange so far. Use `{1}join` to enter the exchange.".format(str(len(self.usr_list)), CONFIG.prefix)) + await ctx.send(f"{str(len(self.usr_list))} people have joined the Secret Santa exchange so far. Use `{CONFIG.prefix}join` to enter the exchange.") return @commands.command() diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 8093dd5..683a978 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -16,16 +16,16 @@ UNREACHABLE = "ERROR: this shouldn't happen." def ARCHIVE_ERROR_LENGTH(msg_url): - return "ERROR: output message is too long pinned message {0} was not archived.".format(msg_url) + return f"ERROR: output message is too long pinned message {msg_url} was not archived." def EXCHANGE_IN_PROGRESS_LEAVE(role): - return "ERROR: The gift exchange is already in progress. Please contact <@224949031514800128> or {0} about leaving.".format(role) + return f"ERROR: The gift exchange is already in progress. Please contact <@224949031514800128> or {role} about leaving." def HAS_NOT_SUBMITTED(usrname): - return "ERROR: " + usrname + " has not submitted either a mailing wishlist URL or gift preferences." + return f"ERROR: {usrname} has not submitted either a mailing wishlist URL or gift preferences." def INVALID_COUNTDOWN_COMMAND(attempted_command): - return "ERROR: invalid countdown option `{0}`.".format(attempted_command) + return f"ERROR: invalid countdown option `{attempted_command}`." def INVALID_COUNTDOWN_NAME(cd_name): - return "ERROR: countdown timer `{0}` does not exist. Use `s!countdown list` to list all countdown timers.".format(cd_name) + return f"ERROR: countdown timer `{cd_name}` does not exist. Use `s!countdown list` to list all countdown timers." def CANNOT_CHANGE_COUNTDOWN(author_name): - return "ERROR: you do not have permission to change that countdown timer. Please contact {0}".format(author_name) + return f"ERROR: you do not have permission to change that countdown timer. Please contact {author_name}" def NO_PERMISSION(role): - return "ERROR: you do not have permissions to do this.\nYou need the {0} role for that.".format(role) + return f"ERROR: you do not have permissions to do this.\nYou need the {role} role for that." diff --git a/helpers/SQLiteHelper.py b/helpers/SQLiteHelper.py index 121e238..50ad125 100644 --- a/helpers/SQLiteHelper.py +++ b/helpers/SQLiteHelper.py @@ -12,7 +12,7 @@ def create_connection(self): self.__connection = sqlite3.connect(self.__connection_path) print("Connection to SQLite DB successful") except Error as e: - print("The error '{0}' occurred".format(e)) + print(f"The error '{e}' occurred") def execute_query(self, query): if(self.__connection == None): @@ -25,7 +25,7 @@ def execute_query(self, query): print("Query executed successfully") return True except Error as e: - print("The error '{0}' occurred".format(e.with_traceback)) + print(f"The error '{e.with_traceback}' occurred") return False def create_table(self, table_name: str, table_format: str): @@ -58,9 +58,9 @@ def create_table(self, table_name: str, table_format: str): ); ''' - table_query = """ - CREATE TABLE IF NOT EXISTS {0} {1}; - """.format(table_name, table_format) + table_query = f""" + CREATE TABLE IF NOT EXISTS {table_name} {table_format}; + """ return self.execute_query(table_query) @@ -85,12 +85,12 @@ def insert_records(self, table_name: str, column_list: str, values: list): ('Leila', 32, 'female', 'France'); ''' value_str = ",".join(values) - table_query = """ + table_query = f""" INSERT INTO - {0} {1} + {table_name} {column_list} VALUES - {2}; - """.format(table_name, column_list, value_str) + {value_str}; + """ return self.execute_query(table_query) @@ -102,19 +102,19 @@ def execute_read_query(self, query): result = cursor.fetchall() return result except Error as e: - print("Problem reading - the error '{0}' occurred".format(e)) + print(f"Problem reading - the error '{e}' occurred") def execute_delete_query(self, table_name: str, conditions: str): - delete_query = "DELETE FROM {0} WHERE {1}".format(table_name, conditions) + delete_query = f"DELETE FROM {table_name} WHERE {conditions}" return self.execute_query(delete_query) def execute_update_query(self, table_name: str, new_column_data: str, conditions: str): - update_query = """ + update_query = f""" UPDATE - {0} + {table_name} SET - {1} + {new_column_data} WHERE - {2} - """.format(table_name, new_column_data, conditions) + {conditions} + """ return self.execute_query(update_query) \ No newline at end of file diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py index eee8b8f..d5e83bb 100644 --- a/helpers/SantaCountdownHelper.py +++ b/helpers/SantaCountdownHelper.py @@ -16,9 +16,9 @@ def __countdown_cmd_set(self, ctx: commands.Context, cd_name: str, cd_time: str) result_str = "" try: pend_test_convert = pendulum.from_format(cd_time, self.pend_format) # check that the format is correct - if(self.sqlhelp.insert_records(self.cd_table_name, "(name, time, user_id)", ["('{0}', '{1}', {2})".format(cd_name, cd_time, ctx.author.id)])): + if(self.sqlhelp.insert_records(self.cd_table_name, "(name, time, user_id)", [f"('{cd_name}', '{cd_time}', {ctx.author.id})"])): diff_str = self.__find_pend_diff_str(pend_test_convert) - result_str = "{0} countdown set for {1} ({2})".format(cd_name, cd_time, diff_str) + result_str = f"{cd_name} countdown set for {cd_time} ({diff_str})" else: result_str = BOT_ERROR.COUNTDOWN_NAME_TAKEN except ValueError as error: @@ -38,15 +38,15 @@ def __countdown_cmd_change(self, ctx: commands.Context, cd_name: str, cd_time: s print(result_str) return result_str - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(self.cd_table_name, cd_name) + query_get_timer_by_name = f"SELECT * FROM {self.cd_table_name} WHERE name=\'{cd_name}\';" query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): if(len(query_result) > 0): (query_id, query_name, query_time, query_user_id) = query_result[0] if(ctx.author.id == query_user_id): - if(self.sqlhelp.execute_update_query(self.cd_table_name, "time=\'{0}\'".format(cd_time), "id={0}".format(query_id))): + if(self.sqlhelp.execute_update_query(self.cd_table_name, f"time=\'{cd_time}\'", f"id={query_id}")): diff_str = self.__find_pend_diff_str(pend_test_convert) - result_str = "Updated countdown for {0}. Now set for {1}".format(cd_name, diff_str) + result_str = f"Updated countdown for {cd_name}. Now set for {diff_str}" else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) else: @@ -59,26 +59,26 @@ def __countdown_cmd_change(self, ctx: commands.Context, cd_name: str, cd_time: s def __countdown_cmd_check(self, cd_name: str): result_str = "" - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(self.cd_table_name, cd_name) + query_get_timer_by_name = f"SELECT * FROM {self.cd_table_name} WHERE name=\'{cd_name}\';" query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] cd_pend = pendulum.from_format(query_time, self.pend_format) diff_str = self.__find_pend_diff_str(cd_pend) - result_str = "Time until {0}: {1}".format(cd_name, diff_str) + result_str = f"Time until {cd_name}: {diff_str}" else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) return result_str def __countdown_cmd_remove(self, ctx: commands.Context, cd_name: str): result_str = "" - query_get_timer_by_name = "SELECT * FROM {0} WHERE name=\'{1}\';".format(self.cd_table_name, cd_name) + query_get_timer_by_name = f"SELECT * FROM {self.cd_table_name} WHERE name=\'{cd_name}\';" query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] if(query_user_id == ctx.author.id): - if(self.sqlhelp.execute_delete_query(self.cd_table_name, "id={0}".format(query_id))): - result_str = "Countdown timer `{0}` removed.".format(query_name) + if(self.sqlhelp.execute_delete_query(self.cd_table_name, f"id={query_id}")): + result_str = f"Countdown timer `{query_name}` removed." else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) else: @@ -90,27 +90,27 @@ def __countdown_cmd_remove(self, ctx: commands.Context, cd_name: str): def __countdown_cmd_list(self, ctx: commands.Context, ): result_str = "" - query_get_all_timers = "SELECT * FROM {0};".format(self.cd_table_name) + query_get_all_timers = f"SELECT * FROM {self.cd_table_name};" query_results = self.sqlhelp.execute_read_query(query_get_all_timers) result_str = "Countdown Name | Owner | Time | Time Until\n" if(query_results != None): for (query_id, query_name, query_time, query_user_id) in query_results: cd_pend = pendulum.from_format(query_time, self.pend_format) # convert to pendulum diff_str = self.__find_pend_diff_str(cd_pend) - time_until_str = "Time until {0}: {1}".format(query_name, diff_str) + time_until_str = f"Time until {query_name}: {diff_str}" cd_owner = ctx.guild.get_member(query_user_id).name - result_str += "{0} | {1} | {2} | {3}\n".format(query_name, cd_owner, query_time, time_until_str) + result_str += f"{query_name} | {cd_owner} | {query_time} | {time_until_str}\n" return result_str def __countdown_cmd_clean(self): result_str = "" - query_get_all_timers = "SELECT * FROM {0};".format(self.cd_table_name) + query_get_all_timers = "SELECT * FROM {self.cd_table_name};" query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns if(query_results != None): for (query_id, query_name, query_time, query_user_id) in query_results: if(not pendulum.from_format(query_time, self.pend_format).is_future()): # if the countdown has passed, delete - result_str += "{0} has passed. Deleting {1} countdown.\n".format(query_time, query_name) - self.sqlhelp.execute_delete_query(self.cd_table_name, "id = {0}".format(query_id)) + result_str += "{query_time} has passed. Deleting {query_name} countdown.\n" + self.sqlhelp.execute_delete_query(self.cd_table_name, f"id = {query_id}") return result_str def __find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): @@ -121,30 +121,30 @@ def __find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): missing_name_str = "" missing_time_str = "" missing_time_hint = "Formatted time ex. `5/17/20 @ 1:00 PM -06:00`" - complete_command_str = "Complete command: `{0}countdown {1}".format(CONFIG.prefix, cd_command) + complete_command_str = f"Complete command: `{CONFIG.prefix}countdown {cd_command}" argument_help = "" if(cd_command == "set"): if(cd_name == ""): - argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) + argument_help = f"{missing_args_str} {missing_name_str} | {missing_time_str}\n{missing_time_hint}\n" + argument_help += f"{complete_command_str} {missing_name_str} | {missing_time_str}`" elif(cd_time == ""): - argument_help = "{0} {1}\n{2}\n".format(missing_args_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) + argument_help = f"{missing_args_str} {missing_time_str}\n{missing_time_hint}\n" + argument_help += f"{complete_command_str} {cd_name} | {missing_time_str}`" elif(cd_command == "change"): if(cd_name == ""): - argument_help = "{0} {1} | {2}\n{3}\n".format(missing_args_str, missing_name_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, missing_name_str, missing_time_str) + argument_help = f"{missing_args_str} {missing_name_str} | {missing_time_str}\n{missing_time_hint}\n" + argument_help += f"{complete_command_str} {missing_name_str} | {missing_time_str}`" elif(cd_time == ""): - argument_help = "{0} {1}\n{2}\n".format(missing_args_str, missing_time_str, missing_time_hint) - argument_help += "{0} {1} | {2}`".format(complete_command_str, cd_name, missing_time_str) + argument_help = f"{missing_args_str} {missing_time_str}\n{missing_time_hint}\n" + argument_help += f"{complete_command_str} {cd_name} | {missing_time_str}`" elif(cd_command == "check"): if(cd_name == ""): - argument_help = "{0} {1}\n".format(missing_args_str, missing_name_str) - argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) + argument_help = f"{missing_args_str} {missing_name_str}\n" + argument_help += f"{complete_command_str} {missing_name_str}`" elif(cd_command == "remove"): if(cd_name == ""): - argument_help = "{0} {1}\n".format(missing_args_str, missing_name_str) - argument_help += "{0} {1}`".format(complete_command_str, missing_name_str) + argument_help = f"{missing_args_str} {missing_name_str}\n" + argument_help += f"{complete_command_str} {missing_name_str}`" elif(cd_command == "list"): pass elif(cd_command == "clean"): @@ -156,7 +156,7 @@ def __find_pend_diff_str(self, pend: pendulum.DateTime): (diff_days, diff_hours, diff_minutes) = (cd_diff.days, cd_diff.hours, cd_diff.minutes) if(not pend.is_future()): (diff_days, diff_hours, diff_minutes) = (-diff_days, -diff_hours, -diff_minutes) - diff_str = "{0} days, {1} hours, {2} minutes from now".format(diff_days, diff_hours, diff_minutes) + diff_str = f"{diff_days} days, {diff_hours} hours, {diff_minutes} minutes from now" return diff_str def run_countdown_command(self, ctx: commands.Context, cd_command: str, cd_name: str, cd_time: str): From 7690f40e0f3e4e3fb159dae47a5cfdd44bf230e5 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sat, 13 Jun 2020 19:48:43 -0700 Subject: [PATCH 156/189] Update pendulum Update pendulum I don't remember why I need pendulum 2.1.0 but I do for something --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9d6dab5..8a7523a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ discord.py configobj # keep back due to pendulum 2.0.4+ failing to build on Windows (2.1.0 installs on Linux) - see https://github.com/sdispater/pendulum/issues/457 -pendulum==2.0.3 +pendulum==2.1.0 urllib3 From 496e8367dd9952b766deef4776f5435ec15b9356 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 28 Jun 2020 12:13:58 -0700 Subject: [PATCH 157/189] Make unique SantaBot countdown tables for each guild Make unique SantaBot countdown tables for each guild Won't allow people using SantaBot in different servers to access the same countdown timers --- helpers/SQLiteHelper.py | 13 +++++++++++++ helpers/SantaCountdownHelper.py | 33 +++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/helpers/SQLiteHelper.py b/helpers/SQLiteHelper.py index 50ad125..dd953a9 100644 --- a/helpers/SQLiteHelper.py +++ b/helpers/SQLiteHelper.py @@ -14,9 +14,22 @@ def create_connection(self): except Error as e: print(f"The error '{e}' occurred") + def if_table_exists(self, table_name): + if(self.__connection == None): + print("Connection has not been created") + return False + + cursor = self.__connection.cursor() + cursor.execute(f''' SELECT count(name) FROM sqlite_master WHERE type='table' AND name='{table_name}' ''') + #if the count is 1, then table exists + if cursor.fetchone()[0]==1 : + return True + return False + def execute_query(self, query): if(self.__connection == None): print("Connection has not been created") + return False cursor = self.__connection.cursor() try: diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py index d5e83bb..d6131ba 100644 --- a/helpers/SantaCountdownHelper.py +++ b/helpers/SantaCountdownHelper.py @@ -10,13 +10,15 @@ def __init__(self, sqlitehelper: SQLiteHelper): self.pend_format = "M/D/YY [@] h:m A Z" self.cd_table_name = "Countdowns" self.sqlhelp = sqlitehelper - self.sqlhelp.create_table(self.cd_table_name, "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") + + def __get_cd_table_name(self, guild_id: str): + return f"{self.cd_table_name}_{guild_id}" def __countdown_cmd_set(self, ctx: commands.Context, cd_name: str, cd_time: str): result_str = "" try: pend_test_convert = pendulum.from_format(cd_time, self.pend_format) # check that the format is correct - if(self.sqlhelp.insert_records(self.cd_table_name, "(name, time, user_id)", [f"('{cd_name}', '{cd_time}', {ctx.author.id})"])): + if(self.sqlhelp.insert_records(self.__get_cd_table_name(ctx.guild.id), "(name, time, user_id)", [f"('{cd_name}', '{cd_time}', {ctx.author.id})"])): diff_str = self.__find_pend_diff_str(pend_test_convert) result_str = f"{cd_name} countdown set for {cd_time} ({diff_str})" else: @@ -38,13 +40,13 @@ def __countdown_cmd_change(self, ctx: commands.Context, cd_name: str, cd_time: s print(result_str) return result_str - query_get_timer_by_name = f"SELECT * FROM {self.cd_table_name} WHERE name=\'{cd_name}\';" + query_get_timer_by_name = f"SELECT * FROM {self.__get_cd_table_name(ctx.guild.id)} WHERE name=\'{cd_name}\';" query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): if(len(query_result) > 0): (query_id, query_name, query_time, query_user_id) = query_result[0] if(ctx.author.id == query_user_id): - if(self.sqlhelp.execute_update_query(self.cd_table_name, f"time=\'{cd_time}\'", f"id={query_id}")): + if(self.sqlhelp.execute_update_query(self.__get_cd_table_name(ctx.guild.id), f"time=\'{cd_time}\'", f"id={query_id}")): diff_str = self.__find_pend_diff_str(pend_test_convert) result_str = f"Updated countdown for {cd_name}. Now set for {diff_str}" else: @@ -57,9 +59,9 @@ def __countdown_cmd_change(self, ctx: commands.Context, cd_name: str, cd_time: s return result_str - def __countdown_cmd_check(self, cd_name: str): + def __countdown_cmd_check(self, ctx: commands.Context, cd_name: str): result_str = "" - query_get_timer_by_name = f"SELECT * FROM {self.cd_table_name} WHERE name=\'{cd_name}\';" + query_get_timer_by_name = f"SELECT * FROM {self.__get_cd_table_name(ctx.guild.id)} WHERE name=\'{cd_name}\';" query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] @@ -72,12 +74,12 @@ def __countdown_cmd_check(self, cd_name: str): def __countdown_cmd_remove(self, ctx: commands.Context, cd_name: str): result_str = "" - query_get_timer_by_name = f"SELECT * FROM {self.cd_table_name} WHERE name=\'{cd_name}\';" + query_get_timer_by_name = f"SELECT * FROM {self.__get_cd_table_name(ctx.guild.id)} WHERE name=\'{cd_name}\';" query_result = self.sqlhelp.execute_read_query(query_get_timer_by_name) if(query_result != None): (query_id, query_name, query_time, query_user_id) = query_result[0] if(query_user_id == ctx.author.id): - if(self.sqlhelp.execute_delete_query(self.cd_table_name, f"id={query_id}")): + if(self.sqlhelp.execute_delete_query(self.__get_cd_table_name(ctx.guild.id), f"id={query_id}")): result_str = f"Countdown timer `{query_name}` removed." else: result_str = BOT_ERROR.INVALID_COUNTDOWN_NAME(cd_name) @@ -90,7 +92,7 @@ def __countdown_cmd_remove(self, ctx: commands.Context, cd_name: str): def __countdown_cmd_list(self, ctx: commands.Context, ): result_str = "" - query_get_all_timers = f"SELECT * FROM {self.cd_table_name};" + query_get_all_timers = f"SELECT * FROM {self.__get_cd_table_name(ctx.guild.id)};" query_results = self.sqlhelp.execute_read_query(query_get_all_timers) result_str = "Countdown Name | Owner | Time | Time Until\n" if(query_results != None): @@ -102,15 +104,15 @@ def __countdown_cmd_list(self, ctx: commands.Context, ): result_str += f"{query_name} | {cd_owner} | {query_time} | {time_until_str}\n" return result_str - def __countdown_cmd_clean(self): + def __countdown_cmd_clean(self, ctx: commands.Context): result_str = "" - query_get_all_timers = "SELECT * FROM {self.cd_table_name};" + query_get_all_timers = f"SELECT * FROM {self.__get_cd_table_name(ctx.guild.id)};" query_results = self.sqlhelp.execute_read_query(query_get_all_timers) # get all the countdowns if(query_results != None): for (query_id, query_name, query_time, query_user_id) in query_results: if(not pendulum.from_format(query_time, self.pend_format).is_future()): # if the countdown has passed, delete result_str += "{query_time} has passed. Deleting {query_name} countdown.\n" - self.sqlhelp.execute_delete_query(self.cd_table_name, f"id = {query_id}") + self.sqlhelp.execute_delete_query(self.__get_cd_table_name(ctx.guild.id), f"id = {query_id}") return result_str def __find_countdown_hints(self, cd_command: str, cd_name: str, cd_time: str): @@ -162,18 +164,21 @@ def __find_pend_diff_str(self, pend: pendulum.DateTime): def run_countdown_command(self, ctx: commands.Context, cd_command: str, cd_name: str, cd_time: str): output = self.__find_countdown_hints(cd_command, cd_name, cd_time) if(output == ""): # no hints were needed + if(not self.sqlhelp.if_table_exists(self.__get_cd_table_name(ctx.guild.id))): # make sure table exists before executing the CD command + self.sqlhelp.create_table(self.__get_cd_table_name(ctx.guild.id), "(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, time TEXT NOT NULL, user_id INTEGER NOT NULL, UNIQUE(name))") + if(cd_command == "set"): output = self.__countdown_cmd_set(ctx, cd_name, cd_time) elif(cd_command == "change"): output = self.__countdown_cmd_change(ctx, cd_name, cd_time) elif(cd_command == "check"): - output = self.__countdown_cmd_check(cd_name) + output = self.__countdown_cmd_check(ctx, cd_name) elif(cd_command == "remove"): output = self.__countdown_cmd_remove(ctx, cd_name) elif(cd_command == "list"): output = self.__countdown_cmd_list(ctx) elif(cd_command == "clean"): - output = self.__countdown_cmd_clean() + output = self.__countdown_cmd_clean(ctx) else: output = BOT_ERROR.INVALID_COUNTDOWN_COMMAND(cd_command) output += "\nCountdown options/sub-commands: `set`, `change`, `check` , `remove`, `list`, `clean`." From 2f96c503fb1303c19d75dd4fc6ac52be37110440 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Tue, 14 Jul 2020 12:48:58 -0700 Subject: [PATCH 158/189] Putting the f in f-string Putting the f in f-string (s!cd clean) F is needed for f-string to be an f-string --- helpers/SantaCountdownHelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py index d6131ba..82fec43 100644 --- a/helpers/SantaCountdownHelper.py +++ b/helpers/SantaCountdownHelper.py @@ -111,7 +111,7 @@ def __countdown_cmd_clean(self, ctx: commands.Context): if(query_results != None): for (query_id, query_name, query_time, query_user_id) in query_results: if(not pendulum.from_format(query_time, self.pend_format).is_future()): # if the countdown has passed, delete - result_str += "{query_time} has passed. Deleting {query_name} countdown.\n" + result_str += f"{query_time} has passed. Deleting {query_name} countdown.\n" self.sqlhelp.execute_delete_query(self.__get_cd_table_name(ctx.guild.id), f"id = {query_id}") return result_str From 9f3da1e1f7f98945a50d0b5f02e91316683c8e04 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Tue, 6 Oct 2020 21:35:28 -0700 Subject: [PATCH 159/189] Update requirements.txt Stop holding back pendulum --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8a7523a..c276fa2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ discord.py configobj -# keep back due to pendulum 2.0.4+ failing to build on Windows (2.1.0 installs on Linux) - see https://github.com/sdispater/pendulum/issues/457 -pendulum==2.1.0 +pendulum urllib3 From 6d2c76caf3cb58a289fdf7158efa1572e2508530 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 1 Nov 2020 14:07:35 -0800 Subject: [PATCH 160/189] #49 use IntEnum for SecretSantaConstants, fix copy-paste errors, add traceback print to exceptions --- cogs/SecretSanta.py | 56 ++++++++++++++++++++------------- helpers/SecretSantaConstants.py | 4 +-- santa-bot.py | 7 +++-- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index dbeb7fc..7b8db9b 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -1,6 +1,7 @@ import CONFIG from configobj import ConfigObj import copy +from traceback import print_exc from discord.ext import commands @@ -55,12 +56,15 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): user.wishlisturl = new_wishlist try: await currAuthor.send(f"New wishlist URL: {new_wishlist}") - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: + except Exception as e: + print_exc(e) try: await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) else: await ctx.send(BOT_ERROR.UNJOINED) @@ -76,7 +80,8 @@ async def getwishlisturl(self, ctx: commands.Context): (index, user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) try: await currAuthor.send(f"Current wishlist destination(s): {user.wishlisturl}") - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) else: await ctx.send(BOT_ERROR.UNJOINED) @@ -88,12 +93,12 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): Set new preferences ''' currAuthor = ctx.author - if self.self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): - if(self.self.SecretSantaHelper.channelIsPrivate(ctx.channel)): + if self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): + if(self.SecretSantaHelper.channelIsPrivate(ctx.channel)): pass else: await ctx.message.delete() - (index, user) = self.self.SecretSantaHelper.get_participant_object(currAuthor, self.usr_list) + (index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) new_prefs = "None" if(len(preferences) == 0): pass @@ -107,13 +112,16 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): user.preferences = new_prefs try: await currAuthor.send(f"New preferences: {new_prefs}") - except: + except Exception as e: + print_exc(e) + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + except Exception as e: + print_exc(e) + try: + await currAuthor.send(BOT_ERROR.INVALID_INPUT) + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) - except: - try: - await currAuthor.send(BOT_ERROR.INVALID_INPUT) - except: - await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) else: await ctx.send(BOT_ERROR.UNJOINED) return @@ -128,7 +136,8 @@ async def getprefs(self, ctx: commands.Context): (index, user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) try: await currAuthor.send(f"Current preference(s): {user.preferences}") - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) else: await ctx.send(BOT_ERROR.UNJOINED) @@ -151,7 +160,8 @@ async def start(self, ctx: commands.Context): try: await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) # select a random partner for each participant if all information is complete and there are enough people to do it @@ -178,7 +188,8 @@ async def start(self, ctx: commands.Context): santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 try: await this_user.send(santa_message) - except: + except Exception as e: + print_exc(e) await currAuthor.send(f"Failed to send message to {this_user.name}#{this_user.discriminator} about their partner. Harass them to turn on server DMs for Secret Santa stuff.") # mark the exchange as in-progress @@ -216,7 +227,8 @@ async def restart(self, ctx: commands.Context): try: await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await ctx.send("`Partner assignment cancelled: participant info incomplete.`") - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) list_changed = self.SecretSantaHelper.usr_list_changed_during_pause(self.usr_list, self.user_left_during_pause) if(list_changed): @@ -271,10 +283,11 @@ async def join(self, ctx: commands.Context): await ctx.send(currAuthor.mention + f" has been added to the {str(ctx.guild)} Secret Santa exchange!" + "\nMore instructions have been DMd to you.") try: userPrompt = f"""Welcome to the __{str(ctx.guild)}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space ]` to set your wishlist URL (you may also add your mailing address).\n - Use `{CONFIG.prefix}setprefs [preferences separated by a space ]` to set gift preferences for your Secret Santa. Put N/A if none.""" + Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space (e.g. \"{CONFIG.prefix}setwishlisturl abc.com xyz.com\")]` to set your wishlist URL (you may also add your mailing address).\n + Use `{CONFIG.prefix}setprefs [preferences separated by a space (e.g. \"{CONFIG.prefix}setprefs dog "stuffed rabbit" cat"\")]` to set gift preferences for your Secret Santa. Put N/A if none.""" await currAuthor.send(userPrompt) - except: + except Exception as e: + print_exc(e) ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) return @@ -367,7 +380,8 @@ async def partnerinfo(self, ctx: commands.Context): try: await currAuthor.send(msg) await ctx.send("The information has been sent to your DMs.") - except: + except Exception as e: + print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) elif((not self.exchange_started) and authorIsParticipant): await ctx.send(BOT_ERROR.NOT_STARTED) diff --git a/helpers/SecretSantaConstants.py b/helpers/SecretSantaConstants.py index 8b2b96f..aa93f80 100644 --- a/helpers/SecretSantaConstants.py +++ b/helpers/SecretSantaConstants.py @@ -1,6 +1,6 @@ -from enum import Enum +from enum import IntEnum -class SecretSantaConstants(Enum): +class SecretSantaConstants(IntEnum): NAME = 0 DISCRIMINATOR = 1 IDSTR = 2 diff --git a/santa-bot.py b/santa-bot.py index bc41ee2..a286996 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,5 +1,6 @@ import logging import os +from traceback import print_exc from configobj import ConfigObj from discord.ext import commands @@ -17,10 +18,12 @@ def start_santa_bot(): config = None try: config = ConfigObj(CONFIG.cfg_path, file_error = True) - except: + except Exception as e: + print_exc(e) try: os.mkdir(CONFIG.bot_folder) - except: + except Exception as e: + print_exc(e) pass config = ConfigObj() config.filename = CONFIG.cfg_path From 0a5321f8b431ec286eab4206084fd0cbdc772463 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 1 Nov 2020 14:15:00 -0800 Subject: [PATCH 161/189] #49 fix messed up character literal, configobj not found doesn't need extra handling --- cogs/SecretSanta.py | 2 +- santa-bot.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 7b8db9b..21647bd 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -284,7 +284,7 @@ async def join(self, ctx: commands.Context): try: userPrompt = f"""Welcome to the __{str(ctx.guild)}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space (e.g. \"{CONFIG.prefix}setwishlisturl abc.com xyz.com\")]` to set your wishlist URL (you may also add your mailing address).\n - Use `{CONFIG.prefix}setprefs [preferences separated by a space (e.g. \"{CONFIG.prefix}setprefs dog "stuffed rabbit" cat"\")]` to set gift preferences for your Secret Santa. Put N/A if none.""" + Use `{CONFIG.prefix}setprefs [preferences separated by a space (e.g. \"{CONFIG.prefix}setprefs dog "stuffed rabbit" cat\")]` to set gift preferences for your Secret Santa. Put N/A if none.""" await currAuthor.send(userPrompt) except Exception as e: print_exc(e) diff --git a/santa-bot.py b/santa-bot.py index a286996..0e2ca00 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,6 +1,5 @@ import logging import os -from traceback import print_exc from configobj import ConfigObj from discord.ext import commands @@ -19,11 +18,9 @@ def start_santa_bot(): try: config = ConfigObj(CONFIG.cfg_path, file_error = True) except Exception as e: - print_exc(e) try: os.mkdir(CONFIG.bot_folder) except Exception as e: - print_exc(e) pass config = ConfigObj() config.filename = CONFIG.cfg_path From c6d5956f602931fc83d41cc42b4df8cc2716f1d7 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 5 Nov 2020 09:45:05 -0800 Subject: [PATCH 162/189] Create CONFIG.py.example --- CONFIG.py.example | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 CONFIG.py.example diff --git a/CONFIG.py.example b/CONFIG.py.example new file mode 100644 index 0000000..9ca5d21 --- /dev/null +++ b/CONFIG.py.example @@ -0,0 +1,19 @@ +import os +import pathlib + +discord_token = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.1234567890" +client_id = "0123456789" +prefix = "s!" +bot_folder = "files" +cfg_name = "botdata_example.cfg" +sqlite_name = "santabotdb_example.sqlite" +dbg_name = "debug_example.log" +role_channel = 9876543210 + + +############################################### +### DO NOT CHANGE BELOW ### +############################################### +cfg_path = os.path.join(str(pathlib.Path(__file__).parent), bot_folder, cfg_name) +sqlite_path = os.path.join(str(pathlib.Path(__file__).parent), bot_folder, sqlite_name) +dbg_path = os.path.join(str(pathlib.Path(__file__).parent), bot_folder, dbg_name) From 0d624b5ef6e582d4dc818e1eb4d91360cd6e3648 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 5 Nov 2020 09:47:53 -0800 Subject: [PATCH 163/189] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a09ac37..957f595 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,10 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Steps to run: 1. Run `pip3 install -r requirements.txt` 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). -3. Replace variables in CONFIG.py - 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start +3. Replace variables in CONFIG.py (I have provided CONFIG.py.example as an example) + + 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start + 3b. Replace other variables as you want - `role_channel` is REQUIRED for using reaction roles - but will throw an error if unassigned - `bot_folder` this is where the .cfg for the Secret Santa participants, the debug log, and the SQLite database files are stored From ef4a74234132173d72b45a9abf6d3df92c75c42f Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 5 Nov 2020 09:51:21 -0800 Subject: [PATCH 164/189] Update README.md --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 957f595..cb74abb 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,17 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). 3. Replace variables in CONFIG.py (I have provided CONFIG.py.example as an example) - 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start - - 3b. Replace other variables as you want - - `role_channel` is REQUIRED for using reaction roles - but will throw an error if unassigned - - `bot_folder` this is where the .cfg for the Secret Santa participants, the debug log, and the SQLite database files are stored - - `prefix` change it to whatever you want otherwise commands default to YOUR_PREFIX_HEREjoin, YOUR_PREFIX_HEREunpin_all, etc. - - `cfg_name`, `dbg_name`, `sqlite_name` don't *need* to do anything here unless you want to + 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start + + 3b. Replace other variables as you want + + - `role_channel` is REQUIRED for using reaction roles - but will throw an error if unassigned + + - `bot_folder` this is where the .cfg for the Secret Santa participants, the debug log, and the SQLite database files are stored + + - `prefix` change it to whatever you want otherwise commands default to YOUR_PREFIX_HEREjoin, YOUR_PREFIX_HEREunpin_all, etc. + + - `cfg_name`, `dbg_name`, `sqlite_name` don't *need* to do anything here unless you want to 4. Run `python3 santa-bot.py` #### Secret Santa Commands: From 85ebb03053e4081186b6279955c17769239c18d3 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 20 Nov 2020 11:03:59 -0800 Subject: [PATCH 165/189] #51 fix listparticipants not printing the participants #51 fix listparticipants not printing the participants I'm dumb Add aliases to SecretSanta commands Convenience --- cogs/SecretSanta.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 21647bd..e8c9793 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -31,7 +31,7 @@ def __init__(self, bot: commands.Bot, config: ConfigObj): self.usr_list.append(usr) self.highest_key = int(key) - @commands.command() + @commands.command(aliases=["setwlurl"]) async def setwishlisturl(self, ctx: commands.Context, *destination:str): ''' [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). @@ -70,7 +70,7 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): await ctx.send(BOT_ERROR.UNJOINED) return - @commands.command() + @commands.command(aliases=["gwlurl"]) async def getwishlisturl(self, ctx: commands.Context): ''' Get current wishlist @@ -87,7 +87,7 @@ async def getwishlisturl(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNJOINED) return - @commands.command() + @commands.command(aliases=["sp"]) async def setprefs(self, ctx: commands.Context, *preferences:str): ''' Set new preferences @@ -126,7 +126,7 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): await ctx.send(BOT_ERROR.UNJOINED) return - @commands.command() + @commands.command(aliases=["gp"]) async def getprefs(self, ctx: commands.Context): ''' Get current preferences @@ -332,25 +332,26 @@ async def end(self, ctx: commands.Context): await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return - @commands.command() + @commands.command(aliases=["lp"]) async def listparticipants(self, ctx: commands.Context): ''' List Secret Santa participants ''' if(ctx.author.top_role == ctx.guild.roles[-1]): if(self.highest_key == 0): - await ctx.send(f"Nobody has signed up for the secret Santa exchange yet. Use `{CONFIG.prefix}join` to enter the exchange.") + await ctx.send(f"```Nobody has signed up for the secret Santa exchange yet. Use `{CONFIG.prefix}join` to enter the exchange.```") else: msg = '```The following people are signed up for the Secret Santa exchange:\n' for user in self.usr_list: this_user = ctx.guild.get_member(user.idstr) msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" msg = msg + f"\nUse `{CONFIG.prefix}join` to enter the exchange.```" + await ctx.send(msg) else: await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return - @commands.command() + @commands.command(aliases=["tp"]) async def totalparticipants(self, ctx: commands.Context): ''' Find out how many people have joined the Secret Santa From a5dfcf61e15e95a174f58e5302577b667cf7114d Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 20 Nov 2020 11:08:32 -0800 Subject: [PATCH 166/189] Be consistent with SecretSanta command aliases Be consistent with SecretSanta command aliases Consistency is key --- cogs/SecretSanta.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index e8c9793..570f234 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -31,7 +31,7 @@ def __init__(self, bot: commands.Bot, config: ConfigObj): self.usr_list.append(usr) self.highest_key = int(key) - @commands.command(aliases=["setwlurl"]) + @commands.command(aliases=["swlurl"]) async def setwishlisturl(self, ctx: commands.Context, *destination:str): ''' [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). @@ -87,7 +87,7 @@ async def getwishlisturl(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNJOINED) return - @commands.command(aliases=["sp"]) + @commands.command(aliases=["sprefs"]) async def setprefs(self, ctx: commands.Context, *preferences:str): ''' Set new preferences @@ -126,7 +126,7 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): await ctx.send(BOT_ERROR.UNJOINED) return - @commands.command(aliases=["gp"]) + @commands.command(aliases=["gprefs"]) async def getprefs(self, ctx: commands.Context): ''' Get current preferences From 44acf062644369554679e4e5e9c926ae56e7a321 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 20 Nov 2020 11:14:25 -0800 Subject: [PATCH 167/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb74abb..22d0399 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - If you don't want the admin-type stuff, just go in and comment out the add_cog(SantaAdministrative(...)) line ### Requirements -- python 3.5.3 or later (can be installed [here](https://www.python.org/downloads/)) +- python 3.6 or later (can be installed [here](https://www.python.org/downloads/)) - pip3 ### Steps to run: From 5cb0946493f5ceffe1f8322927011c35fe7a207e Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 20 Nov 2020 11:21:15 -0800 Subject: [PATCH 168/189] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 22d0399..051d95a 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - `s!join` = join the Secret Santa - `s!leave` = leave the Secret Santa -- `s!setwishlisturl [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. -- `s!getwishlisturl` = bot will PM you your current wishlist -- `s!setprefs [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. -- `s!getprefs` = bot will PM you your current preferences -- `s!listparticipants` **(admin only)** = get the current participants -- `s!totalparticipants` = get the total number of participants +- `s![setwishlisturl|swlurl] [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. +- `s![getwishlisturl|gwlurl]` = bot will PM you your current wishlist +- `s![setprefs|sprefs] [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. +- `s![getprefs|gprefs]` = bot will PM you your current preferences +- `s![listparticipants|lp]` **(admin only)** = get the current participants +- `s![totalparticipants|tp]` = get the total number of participants - `s!partnerinfo` = be DM'd your partner's information - `s!start` **(admin only)** = assign Secret Santa partners - `s!restart` **(admin only)** = attempt to restart Secret Santa after pause without changing partners @@ -45,7 +45,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho #### Administrative Commands: - `s!assign_role_channel CHANNEL` **(admin only)** = change the channel the bot looks at for reaction roles -- `s!archive pins SRC_CHANNEL DEST_CHANNEL` **(admin only)** = archive all pins from the source channel to the destination channel as messages +- `s!archive_pins SRC_CHANNEL DEST_CHANNEL` **(admin only)** = archive all pins from the source channel to the destination channel as messages (ex. s!archive_pins #general #archive) - `s!unpin_all [CHANNEL_ID]` **(admin only)** = unpin all messages in the indicated channel (defaults to the channel the command is called in) #### Utility Commands: From 4f7213c6cba363030f73e826dab796aae1c4486c Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 20 Nov 2020 11:29:25 -0800 Subject: [PATCH 169/189] Update README.md --- README.md | 6 +++--- cogs/SantaAdministrative.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 051d95a..3820485 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - `s!join` = join the Secret Santa - `s!leave` = leave the Secret Santa -- `s![setwishlisturl|swlurl] [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __This field required__. +- `s![setwishlisturl|swlurl] [wishlist URL]` = set your wishlist URL (replaces current). You may also add your mailing address. __wishlist URL is required__. - `s![getwishlisturl|gwlurl]` = bot will PM you your current wishlist -- `s![setprefs|sprefs] [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __This field required__. +- `s![setprefs|sprefs] [specific preferences, the things you like]` = set preferences (replaces current). Put N/A if none. __preferences are required__. - `s![getprefs|gprefs]` = bot will PM you your current preferences - `s![listparticipants|lp]` **(admin only)** = get the current participants - `s![totalparticipants|tp]` = get the total number of participants @@ -45,7 +45,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho #### Administrative Commands: - `s!assign_role_channel CHANNEL` **(admin only)** = change the channel the bot looks at for reaction roles -- `s!archive_pins SRC_CHANNEL DEST_CHANNEL` **(admin only)** = archive all pins from the source channel to the destination channel as messages (ex. s!archive_pins #general #archive) +- `s!archive_pins SRC_CHANNEL DEST_CHANNEL` **(admin only)** = archive all pins from the source channel to the destination channel as messages (ex. `s!archive_pins #general #archive`) - `s!unpin_all [CHANNEL_ID]` **(admin only)** = unpin all messages in the indicated channel (defaults to the channel the command is called in) #### Utility Commands: diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 78c2d1f..4bceec4 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -36,6 +36,8 @@ async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel async def archive_pins(self, ctx: commands.Context, channel_to_archive: discord.TextChannel, channel_to_message: discord.TextChannel): ''' Archive the pins in one channel to another channel as messages + + ex. `s!archive_pins #general #archive` ''' start_message = f"Attempting to archive pinned messages from {channel_to_archive.mention} to {channel_to_message.mention}" From ae0a6e3017a2e217c4c9c15ee753200e8d496395 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Fri, 20 Nov 2020 11:43:51 -0800 Subject: [PATCH 170/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3820485..27606ac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Python library + some other admin-type stuff for my server -- This bot code must be forked/pulled and run locally +- This bot code must be pulled (or forked) and run locally - If you don't want the admin-type stuff, just go in and comment out the add_cog(SantaAdministrative(...)) line ### Requirements From 90974c9502cd55442b774272f058f038a340431e Mon Sep 17 00:00:00 2001 From: irnutsmurt <73799093+irnutsmurt@users.noreply.github.com> Date: Sun, 22 Nov 2020 09:28:39 -0800 Subject: [PATCH 171/189] santabot.service default santabot.service config with comments to assist users in properly configuring it for their environment --- santabot.service | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 santabot.service diff --git a/santabot.service b/santabot.service new file mode 100644 index 0000000..1746a2c --- /dev/null +++ b/santabot.service @@ -0,0 +1,15 @@ +[Unit] +Description=Santa Discord Bot + +[Service] +#Add user and group they belong to. Find default group with "id -gn username" +User=usernamehere +Group=defaultgrouphere +Restart=on-abort +#Add full directory for where santabot lives. If santabot is in /home/pi/Santabot then place that there. +WorkingDirectory= /home/pi/Santabot +#Copy run_santa.sh from Santabot directory into /usr/local/bin/ +ExecStart= /usr/local/bin/run_santa.sh + +[Install] +WantedBy=multi-user.target From e3b8d2610c48d1cb9b4dfa19cacbf440e62b9ef6 Mon Sep 17 00:00:00 2001 From: irnutsmurt <73799093+irnutsmurt@users.noreply.github.com> Date: Sun, 22 Nov 2020 10:06:17 -0800 Subject: [PATCH 172/189] Fixed Readme for SystemD Service SantaBot Corrected some grammar, added a santabot.service example file for modifying or referencing for easier installation of systemd service. --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index cb74abb..48cde99 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,47 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho - `cfg_name`, `dbg_name`, `sqlite_name` don't *need* to do anything here unless you want to 4. Run `python3 santa-bot.py` +### Add a Systemd Service for SantaBot to Autostart on Reboot +To keep SantaBot running even after restart you can create a service. + +1. Copy the 'run_santa.sh' to '/usr/local/bin/' directory + +- sudo cp run_santa.sh /usr/local/bin/ + +2. Create a service file or edit the included 'santabot.service' using your favorite text editor and add the default user name to 'User=' and the Group they belong to in 'Group='. To find out what default group a user belongs use 'id -gn usernamehere' + +- nano santabot.service + + - [Unit] + - Description=Santa Discord Bot + + - [Service] + - User= root + - Group= root + - Restart=on-abort + - WorkingDirectory= "Full Path to Santa Bot Here" + - ExecStart= /usr/local/bin/run_santa.sh + + - [Install] + - WantedBy=multi-user.target + +3. Copy the santabot.service to '/lib/systemd/system/' + +- sudo cp santabot.service /lib/systemd/system/ + +4. Enable the service so it will autostart on reboot, then start it. + +- sudo systemctl enable santabot.service +- sudo systemctl start santabot.service + +5. Then check the status + +- sudo systemctl status santabot.service + +6. When done using the bot for the season, disable using + +- sudo systemctl disable santabot.service + #### Secret Santa Commands: - `s!join` = join the Secret Santa From cd23f2f68cce3b998e08f57f52c0800d74a3f3e0 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 22 Nov 2020 13:37:15 -0800 Subject: [PATCH 173/189] Address #52 by adding more specific user prompts and setting preferences to N/A by default. Also found a bug in get_participant_object(...). Address #52 by adding more specific user prompts and setting preferences to N/A by default. Also found a bug in get_participant_object(...). Make life easier for users by prompting them on next steps for joining the secret santa --- CONFIG.py.example | 2 ++ README.md | 22 +++++++++++++++------- cogs/SecretSanta.py | 30 +++++++++++++++++++++--------- helpers/SecretSantaConstants.py | 1 + helpers/SecretSantaHelpers.py | 4 ++-- helpers/SecretSantaParticipant.py | 2 +- 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/CONFIG.py.example b/CONFIG.py.example index 9ca5d21..e67c566 100644 --- a/CONFIG.py.example +++ b/CONFIG.py.example @@ -9,6 +9,8 @@ cfg_name = "botdata_example.cfg" sqlite_name = "santabotdb_example.sqlite" dbg_name = "debug_example.log" role_channel = 9876543210 +min_budget = 10 +max_budget = 20 ############################################### diff --git a/README.md b/README.md index 58265cd..4bd712e 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,18 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho 3. Replace variables in CONFIG.py (I have provided CONFIG.py.example as an example) 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start - + 3b. Replace other variables as you want - - - `role_channel` is REQUIRED for using reaction roles - but will throw an error if unassigned - - - `bot_folder` this is where the .cfg for the Secret Santa participants, the debug log, and the SQLite database files are stored - + + - `role_channel` is REQUIRED for using reaction roles - will print an error in the console if left as -1 + - `prefix` change it to whatever you want otherwise commands default to YOUR_PREFIX_HEREjoin, YOUR_PREFIX_HEREunpin_all, etc. - + + - `bot_folder` this is where the .cfg for the Secret Santa participants, the debug log, and the SQLite database files are stored + - `cfg_name`, `dbg_name`, `sqlite_name` don't *need* to do anything here unless you want to + + - `min_budget`, `max_budget` are used for the Secret Santa functionality and can be changed depending on what your Discord server agrees upon. 4. Run `python3 santa-bot.py` ### Add a Systemd Service for SantaBot to Autostart on Reboot @@ -68,6 +70,12 @@ To keep SantaBot running even after restart you can create a service. - sudo systemctl disable santabot.service +#### FAQ: +1. What if my wishlist URL or preference is multiple words? + - The bot supports that, just surround it with quotation marks. (e.g. `s!setprefs dog "stuffed rabbit" cat`) +2. Does the wishlist *have* to be a URL? What if one of the Secret Santas prefers sending handmade gifts? + - No that field is really about having a destination for users to send their gifts. Feel free to use addresses instead of URLs if you so desire. Keep in mind that addresses will need to follow question #1 of this FAQ (e.g. `s!setwishlisturl amazonurl/123 "P Sherman 42 Wallaby Way" rightstufanime`) + #### Secret Santa Commands: - `s!join` = join the Secret Santa diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 570f234..aa48845 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -34,7 +34,7 @@ def __init__(self, bot: commands.Bot, config: ConfigObj): @commands.command(aliases=["swlurl"]) async def setwishlisturl(self, ctx: commands.Context, *destination:str): ''' - [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazon.com "P. Sherman 42 Wallaby Way, Sydney" http://rightstufanime.com/). + [Any number of wishlist URLs or mailing addresses] = set wishlist destinations or mailing address. Surround mailing address with quotation marks and separate EACH wishlist destination with a space (eg. amazonurl/123 "Sesame Street" http://rightstufanime.com/). ''' currAuthor = ctx.author if self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): @@ -55,7 +55,13 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): # add the input to the value in the user's class instance user.wishlisturl = new_wishlist try: - await currAuthor.send(f"New wishlist URL: {new_wishlist}") + await currAuthor.send(f"New wishlist URL(s): {new_wishlist}") + if(not user.pref_is_set()): + userPrompt = f"Great! Now please specify what your preferences for your wishlist might be. Use `{CONFIG.prefix}setprefs [preferences separated by a space]` (e.g. `{CONFIG.prefix}setprefs hippopotamus \"stuffed rabbit\" dog`). Defaults to N/A if not entered." + await currAuthor.send(userPrompt) + if(user.wishlisturl_is_set() and user.pref_is_set()): + signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}!setwishlisturl` or `{CONFIG.prefix}!setprefs` any time before the admin begins the Secret Santa." + await currAuthor.send(signup_complete_msg) except Exception as e: print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) @@ -112,6 +118,12 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): user.preferences = new_prefs try: await currAuthor.send(f"New preferences: {new_prefs}") + if(not user.wishlisturl_is_set()): + userPrompt = f"Great! Now please specify what your wishlist URL or mailing address. Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space]` (e.g. `{CONFIG.prefix}setwishlisturl amazonurl/123 \"sesame street\"`) to set your wishlist URL." + await currAuthor.send(userPrompt) + if(user.wishlisturl_is_set() and user.pref_is_set()): + signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}!setwishlisturl` or `{CONFIG.prefix}!setprefs` any time before the admin begins the Secret Santa." + await currAuthor.send(signup_complete_msg) except Exception as e: print_exc(e) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) @@ -181,9 +193,9 @@ async def start(self, ctx: commands.Context): self.config.write() # tell participants who their partner is this_user = ctx.guild.get_member(int(user.idstr)) - message_pt1 = str(partner.name) + "#" + str(partner.discriminator) + " is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the $10-20 range.\n" - message_pt2 = "Their wishlist(s) can be found here: " + partner.wishlisturl + "\n" - message_pt3 = "And their gift preferences can be found here: " + partner.preferences + "\n" + message_pt1 = f"{str(partner.name)}#{str(partner.discriminator)} is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the ${CONFIG.min_budget}-{CONFIG.max_budget} range.\n" + message_pt2 = f"Their wishlist(s) can be found here: {partner.wishlisturl}\n" + message_pt3 = f"And their gift preferences can be found here: {partner.preferences}\n" message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 try: @@ -276,15 +288,15 @@ async def join(self, ctx: commands.Context): self.highest_key = self.highest_key + 1 self.usr_list.append(SecretSantaParticipant(currAuthor.name, currAuthor.discriminator, currAuthor.id, self.highest_key)) # write details of the class instance to config and increment total_users - self.config['members'][str(self.highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, self.highest_key, "", "", ""] + self.config['members'][str(self.highest_key)] = [currAuthor.name, currAuthor.discriminator, currAuthor.id, self.highest_key, "", "N/A", ""] self.config.write() # prompt user about inputting info await ctx.send(currAuthor.mention + f" has been added to the {str(ctx.guild)} Secret Santa exchange!" + "\nMore instructions have been DMd to you.") try: - userPrompt = f"""Welcome to the __{str(ctx.guild)}__ Secret Santa! Please input your wishlist URL and preferences **(by DMing this bot)** so your Secret Santa can send you something.\n - Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space (e.g. \"{CONFIG.prefix}setwishlisturl abc.com xyz.com\")]` to set your wishlist URL (you may also add your mailing address).\n - Use `{CONFIG.prefix}setprefs [preferences separated by a space (e.g. \"{CONFIG.prefix}setprefs dog "stuffed rabbit" cat\")]` to set gift preferences for your Secret Santa. Put N/A if none.""" + userPrompt = f"Welcome to the __{str(ctx.guild)}__ Secret Santa! To complete your enrollment you'll need to input your wishlist URL and preferences (by DMing this bot) so your Secret Santa can send you something\n" + await currAuthor.send(userPrompt) + userPrompt = f".\nFirst we need your wishlist (or the destination for sending gifts). Please use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space]` (e.g. `{CONFIG.prefix}setwishlisturl amazonurl/123 \"sesame street\"`) to set your wishlist URL (you may also add your mailing address)." await currAuthor.send(userPrompt) except Exception as e: print_exc(e) diff --git a/helpers/SecretSantaConstants.py b/helpers/SecretSantaConstants.py index aa93f80..a44c437 100644 --- a/helpers/SecretSantaConstants.py +++ b/helpers/SecretSantaConstants.py @@ -1,6 +1,7 @@ from enum import IntEnum class SecretSantaConstants(IntEnum): + """Constants for indexing into each Secret Santa participant's field when parsing the .cfg file""" NAME = 0 DISCRIMINATOR = 1 IDSTR = 2 diff --git a/helpers/SecretSantaHelpers.py b/helpers/SecretSantaHelpers.py index 056e44c..d17a518 100644 --- a/helpers/SecretSantaHelpers.py +++ b/helpers/SecretSantaHelpers.py @@ -21,12 +21,12 @@ def user_is_participant(self, usrid: discord.User.id, usrlist: list): return True return False - def get_participant_object(self, usrid: discord.User.id, usrlist: list): + def get_participant_object(self, usrid: str, usrlist: list): """takes a discord user ID string and list of participant objects, and returns the first participant object with matching id.""" for (index, person) in enumerate(usrlist): - if(int(person.idstr) == usrid): + if(person.idstr == usrid): return (index, person) return (-1, None) diff --git a/helpers/SecretSantaParticipant.py b/helpers/SecretSantaParticipant.py index c65e6d9..067f046 100644 --- a/helpers/SecretSantaParticipant.py +++ b/helpers/SecretSantaParticipant.py @@ -1,6 +1,6 @@ class SecretSantaParticipant(object): """class defining a participant and info associated with them""" - def __init__(self, name, discriminator, idstr, usrnum, wishlisturl='', preferences='', partnerid=''): + def __init__(self, name, discriminator, idstr, usrnum, wishlisturl="", preferences="N/A", partnerid=""): self.name = name #string containing name of user self.discriminator = discriminator #string containing discriminant of user self.idstr = idstr #string containing id of user From 767179bbdff081031bb5e9ab5d4330cd5e415a83 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 22 Nov 2020 13:58:47 -0800 Subject: [PATCH 174/189] Add Intents in order to get server members Add Intents in order to get server members Required as of discord.py version 1.5 --- README.md | 1 + cogs/SecretSanta.py | 4 ++-- santa-bot.py | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4bd712e..c9fc1e9 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Steps to run: 1. Run `pip3 install -r requirements.txt` 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). + - **IMPORTANT:** you will need to explicitly enable the Server Members Intent under the Bot tab in https://discord.com/developers/applications/ [as pictured](https://i.imgur.com/mvwTiPE.png) 3. Replace variables in CONFIG.py (I have provided CONFIG.py.example as an example) 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index aa48845..dc09712 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -60,7 +60,7 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): userPrompt = f"Great! Now please specify what your preferences for your wishlist might be. Use `{CONFIG.prefix}setprefs [preferences separated by a space]` (e.g. `{CONFIG.prefix}setprefs hippopotamus \"stuffed rabbit\" dog`). Defaults to N/A if not entered." await currAuthor.send(userPrompt) if(user.wishlisturl_is_set() and user.pref_is_set()): - signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}!setwishlisturl` or `{CONFIG.prefix}!setprefs` any time before the admin begins the Secret Santa." + signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}setwishlisturl` or `{CONFIG.prefix}setprefs` any time before the admin begins the Secret Santa." await currAuthor.send(signup_complete_msg) except Exception as e: print_exc(e) @@ -122,7 +122,7 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): userPrompt = f"Great! Now please specify what your wishlist URL or mailing address. Use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space]` (e.g. `{CONFIG.prefix}setwishlisturl amazonurl/123 \"sesame street\"`) to set your wishlist URL." await currAuthor.send(userPrompt) if(user.wishlisturl_is_set() and user.pref_is_set()): - signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}!setwishlisturl` or `{CONFIG.prefix}!setprefs` any time before the admin begins the Secret Santa." + signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}setwishlisturl` or `{CONFIG.prefix}setprefs` any time before the admin begins the Secret Santa." await currAuthor.send(signup_complete_msg) except Exception as e: print_exc(e) diff --git a/santa-bot.py b/santa-bot.py index 0e2ca00..119458e 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -3,6 +3,7 @@ from configobj import ConfigObj from discord.ext import commands +from discord import Intents import CONFIG from cogs.SecretSanta import SecretSanta @@ -40,7 +41,9 @@ def start_santa_bot(): client_log.addHandler(client_handler) # add the cogs - bot = commands.Bot(command_prefix = CONFIG.prefix) + intents = Intents.default() + intents.members = True + bot = commands.Bot(command_prefix = CONFIG.prefix, intents=intents) bot.add_cog(SantaAdministrative(bot)) bot.add_cog(SantaMiscellaneous(bot)) bot.add_cog(SantaUtilities(bot, sqlitehelper)) From c5e8951dfbf516fcd8d00da862c737d047de612d Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 22 Nov 2020 14:04:12 -0800 Subject: [PATCH 175/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9fc1e9..91ad5ed 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Steps to run: 1. Run `pip3 install -r requirements.txt` 2. Once all of the dependencies are installed, create a Discord bot token following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token). - - **IMPORTANT:** you will need to explicitly enable the Server Members Intent under the Bot tab in https://discord.com/developers/applications/ [as pictured](https://i.imgur.com/mvwTiPE.png) + - **IMPORTANT:** you will need to explicitly enable the Server Members Intent under the Bot tab in the [Discord Application Developer Portal](https://discord.com/developers/applications/) ![as pictured](https://i.imgur.com/mvwTiPE.png) 3. Replace variables in CONFIG.py (I have provided CONFIG.py.example as an example) 3a. Replace `discord_token` and `client_id` in CONFIG.py with your bot token - these two are REQUIRED for all functionality and for the bot to even start From 47d3da056b46ded01578b0fa1659f9ab46bab0cc Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Sun, 22 Nov 2020 17:18:56 -0800 Subject: [PATCH 176/189] #55 fix get_participant_object not working #55 fix get_participant_object not working Bot should work --- cogs/SecretSanta.py | 4 ++-- helpers/SecretSantaHelpers.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index dc09712..92ae0d7 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -186,7 +186,7 @@ async def start(self, ctx: commands.Context): # save to config file print("Partner assignment successful") for user in potential_list: - (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(user.idstr, self.usr_list) + (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(int(user.idstr), self.usr_list) (index, partner) = self.SecretSantaHelper.get_participant_object(user.partnerid, potential_list) temp_user.partnerid = user.partnerid self.config['members'][str(user.usrnum)][SecretSantaConstants.PARTNERID] = user.partnerid @@ -384,7 +384,7 @@ async def partnerinfo(self, ctx: commands.Context): currAuthor = ctx.author authorIsParticipant = self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list) if(self.exchange_started and authorIsParticipant): - (usr_index, user) = self.SecretSantaHelper.get_participant_object(currAuthor, self.usr_list) + (usr_index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) (partner_index, partnerobj) = self.SecretSantaHelper.get_participant_object(user.partnerid, self.usr_list) msg = "Your partner is " + partnerobj.name + "#" + partnerobj.discriminator + "\n" msg = msg + "Their wishlist(s) can be found here: " + partnerobj.wishlisturl + "\n" diff --git a/helpers/SecretSantaHelpers.py b/helpers/SecretSantaHelpers.py index d17a518..dfb6dce 100644 --- a/helpers/SecretSantaHelpers.py +++ b/helpers/SecretSantaHelpers.py @@ -21,12 +21,12 @@ def user_is_participant(self, usrid: discord.User.id, usrlist: list): return True return False - def get_participant_object(self, usrid: str, usrlist: list): + def get_participant_object(self, usrid: int, usrlist: list): """takes a discord user ID string and list of participant objects, and returns the first participant object with matching id.""" for (index, person) in enumerate(usrlist): - if(person.idstr == usrid): + if(int(person.idstr) == usrid): return (index, person) return (-1, None) From 898d5870350d91a37b6c2ade8f850befb960796e Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Tue, 24 Nov 2020 16:59:45 -0800 Subject: [PATCH 177/189] Add context (guild, DM) check for commands, unrestrict listparticipants Add context (guild, DM) check for commands Bot should error in a meaningful way when it can't run a command unrestrict listparticipants no point in restricting it --- cogs/SecretSanta.py | 35 +++++++++++++++++++++++------------ helpers/BOT_ERROR.py | 1 + 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 92ae0d7..f333454 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -155,6 +155,7 @@ async def getprefs(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNJOINED) return + @commands.guild_only() @commands.command() async def start(self, ctx: commands.Context): ''' @@ -221,6 +222,7 @@ async def start(self, ctx: commands.Context): await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) return + @commands.guild_only() @commands.command() async def restart(self, ctx: commands.Context): ''' @@ -259,6 +261,7 @@ async def restart(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNREACHABLE) return + @commands.guild_only() @commands.command() async def pause(self, ctx: commands.Context): ''' @@ -325,6 +328,7 @@ async def leave(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNJOINED) return + @commands.guild_only() @commands.command() async def end(self, ctx: commands.Context): ''' @@ -349,18 +353,14 @@ async def listparticipants(self, ctx: commands.Context): ''' List Secret Santa participants ''' - if(ctx.author.top_role == ctx.guild.roles[-1]): - if(self.highest_key == 0): - await ctx.send(f"```Nobody has signed up for the secret Santa exchange yet. Use `{CONFIG.prefix}join` to enter the exchange.```") - else: - msg = '```The following people are signed up for the Secret Santa exchange:\n' - for user in self.usr_list: - this_user = ctx.guild.get_member(user.idstr) - msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" - msg = msg + f"\nUse `{CONFIG.prefix}join` to enter the exchange.```" - await ctx.send(msg) + if(self.highest_key == 0): + await ctx.send(f"```Nobody has signed up for the secret Santa exchange yet. Use `{CONFIG.prefix}join` to enter the exchange.```") else: - await ctx.send(BOT_ERROR.NO_PERMISSION(ctx.guild.roles[-1])) + msg = '```The following people are signed up for the Secret Santa exchange:\n' + for user in self.usr_list: + msg = msg + str(user.name) + "#" + str(user.discriminator) + "\n" + msg = msg + f"\nUse `{CONFIG.prefix}join` to enter the exchange.```" + await ctx.send(msg) return @commands.command(aliases=["tp"]) @@ -404,4 +404,15 @@ async def partnerinfo(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNJOINED) else: await ctx.send(BOT_ERROR.UNREACHABLE) - return \ No newline at end of file + return + + @start.error + @restart.error + @pause.error + @end.error + async def dm_error(self, ctx: commands.Context, error): + if(isinstance(error, commands.NoPrivateMessage)): + await ctx.send(BOT_ERROR.DM_ERROR) + else: + await ctx.send(BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) + return diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 683a978..3dd52bf 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,5 +1,6 @@ ALREADY_JOINED = "ERROR: You have already joined." COUNTDOWN_NAME_TAKEN = "ERROR: That countdown name is already in use." +DM_ERROR = "ERROR: this command must be run in a server context (not via DMs)." DM_FAILED = "ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages." EXCHANGE_IN_PROGRESS = "ERROR: The gift exchange is already in progress." EXCHANGE_STARTED_UNJOINED = "ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join." From 152fc1e4c1d6e56404fa9fa1673b5c0d53b8ca4c Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Tue, 24 Nov 2020 20:45:30 -0800 Subject: [PATCH 178/189] #56 add partner/secret santa DMs Add partner/secret santa DMs feature request --- cogs/SecretSanta.py | 70 +++++++++++++++++++++++++++++++---- helpers/SecretSantaHelpers.py | 17 ++++++--- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index f333454..49da62b 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -38,10 +38,8 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): ''' currAuthor = ctx.author if self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): - if(self.SecretSantaHelper.channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() + if(self.SecretSantaHelper.channel_is_guild(ctx.channel)): + await ctx.message.delete() # delete the message before people can see (index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) new_wishlist = "None" if(len(destination) == 0): @@ -100,10 +98,8 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): ''' currAuthor = ctx.author if self.SecretSantaHelper.user_is_participant(currAuthor.id, self.usr_list): - if(self.SecretSantaHelper.channelIsPrivate(ctx.channel)): - pass - else: - await ctx.message.delete() + if(self.SecretSantaHelper.channel_is_guild(ctx.channel)): + await ctx.message.delete() # delete the message before people can see (index, user) = self.SecretSantaHelper.get_participant_object(currAuthor.id, self.usr_list) new_prefs = "None" if(len(preferences) == 0): @@ -406,6 +402,64 @@ async def partnerinfo(self, ctx: commands.Context): await ctx.send(BOT_ERROR.UNREACHABLE) return + @commands.command() + async def dmpartner(self, ctx: commands.Context, *, message:str): + ''' + DM your Secret Santa partner anonymously (the person you have) via the bot + ''' + if(self.SecretSantaHelper.channel_is_guild(ctx.channel)): + await ctx.message.delete() # delete the message before people can see + (curr_idx, curr_user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list) + if((curr_idx != -1) and self.exchange_started): # user has joined and the exchange has started + partner = self.bot.get_user(int(curr_user.partnerid)) + try: + msg = "You have a message from your secret santa\n" + msg += f"```{message}```" + await partner.send(msg) + print(f"{ctx.author.name}#{ctx.author.discriminator} DMd {partner.name}#{partner.discriminator}: {message}") + return + except Exception as e: + print_exc(e) + await ctx.author.send(f"Failed to send message to {partner.name}#{partner.discriminator} about their partner. Harass them to turn on server DMs for Secret Santa stuff.") + elif(curr_idx == -1): # user has not joined + msg = BOT_ERROR.UNJOINED + await ctx.author.send(msg) + elif(not self.exchange_started): # exchange hasn't started (there are no partners) + msg = BOT_ERROR.NOT_STARTED + await ctx.author.send(msg) + else: + await ctx.send(BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) + return + + @commands.command() + async def dmsanta(self, ctx: commands.Context, *, message:str): + ''' + DM your Secret Santa (the person that has you) via the bot + ''' + if(self.SecretSantaHelper.channel_is_guild(ctx.channel)): + await ctx.message.delete() # delete the message before people can see + (santa_idx, santa_user) = self.SecretSantaHelper.get_participant_object(ctx.author.id, self.usr_list, id_is_partner=True) + if((santa_idx != -1) and self.exchange_started): # user has joined and the exchange has started + santa = self.bot.get_user(int(santa_user.idstr)) + try: + msg = "You have a message from your secret santa partner\n" + msg += f"```{message}```" + await santa.send(msg) + print(f"{ctx.author.name}#{ctx.author.discriminator} DMd {santa.name}#{santa.discriminator}: {message}") + return + except Exception as e: + print_exc(e) + await ctx.author.send(f"Failed to send the message to your secret santa. Their DMs may be off. Please ask your Secret Santa admin.") + elif(santa_idx == -1): # user has not joined + msg = BOT_ERROR.UNJOINED + await ctx.author.send(msg) + elif(not self.exchange_started): # exchange hasn't started (there are no partners) + msg = BOT_ERROR.NOT_STARTED + await ctx.author.send(msg) + else: + await ctx.send(BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) + return + @start.error @restart.error @pause.error diff --git a/helpers/SecretSantaHelpers.py b/helpers/SecretSantaHelpers.py index dfb6dce..ff528f5 100644 --- a/helpers/SecretSantaHelpers.py +++ b/helpers/SecretSantaHelpers.py @@ -1,7 +1,7 @@ import copy import random from discord import Member -from discord.abc import PrivateChannel +from discord.abc import GuildChannel, PrivateChannel import discord from helpers.SecretSantaParticipant import SecretSantaParticipant @@ -15,19 +15,23 @@ def isListOfParticipants(self, usrlist: list): def user_is_participant(self, usrid: discord.User.id, usrlist: list): """Takes a discord user ID string and returns whether a user with that ID is in usr_list""" - print(usrlist) for person in usrlist: if int(person.idstr) == usrid: return True return False - def get_participant_object(self, usrid: int, usrlist: list): + def get_participant_object(self, usrid: int, usrlist: list, id_is_partner=False): """takes a discord user ID string and list of participant objects, and returns the first participant object with matching id.""" for (index, person) in enumerate(usrlist): - if(int(person.idstr) == usrid): - return (index, person) + if(id_is_partner): + if(person.partnerid != ""): + if(int(person.partnerid) == usrid): + return (index, person) + else: + if(int(person.idstr) == usrid): + return (index, person) return (-1, None) def propose_partner_list(self, usrlist: list): @@ -76,6 +80,9 @@ def usr_list_changed_during_pause(self, usrlist: list, usr_left: bool): def channelIsPrivate(self, channel): return isinstance(channel, PrivateChannel) + def channel_is_guild(self, channel): + return isinstance(channel, GuildChannel) + def member_is_mod(self, person: discord.Member, mod_list: list): """Checks that a given member is in the mod list @param person the Member in question""" From 5838446feb4357aed94c8d11b4d1d3ca85affd4a Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 25 Nov 2020 14:18:44 -0800 Subject: [PATCH 179/189] Magic bug fix I guess Magic bug fix I guess ??? --- cogs/SecretSanta.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 49da62b..763b1ac 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -183,8 +183,8 @@ async def start(self, ctx: commands.Context): # save to config file print("Partner assignment successful") for user in potential_list: - (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(int(user.idstr), self.usr_list) - (index, partner) = self.SecretSantaHelper.get_participant_object(user.partnerid, potential_list) + (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(int(user.idstr), self.usr_list) # get the current user + (index, partner) = self.SecretSantaHelper.get_participant_object(int(user.partnerid), potential_list) # get their partner temp_user.partnerid = user.partnerid self.config['members'][str(user.usrnum)][SecretSantaConstants.PARTNERID] = user.partnerid self.config.write() @@ -468,5 +468,6 @@ async def dm_error(self, ctx: commands.Context, error): if(isinstance(error, commands.NoPrivateMessage)): await ctx.send(BOT_ERROR.DM_ERROR) else: + print_exc(error) await ctx.send(BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) return From 21e30c221cd26e2781ab34ae1d3636ba6b3ac38a Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 25 Nov 2020 16:21:09 -0800 Subject: [PATCH 180/189] s!start bug patch 1 s!start bug patch 1 none --- cogs/SecretSanta.py | 13 +++++++------ helpers/BOT_ERROR.py | 2 +- helpers/SecretSantaHelpers.py | 8 +++++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 763b1ac..c31454d 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -1,7 +1,7 @@ import CONFIG from configobj import ConfigObj import copy -from traceback import print_exc +from traceback import print_exc, TracebackException from discord.ext import commands @@ -183,8 +183,8 @@ async def start(self, ctx: commands.Context): # save to config file print("Partner assignment successful") for user in potential_list: - (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(int(user.idstr), self.usr_list) # get the current user - (index, partner) = self.SecretSantaHelper.get_participant_object(int(user.partnerid), potential_list) # get their partner + (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(int(user.idstr), self.usr_list) # get the user's object in usr_list + (index, partner) = self.SecretSantaHelper.get_participant_object(int(user.partnerid), self.usr_list) # get their partner temp_user.partnerid = user.partnerid self.config['members'][str(user.usrnum)][SecretSantaConstants.PARTNERID] = user.partnerid self.config.write() @@ -193,13 +193,13 @@ async def start(self, ctx: commands.Context): message_pt1 = f"{str(partner.name)}#{str(partner.discriminator)} is your Secret Santa partner! Mosey on over to their wishlist URL(s) and pick out a gift! Remember to keep it in the ${CONFIG.min_budget}-{CONFIG.max_budget} range.\n" message_pt2 = f"Their wishlist(s) can be found here: {partner.wishlisturl}\n" message_pt3 = f"And their gift preferences can be found here: {partner.preferences}\n" - message_pt4 = "If you have trouble accessing your partner's wishlist, please contact an admin to get in touch with your partner. This is a *secret* santa, after all!" + message_pt4 = f"If you have trouble accessing your partner's wishlist, try `{CONFIG.prefix}dmpartner` or contact an admin to get in touch with them. This is a *secret* santa, after all!" santa_message = message_pt1 + message_pt2 + message_pt3 + message_pt4 try: await this_user.send(santa_message) except Exception as e: print_exc(e) - await currAuthor.send(f"Failed to send message to {this_user.name}#{this_user.discriminator} about their partner. Harass them to turn on server DMs for Secret Santa stuff.") + await currAuthor.send(f"Failed to send message to {this_user.name}#{this_user.discriminator} about their partner. Ask them to turn on server DMs for Secret Santa stuff.") # mark the exchange as in-progress self.exchange_started = True @@ -468,6 +468,7 @@ async def dm_error(self, ctx: commands.Context, error): if(isinstance(error, commands.NoPrivateMessage)): await ctx.send(BOT_ERROR.DM_ERROR) else: - print_exc(error) + tb = TracebackException.from_exception(error) + await ctx.send(tb) await ctx.send(BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) return diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 3dd52bf..a5ec43b 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,6 +1,6 @@ ALREADY_JOINED = "ERROR: You have already joined." COUNTDOWN_NAME_TAKEN = "ERROR: That countdown name is already in use." -DM_ERROR = "ERROR: this command must be run in a server context (not via DMs)." +DM_ERROR = "ERROR: This command cannot be used in private messages." DM_FAILED = "ERROR: DM with information failed to send. Please turn on server DMs to receive important Secret Santa-related messages." EXCHANGE_IN_PROGRESS = "ERROR: The gift exchange is already in progress." EXCHANGE_STARTED_UNJOINED = "ERROR: The exchange is already in progress. Please contact an admin about pausing the exchange before using s!join." diff --git a/helpers/SecretSantaHelpers.py b/helpers/SecretSantaHelpers.py index ff528f5..3292a2e 100644 --- a/helpers/SecretSantaHelpers.py +++ b/helpers/SecretSantaHelpers.py @@ -23,7 +23,13 @@ def user_is_participant(self, usrid: discord.User.id, usrlist: list): def get_participant_object(self, usrid: int, usrlist: list, id_is_partner=False): """takes a discord user ID string and list of participant objects, and returns the first - participant object with matching id.""" + participant object with matching id. + + Parameters: + usrid (int): the ID of the user you're looking for + usrlist (list): the list the user resides in + id_is_partner (bool): if True, this function will find the SecretSantaParticipant in usrlist with the usrid as its partner + """ for (index, person) in enumerate(usrlist): if(id_is_partner): if(person.partnerid != ""): From 554a269725f510c6d8586207e1a9c31503c9b1af Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 25 Nov 2020 18:00:41 -0800 Subject: [PATCH 181/189] Closes #58 add logging along with helper functions in BOT_ERROR.py to print to console and log Add logging along with helper functions in BOT_ERROR.py to print to console and log It was desperately needed --- cogs/SantaAdministrative.py | 5 ++- cogs/SantaMiscellaneous.py | 7 ++++ cogs/SantaUtilities.py | 6 +++- cogs/SecretSanta.py | 64 +++++++++++++++++++-------------- helpers/BOT_ERROR.py | 16 +++++++++ helpers/SQLiteHelper.py | 17 +++++---- helpers/SantaCountdownHelper.py | 8 +++-- santa-bot.py | 19 +++++++--- 8 files changed, 100 insertions(+), 42 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 4bceec4..0170ef3 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -1,4 +1,5 @@ import pendulum +import logging import discord from discord.ext import commands @@ -12,6 +13,8 @@ class SantaAdministrative(commands.Cog, name='Administrative'): def __init__(self, bot: commands.Bot): self.bot = bot self.role_channel = bot.get_channel(CONFIG.role_channel) if (CONFIG.role_channel != -1) else None + self.logger = logging.getLogger(name="SantaBot.SantaAdministrative") + self.logger.info("Creating cog") @commands.command() @has_permissions(manage_roles=True, ban_members=True) @@ -109,7 +112,7 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): if(self.role_channel == None): # if no role channel assigned, self.role_channel = self.bot.get_channel(CONFIG.role_channel) if (CONFIG.role_channel != -1) else None # attempt to get role channel if(self.role_channel == None): # if that fails (CONFIG.role_channel == -1 or get_channel fails) - print(BOT_ERROR.REACTION_ROLE_UNASSIGNED) # throw failure message + BOT_ERROR.output_error(BOT_ERROR.REACTION_ROLE_UNASSIGNED, self.logger) return # end command else: pass # reassignment of role_channel worked diff --git a/cogs/SantaMiscellaneous.py b/cogs/SantaMiscellaneous.py index 9972612..4d6f5e8 100644 --- a/cogs/SantaMiscellaneous.py +++ b/cogs/SantaMiscellaneous.py @@ -1,9 +1,15 @@ +import logging + from discord.ext import commands + import CONFIG +import helpers.BOT_ERROR as BOT_ERROR class SantaMiscellaneous(commands.Cog, name='Miscellaneous'): def __init__(self, bot: commands.Bot): self.bot = bot + self.logger = logging.getLogger(name="SantaBot.SantaMiscellaneous") + self.logger.info("Creating cog") # @commands.command() ### normally commented out because this bot is not meant to be sharded async def invite(self, ctx: commands.Context): @@ -18,6 +24,7 @@ async def echo(self, ctx: commands.Context, *, content:str): ''' [content] = echos back the [content] ''' + BOT_ERROR.output_info(content, self.logger) await ctx.send(content) @commands.command() diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 5032cc4..6da9c99 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -1,8 +1,10 @@ import urllib3 +import logging from discord.ext import commands import CONFIG +import helpers.BOT_ERROR as BOT_ERROR from helpers.SantaCountdownHelper import SantaCountdownHelper from helpers.SQLiteHelper import SQLiteHelper @@ -10,6 +12,8 @@ class SantaUtilities(commands.Cog, name='Utilities'): def __init__(self, bot: commands.Bot, sqlitehelper: SQLiteHelper): self.bot = bot self.cd_helper = SantaCountdownHelper(sqlitehelper) + self.logger = logging.getLogger(name="SantaBot.SantaUtilities") + self.logger.info("Creating cog") @commands.command(aliases=['cd']) async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): @@ -56,6 +60,6 @@ async def emote(self, ctx: commands.Context, *emotes: str): # output emote_url = f"{base_url}.{img_ext}" - print(f"Name={emote_name}, ID={emote_id}, IMG_TYPE={img_type}") + BOT_ERROR.output_info(f"Name={emote_name}, ID={emote_id}, IMG_TYPE={img_type}, url={emote_url}") await ctx.send(content=emote_url) return diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index c31454d..7a999f5 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -1,10 +1,11 @@ -import CONFIG +from traceback import TracebackException from configobj import ConfigObj import copy -from traceback import print_exc, TracebackException +import logging from discord.ext import commands +import CONFIG import helpers.BOT_ERROR as BOT_ERROR from helpers.SecretSantaConstants import SecretSantaConstants from helpers.SecretSantaHelpers import SecretSantaHelpers @@ -22,8 +23,11 @@ def __init__(self, bot: commands.Bot, config: ConfigObj): self.is_paused = False self.SecretSantaHelper = SecretSantaHelpers() self.config = config + self.logger = logging.getLogger(name="SantaBot.SecretSanta") + self.logger.info("Creating cog") # retrieve data from config file + self.logger.info(f"Reading and loading Secret Santa participants from {CONFIG.cfg_name}") self.exchange_started = self.config['programData'].as_bool('exchange_started') for key in self.config['members']: data = self.config['members'][str(key)] @@ -61,15 +65,17 @@ async def setwishlisturl(self, ctx: commands.Context, *destination:str): signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}setwishlisturl` or `{CONFIG.prefix}setprefs` any time before the admin begins the Secret Santa." await currAuthor.send(signup_complete_msg) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) try: await currAuthor.send(BOT_ERROR.INVALID_INPUT) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) else: await ctx.send(BOT_ERROR.UNJOINED) return @@ -85,8 +91,9 @@ async def getwishlisturl(self, ctx: commands.Context): try: await currAuthor.send(f"Current wishlist destination(s): {user.wishlisturl}") except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) else: await ctx.send(BOT_ERROR.UNJOINED) return @@ -121,15 +128,17 @@ async def setprefs(self, ctx: commands.Context, *preferences:str): signup_complete_msg = f"Congrats, you're now officially enrolled in the Secret Santa! You may change your wishlist URL or preferences with `{CONFIG.prefix}setwishlisturl` or `{CONFIG.prefix}setprefs` any time before the admin begins the Secret Santa." await currAuthor.send(signup_complete_msg) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) try: await currAuthor.send(BOT_ERROR.INVALID_INPUT) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) else: await ctx.send(BOT_ERROR.UNJOINED) return @@ -145,8 +154,9 @@ async def getprefs(self, ctx: commands.Context): try: await currAuthor.send(f"Current preference(s): {user.preferences}") except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) else: await ctx.send(BOT_ERROR.UNJOINED) return @@ -170,18 +180,19 @@ async def start(self, ctx: commands.Context): await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await ctx.send("`Partner assignment cancelled: participant info incomplete.`") except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) # select a random partner for each participant if all information is complete and there are enough people to do it if(all_fields_complete and (len(self.usr_list) > 1)): - print("Proposing a partner list") + BOT_ERROR.output_info("Proposing a partner list", self.logger) potential_list = self.SecretSantaHelper.propose_partner_list(self.usr_list) while(not self.SecretSantaHelper.partners_are_valid(potential_list)): - print("Proposing a partner list") + BOT_ERROR.output_info("Proposing a partner list", self.logger) potential_list = self.SecretSantaHelper.propose_partner_list(self.usr_list) # save to config file - print("Partner assignment successful") + BOT_ERROR.output_info("Partner assignment successful", self.logger) for user in potential_list: (temp_index, temp_user) = self.SecretSantaHelper.get_participant_object(int(user.idstr), self.usr_list) # get the user's object in usr_list (index, partner) = self.SecretSantaHelper.get_participant_object(int(user.partnerid), self.usr_list) # get their partner @@ -198,7 +209,7 @@ async def start(self, ctx: commands.Context): try: await this_user.send(santa_message) except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await currAuthor.send(f"Failed to send message to {this_user.name}#{this_user.discriminator} about their partner. Ask them to turn on server DMs for Secret Santa stuff.") # mark the exchange as in-progress @@ -238,8 +249,9 @@ async def restart(self, ctx: commands.Context): await currAuthor.send(BOT_ERROR.HAS_NOT_SUBMITTED(user.name)) await ctx.send("`Partner assignment cancelled: participant info incomplete.`") except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) list_changed = self.SecretSantaHelper.usr_list_changed_during_pause(self.usr_list, self.user_left_during_pause) if(list_changed): ctx.send(f"User list changed during the pause. Partners must be picked again with `{CONFIG.prefix}start`.") @@ -298,8 +310,9 @@ async def join(self, ctx: commands.Context): userPrompt = f".\nFirst we need your wishlist (or the destination for sending gifts). Please use `{CONFIG.prefix}setwishlisturl [wishlist urls separated by a space]` (e.g. `{CONFIG.prefix}setwishlisturl amazonurl/123 \"sesame street\"`) to set your wishlist URL (you may also add your mailing address)." await currAuthor.send(userPrompt) except Exception as e: - print_exc(e) - ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_exception(e, self.logger) + await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) return @commands.command() @@ -336,7 +349,6 @@ async def end(self, ctx: commands.Context): self.config['programData']['exchange_started'] = False self.highest_key = 0 del self.usr_list[:] - print(len(self.usr_list)) self.config['members'].clear() self.config.write() await ctx.send("Secret Santa ended") @@ -390,8 +402,9 @@ async def partnerinfo(self, ctx: commands.Context): await currAuthor.send(msg) await ctx.send("The information has been sent to your DMs.") except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.send(currAuthor.mention + BOT_ERROR.DM_FAILED) + BOT_ERROR.output_error(currAuthor.mention + BOT_ERROR.DM_FAILED, self.logger) elif((not self.exchange_started) and authorIsParticipant): await ctx.send(BOT_ERROR.NOT_STARTED) elif(self.exchange_started and (not authorIsParticipant)): @@ -416,10 +429,10 @@ async def dmpartner(self, ctx: commands.Context, *, message:str): msg = "You have a message from your secret santa\n" msg += f"```{message}```" await partner.send(msg) - print(f"{ctx.author.name}#{ctx.author.discriminator} DMd {partner.name}#{partner.discriminator}: {message}") + BOT_ERROR.output_info(f"{ctx.author.name}#{ctx.author.discriminator} DMd {partner.name}#{partner.discriminator}: {message}", self.logger) return except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.author.send(f"Failed to send message to {partner.name}#{partner.discriminator} about their partner. Harass them to turn on server DMs for Secret Santa stuff.") elif(curr_idx == -1): # user has not joined msg = BOT_ERROR.UNJOINED @@ -445,10 +458,10 @@ async def dmsanta(self, ctx: commands.Context, *, message:str): msg = "You have a message from your secret santa partner\n" msg += f"```{message}```" await santa.send(msg) - print(f"{ctx.author.name}#{ctx.author.discriminator} DMd {santa.name}#{santa.discriminator}: {message}") + BOT_ERROR.output_info(f"{ctx.author.name}#{ctx.author.discriminator} DMd {santa.name}#{santa.discriminator}: {message}", self.logger) return except Exception as e: - print_exc(e) + BOT_ERROR.output_exception(e, self.logger) await ctx.author.send(f"Failed to send the message to your secret santa. Their DMs may be off. Please ask your Secret Santa admin.") elif(santa_idx == -1): # user has not joined msg = BOT_ERROR.UNJOINED @@ -468,7 +481,6 @@ async def dm_error(self, ctx: commands.Context, error): if(isinstance(error, commands.NoPrivateMessage)): await ctx.send(BOT_ERROR.DM_ERROR) else: - tb = TracebackException.from_exception(error) - await ctx.send(tb) + BOT_ERROR.output_exception(error, self.logger) await ctx.send(BOT_ERROR.UNDETERMINED_CONTACT_CODE_OWNER) return diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index a5ec43b..25146c6 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -1,3 +1,6 @@ +from logging import Logger +from traceback import TracebackException + ALREADY_JOINED = "ERROR: You have already joined." COUNTDOWN_NAME_TAKEN = "ERROR: That countdown name is already in use." DM_ERROR = "ERROR: This command cannot be used in private messages." @@ -30,3 +33,16 @@ def CANNOT_CHANGE_COUNTDOWN(author_name): return f"ERROR: you do not have permission to change that countdown timer. Please contact {author_name}" def NO_PERMISSION(role): return f"ERROR: you do not have permissions to do this.\nYou need the {role} role for that." + +def output_error(message: str, logger: Logger): + print(message) + logger.debug(message) + +def output_info(message: str, logger: Logger): + print(message) + logger.info(message) + +def output_exception(error, logger: Logger): + tb = TracebackException.from_exception(error) + exception_message = ''.join(tb.stack.format()) + output_error(exception_message, logger) diff --git a/helpers/SQLiteHelper.py b/helpers/SQLiteHelper.py index dd953a9..94e0161 100644 --- a/helpers/SQLiteHelper.py +++ b/helpers/SQLiteHelper.py @@ -1,3 +1,4 @@ +import logging import sqlite3 from sqlite3 import Error @@ -5,18 +6,20 @@ class SQLiteHelper(): def __init__(self, connection_path: str): self.__connection_path = connection_path self.__connection = None + self.logger = logging.getLogger('SQLiteHelper') + self.logger.info("Creating SQLiteHelper") def create_connection(self): try: if(self.__connection == None): self.__connection = sqlite3.connect(self.__connection_path) - print("Connection to SQLite DB successful") + self.logger.info("Connection to SQLite DB successful", self.logger) except Error as e: - print(f"The error '{e}' occurred") + self.logger.debug(f"The error '{e}' occurred", self.logger) def if_table_exists(self, table_name): if(self.__connection == None): - print("Connection has not been created") + self.logger.debug("Connection has not been created", self.logger) return False cursor = self.__connection.cursor() @@ -28,17 +31,17 @@ def if_table_exists(self, table_name): def execute_query(self, query): if(self.__connection == None): - print("Connection has not been created") + self.logger.debug("Connection has not been created", self.logger) return False cursor = self.__connection.cursor() try: cursor.execute(query) self.__connection.commit() - print("Query executed successfully") + self.logger.info("Query executed successfully", self.logger) return True except Error as e: - print(f"The error '{e.with_traceback}' occurred") + self.logger.debug(f"The error '{e.with_traceback}' occurred", self.logger) return False def create_table(self, table_name: str, table_format: str): @@ -115,7 +118,7 @@ def execute_read_query(self, query): result = cursor.fetchall() return result except Error as e: - print(f"Problem reading - the error '{e}' occurred") + self.logger.debug(f"Problem reading - the error '{e}' occurred", self.logger) def execute_delete_query(self, table_name: str, conditions: str): delete_query = f"DELETE FROM {table_name} WHERE {conditions}" diff --git a/helpers/SantaCountdownHelper.py b/helpers/SantaCountdownHelper.py index 82fec43..1a588a9 100644 --- a/helpers/SantaCountdownHelper.py +++ b/helpers/SantaCountdownHelper.py @@ -1,4 +1,6 @@ +import logging import pendulum + from discord.ext import commands import helpers.BOT_ERROR as BOT_ERROR @@ -10,6 +12,8 @@ def __init__(self, sqlitehelper: SQLiteHelper): self.pend_format = "M/D/YY [@] h:m A Z" self.cd_table_name = "Countdowns" self.sqlhelp = sqlitehelper + self.logger = logging.getLogger('SantaBot.SantaCountdownHelper') + self.logger.info("Creating SantaCountdownHelper") def __get_cd_table_name(self, guild_id: str): return f"{self.cd_table_name}_{guild_id}" @@ -26,7 +30,7 @@ def __countdown_cmd_set(self, ctx: commands.Context, cd_name: str, cd_time: str) except ValueError as error: expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" result_str = expected + "ex. `5/17/20 @ 1:00 PM -06:00`" - print(result_str) + BOT_ERROR.output_debug(result_str, self.logger) finally: return result_str @@ -37,7 +41,7 @@ def __countdown_cmd_change(self, ctx: commands.Context, cd_name: str, cd_time: s except ValueError as error: expected = "ERROR: inputted time does not match expected format `month/day/year @ hour:minute AM/PM UTC_offset`\n" result_str = expected + " ex. `5/17/20 @ 1:00 PM -06:00`" - print(result_str) + BOT_ERROR.output_debug(result_str, self.logger) return result_str query_get_timer_by_name = f"SELECT * FROM {self.__get_cd_table_name(ctx.guild.id)} WHERE name=\'{cd_name}\';" diff --git a/santa-bot.py b/santa-bot.py index 119458e..40c3a0c 100644 --- a/santa-bot.py +++ b/santa-bot.py @@ -1,5 +1,6 @@ import logging import os +import pathlib from configobj import ConfigObj from discord.ext import commands @@ -34,11 +35,19 @@ def start_santa_bot(): sqlitehelper.create_connection() #set up discord connection debug logging - client_log = logging.getLogger('discord') - client_log.setLevel(logging.DEBUG) - client_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') - client_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) - client_log.addHandler(client_handler) + discord_api_logger = logging.getLogger('discord') + discord_api_logger.setLevel(logging.DEBUG) + discord_api_handler = logging.FileHandler(filename=os.path.join(str(pathlib.Path(__file__).parent), CONFIG.bot_folder, "discord_api.log"), encoding='utf-8', mode='w') + discord_api_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) + discord_api_logger.addHandler(discord_api_handler) + + # set up SantaBot debug logging + santabot_logger = logging.getLogger('SantaBot') + santabot_logger.setLevel(logging.DEBUG) + santabot_log_handler = logging.FileHandler(filename=CONFIG.dbg_path, encoding='utf-8', mode='w') + santabot_log_handler.setLevel(logging.DEBUG) + santabot_log_handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) + santabot_logger.addHandler(santabot_log_handler) # add the cogs intents = Intents.default() From 1e09303d0116d78f191d1ebba10712e3c2b5fdd4 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 25 Nov 2020 18:02:46 -0800 Subject: [PATCH 182/189] #52 only updated CONFIG.py.example and the README but not the actual CONFIG.py #52 only updated CONFIG.py.example and the README but not the actual CONFIG.py This caused a s!start error --- CONFIG.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONFIG.py b/CONFIG.py index 0caa100..6fc2cf1 100644 --- a/CONFIG.py +++ b/CONFIG.py @@ -9,6 +9,8 @@ sqlite_name = "santabotdb.sqlite" dbg_name = "debug.log" role_channel = -1 +min_budget = 10 +max_budget = 20 ############################################### From 873b8375fa834906758b02c6a988ada11367754e Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Wed, 25 Nov 2020 18:07:07 -0800 Subject: [PATCH 183/189] #58 forgot one logger parameter --- cogs/SantaUtilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 6da9c99..976a5de 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -60,6 +60,6 @@ async def emote(self, ctx: commands.Context, *emotes: str): # output emote_url = f"{base_url}.{img_ext}" - BOT_ERROR.output_info(f"Name={emote_name}, ID={emote_id}, IMG_TYPE={img_type}, url={emote_url}") + BOT_ERROR.output_info(f"Name={emote_name}, ID={emote_id}, IMG_TYPE={img_type}, url={emote_url}", self.logger) await ctx.send(content=emote_url) return From e7cf6c6eac720ee151043c662e73f38ef5e4a58a Mon Sep 17 00:00:00 2001 From: irnutsmurt <73799093+irnutsmurt@users.noreply.github.com> Date: Tue, 1 Dec 2020 12:21:27 -0800 Subject: [PATCH 184/189] Added missing details for systemd Realized that there was missing information where you needed to update the run_santa.sh script to include the absolute path for the santa-bot.py script. --- README.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 48cde99..0591a40 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,24 @@ A Discord bot to organize secret santa gift exchanges using the discord.py Pytho ### Add a Systemd Service for SantaBot to Autostart on Reboot To keep SantaBot running even after restart you can create a service. -1. Copy the 'run_santa.sh' to '/usr/local/bin/' directory +1. Copy the `run_santa.sh` to `/usr/local/bin/` directory - sudo cp run_santa.sh /usr/local/bin/ -2. Create a service file or edit the included 'santabot.service' using your favorite text editor and add the default user name to 'User=' and the Group they belong to in 'Group='. To find out what default group a user belongs use 'id -gn usernamehere' +2. Edit `run_santa.sh` in `/usr/local/bin` with your favorite editor to include the absolute path to the santa-bot.py script. For example `/home/pi/Santabot/santa-bot.py`. If you're not sure the absolute, in the Santabot directory type `pwd` + +- pwd + /home/pi/Santabot +- sudo nano /usr/local/bin/run_santa.sh + + - while true + - do + - python3 /home/pi/Santabot/santa-bot.py + - sleep 10 + - done + + +3. Create a service file or edit the included `santabot.service` using your favorite text editor and add the default user name to `User=` and the Group they belong to in `Group=`. To find out what default group a user belongs use `id -gn usernamehere` - nano santabot.service @@ -51,23 +64,23 @@ To keep SantaBot running even after restart you can create a service. - [Install] - WantedBy=multi-user.target -3. Copy the santabot.service to '/lib/systemd/system/' +4. Copy the santabot.service to `/lib/systemd/system/` - sudo cp santabot.service /lib/systemd/system/ -4. Enable the service so it will autostart on reboot, then start it. +5. Enable the service so it will autostart on reboot, then start with the commands: - sudo systemctl enable santabot.service - sudo systemctl start santabot.service -5. Then check the status +6. Then check the status - sudo systemctl status santabot.service -6. When done using the bot for the season, disable using +7. When done using the bot for the season, to disable use both commands: - sudo systemctl disable santabot.service - +- sudo systemctl stop santabot.service #### Secret Santa Commands: - `s!join` = join the Secret Santa From c950ad3c90859e093266a5525e41dfe7f16e06ef Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Tue, 1 Dec 2020 12:57:47 -0800 Subject: [PATCH 185/189] #56 add confirmation message for SS DMs #56 add confirmation message for SS DMs Lets users track message history between SS and partner --- cogs/SecretSanta.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cogs/SecretSanta.py b/cogs/SecretSanta.py index 7a999f5..52e9c65 100644 --- a/cogs/SecretSanta.py +++ b/cogs/SecretSanta.py @@ -430,6 +430,9 @@ async def dmpartner(self, ctx: commands.Context, *, message:str): msg += f"```{message}```" await partner.send(msg) BOT_ERROR.output_info(f"{ctx.author.name}#{ctx.author.discriminator} DMd {partner.name}#{partner.discriminator}: {message}", self.logger) + author_msg = "Message sent to your Secret Santa partner\n" + author_msg += f"```{message}```" + await ctx.author.send(author_msg) return except Exception as e: BOT_ERROR.output_exception(e, self.logger) @@ -459,6 +462,9 @@ async def dmsanta(self, ctx: commands.Context, *, message:str): msg += f"```{message}```" await santa.send(msg) BOT_ERROR.output_info(f"{ctx.author.name}#{ctx.author.discriminator} DMd {santa.name}#{santa.discriminator}: {message}", self.logger) + author_msg = "Message sent to your Secret Santa\n" + author_msg += f"```{message}```" + await ctx.author.send(author_msg) return except Exception as e: BOT_ERROR.output_exception(e, self.logger) From a8020aa34a1ee9515b8007754c1047aed1ca6b4b Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 7 Jan 2021 22:56:01 -0800 Subject: [PATCH 186/189] Make emote roles a little more robust Make emote roles a little more robust Robustness good --- cogs/SantaAdministrative.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index 0170ef3..efd1e3c 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -32,7 +32,7 @@ async def assign_role_channel(self, ctx: commands.Context, reaction_role_channel self.role_channel = ctx.channel # use current channel if(self.role_channel != None): - await ctx.send(content=f"Reaction role channel assigned to {reaction_role_channel.name}") + await ctx.send(content=f"Reaction role channel assigned to {self.role_channel.mention}") @commands.command() @has_permissions(manage_roles=True, ban_members=True) @@ -121,7 +121,7 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): message_id = payload.message_id channel_id = payload.channel_id - emoji = payload.emoji + emoji = str(payload.emoji) guild = self.bot.get_guild(payload.guild_id) user = guild.get_member(payload.user_id) @@ -134,12 +134,20 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): if message != None: content = message.content.split("\n") for line in content: - line_tokens = line.split(" ") - line_tokens = [i for i in line_tokens if i] # remove empty strings for quality of life (doesn't break with extra spaces) - for (idx, token) in enumerate(line_tokens): - if token == "for" and idx > 0 and line_tokens[idx-1] == str(emoji): - role_id = line_tokens[idx+1][3:-1] - role = guild.get_role(int(role_id)) + line_tokens = [i for i in line.split(" ") if i] # remove empty strings for quality of life (doesn't break with extra spaces) + test_emote_idx = for_idx = test_role_id_idx = -1 + try: + for_idx = line_tokens.index("for") + test_emote_idx, test_role_id_idx = for_idx - 1, for_idx + 1 + except: + for_idx = -1 + if (for_idx != -1): # no 'for' or 'for' is in the wrong place + test_emote = line_tokens[test_emote_idx] + test_role_id = int(line_tokens[test_role_id_idx][3:-1]) + BOT_ERROR.output_info(f"Test emote={test_emote.encode('raw_unicode_escape')}, Input emote={emoji.encode('raw_unicode_escape')}", self.logger) + if test_emote == emoji: + role = guild.get_role(test_role_id) + BOT_ERROR.output_info(f"Role added/removed=@{role.name}, user={user.name}", self.logger) if payload.event_type == "REACTION_ADD": await user.add_roles(role) else: From 4d7fc41c27d6d10bd96ff7ef7f81235e47af03a0 Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Thu, 7 Jan 2021 23:32:34 -0800 Subject: [PATCH 187/189] Use discord.PartialEmoji instead of urllib3 for emote urls Use discord.PartialEmoji instead of urllib3 for emote urls No need to access the cdn manually --- cogs/SantaUtilities.py | 24 +++++++----------------- helpers/BOT_ERROR.py | 4 ++-- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/cogs/SantaUtilities.py b/cogs/SantaUtilities.py index 976a5de..91c675e 100644 --- a/cogs/SantaUtilities.py +++ b/cogs/SantaUtilities.py @@ -1,7 +1,7 @@ -import urllib3 import logging from discord.ext import commands +import discord import CONFIG import helpers.BOT_ERROR as BOT_ERROR @@ -41,25 +41,15 @@ async def countdown(self, ctx: commands.Context, command: str="", *, arg=""): return @commands.command() - async def emote(self, ctx: commands.Context, *emotes: str): + async def emote(self, ctx: commands.Context, *emotes: discord.PartialEmoji): ''' - Get the URL to emotes without needing to open the link. + Get the URL to emotes without needing to open the link. Custom emotes only. ''' - http = urllib3.PoolManager() for passed_emote in emotes: - # Create the base URL of the emote - emote_parts = passed_emote.split(sep=":") - (emote_name, emote_id) = (emote_parts[1], (emote_parts[2])[:-1]) - base_url = f"https://cdn.discordapp.com/emojis/{str(emote_id)}" - - # http request to find the appropriate extension - response = http.urlopen('GET', url=base_url) - img_type = response.info()['Content-Type'] - img_ext = img_type.split(sep="/")[1] - http.clear() - - # output - emote_url = f"{base_url}.{img_ext}" + emote_url = str(passed_emote.url) + emote_name = passed_emote.name + emote_id = passed_emote.id + img_type = "gif" if passed_emote.animated else "png" BOT_ERROR.output_info(f"Name={emote_name}, ID={emote_id}, IMG_TYPE={img_type}, url={emote_url}", self.logger) await ctx.send(content=emote_url) return diff --git a/helpers/BOT_ERROR.py b/helpers/BOT_ERROR.py index 25146c6..0ddee39 100644 --- a/helpers/BOT_ERROR.py +++ b/helpers/BOT_ERROR.py @@ -10,11 +10,11 @@ INACCESSIBLE_CHANNEL = "ERROR: the channel specified is not accessible to this bot." INVALID_INPUT = "ERROR: invalid input." MISSING_ARGUMENTS = "ERROR: command usage is missing arguments." -NOT_ENOUGH_SIGNUPS = "ERROR: Secret santa not started. Need more people." +NOT_ENOUGH_SIGNUPS = "ERROR: Secret Santa not started. Need more people." NOT_PAUSED = "ERROR: Secret Santa is not paused" NOT_STARTED = "ERROR: partners have not been assigned yet." REACTION_ROLE_UNASSIGNED = "ERROR: no reaction role channel has been assigned." -SIGNUPS_INCOMPLETE = "ERROR: Signups incomplete. Time for some love through harassment." +SIGNUPS_INCOMPLETE = "ERROR: Signups incomplete. There may be an incomplete wishlisturl." UNDETERMINED_CONTACT_CODE_OWNER = "ERROR: undetermined please contact <@224949031514800128> for more information." # @mentions me, the maintainer of SantaBot UNJOINED = "ERROR: you have not yet joined the Secret Santa exchange. Use s!join to join the exchange." UNREACHABLE = "ERROR: this shouldn't happen." From d919058f55fc07096aabd64f6b80ea5c49c2c99c Mon Sep 17 00:00:00 2001 From: avchikar97 Date: Mon, 18 Jan 2021 22:40:43 -0800 Subject: [PATCH 188/189] Don't check crossed-out lines, use PartialEmoji Don't check crossed-out lines, use PartialEmoji Allows user to temporarily block a role, helps with unicode emoji handling --- cogs/SantaAdministrative.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cogs/SantaAdministrative.py b/cogs/SantaAdministrative.py index efd1e3c..8af05cd 100644 --- a/cogs/SantaAdministrative.py +++ b/cogs/SantaAdministrative.py @@ -121,7 +121,8 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): message_id = payload.message_id channel_id = payload.channel_id - emoji = str(payload.emoji) + emoji = payload.emoji + emoji_str = str(payload.emoji) guild = self.bot.get_guild(payload.guild_id) user = guild.get_member(payload.user_id) @@ -134,6 +135,8 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): if message != None: content = message.content.split("\n") for line in content: + if((line[0:2] == "~~") and (line[-1] == "~")): # don't pay attention to lines that are crossed out + continue line_tokens = [i for i in line.split(" ") if i] # remove empty strings for quality of life (doesn't break with extra spaces) test_emote_idx = for_idx = test_role_id_idx = -1 try: @@ -144,8 +147,13 @@ async def manage_reactions(self, payload: discord.RawReactionActionEvent): if (for_idx != -1): # no 'for' or 'for' is in the wrong place test_emote = line_tokens[test_emote_idx] test_role_id = int(line_tokens[test_role_id_idx][3:-1]) - BOT_ERROR.output_info(f"Test emote={test_emote.encode('raw_unicode_escape')}, Input emote={emoji.encode('raw_unicode_escape')}", self.logger) - if test_emote == emoji: + BOT_ERROR.output_info(f"Test emote={test_emote.encode('raw_unicode_escape')}, Input emote={emoji_str.encode('raw_unicode_escape')}", self.logger) + found_emoji = False + if emoji.is_unicode_emoji(): + found_emoji = (emoji_str == test_emote) + else: + found_emoji = (emoji_str[1:-1] in test_emote) + if found_emoji: role = guild.get_role(test_role_id) BOT_ERROR.output_info(f"Role added/removed=@{role.name}, user={user.name}", self.logger) if payload.event_type == "REACTION_ADD": From 5580a2b5df36ec9d2333cc3d5b9ec56106932f57 Mon Sep 17 00:00:00 2001 From: avchikar97 <14263594+avchikar97@users.noreply.github.com> Date: Sun, 21 Jul 2024 01:33:10 -0700 Subject: [PATCH 189/189] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c276fa2..5bf4960 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -discord.py +discord.py==1.6.0 configobj pendulum urllib3