diff --git a/changelog.md b/changelog.md index c6ad988..a67f21d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,18 @@ +# Changelog for Version 2.2 + +## New features +- Polls will now automatically activate or close and post themselves to the specified channel +- Improved ❔ functionality: Now lists the current votes for each options +- pm!cmd feature is enabled again with more error logging +- Title and options now support most UTF-8 characters, meaning you can put emojis and special characters in your poll + +## Changes and Fixes +- Improved performance and scalability. Should feel a lot more responsive now +- Fixed formatting issues for closed polls +- Export now shows server specific nickname is applicable +- Users can no longer create polls in channels where they don't have "send message" permissions + + # Changelog for Version 2.1 ## New features diff --git a/cogs/poll.py b/cogs/poll.py index 7fd3d94..c716e0a 100644 --- a/cogs/poll.py +++ b/cogs/poll.py @@ -1,3 +1,4 @@ +import asyncio import codecs import datetime import logging @@ -8,8 +9,10 @@ from string import ascii_lowercase, printable import dateparser import pytz +import regex from matplotlib import rcParams from matplotlib.afm import AFM +from pytz import UnknownTimeZoneError from unidecode import unidecode import discord @@ -135,7 +138,7 @@ class Poll: # sanitize input if string is None: raise InvalidInput - string = re.sub("[^{}]+".format(printable), "", string) + string = regex.sub("\p{C}+", "", string) if set(string).issubset(set(' ')): raise InvalidInput return string @@ -738,8 +741,8 @@ class Poll: 'duration': self.duration, 'duration_tz': self.duration_tz, 'time_created': self.time_created, - 'open': await self.is_open(update_db=False), - 'active': await self.is_active(update_db=False), + 'open': self.open, + 'active': self.active, 'activation': self.activation, 'activation_tz': self.activation_tz, 'votes': self.votes @@ -800,7 +803,10 @@ class Poll: member = self.server.get_member(user_id) if self.votes[user_id]['choices'].__len__() == 0: continue - export += f'\n{member.name}' + name = member.nick + if not name: + name = member.name + export += f'\n{name}' if self.votes[user_id]['weight'] != 1: export += f' (weight: {self.votes[user_id]["weight"]})' export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[user_id]['choices']]) @@ -814,7 +820,10 @@ class Poll: member = self.server.get_member(user_id) if self.votes[user_id]['choices'].__len__() == 0: continue - export += f'\n{member.name}' + name = member.nick + if not name: + name = member.name + export += f'\n{name}' if self.votes[user_id]['weight'] != 1: export += f' (weight: {self.votes[user_id]["weight"]})' # export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[user_id]['choices']]) @@ -842,10 +851,12 @@ class Poll: return None async def from_dict(self, d): + self.id = d['_id'] self.server = self.bot.get_server(str(d['server_id'])) self.channel = self.bot.get_channel(str(d['channel_id'])) - self.author = await self.bot.get_user_info(str(d['author'])) + # self.author = await self.bot.get_user_info(str(d['author'])) + self.author = self.server.get_member(d['author']) self.name = d['name'] self.short = d['short'] self.anonymous = d['anonymous'] @@ -981,7 +992,7 @@ class Poll: else: text += f'*You have {self.multiple_choice} choices and can change them.*' else: - text = f'*Final Results of the Poll *' + text = f'*Final Results of the Poll* ' if self.multiple_choice == 0: text += '*(Multiple Choice).*' elif self.multiple_choice == 1: @@ -999,7 +1010,6 @@ class Poll: # embed = await self.add_field_custom(name='**Options**', value=', '.join(self.get_options()), embed=embed) # embed.set_footer(text='bot is in development') - return embed async def post_embed(self, destination=None): @@ -1043,9 +1053,15 @@ class Poll: tz = pytz.timezone('UTC') else: # choose one valid timezone with the offset - tz = pytz.timezone(tz[0]) + try: + tz = pytz.timezone(tz[0]) + except UnknownTimeZoneError: + tz = pytz.UTC else: - tz = pytz.timezone(self.duration_tz) + try: + tz = pytz.timezone(self.duration_tz) + except UnknownTimeZoneError: + tz = pytz.UTC return dt.astimezone(tz) @@ -1144,6 +1160,7 @@ class Poll: if choice in self.votes[user.id]['choices']: if self.anonymous: # anonymous multiple choice -> can't unreact so we toggle with react + logger.warning("Unvoting, should not happen for non anon polls.") await self.unvote(user, option, message, lock) # refresh_poll = False else: @@ -1172,21 +1189,23 @@ class Poll: return # commit - if lock._waiters.__len__() == 0: + #if lock._waiters.__len__() == 0: # updating DB, clearing cache and refresh if necessary - await self.save_to_db() - await self.bot.poll_refresh_q.put_unique_id( - {'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock}) - if self.bot.poll_cache.get(str(self.server.id) + self.short): - del self.bot.poll_cache[str(self.server.id) + self.short] + await self.save_to_db() + # await self.bot.poll_refresh_q.put_unique_id( + # {'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock}) + # if self.bot.poll_cache.get(str(self.server.id) + self.short): + # del self.bot.poll_cache[str(self.server.id) + self.short] # refresh # if refresh_poll: # edit message if there is a real change # await self.bot.edit_message(message, embed=await self.generate_embed()) # self.bot.poll_refresh_q.append(str(self.id)) - else: + #else: # cache the poll until the queue is empty - self.bot.poll_cache[str(self.server.id)+self.short] = self + #self.bot.poll_cache[str(self.server.id)+self.short] = self + # await self.bot.edit_message(message, embed=await self.generate_embed()) + asyncio.ensure_future(self.bot.edit_message(message, embed=await self.generate_embed())) # if refresh_poll: # await self.bot.poll_refresh_q.put_unique_id({'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock}) @@ -1217,17 +1236,19 @@ class Poll: if choice != 'invalid' and choice in self.votes[user.id]['choices']: try: self.votes[user.id]['choices'].remove(choice) - if lock._waiters.__len__() == 0: - # updating DB, clearing cache and refreshing message - await self.save_to_db() - await self.bot.poll_refresh_q.put_unique_id( - {'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock}) - if self.bot.poll_cache.get(str(self.server.id) + self.short): - del self.bot.poll_cache[str(self.server.id) + self.short] - # await self.bot.edit_message(message, embed=await self.generate_embed()) - else: - # cache the poll until the queue is empty - self.bot.poll_cache[str(self.server.id) + self.short] = self + await self.save_to_db() + asyncio.ensure_future(self.bot.edit_message(message, embed=await self.generate_embed())) + # if lock._waiters.__len__() == 0: + # # updating DB, clearing cache and refreshing message + # await self.save_to_db() + # await self.bot.poll_refresh_q.put_unique_id( + # {'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock}) + # if self.bot.poll_cache.get(str(self.server.id) + self.short): + # del self.bot.poll_cache[str(self.server.id) + self.short] + # # await self.bot.edit_message(message, embed=await self.generate_embed()) + # else: + # # cache the poll until the queue is empty + # self.bot.poll_cache[str(self.server.id) + self.short] = self except ValueError: pass diff --git a/cogs/poll_controls.py b/cogs/poll_controls.py index 80ff1e5..ebddb68 100644 --- a/cogs/poll_controls.py +++ b/cogs/poll_controls.py @@ -4,7 +4,9 @@ import copy import datetime import json import logging +import re import shlex +import string import traceback import discord @@ -20,32 +22,47 @@ from essentials.settings import SETTINGS from utils.poll_name_generator import generate_word from essentials.exceptions import StopWizard +## A-Z Emojis for Discord +AZ_EMOJIS = [(b'\\U0001f1a'.replace(b'a', bytes(hex(224 + (6 + i))[2:], "utf-8"))).decode("unicode-escape") for i in + range(26)] class PollControls: def __init__(self, bot): self.bot = bot - self.bot.loop.create_task(self.refresh_polls()) + self.bot.loop.create_task(self.close_polls()) self.ignore_next_removed_reaction = {} - - # General Methods - async def refresh_polls(self): - """This function runs every 5 seconds to refresh poll messages when needed""" + async def close_polls(self): + """This function runs every 60 seconds to schedule prepared polls and close expired polls""" while True: try: - for i in range(self.bot.poll_refresh_q.qsize()): - values = await self.bot.poll_refresh_q.get() - if values.get('lock') and not values.get('lock')._waiters: - p = await Poll.load_from_db(self.bot, str(values.get('sid')), values.get('label')) - if p: - await self.bot.edit_message(values.get('msg'), embed=await p.generate_embed()) + query = self.bot.db.polls.find({'active': False, 'activation': {"$not": re.compile("0")}}) + if query: + for pd in [poll async for poll in query]: + p = Poll(self.bot, load=True) + await p.from_dict(pd) + if p.active: + await self.bot.send_message(p.channel, 'This poll has been scheduled and is active now!') + await p.post_embed(destination=p.channel) - self.bot.poll_refresh_q.task_done() - else: - await self.bot.poll_refresh_q.put_unique_id(values) + query = self.bot.db.polls.find({'open': True, 'duration': {"$not": re.compile("0")}}) + if query: + for pd in [poll async for poll in query]: + p = Poll(self.bot, load=True) + await p.from_dict(pd) + if not p.open: + await self.bot.send_message(p.channel, 'This poll has reached the deadline and is closed!') + await p.post_embed(destination=p.channel) except AttributeError: + #Database not loaded yet + logger.warning("Attribute Error in close_polls loop") pass + except: + #Never break this loop due to an error + logger.error("Other Error in close_polls loop") + pass + await asyncio.sleep(5) def get_lock(self, server_id): @@ -92,7 +109,7 @@ class PollControls: if short is None: pre = await get_server_pre(self.bot, ctx.message.server) - error = f'Please specify the label of a poll after the close command. \n' \ + error = f'Please specify the label of a poll after the activate command. \n' \ f'`{pre}activate `' await self.say_error(ctx, error) else: @@ -129,7 +146,7 @@ class PollControls: if short is None: pre = await get_server_pre(self.bot, ctx.message.server) error = f'Please specify the label of a poll after the delete command. \n' \ - f'`{pre}close `' + f'`{pre}delete `' await self.say_error(ctx, error) else: p = await Poll.load_from_db(self.bot, str(server.id), short) @@ -201,8 +218,8 @@ class PollControls: if short is None: pre = await get_server_pre(self.bot, ctx.message.server) - error = f'Please specify the label of a poll after the close command. \n' \ - f'`{pre}close `' + error = f'Please specify the label of a poll after the export command. \n' \ + f'`{pre}export `' await self.say_error(ctx, error) else: p = await Poll.load_from_db(self.bot, str(server.id), short) @@ -253,7 +270,7 @@ class PollControls: else: return - def item_fct(i,item): + def item_fct(i, item): return f':black_small_square: **{item["short"]}**: {item["name"]}' title = f' Listing {short} polls' @@ -281,78 +298,81 @@ class PollControls: @commands.command(pass_context=True) async def cmd(self, ctx, *, cmd=None): '''The old, command style way paired with the wizard.''' - await self.say_embed(ctx, say_text='This command is temporarily disabled.') - # server = await ask_for_server(self.bot, ctx.message) - # if not server: - # return - # pre = await get_server_pre(self.bot, server) - # - # # generate the argparser and handle invalid stuff - # descr = 'Accept poll settings via commandstring. \n\n' \ - # '**Wrap all arguments in quotes like this:** \n' \ - # f'{pre}cmd -question \"What tea do you like?\" -o \"green, black, chai\"\n\n' \ - # 'The Order of arguments doesn\'t matter. If an argument is missing, it will use the default value. ' \ - # 'If an argument is invalid, the wizard will step in. ' \ - # 'If the command string is invalid, you will get this error :)' - # parser = argparse.ArgumentParser(description=descr, formatter_class=CustomFormatter, add_help=False) - # parser.add_argument('-question', '-q') - # parser.add_argument('-label', '-l', default=str(await generate_word(self.bot, server.id))) - # parser.add_argument('-options', '-o') - # parser.add_argument('-multiple_choice', '-mc', default='1') - # parser.add_argument('-roles', '-r', default='all') - # parser.add_argument('-weights', '-w', default='none') - # parser.add_argument('-deadline', '-d', default='0') - # parser.add_argument('-anonymous', '-a', action="store_true") - # - # helpstring = parser.format_help() - # helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ") - # - # if cmd and cmd == 'help': - # await self.say_embed(ctx, say_text=helpstring) - # return - # - # try: - # cmds = shlex.split(cmd) - # except ValueError: - # await self.say_error(ctx, error_text=helpstring) - # return - # except: - # return - # - # try: - # args, unknown_args = parser.parse_known_args(cmds) - # except SystemExit: - # await self.say_error(ctx, error_text=helpstring) - # return - # except: - # return - # - # if unknown_args: - # error_text = f'**There was an error reading the command line options!**.\n' \ - # f'Most likely this is because you didn\'t surround the arguments with double quotes like this: ' \ - # f'`{pre}cmd -q "question of the poll" -o "yes, no, maybe"`' \ - # f'\n\nHere are the arguments I could not understand:\n' - # error_text += '`'+'\n'.join(unknown_args)+'`' - # error_text += f'\n\nHere are the arguments which are ok:\n' - # error_text += '`' + '\n'.join([f'{k}: {v}' for k, v in vars(args).items()]) + '`' - # - # await self.say_error(ctx, error_text=error_text, footer_text=f'type `{pre}cmd help` for details.') - # return - # - # # pass arguments to the wizard - # async def route(poll): - # await poll.set_name(force=args.question) - # await poll.set_short(force=args.label) - # await poll.set_anonymous(force=f'{"yes" if args.anonymous else "no"}') - # await poll.set_options_reaction(force=args.options) - # await poll.set_multiple_choice(force=args.multiple_choice) - # await poll.set_roles(force=args.roles) - # await poll.set_weights(force=args.weights) - # await poll.set_duration(force=args.deadline) - # - # poll = await self.wizard(ctx, route, server) - # if poll: - # await poll.post_embed(destination=poll.channel) + # await self.say_embed(ctx, say_text='This command is temporarily disabled.') + server = await ask_for_server(self.bot, ctx.message) + if not server: + return + pre = await get_server_pre(self.bot, server) + try: + # generate the argparser and handle invalid stuff + descr = 'Accept poll settings via commandstring. \n\n' \ + '**Wrap all arguments in quotes like this:** \n' \ + f'{pre}cmd -question \"What tea do you like?\" -o \"green, black, chai\"\n\n' \ + 'The Order of arguments doesn\'t matter. If an argument is missing, it will use the default value. ' \ + 'If an argument is invalid, the wizard will step in. ' \ + 'If the command string is invalid, you will get this error :)' + parser = argparse.ArgumentParser(description=descr, formatter_class=CustomFormatter, add_help=False) + parser.add_argument('-question', '-q') + parser.add_argument('-label', '-l', default=str(await generate_word(self.bot, server.id))) + parser.add_argument('-options', '-o') + parser.add_argument('-multiple_choice', '-mc', default='1') + parser.add_argument('-roles', '-r', default='all') + parser.add_argument('-weights', '-w', default='none') + parser.add_argument('-deadline', '-d', default='0') + parser.add_argument('-anonymous', '-a', action="store_true") + + helpstring = parser.format_help() + helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ") + + if cmd and cmd == 'help': + await self.say_embed(ctx, say_text=helpstring) + return + + try: + cmds = shlex.split(cmd) + except ValueError: + await self.say_error(ctx, error_text=helpstring) + return + except: + return + + try: + args, unknown_args = parser.parse_known_args(cmds) + except SystemExit: + await self.say_error(ctx, error_text=helpstring) + return + except: + return + + if unknown_args: + error_text = f'**There was an error reading the command line options!**.\n' \ + f'Most likely this is because you didn\'t surround the arguments with double quotes like this: ' \ + f'`{pre}cmd -q "question of the poll" -o "yes, no, maybe"`' \ + f'\n\nHere are the arguments I could not understand:\n' + error_text += '`'+'\n'.join(unknown_args)+'`' + error_text += f'\n\nHere are the arguments which are ok:\n' + error_text += '`' + '\n'.join([f'{k}: {v}' for k, v in vars(args).items()]) + '`' + + await self.say_error(ctx, error_text=error_text, footer_text=f'type `{pre}cmd help` for details.') + return + + # pass arguments to the wizard + async def route(poll): + await poll.set_name(force=args.question) + await poll.set_short(force=args.label) + await poll.set_anonymous(force=f'{"yes" if args.anonymous else "no"}') + await poll.set_options_reaction(force=args.options) + await poll.set_multiple_choice(force=args.multiple_choice) + await poll.set_roles(force=args.roles) + await poll.set_weights(force=args.weights) + await poll.set_duration(force=args.deadline) + + poll = await self.wizard(ctx, route, server) + if poll: + await poll.post_embed(destination=poll.channel) + except Exception as error: + logger.error("ERROR IN pm!cmd") + logger.exception(error) @commands.command(pass_context=True) @@ -481,21 +501,29 @@ class PollControls: # check if removed by the bot.. this is a bit hacky but discord doesn't provide the correct info... message_id = data.get('message_id') user_id = data.get('user_id') - if self.ignore_next_removed_reaction.get(str(message_id)+str(emoji)) == user_id: - del self.ignore_next_removed_reaction[str(message_id)+str(emoji)] + if self.ignore_next_removed_reaction.get(str(message_id) + str(emoji)) == user_id: + del self.ignore_next_removed_reaction[str(message_id) + str(emoji)] return - # check if we can find a poll label channel_id = data.get('channel_id') channel = self.bot.get_channel(channel_id) - user = await self.bot.get_user_info(user_id) # only do this once + # user = await self.bot.get_user_info(user_id) # only do this once + + server_id = data.get('guild_id') + if server_id: + server = self.bot.get_server(server_id) + user = server.get_member(user_id) + else: + user = await self.bot.get_user_info(user_id) # only do this once, this is rate limited + if not channel: # discord rapidly closes dm channels by desing # put private channels back into the bots cache and try again await self.bot.start_private_message(user) channel = self.bot.get_channel(channel_id) + message = await self.bot.get_message(channel=channel, id=message_id) label = None if message and message.embeds: @@ -508,28 +536,22 @@ class PollControls: if not label: return - # fetch poll # create message object for the reaction sender, to get correct server - user_msg = copy.deepcopy(message) - user_msg.author = user - server = await ask_for_server(self.bot, user_msg, label) + if not server_id: + user_msg = copy.deepcopy(message) + user_msg.author = user + server = await ask_for_server(self.bot, user_msg, label) + server_id = server.id # this is exclusive - - lock = self.get_lock(server.id) + lock = self.get_lock(server_id) async with lock: - # try to load poll form cache - p = self.bot.poll_cache.get(str(server.id) + label) - if not p: - p = await Poll.load_from_db(self.bot, server.id, label) + p = await Poll.load_from_db(self.bot, server_id, label) if not isinstance(p, Poll): return - if not p.anonymous: # for anonymous polls we can't unvote because we need to hide reactions - member = server.get_member(user_id) - await p.unvote(member, emoji, message, lock) - + await p.unvote(user, emoji, message, lock) async def do_on_reaction_add(self, data): # dont look at bot's own reactions @@ -576,9 +598,7 @@ class PollControls: # hopefully it will scale well enough or I need a different solution lock = self.get_lock(server.id) async with lock: - p = self.bot.poll_cache.get(str(server.id)+label) - if not p: - p = await Poll.load_from_db(self.bot, server.id, label) + p = await Poll.load_from_db(self.bot, server.id, label) if not isinstance(p, Poll): return @@ -624,8 +644,9 @@ class PollControls: if user.id in p.votes: if p.votes[user.id]['choices'].__len__() > 0: choices = ', '.join([p.options_reaction[c] for c in p.votes[user.id]['choices']]) - embed.add_field(name=f'{"Your current votes (can be changed as long as the poll is open):" if is_open else "Your final votes:"}', - value=choices, inline=False) + embed.add_field( + name=f'{"Your current votes (can be changed as long as the poll is open):" if is_open else "Your final votes:"}', + value=choices, inline=False) # weight if vote_rights: @@ -646,11 +667,44 @@ class PollControls: elif deadline == 0: time_left = 'Until manually closed.' else: - time_left = str(deadline-datetime.datetime.utcnow().replace(tzinfo=pytz.utc)).split('.', 2)[0] + time_left = str(deadline - datetime.datetime.utcnow().replace(tzinfo=pytz.utc)).split('.', 2)[0] embed.add_field(name='Time left in the poll:', value=time_left, inline=False) await self.bot.send_message(user, embed=embed) + + # send current details of who currently voted for what + if not p.anonymous and p.votes.__len__() > 0: + msg = '--------------------------------------------\n' \ + 'CURRENT VOTES\n' \ + '--------------------------------------------\n' + for i, o in enumerate(p.options_reaction): + if not p.options_reaction_default: + msg += AZ_EMOJIS[i] + " " + msg += "**" +o+":**" + c = 0 + for user_id in p.votes: + member = server.get_member(user_id) + if not member or i not in p.votes[user_id]['choices']: + continue + c += 1 + name = member.nick + if not name: + name = member.name + msg += f'\n{name}' + if p.votes[user_id]['weight'] != 1: + msg += f' (weight: {p.votes[user_id]["weight"]})' + # msg += ': ' + ', '.join([AZ_EMOJIS[c]+" "+p.options_reaction[c] for c in p.votes[user_id]['choices']]) + if msg.__len__() > 1500: + await self.bot.send_message(user, msg) + msg = '' + if c == 0: + msg += '\nNo votes for this option yet.' + msg += '\n\n' + + if msg.__len__() > 0: + await self.bot.send_message(user, msg) + return # Assume: User wants to vote with reaction @@ -664,7 +718,7 @@ class PollControls: # check if we need to remove reactions (this will trigger on_reaction_remove) if str(channel.type) != 'private' and p.anonymous: # immediately remove reaction and to be safe, remove all reactions - self.ignore_next_removed_reaction[str(message.id)+str(emoji)] = user_id + self.ignore_next_removed_reaction[str(message.id) + str(emoji)] = user_id await self.bot.remove_reaction(message, emoji, user) # order here is crucial since we can't determine if a reaction was removed by the bot or user @@ -673,13 +727,13 @@ class PollControls: # cant do this until we figure out how to see who removed the reaction? # for now MC 1 is like MC x - # if str(channel.type) != 'private' and p.multiple_choice == 1: - # # remove all other reactions - # # if lock._waiters.__len__() == 0: - # for r in message.reactions: - # if r.emoji and r.emoji != emoji: - # await self.bot.remove_reaction(message, r.emoji, user) - # pass + if str(channel.type) != 'private' and p.multiple_choice == 1: + # remove all other reactions + # if lock._waiters.__len__() == 0: + for r in message.reactions: + if r.emoji and r.emoji != emoji: + await self.bot.remove_reaction(message, r.emoji, user) + pass def setup(bot): diff --git a/essentials/multi_server.py b/essentials/multi_server.py index b345cfc..332ba7f 100644 --- a/essentials/multi_server.py +++ b/essentials/multi_server.py @@ -100,8 +100,12 @@ async def ask_for_channel(bot, server, message): if str(message.channel.type) == 'text': return message.channel + # build channel list that the user is allowed to send messages to + user = message.author + member = server.get_member(user.id) + channel_list = [c for c in server.channels if str(c.type) == 'text' and c.permissions_for(member).send_messages] + # if exactly 1 channel, return it - channel_list = [c for c in server.channels if str(c.type) == 'text'] if channel_list.__len__() == 1: return channel_list[0] diff --git a/pollmaster.py b/pollmaster.py index fcd1600..13a7fdc 100644 --- a/pollmaster.py +++ b/pollmaster.py @@ -56,7 +56,7 @@ async def on_ready(): bot.db = mongo.pollmaster bot.session = aiohttp.ClientSession() print(bot.db) - await bot.change_presence(game=discord.Game(name=f'pm!help - v2.1 is live!')) + await bot.change_presence(game=discord.Game(name=f'pm!help - v2.2')) # check discord server configs try: @@ -69,11 +69,12 @@ async def on_ready(): {'$set': {'prefix': 'pm!', 'admin_role': 'polladmin', 'user_role': 'polluser'}}, upsert=True ) - try: - await import_old_database(bot, server) - print(str(server), "updated.") - except: - print(str(server.id), "failed.") + # stopping migration support. + # try: + # await import_old_database(bot, server) + # print(str(server), "updated.") + # except: + # print(str(server.id), "failed.") except: print("Problem verifying servers.") @@ -82,9 +83,8 @@ async def on_ready(): # global locks and caches for performance when voting rapidly bot.locks = {} - bot.poll_cache = {} - # bot.poll_refresh_q = {} - bot.poll_refresh_q = UniqueQueue() + # bot.poll_cache = {} + # bot.poll_refresh_q = UniqueQueue() print("Servers verified. Bot running.") diff --git a/readme.md b/readme.md index 280cc6a..ce49ab6 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# Pollmaster V 2.1 +# Pollmaster V 2.2 ## Overview @@ -34,6 +34,7 @@ Here is how Pollmaster looks in action: | pm!help | Shows an interactive help menu | | pm!new | Starts a new poll with all the settings | | pm!quick | Starts a new poll with just a question and options | +| pm!show