async def takedown(self, context, student_id: int, *post): post = ' '.join(post) if not post: await context.send( 'You must supply the user to stand down and the post you are standing them down from, ' f'usage: `{helpers.PREFIX}takedown <STUDENT NUMBER> <POST>`') return matching_posts = helpers.match_post(post) if not matching_posts: await context.send( 'Looks like that post isn\'t available for this election, ' f'use `{helpers.PREFIX}posts` to see the posts up for election`' ) return post = matching_posts[0] if student_id not in helpers.standing[post]: await context.send( 'Looks like this user isn\'t standing for this post') return helpers.email_secretary(str(helpers.standing[post][student_id][0]), post, stood_down=True) del helpers.standing[post][student_id] helpers.save_standing() helpers.log( f'{student_id} has been stood down from standing for {post}') await context.send( f'{student_id} has been stood down from running for {post}')
async def resetname(self, context, student_id: int): if student_id not in helpers.preferred_names: await context.send( 'The supplied student ID has not updated their name') return async with helpers.current_live_post_lock.reader_lock: if helpers.current_live_post: await context.send( 'I\'m afraid you can\'t reset a name whilst a vote is ongoing, ' f'please wait until the vote has finished, or end it early using `{helpers.PREFIX}end`' ) return del helpers.preferred_names[student_id] union_name = helpers.get_members()[student_id] for post in helpers.standing: if student_id in helpers.standing[post]: helpers.standing[post][student_id] = ( Candidate(union_name), helpers.standing[post][student_id][1], context.author.id) helpers.save_names() helpers.save_standing() helpers.log(f'The name used for {student_id} has been reset') await context.send(f'The name used for {student_id} has been reset')
async def standdown(self, context, *post): post = ' '.join(post) if not post: await context.send( f'Must supply the post you are standing down from, usage: `{helpers.PREFIX}standdown <POST>`' ) return matching_posts = helpers.match_post(post) if not matching_posts: await context.send( 'Looks like that post isn\'t available for this election, ' f'use `{helpers.PREFIX}posts` to see the posts up for election`' ) return post = matching_posts[0] author = context.author.id if helpers.registered_members[author] not in helpers.standing[post]: await context.send('Looks like you weren\'t standing for this post' ) return helpers.email_secretary(str( helpers.standing[post][helpers.registered_members[author]][0]), post, stood_down=True) del helpers.standing[post][helpers.registered_members[author]] helpers.save_standing() helpers.log( f'{helpers.registered_members[author]} has stood down from standing for {post}' ) await context.send(f'You have stood down from running for {post}')
async def rename(self, context, old_post, new_post): matching_posts = helpers.match_post(old_post) if not matching_posts: await context.send(f'{old_post} doesn\'t exist') return helpers.standing[new_post] = helpers.standing.pop(matching_posts[0]) helpers.save_standing() helpers.log( f'The post of {matching_posts[0]} has been renamed to {new_post}') await context.send( f'The post of {matching_posts[0]} has been renamed to {new_post}')
async def setup(self, context, *post): post = ' '.join(post) matching_posts = helpers.match_post(post) if matching_posts: await context.send(f'{post} already exists') return helpers.standing[post] = { 0: (Candidate('RON (Re-Open Nominations)'), '*****@*****.**', 42) } helpers.save_standing() helpers.log(f'The post of {post} has been created') await context.send(f'The post of {post} has been created')
async def begin(self, context, *post): post = ' '.join(post) if not post: await context.send( 'Must supply the post/referendum title you are starting the vote for, usage:' f'`{helpers.PREFIX}begin <POST/TITLE>`') return matching_posts = helpers.match_post(post) type = 'POST' if not matching_posts: matching_posts = helpers.match_referendum(post) type = 'REFERENDUM' if not matching_posts: await context.send( 'Looks like that post/referendum isn\'t available for this election, ' f'use `{helpers.PREFIX}posts` to see the posts up for election or ' f'or use `{helpers.PREFIX}referenda` to see the referenda that will be voted upon' ) return async with helpers.current_live_post_lock.writer_lock: if helpers.current_live_post: await context.send( 'You can\'t start a new vote until the last one has finished' ) return post = matching_posts[0] helpers.current_live_post = (type, post) helpers.log(f'Voting has now begun for: {post}') if type == 'POST': await self.distribute_all_post_ballots(post) await context.send( f'Voting has now begun for: {post}\n' 'All registered voters will have just received a message from me. ' 'Please vote by reacting to the candidates listed in your DMs where ' ':one: is your top candidate, :two: is your second top candidate, etc. ' 'You do not need to put a ranking in for every candidate') else: await self.distribute_all_referenda_ballots(post) await context.send( f'Voting has now begun for: {post}\n' 'All registered voters will have just received a message from me. Please vote by ' 'reacting :ballot_box_with_check: to either the \'For\' or \'Against\' message ' 'in your DMs')
async def referendum(self, context, title, *description): description = ' '.join(description) if description.startswith('\''): description = description.strip('\'') matching_referenda = helpers.match_referendum(title) if matching_referenda: await context.send(f'{title} already exists') return helpers.referenda[title] = description helpers.save_referenda() helpers.log(f'The referendum for \"{title}\" has been created') await context.send(f'The referendum for \"{title}\" has been created')
async def changename(self, context, *name): name = ' '.join(name) if not name: await context.send( f'Must supply the name you are wanting to change to, usage: `{helpers.PREFIX}changename <NAME>`' ) return if name.startswith('\''): name = name.strip('\'') author = context.author.id if author not in helpers.registered_members: await context.send( 'It looks like you\'re not registered yet, you must first register using ' f'`{helpers.PREFIX}register <STUDENT NUMBER>` before you can update your name' ) return async with helpers.current_live_post_lock.reader_lock: if helpers.current_live_post: await context.send( 'I\'m afraid you can\'t change your name whilst a vote is ongoing, ' 'please wait until the vote has finished') return author_id = helpers.registered_members[author] helpers.preferred_names[author_id] = name for post in helpers.standing: if author_id in helpers.standing[post]: helpers.standing[post][author_id] = ( Candidate(name), helpers.standing[post][author_id][1], author) helpers.save_names() helpers.save_standing() await context.send(f'The bot now recognises your name to be {name}') helpers.log( f'{context.author.name}({author_id}) has changed their name to {name}' )
async def end(self, context): voting_channel = await self.bot.fetch_channel(helpers.VOTING_CHANNEL_ID ) committee_channel = await self.bot.fetch_channel( helpers.COMMITTEE_CHANNEL_ID) last_live_post = helpers.current_live_post async with helpers.current_live_post_lock.writer_lock: helpers.current_live_post = None helpers.voting_messages.clear() async with helpers.voted_lock: helpers.voted.clear() await voting_channel.send( f'Voting has now ended for: {last_live_post[1]}') async with helpers.votes_lock.writer_lock: if last_live_post[0] == 'POST': results = pyrankvote.instant_runoff_voting([ i for i, _, _ in helpers.standing[ last_live_post[1]].values() ], helpers.votes) else: results = pyrankvote.instant_runoff_voting( helpers.referendum_options, helpers.votes) helpers.votes.clear() # Announce the scores and the winner to the committee winner = results.get_winners()[0] helpers.log(f'Result: {results}') helpers.log(f'Winner: {winner}') if last_live_post[0] == 'POST': await committee_channel.send( 'The votes were tallied as follows:\n' f'```{results}```\n' f'The winning candidate for the post of {last_live_post[1]} is: {winner}' ) else: await committee_channel.send( 'The votes were tallied as follows:\n' f'```{results}```\n' f'The result for the referendum on {last_live_post[1]} is: {winner}' ) helpers.log(f'Voting has now ended for: {last_live_post[1]}') for voter in helpers.registered_members: user = await self.bot.fetch_user(voter) await user.send(f'Voting has now ended for: {last_live_post[1]}')
async def register(self, context, student_number: int): author = context.author.id members = helpers.get_members() output_str = 'Error' if student_number in members: if author in helpers.registered_members: output_str = f'Looks like your Discord username is already registered to {helpers.registered_members[author]}' elif student_number in helpers.registered_members.values(): output_str = ( 'Looks like your student ID is already registered to someone else, ' 'please contact a committee member') other_user_id = [ key for key, value in helpers.registered_members.items() if value == student_number ][0] other_user = await self.bot.fetch_user(other_user_id).name helpers.log( f'{context.author} tried to register student ID {student_number}, ' f'but it\'s already registered to {other_user}') else: helpers.registered_members[author] = student_number output_str = ( f'Thank you {members[helpers.registered_members[author]]}, you are now registered. ' 'If you\'d like to change the name used by the bot, use ' f'`{helpers.PREFIX}changename <NAME>`\n\n{helpers.RULES_STRING}' ) helpers.log( f'{helpers.registered_members[author]} is now registered ({len(helpers.registered_members)} total)' ) else: output_str = f'Looks like you\'re not a member yet, please become a member here: {helpers.JOIN_LINK}' helpers.log( f'{context.author.name} has failed to register because they are not a member' ) helpers.save_voters() await context.send(output_str)
async def on_command_error(context, error): if isinstance(error, commands.errors.CommandNotFound): helpers.log(error) await context.channel.send( f'I couldn\'t find that command, please use {PREFIX}help for a list of commands.' )
async def submit(self, context, code): # Only work for users who got sent messages author = context.author.id if author not in helpers.voting_messages: return if code.upper() != helpers.VOTING_CODE: await context.send( 'The code you have supplied is incorrect, ' 'you must use the one given out in the election call, your vote was not cast' ) return valid = await self.validate(context) if not valid: await context.send( 'Your vote was not cast, please correct your ballot and resubmit' ) return async with helpers.voted_lock: if author in helpers.voted: await context.send( 'You have already cast your vote and it cannot be changed') return async with helpers.votes_lock.reader_lock: async with helpers.current_live_post_lock.reader_lock: if helpers.current_live_post[0] == 'POST': ballot_list = [''] * len( helpers.standing[helpers.current_live_post[1]]) # Create ballot for candidate, message_id in helpers.voting_messages[ author]: message = await context.author.fetch_message( message_id) if message.reactions: reaction = message.reactions[0].emoji ballot_list[helpers.EMOJI_LOOKUP[ reaction]] = helpers.standing[ helpers. current_live_post[1]][candidate][0] helpers.votes.append( Ballot(ranked_candidates=[ ballot for ballot in ballot_list if str(ballot) != '' ])) else: # Create ballot for option, message_id in helpers.voting_messages[ author]: message = await context.author.fetch_message( message_id) if message.reactions: helpers.votes.append( Ballot(ranked_candidates=[option])) break else: helpers.votes.append(Ballot(ranked_candidates=[])) helpers.voted.append(author) await context.send('Your vote was successfully cast') helpers.log( f'Votes cast: {len(helpers.votes)} - Votes not yet cast: {len(helpers.registered_members)-len(helpers.votes)}' )
async def stand(self, context, *input): if not input: await context.send( 'Must supply the post you are running for and a valid email address, ' f'usage:`{helpers.PREFIX}stand <POST> <EMAIL>`') return email = input[-1] post = ' '.join(input[:-1]) if not post: await context.send( 'Must supply the post you are running for and a valid email address, ' f'usage:`{helpers.PREFIX}stand <POST> <EMAIL>`') return if '@' not in email: await context.send( 'Must supply the post you are running for and a valid email address, ' f'usage:`{helpers.PREFIX}stand <POST> <EMAIL>`') return matching_posts = helpers.match_post(post) if not matching_posts: await context.send( 'Looks like that post isn\'t available for this election, ' f'use `{helpers.PREFIX}posts` to see the posts up for election' ) return post = matching_posts[0] async with helpers.current_live_post_lock.reader_lock: if helpers.current_live_post: if post == helpers.current_live_post[1]: await context.send( f'I\'m afraid voting for {post} has already begun, you cannot stand for this post' ) return author = context.author.id members = helpers.get_members() output_str = 'Error' if author in helpers.registered_members: if [ i for i in helpers.standing[post] if i == helpers.registered_members[author] ]: output_str = ( f'It looks like you, {members[helpers.registered_members[author]]} are already ' f'standing for the position of: {post}') else: helpers.standing[post][ helpers.registered_members[author]] = (Candidate( members[helpers.registered_members[author]]), email, author) output_str = ( f'Congratulations {members[helpers.registered_members[author]]}, ' f'you are now standing for the position of {post}. If you no longer wish to stand, you ' f'can send `{helpers.PREFIX}standdown {post}`\n\n' 'Now you\'ll need to prepare a 2 minute speech to be given in the election call.\n' f'If you have any questions please contact the secretary {helpers.SECRETARY_NAME}' f'({helpers.SECRETARY_EMAIL}), or someone else on the committee.\n' 'If you can\'t make it to the actual election call, you must get in touch with the ' 'secretary ASAP to sort out alternative arrangements.') helpers.log( f'{context.author.name}({helpers.registered_members[author]}) is now standing for {post}' ) helpers.email_secretary( members[helpers.registered_members[author]], post) else: output_str = ( 'Looks like you\'re not registered yet, ' f'please register using `{helpers.PREFIX}register <STUDENT NUMBER>`' ) helpers.log( f'{context.author.name} has failed to stand for {post} because they are not registered' ) helpers.save_standing() await context.send(output_str)