working on 2.4

mention command
started with debug command
export for custom votes
finished cmd
This commit is contained in:
Matthias Nadler 2019-06-17 23:29:27 +02:00
parent c2633240c1
commit 99898c9a14
6 changed files with 581 additions and 184 deletions

View File

@ -51,7 +51,7 @@ class Help(commands.Cog):
if page == '🏠': if page == '🏠':
## POLL CREATION SHORT ## POLL CREATION SHORT
embed.add_field(name='🆕 Making New Polls', embed.add_field(name='🆕 Making New Polls',
value=f'`{pre}quick` | `{pre}new` | `{pre}prepare` | `{pre}cmd <args>`', inline=False) value=f'`{pre}quick` | `{pre}new` | `{pre}advanced` | `{pre}prepare` | `{pre}cmd <args>`', inline=False)
# embed.add_field(name='Commands', value=f'`{pre}quick` | `{pre}new` | `{pre}prepared`', inline=False) # embed.add_field(name='Commands', value=f'`{pre}quick` | `{pre}new` | `{pre}prepared`', inline=False)
# embed.add_field(name='Arguments', value=f'Arguments: `<poll question>` (optional)', inline=False) # embed.add_field(name='Arguments', value=f'Arguments: `<poll question>` (optional)', inline=False)
# embed.add_field(name='Examples', value=f'Examples: `{pre}new` | `{pre}quick What is the greenest color?`', # embed.add_field(name='Examples', value=f'Examples: `{pre}new` | `{pre}quick What is the greenest color?`',
@ -221,6 +221,48 @@ class Help(commands.Cog):
# cleanup # cleanup
await ctx.message.delete() await ctx.message.delete()
@commands.Cog.listener()
async def on_message(self, message):
if message.content.startswith("@mention "):
channel = message.channel
if not isinstance(channel, discord.TextChannel):
await channel.send("@mention can only be used in a server text channel.")
return
guild = message.guild
if not guild:
await channel.send("Could not determine your server.")
return
tag = message.content.split()[1].lower()
if tag == "prefix":
pre = await get_server_pre(self.bot, guild)
# await channel.send(f'The prefix for this server/channel is: \n {pre} \n To change it type: \n'
# f'{pre}prefix <new_prefix>')
await channel.send(pre)
elif message.content == "@debug":
channel = message.channel
if not isinstance(channel, discord.TextChannel):
await channel.send("@debug can only be used in a server text channel.")
return
guild = message.guild
if not guild:
await channel.send("Could not determine your server.")
return
status_msg = ''
permissions = channel.permissions_for(guild.me)
if not permissions.send_messages:
await message.author.send(f'I don\'t have permission to send text messages in channel {channel} '
f'on server {guild}')
return
status_msg += f' ✅ Sending messages\n'
await channel.send(status_msg)
def setup(bot): def setup(bot):
global logger global logger

View File

@ -11,23 +11,24 @@ import pytz
from discord.ext import commands from discord.ext import commands
from utils.misc import CustomFormatter from utils.misc import CustomFormatter
from .poll import Poll from models.poll import Poll
from utils.paginator import embed_list_paginated from utils.paginator import embed_list_paginated
from essentials.multi_server import get_server_pre, ask_for_server, ask_for_channel from essentials.multi_server import get_server_pre, ask_for_server, ask_for_channel
from essentials.settings import SETTINGS from essentials.settings import SETTINGS
from utils.poll_name_generator import generate_word from utils.poll_name_generator import generate_word
from essentials.exceptions import StopWizard from essentials.exceptions import StopWizard
## A-Z Emojis for Discord # 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 AZ_EMOJIS = [(b'\\U0001f1a'.replace(b'a', bytes(hex(224 + (6 + i))[2:], "utf-8"))).decode("unicode-escape") for i in
range(26)] range(26)]
class PollControls(commands.Cog): class PollControls(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
#self.bot.loop.create_task(self.close_polls()) self.bot.loop.create_task(self.close_polls())
self.ignore_next_removed_reaction = {} self.ignore_next_removed_reaction = {}
#
# # General Methods # # General Methods
def get_label(self, message: discord.Message): def get_label(self, message: discord.Message):
@ -45,6 +46,10 @@ class PollControls(commands.Cog):
"""This function runs every 60 seconds to schedule prepared polls and close expired polls""" """This function runs every 60 seconds to schedule prepared polls and close expired polls"""
while True: while True:
try: try:
if not hasattr(self.bot, 'db'):
await asyncio.sleep(30)
continue
query = self.bot.db.polls.find({'active': False, 'activation': {"$not": re.compile("0")}}) query = self.bot.db.polls.find({'active': False, 'activation': {"$not": re.compile("0")}})
if query: if query:
for pd in [poll async for poll in query]: for pd in [poll async for poll in query]:
@ -83,7 +88,7 @@ class PollControls(commands.Cog):
logger.exception(ex) logger.exception(ex)
pass pass
await asyncio.sleep(30) await asyncio.sleep(60)
def get_lock(self, server_id): def get_lock(self, server_id):
if not self.bot.locks.get(server_id): if not self.bot.locks.get(server_id):
@ -119,7 +124,7 @@ class PollControls(commands.Cog):
embed.set_footer(text=footer_text) embed.set_footer(text=footer_text)
await ctx.send(embed=embed) await ctx.send(embed=embed)
# # Commands # Commands
@commands.command() @commands.command()
async def activate(self, ctx, *, short=None): async def activate(self, ctx, *, short=None):
"""Activate a prepared poll. Parameter: <label>""" """Activate a prepared poll. Parameter: <label>"""
@ -229,6 +234,31 @@ class PollControls(commands.Cog):
await self.say_error(ctx, error) await self.say_error(ctx, error)
await ctx.invoke(self.show) await ctx.invoke(self.show)
@commands.command()
async def copy(self, ctx, *, short=None):
'''Copy a poll. Parameter: <label>'''
server = await ask_for_server(self.bot, ctx.message, short)
if not server:
return
if short is None:
pre = await get_server_pre(self.bot, ctx.message.guild)
error = f'Please specify the label of a poll after the copy command. \n' \
f'`{pre}copy <poll_label>`'
await self.say_error(ctx, error)
else:
p = await Poll.load_from_db(self.bot, server.id, short)
if p is not None:
text = await get_server_pre(self.bot, server) + p.to_command()
await self.say_embed(ctx, text, title="Paste this to create a copy of the poll")
else:
error = f'Poll with label "{short}" was not found. Listing all open polls.'
# pre = await get_server_pre(self.bot, ctx.message.server)
# footer = f'Type {pre}show to display all polls'
await self.say_error(ctx, error)
await ctx.invoke(self.show)
@commands.command() @commands.command()
async def export(self, ctx, *, short=None): async def export(self, ctx, *, short=None):
'''Export a poll. Parameter: <label>''' '''Export a poll. Parameter: <label>'''
@ -317,87 +347,96 @@ class PollControls(commands.Cog):
pre = await get_server_pre(self.bot, server) pre = await get_server_pre(self.bot, server)
footer = f'Type {pre}show to display all polls' footer = f'Type {pre}show to display all polls'
await self.say_error(ctx, error, footer) await self.say_error(ctx, error, footer)
#
# @commands.command() @commands.command()
# async def cmd(self, ctx, *, cmd=None): async def cmd(self, ctx, *, cmd=None):
# '''The old, command style way paired with the wizard.''' '''The old, command style way paired with the wizard.'''
# await self.say_embed(ctx, say_text='This command is temporarily disabled.') # await self.say_embed(ctx, say_text='This command is temporarily disabled.')
# # server = await ask_for_server(self.bot, ctx.message)
# # if not server: server = await ask_for_server(self.bot, ctx.message)
# # return if not server:
# # pre = await get_server_pre(self.bot, server) return
# # try: pre = await get_server_pre(self.bot, server)
# # # generate the argparser and handle invalid stuff try:
# # descr = 'Accept poll settings via commandstring. \n\n' \ # generate the argparser and handle invalid stuff
# # '**Wrap all arguments in quotes like this:** \n' \ descr = 'Accept poll settings via commandstring. \n\n' \
# # f'{pre}cmd -question \"What tea do you like?\" -o \"green, black, chai\"\n\n' \ '**Wrap all arguments in quotes like this:** \n' \
# # 'The Order of arguments doesn\'t matter. If an argument is missing, it will use the default value. ' \ f'{pre}cmd -question \"What tea do you like?\" -o \"green, black, chai\"\n\n' \
# # 'If an argument is invalid, the wizard will step in. ' \ 'The Order of arguments doesn\'t matter. If an argument is missing, it will use the default value. ' \
# # 'If the command string is invalid, you will get this error :)' 'If an argument is invalid, the wizard will step in. ' \
# # parser = argparse.ArgumentParser(description=descr, formatter_class=CustomFormatter, add_help=False) 'If the command string is invalid, you will get this error :)'
# # parser.add_argument('-question', '-q') parser = argparse.ArgumentParser(description=descr, formatter_class=CustomFormatter, add_help=False)
# # parser.add_argument('-label', '-l', default=str(await generate_word(self.bot, server.id))) parser.add_argument('-question', '-q')
# # parser.add_argument('-options', '-o') parser.add_argument('-label', '-l', default=str(await generate_word(self.bot, server.id)))
# # parser.add_argument('-multiple_choice', '-mc', default='1') parser.add_argument('-anonymous', '-a', action="store_true")
# # parser.add_argument('-roles', '-r', default='all') parser.add_argument('-options', '-o')
# # parser.add_argument('-weights', '-w', default='none') parser.add_argument('-survey_flags', '-sf', default='0')
# # parser.add_argument('-deadline', '-d', default='0') parser.add_argument('-multiple_choice', '-mc', default='1')
# # parser.add_argument('-anonymous', '-a', action="store_true") parser.add_argument('-hide_votes', '-h', action="store_true")
# # parser.add_argument('-roles', '-r', default='all')
# # helpstring = parser.format_help() parser.add_argument('-weights', '-w', default='none')
# # helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ") parser.add_argument('-prepare', '-p', default='-1')
# # parser.add_argument('-deadline', '-d', default='0')
# # if cmd and cmd == 'help':
# # await self.say_embed(ctx, say_text=helpstring) helpstring = parser.format_help()
# # return helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ")
# #
# # try: if not cmd or len(cmd) < 2 or cmd == 'help':
# # cmds = shlex.split(cmd) # Shlex will block if the string is empty
# # except ValueError: await self.say_embed(ctx, say_text=helpstring)
# # await self.say_error(ctx, error_text=helpstring) return
# # return
# # except: try:
# # return cmds = shlex.split(cmd)
# # except ValueError:
# # try: await self.say_error(ctx, error_text=helpstring)
# # args, unknown_args = parser.parse_known_args(cmds) return
# # except SystemExit: except:
# # await self.say_error(ctx, error_text=helpstring) return
# # return
# # except: try:
# # return args, unknown_args = parser.parse_known_args(cmds)
# # except SystemExit:
# # if unknown_args: await self.say_error(ctx, error_text=helpstring)
# # error_text = f'**There was an error reading the command line options!**.\n' \ return
# # f'Most likely this is because you didn\'t surround the arguments with double quotes like this: ' \ except:
# # f'`{pre}cmd -q "question of the poll" -o "yes, no, maybe"`' \ return
# # f'\n\nHere are the arguments I could not understand:\n'
# # error_text += '`'+'\n'.join(unknown_args)+'`' if unknown_args:
# # error_text += f'\n\nHere are the arguments which are ok:\n' error_text = f'**There was an error reading the command line options!**.\n' \
# # error_text += '`' + '\n'.join([f'{k}: {v}' for k, v in vars(args).items()]) + '`' 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"`' \
# # await self.say_error(ctx, error_text=error_text, footer_text=f'type `{pre}cmd help` for details.') f'\n\nHere are the arguments I could not understand:\n'
# # return error_text += '`'+'\n'.join(unknown_args)+'`'
# # error_text += f'\n\nHere are the arguments which are ok:\n'
# # # pass arguments to the wizard error_text += '`' + '\n'.join([f'{k}: {v}' for k, v in vars(args).items()]) + '`'
# # async def route(poll):
# # await poll.set_name(force=args.question) await self.say_error(ctx, error_text=error_text, footer_text=f'type `{pre}cmd help` for details.')
# # await poll.set_short(force=args.label) return
# # await poll.set_anonymous(force=f'{"yes" if args.anonymous else "no"}')
# # await poll.set_options_reaction(force=args.options) # pass arguments to the wizard
# # await poll.set_multiple_choice(force=args.multiple_choice) async def route(poll):
# # await poll.set_roles(force=args.roles) await poll.set_name(ctx, force=args.question)
# # await poll.set_weights(force=args.weights) await poll.set_short(ctx, force=args.label)
# # await poll.set_duration(force=args.deadline) await poll.set_anonymous(ctx, force=f'{"yes" if args.anonymous else "no"}')
# # await poll.set_options_reaction(ctx, force=args.options)
# # poll = await self.wizard(ctx, route, server) await poll.set_survey_flags(ctx, force=args.survey_flags)
# # if poll: await poll.set_multiple_choice(ctx, force=args.multiple_choice)
# # await poll.post_embed(destination=poll.channel) await poll.set_hide_vote_count(ctx, force=f'{"yes" if args.hide_votes else "no"}')
# # except Exception as error: await poll.set_roles(ctx, force=args.roles)
# # logger.error("ERROR IN pm!cmd") await poll.set_weights(ctx, force=args.weights)
# # logger.exception(error) await poll.set_preparation(ctx, force=args.prepare)
# await poll.set_duration(ctx, force=args.deadline)
#
poll = await self.wizard(ctx, route, server)
if poll:
await poll.post_embed(poll.channel)
except Exception as error:
logger.error("ERROR IN pm!cmd")
logger.exception(error)
@commands.command() @commands.command()
async def quick(self, ctx, *, cmd=None): async def quick(self, ctx, *, cmd=None):
'''Create a quick poll with just a question and some options. Parameters: <Question> (optional)''' '''Create a quick poll with just a question and some options. Parameters: <Question> (optional)'''
@ -411,6 +450,7 @@ class PollControls(commands.Cog):
await poll.set_anonymous(ctx, force='no') await poll.set_anonymous(ctx, force='no')
await poll.set_options_reaction(ctx) await poll.set_options_reaction(ctx)
await poll.set_multiple_choice(ctx, force='1') await poll.set_multiple_choice(ctx, force='1')
await poll.set_hide_vote_count(ctx, force='no')
await poll.set_roles(ctx, force='all') await poll.set_roles(ctx, force='all')
await poll.set_weights(ctx, force='none') await poll.set_weights(ctx, force='none')
await poll.set_duration(ctx, force='0') await poll.set_duration(ctx, force='0')
@ -432,7 +472,9 @@ class PollControls(commands.Cog):
await poll.set_preparation(ctx) await poll.set_preparation(ctx)
await poll.set_anonymous(ctx) await poll.set_anonymous(ctx)
await poll.set_options_reaction(ctx) await poll.set_options_reaction(ctx)
await poll.set_survey_flags(ctx)
await poll.set_multiple_choice(ctx) await poll.set_multiple_choice(ctx)
await poll.set_hide_vote_count(ctx)
await poll.set_roles(ctx) await poll.set_roles(ctx)
await poll.set_weights(ctx) await poll.set_weights(ctx)
await poll.set_duration(ctx) await poll.set_duration(ctx)
@ -441,6 +483,29 @@ class PollControls(commands.Cog):
if poll: if poll:
await poll.post_embed(ctx.message.author) await poll.post_embed(ctx.message.author)
@commands.command()
async def advanced(self, ctx, *, cmd=None):
"""Poll with more options. Parameters: <Question> (optional)"""
server = await ask_for_server(self.bot, ctx.message)
if not server:
return
async def route(poll):
await poll.set_name(ctx, force=cmd)
await poll.set_short(ctx)
await poll.set_anonymous(ctx)
await poll.set_options_reaction(ctx)
await poll.set_survey_flags(ctx)
await poll.set_multiple_choice(ctx)
await poll.set_hide_vote_count(ctx)
await poll.set_roles(ctx)
await poll.set_weights(ctx)
await poll.set_duration(ctx)
poll = await self.wizard(ctx, route, server)
if poll:
await poll.post_embed(poll.channel)
@commands.command() @commands.command()
async def new(self, ctx, *, cmd=None): async def new(self, ctx, *, cmd=None):
"""Start the poll wizard to create a new poll step by step. Parameters: <Question> (optional)""" """Start the poll wizard to create a new poll step by step. Parameters: <Question> (optional)"""
@ -454,8 +519,9 @@ class PollControls(commands.Cog):
await poll.set_anonymous(ctx) await poll.set_anonymous(ctx)
await poll.set_options_reaction(ctx) await poll.set_options_reaction(ctx)
await poll.set_multiple_choice(ctx) await poll.set_multiple_choice(ctx)
await poll.set_roles(ctx) await poll.set_hide_vote_count(ctx, force='no')
await poll.set_weights(ctx) await poll.set_roles(ctx, force='all')
await poll.set_weights(ctx, force='none')
await poll.set_duration(ctx) await poll.set_duration(ctx)
poll = await self.wizard(ctx, route, server) poll = await self.wizard(ctx, route, server)
@ -468,13 +534,14 @@ class PollControls(commands.Cog):
if not channel: if not channel:
return return
pre = await get_server_pre(self.bot, server)
# Permission Check # Permission Check
member = server.get_member(ctx.message.author.id) member = server.get_member(ctx.message.author.id)
if not member.guild_permissions.manage_guild: if not member.guild_permissions.manage_guild:
result = await self.bot.db.config.find_one({'_id': str(server.id)}) result = await self.bot.db.config.find_one({'_id': str(server.id)})
if result and result.get('admin_role') not in [r.name for r in member.roles] and result.get( if result and result.get('admin_role') not in [r.name for r in member.roles] and result.get(
'user_role') not in [r.name for r in member.roles]: 'user_role') not in [r.name for r in member.roles]:
pre = await get_server_pre(self.bot, server)
await ctx.message.author.send('You don\'t have sufficient rights to start new polls on this server. ' await ctx.message.author.send('You don\'t have sufficient rights to start new polls on this server. '
'A server administrator has to assign the user or admin role to you. ' 'A server administrator has to assign the user or admin role to you. '
f'To view and set the permissions, an admin can use `{pre}userrole` and ' f'To view and set the permissions, an admin can use `{pre}userrole` and '
@ -485,35 +552,20 @@ class PollControls(commands.Cog):
poll = Poll(self.bot, ctx, server, channel) poll = Poll(self.bot, ctx, server, channel)
## Route to define object, passed as argument for different constructors ## Route to define object, passed as argument for different constructors
if ctx.message and ctx.message.content and not ctx.message.content.startswith(f'{pre}cmd '):
poll.wizard_messages.append(ctx.message)
try: try:
await route(poll) await route(poll)
poll.finalize() poll.finalize()
await poll.clean_up(ctx.channel)
except StopWizard: except StopWizard:
await poll.clean_up(ctx.channel)
return return
# Finalize # Finalize
await poll.save_to_db() await poll.save_to_db()
return poll return poll
# # BOT EVENTS (@bot.event)
# async def on_socket_raw_receive(self, raw_msg):
# print(raw_msg)
# if not isinstance(raw_msg, str):
# return
# msg = json.loads(raw_msg)
# type = msg.get("t")
# data = msg.get("d")
# if not data:
# return
# # emoji = data.get("emoji")
# # user_id = data.get("user_id")
# # message_id = data.get("message_id")
# if type == "MESSAGE_REACTION_ADD":
# await self.do_on_reaction_add(data)
# elif type == "MESSAGE_REACTION_REMOVE":
# #await self.do_on_reaction_remove(data)
# pass
@commands.Cog.listener() @commands.Cog.listener()
async def on_raw_reaction_remove(self, data): async def on_raw_reaction_remove(self, data):
# get emoji symbol # get emoji symbol
@ -672,6 +724,10 @@ class PollControls(commands.Cog):
description='', color=SETTINGS.color) description='', color=SETTINGS.color)
embed.set_author(name=f" >> {p.short}", icon_url=SETTINGS.author_icon) embed.set_author(name=f" >> {p.short}", icon_url=SETTINGS.author_icon)
# created by
created_by = server.get_member(int(p.author.id))
embed.add_field(name=f'Created by:', value=f'{created_by if created_by else "<Deleted User>"}', inline=False)
# vote rights # vote rights
vote_rights = await p.has_required_role(member) vote_rights = await p.has_required_role(member)
embed.add_field(name=f'{"Can you vote?" if is_open else "Could you vote?"}', embed.add_field(name=f'{"Can you vote?" if is_open else "Could you vote?"}',
@ -679,7 +735,7 @@ class PollControls(commands.Cog):
# edit rights # edit rights
edit_rights = False edit_rights = False
if str(member.id) == str(p.author): if str(member.id) == str(p.author.id):
edit_rights = True edit_rights = True
elif member.guild_permissions.manage_guild: elif member.guild_permissions.manage_guild:
edit_rights = True edit_rights = True
@ -724,7 +780,7 @@ class PollControls(commands.Cog):
await user.send(embed=embed) await user.send(embed=embed)
# send current details of who currently voted for what # send current details of who currently voted for what
if not p.anonymous and p.votes.__len__() > 0: if (not p.open or not p.hide_count) and not p.anonymous and p.votes.__len__() > 0:
msg = '--------------------------------------------\n' \ msg = '--------------------------------------------\n' \
'CURRENT VOTES\n' \ 'CURRENT VOTES\n' \
'--------------------------------------------\n' '--------------------------------------------\n'
@ -734,17 +790,21 @@ class PollControls(commands.Cog):
msg += "**" +o+":**" msg += "**" +o+":**"
c = 0 c = 0
for user_id in p.votes: for user_id in p.votes:
member = server.get_member(user_id) member = server.get_member(int(user_id))
if not member or i not in p.votes[str(user_id)]['choices']: if not member or i not in p.votes[str(user_id)]['choices']:
continue continue
c += 1 c += 1
name = member.nick name = member.nick
if not name: if not name:
name = member.name name = member.name
if not name:
name = "<Deleted User>"
msg += f'\n{name}' msg += f'\n{name}'
if p.votes[str(user_id)]['weight'] != 1: if p.votes[str(user_id)]['weight'] != 1:
msg += f' (weight: {p.votes[str(user_id)]["weight"]})' msg += f' (weight: {p.votes[str(user_id)]["weight"]})'
# msg += ': ' + ', '.join([AZ_EMOJIS[c]+" "+p.options_reaction[c] for c in p.votes[user_id]['choices']]) # msg += ': ' + ', '.join([AZ_EMOJIS[c]+" "+p.options_reaction[c] for c in p.votes[user_id]['choices']])
if i in p.survey_flags:
msg += f': {p.votes[str(user_id)]["answers"][p.survey_flags.index(i)]}'
if msg.__len__() > 1500: if msg.__len__() > 1500:
await user.send(msg) await user.send(msg)
msg = '' msg = ''
@ -754,6 +814,29 @@ class PollControls(commands.Cog):
if msg.__len__() > 0: if msg.__len__() > 0:
await user.send(msg) await user.send(msg)
elif (not p.open or not p.hide_count) and p.anonymous and p.survey_flags.__len__() > 0 and p.votes.__len__() > 0:
msg = '--------------------------------------------\n' \
'Custom Answers (Anonymous)\n' \
'--------------------------------------------\n'
has_answers = False
for i, o in enumerate(p.options_reaction):
if i not in p.survey_flags:
continue
custom_answers = ''
for user_id in p.votes:
if i in p.votes[str(user_id)]["choices"]:
has_answers = True
custom_answers += f'\n{p.votes[str(user_id)]["answers"][p.survey_flags.index(i)]}'
if custom_answers.__len__() > 0:
msg += AZ_EMOJIS[i] + " "
msg += "**" + o + ":**"
msg += custom_answers
msg += '\n\n'
if msg.__len__() > 1500:
await user.send(msg)
msg = ''
if has_answers and msg.__len__() > 0:
await user.send(msg)
return return
# Assume: User wants to vote with reaction # Assume: User wants to vote with reaction
@ -770,7 +853,6 @@ class PollControls(commands.Cog):
self.ignore_next_removed_reaction[str(message.id) + str(emoji)] = user_id self.ignore_next_removed_reaction[str(message.id) + str(emoji)] = user_id
asyncio.ensure_future(message.remove_reaction(emoji, user)) asyncio.ensure_future(message.remove_reaction(emoji, user))
# order here is crucial since we can't determine if a reaction was removed by the bot or user # order here is crucial since we can't determine if a reaction was removed by the bot or user
# update database with vote # update database with vote
await p.vote(member, emoji, message, lock) await p.vote(member, emoji, message, lock)
@ -785,6 +867,7 @@ class PollControls(commands.Cog):
# await message.remove_reaction(r.emoji, user) # await message.remove_reaction(r.emoji, user)
# pass # pass
def setup(bot): def setup(bot):
global logger global logger
logger = logging.getLogger('bot') logger = logging.getLogger('bot')

View File

@ -21,7 +21,7 @@ async def get_pre(bot, message):
async def get_server_pre(bot, server): async def get_server_pre(bot, server):
'''Gets the prefix for a server.''' """Gets the prefix for a server."""
try: try:
#result = await bot.db.config.find_one({'_id': str(server.id)}) #result = await bot.db.config.find_one({'_id': str(server.id)})
result = bot.pre[str(server.id)] result = bot.pre[str(server.id)]
@ -33,7 +33,7 @@ async def get_server_pre(bot, server):
async def get_servers(bot, message, short=None): async def get_servers(bot, message, short=None):
'''Get best guess of relevant shared servers''' """Get best guess of relevant shared servers"""
if message.guild is None: if message.guild is None:
list_of_shared_servers = [] list_of_shared_servers = []
for s in bot.guilds: for s in bot.guilds:

0
models/__init__.py Normal file
View File

View File

@ -3,7 +3,8 @@ import codecs
import datetime import datetime
import logging import logging
import os import os
import re import random
import dateparser import dateparser
import pytz import pytz
import regex import regex
@ -23,13 +24,13 @@ from utils.misc import possible_timezones
logger = logging.getLogger('bot') logger = logging.getLogger('bot')
## Helvetica is the closest font to Whitney (discord uses Whitney) in afm # Helvetica is the closest font to Whitney (discord uses Whitney) in afm
## This is used to estimate text width and adjust the layout of the embeds # This is used to estimate text width and adjust the layout of the embeds
afm_fname = os.path.join(rcParams['datapath'], 'fonts', 'afm', 'phvr8a.afm') afm_fname = os.path.join(rcParams['datapath'], 'fonts', 'afm', 'phvr8a.afm')
with open(afm_fname, 'rb') as fh: with open(afm_fname, 'rb') as fh:
afm = AFM(fh) afm = AFM(fh)
## A-Z Emojis for Discord # 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 AZ_EMOJIS = [(b'\\U0001f1a'.replace(b'a', bytes(hex(224 + (6 + i))[2:], "utf-8"))).decode("unicode-escape") for i in
range(26)] range(26)]
@ -57,10 +58,12 @@ class Poll:
self.name = "Quick Poll" self.name = "Quick Poll"
self.short = str(uuid4())[0:23] self.short = str(uuid4())[0:23]
self.anonymous = False self.anonymous = False
self.hide_count = False
self.reaction = True self.reaction = True
self.multiple_choice = 1 self.multiple_choice = 1
self.options_reaction = ['yes', 'no'] self.options_reaction = ['yes', 'no']
self.options_reaction_default = False self.options_reaction_default = False
self.survey_flags = []
# self.options_traditional = [] # self.options_traditional = []
# self.options_traditional_default = False # self.options_traditional_default = False
self.roles = ['@everyone'] self.roles = ['@everyone']
@ -76,6 +79,18 @@ class Poll:
self.activation_tz = 0.0 self.activation_tz = 0.0
self.votes = {} self.votes = {}
self.wizard_messages = []
def get_preset_options(self, number):
if number == 1:
return ['', '']
elif number == 2:
return ['👍', '🤐', '👎']
elif number == 3:
return ['😍', '👍', '🤐', '👎', '🤢']
elif number == 4:
return ['in favour', 'against', 'abstaining']
async def is_open(self, update_db=True): async def is_open(self, update_db=True):
if self.server is None: if self.server is None:
self.open = True self.open = True
@ -102,7 +117,9 @@ class Poll:
embed = discord.Embed(title="Poll creation Wizard", description=text, color=SETTINGS.color) embed = discord.Embed(title="Poll creation Wizard", description=text, color=SETTINGS.color)
if footer: if footer:
embed.set_footer(text="Type `stop` to cancel the wizard.") embed.set_footer(text="Type `stop` to cancel the wizard.")
return await ctx.send(embed=embed) msg = await ctx.send(embed=embed)
self.wizard_messages.append(msg)
return msg
async def wizard_says_edit(self, message, text, add=False): async def wizard_says_edit(self, message, text, add=False):
if add and message.embeds.__len__() > 0: if add and message.embeds.__len__() > 0:
@ -127,9 +144,13 @@ class Poll:
"""Pre-parse user input for wizard""" """Pre-parse user input for wizard"""
def check(m): def check(m):
return m.author == self.author return m.author == self.author
try:
reply = await self.bot.wait_for('message', timeout=180, check=check)
except asyncio.TimeoutError:
raise StopWizard
reply = await self.bot.wait_for('message', check=check)
if reply and reply.content: if reply and reply.content:
self.wizard_messages.append(reply)
if reply.content.startswith(await get_pre(self.bot, reply)): if reply.content.startswith(await get_pre(self.bot, reply)):
await self.wizard_says(ctx, f'You can\'t use bot commands during the Poll Creation Wizard.\n' await self.wizard_says(ctx, f'You can\'t use bot commands during the Poll Creation Wizard.\n'
f'Stopping the Wizard and then executing the command:\n`{reply.content}`', f'Stopping the Wizard and then executing the command:\n`{reply.content}`',
@ -144,7 +165,8 @@ class Poll:
else: else:
raise InvalidInput raise InvalidInput
def sanitize_string(self, string): @staticmethod
def sanitize_string(string):
"""Sanitize user input for wizard""" """Sanitize user input for wizard"""
# sanitize input # sanitize input
if string is None: if string is None:
@ -239,7 +261,6 @@ class Poll:
await self.add_error(message, await self.add_error(message,
f'**The label `{reply}` is not unique on this server. Choose a different one!**') f'**The label `{reply}` is not unique on this server. Choose a different one!**')
async def set_preparation(self, ctx, force=None): async def set_preparation(self, ctx, force=None):
"""Set the preparation conditions for the Poll.""" """Set the preparation conditions for the Poll."""
async def get_valid(in_reply): async def get_valid(in_reply):
@ -269,6 +290,9 @@ class Poll:
raise DateOutOfRange(dt) raise DateOutOfRange(dt)
return dt return dt
if str(force) == '-1':
return
try: try:
dt = await get_valid(force) dt = await get_valid(force)
self.activation = dt self.activation = dt
@ -302,7 +326,7 @@ class Poll:
self.active = False self.active = False
break break
except InvalidInput: except InvalidInput:
await self.add_error(message, '**I could not understand that format.**') await self.add_error(message, '**Specify the activation time in a format i can understand.**')
except TypeError: except TypeError:
await self.add_error(message, '**Type Error.**') await self.add_error(message, '**Type Error.**')
except DateOutOfRange as e: except DateOutOfRange as e:
@ -404,7 +428,6 @@ class Poll:
except OutOfRange: except OutOfRange:
await self.add_error(message, '**You can\'t have more choices than options.**') await self.add_error(message, '**You can\'t have more choices than options.**')
async def set_options_reaction(self, ctx, force=None): async def set_options_reaction(self, ctx, force=None):
"""Set the answers / options of the Poll.""" """Set the answers / options of the Poll."""
async def get_valid(in_reply): async def get_valid(in_reply):
@ -418,7 +441,7 @@ class Poll:
return int(split[0]) return int(split[0])
else: else:
raise WrongNumberOfArguments raise WrongNumberOfArguments
elif 1 < split.__len__() <= 26: elif 1 < split.__len__() <= 18:
split = [self.sanitize_string(o) for o in split] split = [self.sanitize_string(o) for o in split]
if any([len(o) < 1 for o in split]): if any([len(o) < 1 for o in split]):
raise InvalidInput raise InvalidInput
@ -427,22 +450,11 @@ class Poll:
else: else:
raise WrongNumberOfArguments raise WrongNumberOfArguments
def get_preset_options(number):
if number == 1:
return ['', '']
elif number == 2:
return ['👍', '🤐', '👎']
elif number == 3:
return ['😍', '👍', '🤐', '👎', '🤢']
elif number == 4:
return ['in favour', 'against', 'abstaining']
try: try:
options = await get_valid(force) options = await get_valid(force)
self.options_reaction_default = False self.options_reaction_default = False
if isinstance(options, int): if isinstance(options, int):
self.options_reaction = get_preset_options(options) self.options_reaction = self.get_preset_options(options)
if options <= 3: if options <= 3:
self.options_reaction_default = True self.options_reaction_default = True
else: else:
@ -473,7 +485,7 @@ class Poll:
options = await get_valid(reply) options = await get_valid(reply)
self.options_reaction_default = False self.options_reaction_default = False
if isinstance(options, int): if isinstance(options, int):
self.options_reaction = get_preset_options(options) self.options_reaction = self.get_preset_options(options)
if options <= 3: if options <= 3:
self.options_reaction_default = True self.options_reaction_default = True
else: else:
@ -483,11 +495,115 @@ class Poll:
except InvalidInput: except InvalidInput:
await self.add_error(message, await self.add_error(message,
'**Invalid entry. Type `1`, `2`, `3` or `4` or a comma separated list of ' '**Invalid entry. Type `1`, `2`, `3` or `4` or a comma separated list of '
'up to 26 options.**') 'up to 18 options.**')
except WrongNumberOfArguments: except WrongNumberOfArguments:
await self.add_error(message, await self.add_error(message,
'**You need more than 1 option! Type them in a comma separated list.**') '**You need more than 1 and less than 19 options! '
'Type them in a comma separated list.**')
async def set_survey_flags(self, ctx, force=None):
"""Decide which Options will ask for user input."""
async def get_valid(in_reply):
if not in_reply:
raise InvalidInput
split = [r.strip() for r in in_reply.split(",")]
if not split or split.__len__() == 1 and split[0] == '0':
return []
if not all([r.isdigit() for r in split]):
raise ExpectedInteger
if any([1 > int(r) or int(r) > len(self.options_reaction) for r in split]):
raise OutOfRange
return [int(r)-1 for r in split]
if self.options_reaction_default:
return
try:
self.survey_flags = await get_valid(force)
return
except InputError:
pass
text = ("**Which options should ask the user for a custom answer?**\n"
"Type `0` to skip survey options.\n"
"If you want multiple survey options, separate the numbers with a comma.\n"
"\n"
"`0 - None (classic poll)`\n"
)
for i, option in enumerate(self.options_reaction):
text += f'`{i + 1} - {option}`\n'
text += ("\n"
"If the user votes for one of these options, the bot will PM them and ask them to provide a text "
"input. You can use this to do surveys or to gather feedback for example.\n")
message = await self.wizard_says(ctx, text)
while True:
try:
if force:
reply = force
force = None
else:
reply = await self.get_user_reply(ctx)
self.survey_flags = await get_valid(reply)
await self.add_vaild(
message, f'{"None" if self.survey_flags.__len__() == 0 else ", ".join(str(f + 1) for f in self.survey_flags)}'
)
break
except InvalidInput:
await self.add_error(message, '**I can\'t read this input.**')
except ExpectedInteger:
await self.add_error(message, '**Only type positive numbers separated by a comma.**')
except OutOfRange:
await self.add_error(message, '**Only type numbers you can see in the list.**')
async def set_hide_vote_count(self, ctx, force=None):
"""Determine the live vote count is hidden or shown."""
async def get_valid(in_reply):
if not in_reply:
raise InvalidInput
is_true = ['yes', '1']
is_false = ['no', '0']
in_reply = self.sanitize_string(in_reply)
if not in_reply:
raise InvalidInput
elif in_reply.lower() in is_true:
return True
elif in_reply.lower() in is_false:
return False
else:
raise InvalidInput
try:
self.hide_count = await get_valid(force)
return
except InputError:
pass
text = ("**Do you want to hide the live vote count?**\n"
"\n"
"`0 - No, show it (Default)`\n"
"`1 - Yes, hide it`\n"
"\n"
"You will still be able to see the vote count once the poll is closed. This settings will just hide "
"the vote count while the poll is active.")
message = await self.wizard_says(ctx, text)
while True:
try:
if force:
reply = force
force = None
else:
reply = await self.get_user_reply(ctx)
self.hide_count = await get_valid(reply)
await self.add_vaild(message, f'{"Yes" if self.hide_count else "No"}')
break
except InvalidInput:
await self.add_error(message, '**You can only answer with `yes` | `1` or `no` | `0`!**')
async def set_roles(self, ctx, force=None): async def set_roles(self, ctx, force=None):
"""Set role restrictions for the Poll.""" """Set role restrictions for the Poll."""
@ -500,7 +616,7 @@ class Poll:
if split.__len__() == 1 and split[0] in ['0', 'all', 'everyone']: if split.__len__() == 1 and split[0] in ['0', 'all', 'everyone']:
return ['@everyone'] return ['@everyone']
if n_roles <= 20 and force is None: if n_roles <= 20 and not force:
if not all([r.isdigit() for r in split]): if not all([r.isdigit() for r in split]):
raise ExpectedInteger raise ExpectedInteger
elif any([int(r) > n_roles for r in split]): elif any([int(r) > n_roles for r in split]):
@ -614,6 +730,7 @@ class Poll:
force = None force = None
else: else:
reply = await self.get_user_reply(ctx) reply = await self.get_user_reply(ctx)
print(reply)
w_n = await get_valid(reply, self.server.roles) w_n = await get_valid(reply, self.server.roles)
self.weights_roles = w_n[0] self.weights_roles = w_n[0]
self.weights_numbers = w_n[1] self.weights_numbers = w_n[1]
@ -690,7 +807,7 @@ class Poll:
await self.add_vaild(message, self.duration.strftime('%d-%b-%Y %H:%M %Z')) await self.add_vaild(message, self.duration.strftime('%d-%b-%Y %H:%M %Z'))
break break
except InvalidInput: except InvalidInput:
await self.add_error(message, '**I could not understand that format.**') await self.add_error(message, '**Specify the deadline in a format I can understand.**')
except TypeError: except TypeError:
await self.add_error(message, '**Type Error.**') await self.add_error(message, '**Type Error.**')
except DateOutOfRange as e: except DateOutOfRange as e:
@ -699,6 +816,83 @@ class Poll:
def finalize(self): def finalize(self):
self.time_created = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) self.time_created = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
async def clean_up(self, channel):
if isinstance(channel, discord.TextChannel):
await channel.delete_messages(self.wizard_messages)
async def ask_for_input_DM(self, user, title, text):
embed = discord.Embed(title=title, description=text, color=SETTINGS.color)
embed.set_footer(text='You can answer anywhere.')
message = await user.send(embed=embed)
def check(m):
return m.author == user
try:
reply = await self.bot.wait_for('message', timeout=15, check=check)
if reply and reply.content:
reply = reply.content
else:
return None
except asyncio.TimeoutError:
if message.embeds.__len__() > 0:
embed.description = embed.description + '\n\n:exclamation: Request timed out. Vote was counted, ' \
'but no custom answer recorded.'
await message.edit(embed=embed)
return None
try:
reply = self.sanitize_string(reply)
except InvalidInput:
embed = discord.Embed(title=title,
description="Invalid Input. To try again, un-vote and re-vote the option.",
color=SETTINGS.color
)
await user.send(embed=embed)
if message.embeds.__len__() > 0:
embed.description = embed.description + '\n\n' + reply
await message.edit(embed=embed)
return reply
def to_command(self):
# make new label by increasing a counter at the end
try:
new_nr = int(self.short[-1]) + 1
new_label = self.short[:-1] + str(new_nr)
except ValueError:
new_label = self.short + "2"
cmd = "cmd"
cmd += " -q \"" + self.name + "\""
cmd += " -l \"" + new_label + "\""
if self.anonymous:
cmd += " -a"
if self.options_reaction_default:
for i in range(1, 5):
if self.get_preset_options(i) == self.options_reaction:
cmd += " -o \"" + str(i) + "\""
else:
cmd += " -o \"" + ", ".join(self.options_reaction) + "\""
cmd += " -sf \"" + ", ".join([str(x+1) for x in self.survey_flags]) + "\""
cmd += " -mc \"" + str(self.multiple_choice) + "\""
if self.hide_count:
cmd += " -h"
if self.roles != ["@everyone"]:
cmd += " -r \"" + ", ".join(self.roles) + "\""
if not self.active:
cmd += " -p \"specify activation time\""
if self.duration == 0:
cmd += " -d \"0\""
else:
cmd += " -d \"specify deadline\""
return cmd
async def to_dict(self): async def to_dict(self):
if self.channel is None: if self.channel is None:
cid = 0 cid = 0
@ -715,11 +909,13 @@ class Poll:
'name': self.name, 'name': self.name,
'short': self.short, 'short': self.short,
'anonymous': self.anonymous, 'anonymous': self.anonymous,
'hide_count': self.hide_count,
'reaction': self.reaction, 'reaction': self.reaction,
'multiple_choice': self.multiple_choice, 'multiple_choice': self.multiple_choice,
'options_reaction': self.options_reaction, 'options_reaction': self.options_reaction,
'reaction_default': self.options_reaction_default, 'reaction_default': self.options_reaction_default,
#'options_traditional': self.options_traditional, #'options_traditional': self.options_traditional,
'survey_flags': self.survey_flags,
'roles': self.roles, 'roles': self.roles,
'weights_roles': self.weights_roles, 'weights_roles': self.weights_roles,
'weights_numbers': self.weights_numbers, 'weights_numbers': self.weights_numbers,
@ -766,7 +962,7 @@ class Poll:
f'Question / Name: {self.name}\n' f'Question / Name: {self.name}\n'
f'Label: {self.short}\n' f'Label: {self.short}\n'
f'Anonymous: {"Yes" if self.anonymous else "No"}\n' f'Anonymous: {"Yes" if self.anonymous else "No"}\n'
f'Multiple choice: {"Yes" if self.multiple_choice else "No"}\n' f'# Choices: {"Multiple" if self.multiple_choice == 0 else self.multiple_choice}\n'
f'Answer options: {", ".join(self.options_reaction)}\n' f'Answer options: {", ".join(self.options_reaction)}\n'
f'Allowed roles: {", ".join(self.roles) if self.roles.__len__() > 0 else "@everyone"}\n' f'Allowed roles: {", ".join(self.roles) if self.roles.__len__() > 0 else "@everyone"}\n'
f'Weights for roles: {weight_str}\n' f'Weights for roles: {weight_str}\n'
@ -795,10 +991,21 @@ class Poll:
if not name: if not name:
name = member.name name = member.name
export += f'\n{name}' export += f'\n{name}: '
if self.votes[str(user_id)]['weight'] != 1: if self.votes[str(user_id)]['weight'] != 1:
export += f' (weight: {self.votes[str(user_id)]["weight"]})' export += f' (weight: {self.votes[str(user_id)]["weight"]})'
export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']]) # export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']])
choice_text_list = []
for choice in self.votes[str(user_id)]['choices']:
choice_text = self.options_reaction[choice]
if choice in self.survey_flags:
choice_text += " ("\
+ self.votes[str(user_id)]["answers"][self.survey_flags.index(choice)] \
+ ") "
choice_text_list.append(choice_text)
if choice_text_list:
export += ', '.join(choice_text_list)
export += '\n' export += '\n'
else: else:
export += '--------------------------------------------\n' \ export += '--------------------------------------------\n' \
@ -821,6 +1028,28 @@ class Poll:
# export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']]) # export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']])
export += '\n' export += '\n'
if self.survey_flags.__len__() > 0:
export += '--------------------------------------------\n' \
'CUSTOM ANSWERS (RANDOM ORDER)\n' \
'--------------------------------------------'
for i, o in enumerate(self.options_reaction):
if i not in self.survey_flags:
continue
custom_answers = []
for user_id in self.votes:
if i in self.votes[str(user_id)]["choices"]:
custom_answers.append(f'\n{self.votes[str(user_id)]["answers"][self.survey_flags.index(i)]}')
export += "\n" + o + ":"
if custom_answers.__len__() > 0:
random.shuffle(custom_answers) # randomize answers per question
for answer in custom_answers:
export += answer
export += '\n'
else:
export += "No custom answers were submitted."
export += '\n'
export += ('--------------------------------------------\n' export += ('--------------------------------------------\n'
'BOT DETAILS\n' 'BOT DETAILS\n'
'--------------------------------------------\n' '--------------------------------------------\n'
@ -854,6 +1083,13 @@ class Poll:
self.name = d['name'] self.name = d['name']
self.short = d['short'] self.short = d['short']
self.anonymous = d['anonymous'] self.anonymous = d['anonymous']
# backwards compatibility
if 'hide_count' in d.keys():
self.hide_count = d['hide_count']
else:
self.hide_count = False
self.reaction = d['reaction'] self.reaction = d['reaction']
# backwards compatibility for multiple choice # backwards compatibility for multiple choice
@ -872,6 +1108,13 @@ class Poll:
self.options_reaction = d['options_reaction'] self.options_reaction = d['options_reaction']
self.options_reaction_default = d['reaction_default'] self.options_reaction_default = d['reaction_default']
# self.options_traditional = d['options_traditional'] # self.options_traditional = d['options_traditional']
# backwards compatibility
if 'survey_flags' in d.keys():
self.survey_flags = d['survey_flags']
else:
self.survey_flags = []
self.roles = d['roles'] self.roles = d['roles']
self.weights_roles = d['weights_roles'] self.weights_roles = d['weights_roles']
self.weights_numbers = d['weights_numbers'] self.weights_numbers = d['weights_numbers']
@ -945,6 +1188,7 @@ class Poll:
embed = await self.add_field_custom(name='**Poll Question**', value=self.name, embed=embed) embed = await self.add_field_custom(name='**Poll Question**', value=self.name, embed=embed)
if self.roles != ['@everyone']:
embed = await self.add_field_custom(name='**Roles**', value=', '.join(self.roles), embed=embed) embed = await self.add_field_custom(name='**Roles**', value=', '.join(self.roles), embed=embed)
if len(self.weights_roles) > 0: if len(self.weights_roles) > 0:
weights = [] weights = []
@ -954,9 +1198,10 @@ class Poll:
embed = await self.add_field_custom(name='**Anonymous**', value=self.anonymous, embed=embed) embed = await self.add_field_custom(name='**Anonymous**', value=self.anonymous, embed=embed)
# embed = await self.add_field_custom(name='**Multiple Choice**', value=self.multiple_choice, embed=embed) if self.duration != 0:
embed = await self.add_field_custom(name='**Deadline**', value=await self.get_poll_status(), embed=embed) embed = await self.add_field_custom(name='**Deadline**', value=await self.get_poll_status(), embed=embed)
embed = await self.add_field_custom(name='**Author**', value=self.author.name, embed=embed)
# embed = await self.add_field_custom(name='**Author**', value=self.author.name, embed=embed)
if self.reaction: if self.reaction:
if self.options_reaction_default: if self.options_reaction_default:
@ -976,34 +1221,47 @@ class Poll:
vote_display.append(f'{r} {self.count_votes(i)}') vote_display.append(f'{r} {self.count_votes(i)}')
embed = await self.add_field_custom(name=text, value=' '.join(vote_display), embed=embed) embed = await self.add_field_custom(name=text, value=' '.join(vote_display), embed=embed)
else: else:
embed.add_field(name='\u200b', value='\u200b', inline=False) # embed.add_field(name='\u200b', value='\u200b', inline=False)
if await self.is_open(): if await self.is_open():
text = f'*Vote by adding reactions to the poll*. ' head = ""
if self.multiple_choice == 0: if self.multiple_choice == 0:
text += '*You can vote for multiple options.*' head += 'You can vote for multiple options:'
elif self.multiple_choice == 1: elif self.multiple_choice == 1:
text += '*You have 1 vote, but can change it.*' head += 'You have 1 vote:'
else: else:
text += f'*You have {self.multiple_choice} choices and can change them.*' head += f'You can vote for {self.multiple_choice} options:'
else: else:
text = f'*Final Results of the Poll* ' head = f'Final Results of the Poll '
if self.multiple_choice == 0: if self.multiple_choice == 0:
text += '*(Multiple Choice).*' head += '(Multiple Choice):'
elif self.multiple_choice == 1: elif self.multiple_choice == 1:
text += '*(Single Choice).*' head += '(Single Choice):'
else: else:
text += f'*(With up to {self.multiple_choice} choices).*' head += f'(With up to {self.multiple_choice} choices):'
embed = await self.add_field_custom(name='**Options**', value=text, embed=embed) # embed = await self.add_field_custom(name='**Options**', value=text, embed=embed)
options_text = '**' + head + '**\n'
for i, r in enumerate(self.options_reaction): for i, r in enumerate(self.options_reaction):
embed = await self.add_field_custom( custom_icon = ''
name=f':regional_indicator_{ascii_lowercase[i]}: {self.count_votes(i)}', if i in self.survey_flags:
value=r, custom_icon = '🖊'
embed=embed options_text += f':regional_indicator_{ascii_lowercase[i]}:{custom_icon} {r}'
) if self.hide_count and self.open:
options_text += '\n'
else:
options_text += f' **- {self.count_votes(i)} Votes**\n'
# embed = await self.add_field_custom(
# name=f':regional_indicator_{ascii_lowercase[i]}:{custom_icon} {self.count_votes(i)}',
# value=r,
# embed=embed
# )
embed.add_field(name='\u200b', value=options_text, inline=False)
# else: # else:
# embed = await self.add_field_custom(name='**Options**', value=', '.join(self.get_options()), embed=embed) # embed = await self.add_field_custom(name='**Options**', value=', '.join(self.get_options()), embed=embed)
custom_text = ""
embed.set_footer(text='React with ❔ to get info. It is not a vote option.') if len(self.survey_flags) > 0:
custom_text = " 🖊 next to an option means you can submit a custom answer."
embed.set_footer(text='React with ❔ to get info. It is not a vote option.' + custom_text)
return embed return embed
async def post_embed(self, destination): async def post_embed(self, destination):
@ -1129,7 +1387,7 @@ class Poll:
weight = max(valid_weights) weight = max(valid_weights)
if str(user.id) not in self.votes: if str(user.id) not in self.votes:
self.votes[str(user.id)] = {'weight': weight, 'choices': []} self.votes[str(user.id)] = {'weight': weight, 'choices': [], 'answers': []}
else: else:
self.votes[str(user.id)]['weight'] = weight self.votes[str(user.id)]['weight'] = weight
@ -1157,6 +1415,20 @@ class Poll:
await user.send(embed=embed) await user.send(embed=embed)
# refresh_poll = False # refresh_poll = False
else: else:
if choice in self.survey_flags:
if len(self.votes[str(user.id)]['answers']) != len(self.survey_flags):
self.votes[str(user.id)]['answers'] = ['' for i in range(len(self.survey_flags))]
custom_input = await self.ask_for_input_DM(
user,
"Custom Answer",
"For this vote option you can provide a custom reply. "
"Note that everyone will be able to see the answer. If you don't want to provide a "
"custom answer, type \"-\""
)
if not custom_input or custom_input.lower() == "-":
custom_input = "No Answer"
self.votes[str(user.id)]['answers'][self.survey_flags.index(choice)] = custom_input
self.votes[str(user.id)]['choices'].append(choice) self.votes[str(user.id)]['choices'].append(choice)
self.votes[str(user.id)]['choices'] = list(set(self.votes[str(user.id)]['choices'])) self.votes[str(user.id)]['choices'] = list(set(self.votes[str(user.id)]['choices']))
# else: # else:
@ -1175,12 +1447,9 @@ class Poll:
# commit # commit
await self.save_to_db() await self.save_to_db()
if not self.hide_count:
asyncio.ensure_future(message.edit(embed=await self.generate_embed())) asyncio.ensure_future(message.edit(embed=await self.generate_embed()))
async def unvote(self, user, option, message, lock): async def unvote(self, user, option, message, lock):
if not await self.is_open(): if not await self.is_open():
# refresh to show closed poll # refresh to show closed poll
@ -1205,8 +1474,11 @@ class Poll:
if choice != 'invalid' and choice in self.votes[str(user.id)]['choices']: if choice != 'invalid' and choice in self.votes[str(user.id)]['choices']:
try: try:
if choice in self.survey_flags and len(self.votes[str(user.id)]['answers']) == len(self.survey_flags):
self.votes[str(user.id)]['answers'][self.survey_flags.index(choice)] = ''
self.votes[str(user.id)]['choices'].remove(choice) self.votes[str(user.id)]['choices'].remove(choice)
await self.save_to_db() await self.save_to_db()
if not self.hide_count:
asyncio.ensure_future(message.edit(embed=await self.generate_embed())) asyncio.ensure_future(message.edit(embed=await self.generate_embed()))
except ValueError: except ValueError:
pass pass

View File

@ -62,7 +62,7 @@ async def on_ready():
try: try:
db_server_ids = [entry['_id'] async for entry in bot.db.config.find({}, {})] db_server_ids = [entry['_id'] async for entry in bot.db.config.find({}, {})]
for server in bot.guilds: for server in bot.guilds:
if server.id not in db_server_ids: if str(server.id) not in db_server_ids:
# create new config entry # create new config entry
await bot.db.config.update_one( await bot.db.config.update_one(
{'_id': str(server.id)}, {'_id': str(server.id)},