Merge pull request #19 from matnad/rewrite

Migration to rewrite
This commit is contained in:
Matthias Nadler 2019-04-12 20:51:19 +02:00 committed by GitHub
commit dd1573798b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 583 additions and 710 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# IDE Folders
\.idea/
# PY VENV
venv/
# Tokens and Passwwords
essentials/secrets\.py
pollmaster\.log
cogs/__pycache__/
essentials/__pycache__/
utils/__pycache__/

View File

@ -1,26 +1,15 @@
import logging import logging
import discord
from discord.ext import commands from discord.ext import commands
class Config: class Config(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@commands.command(pass_context=True) @commands.command()
@commands.has_permissions(manage_server=True) @commands.has_permissions(manage_guild=True)
async def prefix(self, ctx, *, pre): async def prefix(self, ctx, *, pre):
'''Set a custom prefix for the server.''' '''Set a custom prefix for the server.'''
server = ctx.message.server server = ctx.message.guild
# result = await self.bot.db.config.find_one({'_id': str(server_id)})
# print(f'result: `{result}`')
# if not result:
# await self.bot.db.config.insert_one({'_id': str(server_id)}, {'$set': {'_id': str(server_id), 'prefix': str(pre)}})
# self.bot.say(f'The server prefix has been set to `{pre}` Use `{pre}prefix <prefix>` to change it again.')
# return
#result['prefix'] = str(pre)
if pre.endswith('\w'): if pre.endswith('\w'):
pre = pre[:-2]+' ' pre = pre[:-2]+' '
msg = f'The server prefix has been set to `{pre}` Use `{pre}prefix <prefix>` to change it again.' msg = f'The server prefix has been set to `{pre}` Use `{pre}prefix <prefix>` to change it again.'
@ -30,51 +19,51 @@ class Config:
await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'prefix': str(pre)}}, upsert=True) await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'prefix': str(pre)}}, upsert=True)
self.bot.pre[str(server.id)] = str(pre) self.bot.pre[str(server.id)] = str(pre)
await self.bot.say(msg) await ctx.send(msg)
@commands.command(pass_context=True) @commands.command()
@commands.has_permissions(manage_server=True) @commands.has_permissions(manage_guild=True)
async def adminrole(self, ctx, *, role=None): async def adminrole(self, ctx, *, role=None):
'''Set or show the Admin Role. Members with this role can create polls and manage ALL polls. Parameter: <role> (optional)''' '''Set or show the Admin Role. Members with this role can create polls and manage ALL polls. Parameter: <role> (optional)'''
server = ctx.message.server server = ctx.message.guild
if not role: if not role:
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'): if result and result.get('admin_role'):
await self.bot.say(f'The admin role restricts which users are able to create and manage ALL polls on this server. \n' await ctx.send(f'The admin role restricts which users are able to create and manage ALL polls on this server. \n'
f'The current admin role is `{result.get("admin_role")}`. ' f'The current admin role is `{result.get("admin_role")}`. '
f'To change it type `{result.get("prefix")}adminrole <role name>`') f'To change it type `{result.get("prefix")}adminrole <role name>`')
else: else:
await self.bot.say(f'The admin role restricts which users are able to create and manage ALL polls on this server. \n' await ctx.send(f'The admin role restricts which users are able to create and manage ALL polls on this server. \n'
f'No admin role set. ' f'No admin role set. '
f'To set one type `{result["prefix"]}adminrole <role name>`') f'To set one type `{result["prefix"]}adminrole <role name>`')
elif role in [r.name for r in server.roles]: elif role in [r.name for r in server.roles]:
await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'admin_role': str(role)}}, upsert=True) await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'admin_role': str(role)}}, upsert=True)
await self.bot.say(f'Server role `{role}` can now manage all polls.') await ctx.send(f'Server role `{role}` can now manage all polls.')
else: else:
await self.bot.say(f'Server role `{role}` not found.') await ctx.send(f'Server role `{role}` not found.')
@commands.command(pass_context=True) @commands.command()
@commands.has_permissions(manage_server=True) @commands.has_permissions(manage_guild=True)
async def userrole(self, ctx, *, role=None): async def userrole(self, ctx, *, role=None):
'''Set or show the User Role. Members with this role can create polls and manage their own polls. Parameter: <role> (optional)''' '''Set or show the User Role. Members with this role can create polls and manage their own polls. Parameter: <role> (optional)'''
server = ctx.message.server server = ctx.message.guild
if not role: if not role:
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('user_role'): if result and result.get('user_role'):
await self.bot.say(f'The user role restricts which users are able to create and manage their own polls. \n' await ctx.send(f'The user role restricts which users are able to create and manage their own polls. \n'
f'The current user role is `{result.get("user_role")}`. ' f'The current user role is `{result.get("user_role")}`. '
f'To change it type `{result.get("prefix")}userrole <role name>`') f'To change it type `{result.get("prefix")}userrole <role name>`')
else: else:
await self.bot.say(f'The user role restricts which users are able to create and manage their own polls. \n' await ctx.send(f'The user role restricts which users are able to create and manage their own polls. \n'
f'No user role set. ' f'No user role set. '
f'To set one type `{result.get("prefix")}userrole <role name>`') f'To set one type `{result.get("prefix")}userrole <role name>`')
elif role in [r.name for r in server.roles]: elif role in [r.name for r in server.roles]:
await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'user_role': str(role)}}, upsert=True) await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'user_role': str(role)}}, upsert=True)
await self.bot.say(f'Server role `{role}` can now create and manage their own polls.') await ctx.send(f'Server role `{role}` can now create and manage their own polls.')
else: else:
await self.bot.say(f'Server role `{role}` not found.') await ctx.send(f'Server role `{role}` not found.')
def setup(bot): def setup(bot):
global logger global logger

View File

@ -2,10 +2,12 @@ import dbl
import asyncio import asyncio
import logging import logging
from discord.ext import commands
from essentials.settings import SETTINGS from essentials.settings import SETTINGS
class DiscordBotsOrgAPI: class DiscordBotsOrgAPI(commands.Cog):
"""Handles interactions with the discordbots.org API""" """Handles interactions with the discordbots.org API"""
def __init__(self, bot): def __init__(self, bot):
@ -22,7 +24,7 @@ class DiscordBotsOrgAPI:
try: try:
if SETTINGS.mode == 'production': if SETTINGS.mode == 'production':
await self.dblpy.post_server_count() await self.dblpy.post_server_count()
logger.info('posted server count ({})'.format(len(self.bot.servers))) logger.info('posted server count ({})'.format(len(self.bot.guilds)))
except Exception as e: except Exception as e:
logger.exception('Failed to post server count\n{}: {}'.format(type(e).__name__, e)) logger.exception('Failed to post server count\n{}: {}'.format(type(e).__name__, e))
await asyncio.sleep(1800) await asyncio.sleep(1800)

View File

@ -1,3 +1,4 @@
import asyncio
import logging import logging
import discord import discord
@ -7,37 +8,38 @@ from essentials.multi_server import get_server_pre, ask_for_server
from essentials.settings import SETTINGS from essentials.settings import SETTINGS
class Help: class Help(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.pages = ['🏠', '🆕', '🔍', '🕹', '🛠', '💖'] self.pages = ['🏠', '🆕', '🔍', '🕹', '🛠', '💖']
async def embed_list_reaction_handler(self, page, pre, msg=None): async def embed_list_reaction_handler(self, ctx, page, pre, msg=None):
embed = self.get_help_embed(page, pre) embed = self.get_help_embed(page, pre)
if msg is None: if msg is None:
msg = await self.bot.say(embed=embed) msg = await ctx.send(embed=embed)
# add reactions # add reactions
for emoji in self.pages: for emoji in self.pages:
await self.bot.add_reaction(msg, emoji) await msg.add_reaction(emoji)
else: else:
await self.bot.edit_message(msg, embed=embed) await msg.edit(embed=embed)
# wait for reactions (2 minutes) # wait for reactions (3 minutes)
def check(reaction, user): def check(rct, usr):
return reaction.emoji if user != self.bot.user else False return True if usr != self.bot.user and str(rct.emoji) in self.pages and rct.message.id == msg.id else False
res = await self.bot.wait_for_reaction(emoji=self.pages, message=msg, timeout=180, check=check) try:
reaction, user = await self.bot.wait_for('reaction_add', timeout=180, check=check)
# redirect on reaction except asyncio.TimeoutError:
if res is None: await msg.delete()
await self.bot.delete_message(msg)
return None return None
else: else:
if str(res.reaction.message.channel.type) != 'private': if isinstance(reaction.message.channel, discord.TextChannel):
await self.bot.remove_reaction(res.reaction.message, res.reaction.emoji, res.user) await reaction.message.remove_reaction(reaction.emoji, user)
return res return reaction
def get_help_embed(self, page, pre): def get_help_embed(self, page, pre):
@ -202,22 +204,22 @@ class Help:
return embed return embed
@commands.command(pass_context=True) @commands.command()
async def help(self, ctx, *, topic=None): async def help(self, ctx, *, topic=None):
server = await ask_for_server(self.bot, ctx.message) server = await ask_for_server(self.bot, ctx.message)
pre = await get_server_pre(self.bot, server) pre = await get_server_pre(self.bot, server)
res = 1 rct = 1
while res is not None: while rct is not None:
if res == 1: if rct == 1:
page = '🏠' page = '🏠'
msg = None msg = None
else: else:
page = res.reaction.emoji page = rct.emoji
msg = res.reaction.message msg = rct.message
res = await self.embed_list_reaction_handler(page, pre, msg) rct = await self.embed_list_reaction_handler(ctx, page, pre, msg)
# print(res.user, res.reaction, res.reaction.emoji) # print(res.user, res.reaction, res.reaction.emoji)
# cleanup # cleanup
await self.bot.delete_message(ctx.message) await ctx.message.delete()
def setup(bot): def setup(bot):

View File

@ -4,18 +4,18 @@ import datetime
import logging import logging
import os import os
import re import re
from uuid import uuid4
from string import ascii_lowercase, printable
import dateparser import dateparser
import pytz import pytz
import regex import regex
import discord
from uuid import uuid4
from string import ascii_lowercase
from matplotlib import rcParams from matplotlib import rcParams
from matplotlib.afm import AFM from matplotlib.afm import AFM
from pytz import UnknownTimeZoneError from pytz import UnknownTimeZoneError
from unidecode import unidecode from unidecode import unidecode
import discord
from essentials.multi_server import get_pre from essentials.multi_server import get_pre
from essentials.exceptions import * from essentials.exceptions import *
from essentials.settings import SETTINGS from essentials.settings import SETTINGS
@ -32,8 +32,9 @@ with open(afm_fname, 'rb') as 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)]
class Poll:
class Poll:
def __init__(self, bot, ctx=None, server=None, channel=None, load=False): def __init__(self, bot, ctx=None, server=None, channel=None, load=False):
self.bot = bot self.bot = bot
@ -41,7 +42,7 @@ class Poll:
if not load and ctx: if not load and ctx:
if server is None: if server is None:
server = ctx.message.server server = ctx.message.guild
if channel is None: if channel is None:
channel = ctx.message.channel channel = ctx.message.channel
@ -97,41 +98,45 @@ class Poll:
await self.save_to_db() await self.save_to_db()
return self.active return self.active
async def wizard_says(self, text, footer=True): async def wizard_says(self, ctx, text, footer=True):
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: embed.set_footer(text="Type `stop` to cancel the wizard.") if footer:
return await self.bot.say(embed=embed) embed.set_footer(text="Type `stop` to cancel the wizard.")
return await ctx.send(embed=embed)
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:
text = message.embeds[0]['description'] + text text = message.embeds[0].description + text
embed = discord.Embed(title="Poll creation Wizard", description=text, color=SETTINGS.color) embed = discord.Embed(title="Poll creation Wizard", description=text, color=SETTINGS.color)
embed.set_footer(text="Type `stop` to cancel the wizard.") embed.set_footer(text="Type `stop` to cancel the wizard.")
return await self.bot.edit_message(message, embed=embed) return await message.edit(embed=embed)
async def add_error(self, message, error): async def add_error(self, message, error):
text = '' text = ''
if message.embeds.__len__() > 0: if message.embeds.__len__() > 0:
text = message.embeds[0]['description'] + '\n\n:exclamation: ' + error text = message.embeds[0].description + '\n\n:exclamation: ' + error
return await self.wizard_says_edit(message, text) return await self.wizard_says_edit(message, text)
async def add_vaild(self, message, string): async def add_vaild(self, message, string):
text = '' text = ''
if message.embeds.__len__() > 0: if message.embeds.__len__() > 0:
text = message.embeds[0]['description'] + '\n\n' + string text = message.embeds[0].description + '\n\n' + string
return await self.wizard_says_edit(message, text) return await self.wizard_says_edit(message, text)
async def get_user_reply(self): async def get_user_reply(self, ctx):
"""Pre-parse user input for wizard""" """Pre-parse user input for wizard"""
reply = await self.bot.wait_for_message(author=self.author) def check(m):
return m.author == self.author
reply = await self.bot.wait_for('message', check=check)
if reply and reply.content: if reply and reply.content:
if reply.content.startswith(await get_pre(self.bot, reply)): if reply.content.startswith(await get_pre(self.bot, reply)):
await self.wizard_says(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}`',
footer=False) footer=False)
raise StopWizard raise StopWizard
elif reply.content.lower() == 'stop': elif reply.content.lower() == 'stop':
await self.wizard_says('Poll Wizard stopped.', footer=False) await self.wizard_says(ctx, 'Poll Wizard stopped.', footer=False)
raise StopWizard raise StopWizard
else: else:
@ -149,7 +154,7 @@ class Poll:
raise InvalidInput raise InvalidInput
return string return string
async def set_name(self, force=None): async def set_name(self, ctx, force=None):
"""Set the Question / Name of the Poll.""" """Set the Question / Name of the Poll."""
async def get_valid(in_reply): async def get_valid(in_reply):
if not in_reply: if not in_reply:
@ -172,7 +177,7 @@ class Poll:
text = ("**What is the question of your poll?**\n" text = ("**What is the question of your poll?**\n"
"Try to be descriptive without writing more than one sentence.") "Try to be descriptive without writing more than one sentence.")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -180,14 +185,14 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
self.name = await get_valid(reply) self.name = await get_valid(reply)
await self.add_vaild(message, self.name) await self.add_vaild(message, self.name)
break break
except InvalidInput: except InvalidInput:
await self.add_error(message, '**Keep the name between 3 and 200 valid characters**') await self.add_error(message, '**Keep the name between 3 and 200 valid characters**')
async def set_short(self, force=None): async def set_short(self, ctx, force=None):
"""Set the label of the Poll.""" """Set the label of the Poll."""
async def get_valid(in_reply): async def get_valid(in_reply):
if not in_reply: if not in_reply:
@ -214,7 +219,7 @@ class Poll:
text = """Great. **Now type a unique one word identifier, a label, for your poll.** text = """Great. **Now type a unique one word identifier, a label, for your poll.**
This label will be used to refer to the poll. Keep it short and significant.""" This label will be used to refer to the poll. Keep it short and significant."""
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -222,7 +227,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
self.short = await get_valid(reply) self.short = await get_valid(reply)
await self.add_vaild(message, self.short) await self.add_vaild(message, self.short)
break break
@ -235,7 +240,7 @@ class Poll:
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, 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):
if not in_reply: if not in_reply:
@ -278,7 +283,7 @@ class Poll:
"it manually. **Type `0` to activate it manually or tell me when you want to activate it** by " "it manually. **Type `0` to activate it manually or tell me when you want to activate it** by "
"typing an absolute or relative date. You can specify a timezone if you want.\n" "typing an absolute or relative date. You can specify a timezone if you want.\n"
"Examples: `in 2 days`, `next week CET`, `may 3rd 2019`, `9.11.2019 9pm EST` ") "Examples: `in 2 days`, `next week CET`, `may 3rd 2019`, `9.11.2019 9pm EST` ")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -286,7 +291,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
dt = await get_valid(reply) dt = await get_valid(reply)
self.activation = dt self.activation = dt
if self.activation == 0: if self.activation == 0:
@ -303,7 +308,7 @@ class Poll:
except DateOutOfRange as e: except DateOutOfRange as e:
await self.add_error(message, f'**{e.date.strftime("%d-%b-%Y %H:%M")} is in the past.**') await self.add_error(message, f'**{e.date.strftime("%d-%b-%Y %H:%M")} is in the past.**')
async def set_anonymous(self, force=None): async def set_anonymous(self, ctx, force=None):
"""Determine if poll is anonymous.""" """Determine if poll is anonymous."""
async def get_valid(in_reply): async def get_valid(in_reply):
if not in_reply: if not in_reply:
@ -334,7 +339,7 @@ class Poll:
"An anonymous poll has the following effects:\n" "An anonymous poll has the following effects:\n"
"🔹 You will never see who voted for which option\n" "🔹 You will never see who voted for which option\n"
"🔹 Once the poll is closed, you will see who participated (but not their choice)") "🔹 Once the poll is closed, you will see who participated (but not their choice)")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -342,40 +347,14 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
self.anonymous = await get_valid(reply) self.anonymous = await get_valid(reply)
await self.add_vaild(message, f'{"Yes" if self.anonymous else "No"}') await self.add_vaild(message, f'{"Yes" if self.anonymous else "No"}')
break break
except InvalidInput: except InvalidInput:
await self.add_error(message, '**You can only answer with `yes` | `1` or `no` | `0`!**') await self.add_error(message, '**You can only answer with `yes` | `1` or `no` | `0`!**')
async def set_multiple_choice(self, ctx, force=None):
# async def set_reaction(self, force=None):
# ''' Currently everything is reaction, this is not needed'''
# if force is not None and force in [True, False]:
# self.reaction = force
# return
# if self.stopped: return
# text = """**Do you want your users to vote by adding reactions to the poll? Type `yes` or `no`.**
# Reaction voting typically has the following properties:
# :small_blue_diamond: Voting is quick and painless (no typing required)
# :small_blue_diamond: Multiple votes are possible
# :small_blue_diamond: Not suited for a large number of options
# :small_blue_diamond: Not suited for long running polls"""
# message = await self.wizard_says(text)
#
# reply = ''
# while reply not in ['yes', 'no']:
# if reply != '':
# await self.add_error(message, '**You can only answer with `yes` or `no`!**')
# reply = await self.get_user_reply()
# if self.stopped: break
# if isinstance(reply, str): reply = reply.lower()
#
# self.reaction = reply == 'yes'
# return self.reaction
async def set_multiple_choice(self, force=None):
"""Determine if poll is multiple choice.""" """Determine if poll is multiple choice."""
async def get_valid(in_reply): async def get_valid(in_reply):
if not in_reply: if not in_reply:
@ -406,7 +385,7 @@ class Poll:
"\n" "\n"
"If the maximum choices are reached for a voter, they have to unvote an option before being able to " "If the maximum choices are reached for a voter, they have to unvote an option before being able to "
"vote for a different one.") "vote for a different one.")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -414,7 +393,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
self.multiple_choice = await get_valid(reply) self.multiple_choice = await get_valid(reply)
await self.add_vaild(message, f'{self.multiple_choice if self.multiple_choice > 0 else "No Limit"}') await self.add_vaild(message, f'{self.multiple_choice if self.multiple_choice > 0 else "No Limit"}')
break break
@ -426,7 +405,7 @@ class Poll:
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, 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):
if not in_reply: if not in_reply:
@ -482,7 +461,7 @@ class Poll:
"\n" "\n"
"Example for custom options:\n" "Example for custom options:\n"
"**apple juice, banana ice cream, kiwi slices** ") "**apple juice, banana ice cream, kiwi slices** ")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -490,7 +469,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
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):
@ -510,7 +489,7 @@ class Poll:
'**You need more than 1 option! Type them in a comma separated list.**') '**You need more than 1 option! Type them in a comma separated list.**')
async def set_roles(self, force=None): async def set_roles(self, ctx, force=None):
"""Set role restrictions for the Poll.""" """Set role restrictions for the Poll."""
async def get_valid(in_reply, roles): async def get_valid(in_reply, roles):
n_roles = roles.__len__() n_roles = roles.__len__()
@ -563,7 +542,7 @@ class Poll:
"Type `0`, `all` or `everyone` to have no restrictions.\n" "Type `0`, `all` or `everyone` to have no restrictions.\n"
"Type out the role names, separated by a comma, to restrict voting to specific roles:\n" "Type out the role names, separated by a comma, to restrict voting to specific roles:\n"
"`moderators, Editors, vips` (hint: role names are case sensitive!)\n") "`moderators, Editors, vips` (hint: role names are case sensitive!)\n")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -571,7 +550,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
self.roles = await get_valid(reply, roles) self.roles = await get_valid(reply, roles)
await self.add_vaild(message, f'{", ".join(self.roles)}') await self.add_vaild(message, f'{", ".join(self.roles)}')
break break
@ -584,7 +563,7 @@ class Poll:
except InvalidRoles as e: except InvalidRoles as e:
await self.add_error(message, f'**The following roles are invalid: {e.roles}**') await self.add_error(message, f'**The following roles are invalid: {e.roles}**')
async def set_weights(self, force=None): async def set_weights(self, ctx, force=None):
"""Set role weights for the poll.""" """Set role weights for the poll."""
async def get_valid(in_reply, server_roles): async def get_valid(in_reply, server_roles):
if not in_reply: if not in_reply:
@ -626,7 +605,7 @@ class Poll:
"A weight for the role `moderator` of `2` for example will automatically count the votes of all the moderators twice.\n" "A weight for the role `moderator` of `2` for example will automatically count the votes of all the moderators twice.\n"
"To assign weights type the role, followed by a colon, followed by the weight like this:\n" "To assign weights type the role, followed by a colon, followed by the weight like this:\n"
"`moderator: 2, newbie: 0.5`") "`moderator: 2, newbie: 0.5`")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -634,7 +613,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
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]
@ -654,7 +633,7 @@ class Poll:
except WrongNumberOfArguments: except WrongNumberOfArguments:
await self.add_error(message, f'**Not every role has a weight assigned.**') await self.add_error(message, f'**Not every role has a weight assigned.**')
async def set_duration(self, force=None): async def set_duration(self, ctx, force=None):
"""Set the duration /deadline for the Poll.""" """Set the duration /deadline for the Poll."""
async def get_valid(in_reply): async def get_valid(in_reply):
if not in_reply: if not in_reply:
@ -693,7 +672,7 @@ class Poll:
"You can specify a timezone if you want.\n" "You can specify a timezone if you want.\n"
"\n" "\n"
"Examples: `in 6 hours` or `next week CET` or `aug 15th 5:10` or `15.8.2019 11pm EST`") "Examples: `in 6 hours` or `next week CET` or `aug 15th 5:10` or `15.8.2019 11pm EST`")
message = await self.wizard_says(text) message = await self.wizard_says(ctx, text)
while True: while True:
try: try:
@ -701,7 +680,7 @@ class Poll:
reply = force reply = force
force = None force = None
else: else:
reply = await self.get_user_reply() reply = await self.get_user_reply(ctx)
dt = await get_valid(reply) dt = await get_valid(reply)
self.duration = dt self.duration = dt
if self.duration == 0: if self.duration == 0:
@ -807,15 +786,15 @@ class Poll:
for user_id in self.votes: for user_id in self.votes:
member = self.server.get_member(user_id) member = self.server.get_member(user_id)
if self.votes[user_id]['choices'].__len__() == 0: if self.votes[str(member.id)]['choices'].__len__() == 0:
continue continue
name = member.nick name = member.nick
if not name: if not name:
name = member.name name = member.name
export += f'\n{name}' export += f'\n{name}'
if self.votes[user_id]['weight'] != 1: if self.votes[str(member.id)]['weight'] != 1:
export += f' (weight: {self.votes[user_id]["weight"]})' export += f' (weight: {self.votes[str(user_id)]["weight"]})'
export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[user_id]['choices']]) export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']])
export += '\n' export += '\n'
else: else:
export += '--------------------------------------------\n' \ export += '--------------------------------------------\n' \
@ -824,15 +803,15 @@ class Poll:
for user_id in self.votes: for user_id in self.votes:
member = self.server.get_member(user_id) member = self.server.get_member(user_id)
if self.votes[user_id]['choices'].__len__() == 0: if self.votes[str(user_id)]['choices'].__len__() == 0:
continue continue
name = member.nick name = member.nick
if not name: if not name:
name = member.name name = member.name
export += f'\n{name}' export += f'\n{name}'
if self.votes[user_id]['weight'] != 1: if self.votes[str(user_id)]['weight'] != 1:
export += f' (weight: {self.votes[user_id]["weight"]})' export += f' (weight: {self.votes[str(user_id)]["weight"]})'
# export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[user_id]['choices']]) # export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']])
export += '\n' export += '\n'
export += ('--------------------------------------------\n' export += ('--------------------------------------------\n'
@ -859,11 +838,10 @@ class Poll:
async def from_dict(self, d): async def from_dict(self, d):
self.id = d['_id'] self.id = d['_id']
self.server = self.bot.get_server(str(d['server_id'])) self.server = self.bot.get_guild(int(d['server_id']))
self.channel = self.bot.get_channel(str(d['channel_id'])) self.channel = self.bot.get_channel(int(d['channel_id']))
# self.author = await self.bot.get_user_info(str(d['author']))
if self.server: if self.server:
self.author = self.server.get_member(d['author']) self.author = self.server.get_member(int(d['author']))
else: else:
self.author = None self.author = None
self.name = d['name'] self.name = d['name']
@ -911,7 +889,7 @@ class Poll:
@staticmethod @staticmethod
async def load_from_db(bot, server_id, short, ctx=None, ): async def load_from_db(bot, server_id, short, ctx=None, ):
query = await bot.db.polls.find_one({'server_id': str(server_id), 'short': str(short)}) query = await bot.db.polls.find_one({'server_id': str(server_id), 'short': short})
if query is not None: if query is not None:
p = Poll(bot, ctx, load=True) p = Poll(bot, ctx, load=True)
await p.from_dict(query) await p.from_dict(query)
@ -1018,34 +996,25 @@ class Poll:
# 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)
# embed.set_footer(text='bot is in development') embed.set_footer(text='React with ❔ to get info. It is not a vote option.')
return embed return embed
async def post_embed(self, destination=None): async def post_embed(self, destination):
if destination is None: msg = await destination.send(embed=await self.generate_embed())
msg = await self.bot.say(embed=await self.generate_embed())
else:
msg = await self.bot.send_message(destination=destination, embed= await self.generate_embed())
if self.reaction and await self.is_open() and await self.is_active(): if self.reaction and await self.is_open() and await self.is_active():
if self.options_reaction_default: if self.options_reaction_default:
for r in self.options_reaction: for r in self.options_reaction:
await self.bot.add_reaction( await msg.add_reaction(r)
msg, await msg.add_reaction('')
r
)
await self.bot.add_reaction(msg, '')
return msg return msg
else: else:
for i, r in enumerate(self.options_reaction): for i, r in enumerate(self.options_reaction):
await self.bot.add_reaction( await msg.add_reaction(AZ_EMOJIS[i])
msg, await msg.add_reaction('')
AZ_EMOJIS[i]
)
await self.bot.add_reaction(msg, '')
return msg return msg
elif not await self.is_open(): elif not await self.is_open():
await self.bot.add_reaction(msg, '') await msg.add_reaction('')
await self.bot.add_reaction(msg, '📎') await msg.add_reaction('📎')
else: else:
return msg return msg
@ -1135,8 +1104,8 @@ class Poll:
async def vote(self, user, option, message, lock): async def vote(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
await self.bot.edit_message(message, embed=await self.generate_embed()) await message.edit(embed=await self.generate_embed())
await self.bot.clear_reactions(message) await message.clear_reactions()
return return
elif not await self.is_active(): elif not await self.is_active():
return return
@ -1153,9 +1122,9 @@ 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[user.id] = {'weight': weight, 'choices': []} self.votes[str(user.id)] = {'weight': weight, 'choices': []}
else: else:
self.votes[user.id]['weight'] = weight self.votes[str(user.id)]['weight'] = weight
if self.options_reaction_default: if self.options_reaction_default:
if option in self.options_reaction: if option in self.options_reaction:
@ -1166,23 +1135,23 @@ class Poll:
if choice != 'invalid': if choice != 'invalid':
# if self.multiple_choice != 1: # more than 1 choice (0 = no limit) # if self.multiple_choice != 1: # more than 1 choice (0 = no limit)
if choice in self.votes[user.id]['choices']: if choice in self.votes[str(user.id)]['choices']:
if self.anonymous: if self.anonymous:
# anonymous multiple choice -> can't unreact so we toggle with react # anonymous multiple choice -> can't unreact so we toggle with react
logger.warning("Unvoting, should not happen for non anon polls.") logger.warning("Unvoting, should not happen for non anon polls.")
await self.unvote(user, option, message, lock) await self.unvote(user, option, message, lock)
# refresh_poll = False # refresh_poll = False
else: else:
if self.multiple_choice > 0 and self.votes[user.id]['choices'].__len__() >= self.multiple_choice: if self.multiple_choice > 0 and self.votes[str(user.id)]['choices'].__len__() >= self.multiple_choice:
say_text = f'You have reached the **maximum choices of {self.multiple_choice}** for this poll. ' \ say_text = f'You have reached the **maximum choices of {self.multiple_choice}** for this poll. ' \
f'Before you can vote again, you need to unvote one of your choices.' f'Before you can vote again, you need to unvote one of your choices.'
embed = discord.Embed(title='', description=say_text, colour=SETTINGS.color) embed = discord.Embed(title='', description=say_text, colour=SETTINGS.color)
embed.set_author(name='Pollmaster', icon_url=SETTINGS.author_icon) embed.set_author(name='Pollmaster', icon_url=SETTINGS.author_icon)
await self.bot.send_message(user, embed=embed) await user.send(embed=embed)
# refresh_poll = False # refresh_poll = False
else: else:
self.votes[user.id]['choices'].append(choice) self.votes[str(user.id)]['choices'].append(choice)
self.votes[user.id]['choices'] = list(set(self.votes[user.id]['choices'])) self.votes[str(user.id)]['choices'] = list(set(self.votes[str(user.id)]['choices']))
# else: # else:
# if [choice] == self.votes[user.id]['choices']: # if [choice] == self.votes[user.id]['choices']:
# # refresh_poll = False # # refresh_poll = False
@ -1198,26 +1167,9 @@ class Poll:
return return
# commit # commit
#if lock._waiters.__len__() == 0:
# updating DB, clearing cache and refresh if necessary
await self.save_to_db() await self.save_to_db()
# await self.bot.poll_refresh_q.put_unique_id( asyncio.ensure_future(message.edit(embed=await self.generate_embed()))
# {'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:
# cache the poll until the queue is empty
#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})
@ -1225,13 +1177,15 @@ class Poll:
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
await self.bot.edit_message(message, embed=await self.generate_embed()) await message.edit(embed=await self.generate_embed())
await self.bot.clear_reactions(message) if not isinstance(message.channel, discord.abc.PrivateChannel):
await message.clear_reactions()
return return
elif not await self.is_active(): elif not await self.is_active():
return return
if str(user.id) not in self.votes: return if str(user.id) not in self.votes:
return
choice = 'invalid' choice = 'invalid'
@ -1242,24 +1196,14 @@ class Poll:
if option in AZ_EMOJIS: if option in AZ_EMOJIS:
choice = AZ_EMOJIS.index(option) choice = AZ_EMOJIS.index(option)
if choice != 'invalid' and choice in self.votes[user.id]['choices']: if choice != 'invalid' and choice in self.votes[str(user.id)]['choices']:
try: try:
self.votes[user.id]['choices'].remove(choice) self.votes[str(user.id)]['choices'].remove(choice)
await self.save_to_db() await self.save_to_db()
asyncio.ensure_future(self.bot.edit_message(message, embed=await self.generate_embed())) asyncio.ensure_future(message.edit(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: except ValueError:
pass pass
async def has_required_role(self, user): async def has_required_role(self, user):
return not set([r.name for r in user.roles]).isdisjoint(self.roles) return not set([r.name for r in user.roles]).isdisjoint(self.roles)

View File

@ -1,13 +1,9 @@
import argparse import argparse
import asyncio import asyncio
import copy
import datetime import datetime
import json
import logging import logging
import re import re
import shlex import shlex
import string
import traceback
import discord import discord
import pytz import pytz
@ -26,13 +22,25 @@ from essentials.exceptions import StopWizard
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: 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
def get_label(self, message : discord.Message):
label = None
if message and message.embeds:
embed = message.embeds[0]
label_object = embed.author
if label_object:
label_full = label_object.name
if label_full and label_full.startswith('>> '):
label = label_full[3:]
return label
# General Methods
async def close_polls(self): async def close_polls(self):
"""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:
@ -46,8 +54,8 @@ class PollControls:
continue continue
if p.active: if p.active:
try: try:
await self.bot.send_message(p.channel, 'This poll has been scheduled and is active now!') await p.channel.send('This poll has been scheduled and is active now!')
await p.post_embed(destination=p.channel) await p.post_embed(p.channel)
except: except:
continue continue
@ -60,8 +68,8 @@ class PollControls:
continue continue
if not p.open: if not p.open:
try: try:
await self.bot.send_message(p.channel, 'This poll has reached the deadline and is closed!') await p.channel.send('This poll has reached the deadline and is closed!')
await p.post_embed(destination=p.channel) await p.post_embed(p.channel)
except: except:
continue continue
except AttributeError as ae: except AttributeError as ae:
@ -78,15 +86,15 @@ class PollControls:
await asyncio.sleep(30) await asyncio.sleep(30)
def get_lock(self, server_id): def get_lock(self, server_id):
if not self.bot.locks.get(str(server_id)): if not self.bot.locks.get(server_id):
self.bot.locks[server_id] = asyncio.Lock() self.bot.locks[server_id] = asyncio.Lock()
return self.bot.locks.get(str(server_id)) return self.bot.locks.get(server_id)
async def is_admin_or_creator(self, ctx, server, owner_id, error_msg=None): async def is_admin_or_creator(self, ctx, server, owner_id, error_msg=None):
member = server.get_member(ctx.message.author.id) member = server.get_member(ctx.message.author.id)
if member.id == owner_id: if member.id == owner_id:
return True return True
elif member.server_permissions.manage_server: elif member.guild_permissions.manage_guild:
return True return True
else: else:
result = await self.bot.db.config.find_one({'_id': str(server.id)}) result = await self.bot.db.config.find_one({'_id': str(server.id)})
@ -94,7 +102,7 @@ class PollControls:
return True return True
else: else:
if error_msg is not None: if error_msg is not None:
await self.bot.send_message(ctx.message.author, error_msg) await ctx.send(ctx.message.author, error_msg)
return False return False
async def say_error(self, ctx, error_text, footer_text=None): async def say_error(self, ctx, error_text, footer_text=None):
@ -102,17 +110,17 @@ class PollControls:
embed.set_author(name='Error', icon_url=SETTINGS.author_icon) embed.set_author(name='Error', icon_url=SETTINGS.author_icon)
if footer_text is not None: if footer_text is not None:
embed.set_footer(text=footer_text) embed.set_footer(text=footer_text)
await self.bot.say(embed=embed) await ctx.send(embed=embed)
async def say_embed(self, ctx, say_text='', title='Pollmaster', footer_text=None): async def say_embed(self, ctx, say_text='', title='Pollmaster', footer_text=None):
embed = discord.Embed(title='', description=say_text, colour=SETTINGS.color) embed = discord.Embed(title='', description=say_text, colour=SETTINGS.color)
embed.set_author(name=title, icon_url=SETTINGS.author_icon) embed.set_author(name=title, icon_url=SETTINGS.author_icon)
if footer_text is not None: if footer_text is not None:
embed.set_footer(text=footer_text) embed.set_footer(text=footer_text)
await self.bot.say(embed=embed) await ctx.send(embed=embed)
# Commands # # Commands
@commands.command(pass_context=True) @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>"""
server = await ask_for_server(self.bot, ctx.message, short) server = await ask_for_server(self.bot, ctx.message, short)
@ -120,12 +128,12 @@ class PollControls:
return return
if short is None: if short is None:
pre = await get_server_pre(self.bot, ctx.message.server) pre = await get_server_pre(self.bot, ctx.message.guild)
error = f'Please specify the label of a poll after the activate command. \n' \ error = f'Please specify the label of a poll after the activate command. \n' \
f'`{pre}activate <poll_label>`' f'`{pre}activate <poll_label>`'
await self.say_error(ctx, error) await self.say_error(ctx, error)
else: else:
p = await Poll.load_from_db(self.bot, str(server.id), short) p = await Poll.load_from_db(self.bot, server.id, short)
if p is not None: if p is not None:
# check if already active, then just do nothing # check if already active, then just do nothing
if await p.is_active(): if await p.is_active():
@ -143,25 +151,25 @@ class PollControls:
await p.save_to_db() await p.save_to_db()
await ctx.invoke(self.show, short) await ctx.invoke(self.show, short)
else: else:
error = f'Poll with label "{short}" was not found.' error = f'Poll with label "{short}" was not found. Listing prepared polls.'
# pre = await get_server_pre(self.bot, ctx.message.server) # pre = await get_server_pre(self.bot, ctx.message.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) await self.say_error(ctx, error)
await ctx.invoke(self.show) await ctx.invoke(self.show, 'prepared')
@commands.command(pass_context=True) @commands.command()
async def delete(self, ctx, *, short=None): async def delete(self, ctx, *, short=None):
'''Delete a poll. Parameter: <label>''' '''Delete a poll. Parameter: <label>'''
server = await ask_for_server(self.bot, ctx.message, short) server = await ask_for_server(self.bot, ctx.message, short)
if not server: if not server:
return return
if short is None: if short is None:
pre = await get_server_pre(self.bot, ctx.message.server) pre = await get_server_pre(self.bot, ctx.message.guild)
error = f'Please specify the label of a poll after the delete command. \n' \ error = f'Please specify the label of a poll after the delete command. \n' \
f'`{pre}delete <poll_label>`' f'`{pre}delete <poll_label>`'
await self.say_error(ctx, error) await self.say_error(ctx, error)
else: else:
p = await Poll.load_from_db(self.bot, str(server.id), short) p = await Poll.load_from_db(self.bot, server.id, short)
if p is not None: if p is not None:
# Permission Check: Admin or Creator # Permission Check: Admin or Creator
if not await self.is_admin_or_creator( if not await self.is_admin_or_creator(
@ -172,22 +180,22 @@ class PollControls:
return False return False
# Delete Poll # Delete Poll
result = await self.bot.db.polls.delete_one({'server_id': server.id, 'short': short}) result = await self.bot.db.polls.delete_one({'server_id': str(server.id), 'short': short})
if result.deleted_count == 1: if result.deleted_count == 1:
say = f'Poll with label "{short}" was successfully deleted. This action can\'t be undone!' say = f'Poll with label "{short}" was successfully deleted. This action can\'t be undone!'
title = 'Poll deleted' title = 'Poll deleted'
await self.say_embed(ctx, say, title) await self.say_embed(ctx, say, title)
else: else:
error = f'Action failed. Poll could not be deleted. You should probably report his error to the dev, thanks!`' error = f'Action failed. Poll could not be deleted. You should probably report his error to the dev, thanks!'
await self.say_error(ctx, error) await self.say_error(ctx, error)
else: else:
error = f'Poll with label "{short}" was not found.' error = f'Poll with label "{short}" was not found.'
pre = await get_server_pre(self.bot, ctx.message.server) pre = await get_server_pre(self.bot, ctx.message.guild)
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(pass_context=True) @commands.command()
async def close(self, ctx, *, short=None): async def close(self, ctx, *, short=None):
'''Close a poll. Parameter: <label>''' '''Close a poll. Parameter: <label>'''
server = await ask_for_server(self.bot, ctx.message, short) server = await ask_for_server(self.bot, ctx.message, short)
@ -195,12 +203,12 @@ class PollControls:
return return
if short is None: if short is None:
pre = await get_server_pre(self.bot, ctx.message.server) pre = await get_server_pre(self.bot, ctx.message.guild)
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 close command. \n' \
f'`{pre}close <poll_label>`' f'`{pre}close <poll_label>`'
await self.say_error(ctx, error) await self.say_error(ctx, error)
else: else:
p = await Poll.load_from_db(self.bot, str(server.id), short) p = await Poll.load_from_db(self.bot, server.id, short)
if p is not None: if p is not None:
# Permission Check: Admin or Creator # Permission Check: Admin or Creator
if not await self.is_admin_or_creator( if not await self.is_admin_or_creator(
@ -215,13 +223,13 @@ class PollControls:
await p.save_to_db() await p.save_to_db()
await ctx.invoke(self.show, short) await ctx.invoke(self.show, short)
else: else:
error = f'Poll with label "{short}" was not found.' error = f'Poll with label "{short}" was not found. Listing all open polls.'
# pre = await get_server_pre(self.bot, ctx.message.server) # pre = await get_server_pre(self.bot, ctx.message.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) await self.say_error(ctx, error)
await ctx.invoke(self.show) await ctx.invoke(self.show)
@commands.command(pass_context=True) @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>'''
server = await ask_for_server(self.bot, ctx.message, short) server = await ask_for_server(self.bot, ctx.message, short)
@ -229,26 +237,29 @@ class PollControls:
return return
if short is None: if short is None:
pre = await get_server_pre(self.bot, ctx.message.server) pre = await get_server_pre(self.bot, ctx.message.guild)
error = f'Please specify the label of a poll after the export command. \n' \ error = f'Please specify the label of a poll after the export command. \n' \
f'`{pre}export <poll_label>`' f'`{pre}export <poll_label>`'
await self.say_error(ctx, error) await self.say_error(ctx, error)
else: else:
p = await Poll.load_from_db(self.bot, str(server.id), short) p = await Poll.load_from_db(self.bot, server.id, short)
if p is not None: if p is not None:
if p.open: if p.open:
pre = await get_server_pre(self.bot, ctx.message.server) pre = await get_server_pre(self.bot, ctx.message.guild)
error_text = f'You can only export closed polls. \nPlease `{pre}close {short}` the poll first or wait for the deadline.' error_text = f'You can only export closed polls. \nPlease `{pre}close {short}` the poll first or wait for the deadline.'
await self.say_error(ctx, error_text) await self.say_error(ctx, error_text)
else: else:
# sending file # sending file
file = await p.export() file_name = await p.export()
if file is not None: if file_name is not None:
await self.bot.send_file( await ctx.message.author.send('Sending you the requested export of "{}".'.format(p.short),
ctx.message.author, file=discord.File(file_name)
file,
content='Sending you the requested export of "{}".'.format(p.short)
) )
# await self.bot.send_file(
# ctx.message.author,
# file_name,
# content='Sending you the requested export of "{}".'.format(p.short)
# )
else: else:
error_text = 'Could not export the requested poll. \nPlease report this to the developer.' error_text = 'Could not export the requested poll. \nPlease report this to the developer.'
await self.say_error(ctx, error_text) await self.say_error(ctx, error_text)
@ -259,7 +270,7 @@ class PollControls:
await self.say_error(ctx, error) await self.say_error(ctx, error)
await ctx.invoke(self.show) await ctx.invoke(self.show)
@commands.command(pass_context=True) @commands.command()
async def show(self, ctx, short='open', start=0): async def show(self, ctx, short='open', start=0):
'''Show a list of open polls or show a specific poll. Parameters: "open" (default), "closed", "prepared" or <label>''' '''Show a list of open polls or show a specific poll. Parameters: "open" (default), "closed", "prepared" or <label>'''
@ -292,102 +303,102 @@ class PollControls:
# msg = await self.embed_list_paginated(ctx, polls, item_fct, embed, per_page=8) # msg = await self.embed_list_paginated(ctx, polls, item_fct, embed, per_page=8)
pre = await get_server_pre(self.bot, server) pre = await get_server_pre(self.bot, server)
footer_text = f'type {pre}show <label> to display a poll. ' footer_text = f'type {pre}show <label> to display a poll. '
msg = await embed_list_paginated(self.bot, pre, polls, item_fct, embed, footer_prefix=footer_text, msg = await embed_list_paginated(ctx, self.bot, pre, polls, item_fct, embed, footer_prefix=footer_text,
per_page=10) per_page=10)
else: else:
p = await Poll.load_from_db(self.bot, str(server.id), short) p = await Poll.load_from_db(self.bot, server.id, short)
if p is not None: if p is not None:
error_msg = 'This poll is inactive and you have no rights to display or view it.' error_msg = 'This poll is inactive and you have no rights to display or view it.'
if not await p.is_active() and not await self.is_admin_or_creator(ctx, server, p.author, error_msg): if not await p.is_active() and not await self.is_admin_or_creator(ctx, server, p.author, error_msg):
return return
await p.post_embed() await p.post_embed(ctx)
else: else:
error = f'Poll with label {short} was not found.' error = f'Poll with label {short} was not found.'
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(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)
# 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() # @commands.command()
# helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ") # 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)
# # 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)
# #
# if cmd and cmd == 'help':
# await self.say_embed(ctx, say_text=helpstring)
# return
# #
# try: @commands.command()
# 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)
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)'''
server = await ask_for_server(self.bot, ctx.message) server = await ask_for_server(self.bot, ctx.message)
@ -395,77 +406,76 @@ class PollControls:
return return
async def route(poll): async def route(poll):
await poll.set_name(force=cmd) await poll.set_name(ctx, force=cmd)
await poll.set_short(force=str(await generate_word(self.bot, server.id))) await poll.set_short(ctx, force=str(await generate_word(self.bot, server.id)))
await poll.set_anonymous(force='no') await poll.set_anonymous(ctx, force='no')
await poll.set_options_reaction() await poll.set_options_reaction(ctx)
await poll.set_multiple_choice(force='1') await poll.set_multiple_choice(ctx, force='1')
await poll.set_roles(force='all') await poll.set_roles(ctx, force='all')
await poll.set_weights(force='none') await poll.set_weights(ctx, force='none')
await poll.set_duration(force='0') await poll.set_duration(ctx, force='0')
poll = await self.wizard(ctx, route, server) poll = await self.wizard(ctx, route, server)
if poll: if poll:
await poll.post_embed(destination=poll.channel) await poll.post_embed(poll.channel)
@commands.command(pass_context=True) @commands.command()
async def prepare(self, ctx, *, cmd=None): async def prepare(self, ctx, *, cmd=None):
'''Prepare a poll to use later. Parameters: <Question> (optional) ''' """Prepare a poll to use later. Parameters: <Question> (optional)"""
server = await ask_for_server(self.bot, ctx.message) server = await ask_for_server(self.bot, ctx.message)
if not server: if not server:
return return
async def route(poll): async def route(poll):
await poll.set_name(force=cmd) await poll.set_name(ctx, force=cmd)
await poll.set_short() await poll.set_short(ctx)
await poll.set_preparation() await poll.set_preparation(ctx)
await poll.set_anonymous() await poll.set_anonymous(ctx)
await poll.set_options_reaction() await poll.set_options_reaction(ctx)
await poll.set_multiple_choice() await poll.set_multiple_choice(ctx)
await poll.set_roles() await poll.set_roles(ctx)
await poll.set_weights() await poll.set_weights(ctx)
await poll.set_duration() await poll.set_duration(ctx)
poll = await self.wizard(ctx, route, server) poll = await self.wizard(ctx, route, server)
if poll: if poll:
await poll.post_embed(destination=ctx.message.author) await poll.post_embed(ctx.message.author)
@commands.command(pass_context=True) @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)"""
server = await ask_for_server(self.bot, ctx.message) server = await ask_for_server(self.bot, ctx.message)
if not server: if not server:
return return
async def route(poll): async def route(poll):
await poll.set_name(force=cmd) await poll.set_name(ctx, force=cmd)
await poll.set_short() await poll.set_short(ctx)
await poll.set_anonymous() await poll.set_anonymous(ctx)
await poll.set_options_reaction() await poll.set_options_reaction(ctx)
await poll.set_multiple_choice() await poll.set_multiple_choice(ctx)
await poll.set_roles() await poll.set_roles(ctx)
await poll.set_weights() await poll.set_weights(ctx)
await poll.set_duration() await poll.set_duration(ctx)
poll = await self.wizard(ctx, route, server) poll = await self.wizard(ctx, route, server)
if poll: if poll:
await poll.post_embed(destination=poll.channel) await poll.post_embed(poll.channel)
# The Wizard! # The Wizard!
async def wizard(self, ctx, route, server): async def wizard(self, ctx, route, server):
channel = await ask_for_channel(self.bot, server, ctx.message) channel = await ask_for_channel(ctx, self.bot, server, ctx.message)
if not channel: if not channel:
return return
# Permission Check # Permission Check
member = server.get_member(ctx.message.author.id) member = server.get_member(ctx.message.author.id)
if not member.server_permissions.manage_server: 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) pre = await get_server_pre(self.bot, server)
await self.bot.send_message(ctx.message.author, await ctx.message.author.send('You don\'t have sufficient rights to start new polls on this server. '
'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 '
f'`{pre}adminrole`') f'`{pre}adminrole`')
@ -485,126 +495,156 @@ class PollControls:
await poll.save_to_db() await poll.save_to_db()
return poll return poll
# BOT EVENTS (@bot.event) # # BOT EVENTS (@bot.event)
async def on_socket_raw_receive(self, raw_msg): # async def on_socket_raw_receive(self, raw_msg):
if not isinstance(raw_msg, str): # print(raw_msg)
return # if not isinstance(raw_msg, str):
msg = json.loads(raw_msg) # return
type = msg.get("t") # msg = json.loads(raw_msg)
data = msg.get("d") # type = msg.get("t")
if not data: # data = msg.get("d")
return # if not data:
emoji = data.get("emoji") # return
user_id = data.get("user_id") # # emoji = data.get("emoji")
message_id = data.get("message_id") # # user_id = data.get("user_id")
if type == "MESSAGE_REACTION_ADD": # # message_id = data.get("message_id")
await self.do_on_reaction_add(data) # if type == "MESSAGE_REACTION_ADD":
elif type == "MESSAGE_REACTION_REMOVE": # await self.do_on_reaction_add(data)
await self.do_on_reaction_remove(data) # elif type == "MESSAGE_REACTION_REMOVE":
# #await self.do_on_reaction_remove(data)
# pass
async def do_on_reaction_remove(self, data): @commands.Cog.listener()
async def on_raw_reaction_remove(self, data):
# get emoji symbol # get emoji symbol
emoji = data.get('emoji') emoji = data.emoji
if emoji: if emoji:
emoji = emoji.get('name') emoji = emoji.name
if not emoji: if not emoji:
return return
# check if removed by the bot.. this is a bit hacky but discord doesn't provide the correct info... # 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') message_id = data.message_id
user_id = data.get('user_id') user_id = data.user_id
if self.ignore_next_removed_reaction.get(str(message_id) + str(emoji)) == 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)] del self.ignore_next_removed_reaction[str(message_id) + str(emoji)]
return return
# check if we can find a poll label # check if we can find a poll label
channel_id = data.get('channel_id') message_id = data.message_id
channel_id = data.channel_id
channel = self.bot.get_channel(channel_id) channel = self.bot.get_channel(channel_id)
# user = await self.bot.get_user_info(user_id) # only do this once
server_id = data.get('guild_id') if isinstance(channel, discord.TextChannel):
if server_id: server = channel.guild
server = self.bot.get_server(server_id)
user = server.get_member(user_id) user = server.get_member(user_id)
else: message = self.bot.message_cache.get(message_id)
user = await self.bot.get_user_info(user_id) # only do this once, this is rate limited if message is None:
message = await channel.fetch_message(id=message_id)
if not channel: self.bot.message_cache.put(message_id, message)
# discord rapidly closes dm channels by desing label = self.get_label(message)
# 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:
embed = message.embeds[0]
label_object = embed.get('author')
if label_object:
label_full = label_object.get('name')
if label_full and label_full.startswith('>> '):
label = label_full[3:]
if not label: if not label:
return return
elif isinstance(channel, discord.DMChannel):
user = await self.bot.fetch_user(user_id) # only do this once
message = self.bot.message_cache.get(message_id)
if message is None:
message = await channel.fetch_message(id=message_id)
self.bot.message_cache.put(message_id, message)
label = self.get_label(message)
if not label:
return
server = await ask_for_server(self.bot, message, label)
# create message object for the reaction sender, to get correct server elif not channel:
if not server_id: # discord rapidly closes dm channels by desing
user_msg = copy.deepcopy(message) # put private channels back into the bots cache and try again
user_msg.author = user user = await self.bot.fetch_user(user_id) # only do this once
server = await ask_for_server(self.bot, user_msg, label) await user.create_dm()
server_id = server.id channel = self.bot.get_channel(channel_id)
message = self.bot.message_cache.get(message_id)
if message is None:
message = await channel.fetch_message(id=message_id)
self.bot.message_cache.put(message_id, message)
label = self.get_label(message)
if not label:
return
server = await ask_for_server(self.bot, message, label)
else:
return
# this is exclusive # this is exclusive
lock = self.get_lock(server_id) lock = self.get_lock(server.id)
async with lock: async with lock:
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): if not isinstance(p, Poll):
return return
if not p.anonymous: if not p.anonymous:
# for anonymous polls we can't unvote because we need to hide reactions # for anonymous polls we can't unvote because we need to hide reactions
await p.unvote(user, emoji, message, lock) await p.unvote(user, emoji, message, lock)
async def do_on_reaction_add(self, data): @commands.Cog.listener()
async def on_raw_reaction_add(self, data):
# dont look at bot's own reactions # dont look at bot's own reactions
user_id = data.get('user_id') user_id = data.user_id
if user_id == self.bot.user.id: if user_id == self.bot.user.id:
return return
# get emoji symbol # get emoji symbol
emoji = data.get('emoji') emoji = data.emoji
if emoji: if emoji:
emoji = emoji.get('name') emoji = emoji.name
if not emoji: if not emoji:
return return
# check if we can find a poll label # check if we can find a poll label
message_id = data.get('message_id') message_id = data.message_id
channel_id = data.get('channel_id') channel_id = data.channel_id
channel = self.bot.get_channel(channel_id) channel = self.bot.get_channel(channel_id)
user = await self.bot.get_user_info(user_id) # only do this once
if not channel: if isinstance(channel, discord.TextChannel):
server = channel.guild
user = server.get_member(user_id)
message = self.bot.message_cache.get(message_id)
if message is None:
message = await channel.fetch_message(id=message_id)
self.bot.message_cache.put(message_id, message)
label = self.get_label(message)
if not label:
return
elif isinstance(channel, discord.DMChannel):
user = await self.bot.fetch_user(user_id) # only do this once
message = self.bot.message_cache.get(message_id)
if message is None:
message = await channel.fetch_message(id=message_id)
self.bot.message_cache.put(message_id, message)
label = self.get_label(message)
if not label:
return
server = await ask_for_server(self.bot, message, label)
elif not channel:
# discord rapidly closes dm channels by desing # discord rapidly closes dm channels by desing
# put private channels back into the bots cache and try again # put private channels back into the bots cache and try again
await self.bot.start_private_message(user) user = await self.bot.fetch_user(user_id) # only do this once
await user.create_dm()
channel = self.bot.get_channel(channel_id) channel = self.bot.get_channel(channel_id)
message = await self.bot.get_message(channel=channel, id=message_id) message = self.bot.message_cache.get(message_id)
label = None if message is None:
if message and message.embeds: message = await channel.fetch_message(id=message_id)
embed = message.embeds[0] self.bot.message_cache.put(message_id, message)
label_object = embed.get('author') label = self.get_label(message)
if label_object:
label_full = label_object.get('name')
if label_full and label_full.startswith('>> '):
label = label_full[3:]
if not label: if not label:
return return
server = await ask_for_server(self.bot, message, label)
else:
return
# fetch poll # fetch poll
# create message object for the reaction sender, to get correct server # create message object for the reaction sender, to get correct server
user_msg = copy.deepcopy(message) # user_msg = copy.deepcopy(message)
user_msg.author = user # user_msg.author = user
server = await ask_for_server(self.bot, user_msg, label) # server = await ask_for_server(self.bot, user_msg, label)
# this is exclusive to keep database access sequential # this is exclusive to keep database access sequential
# hopefully it will scale well enough or I need a different solution # hopefully it will scale well enough or I need a different solution
@ -617,12 +657,10 @@ class PollControls:
# export # export
if emoji == '📎': if emoji == '📎':
# sending file # sending file
file = await p.export() file_name = await p.export()
if file is not None: if file_name is not None:
await self.bot.send_file( await user.send('Sending you the requested export of "{}".'.format(p.short),
user, file=discord.File(file_name)
file,
content='Sending you the requested export of "{}".'.format(p.short)
) )
return return
@ -643,7 +681,7 @@ class PollControls:
edit_rights = False edit_rights = False
if str(member.id) == str(p.author): if str(member.id) == str(p.author):
edit_rights = True edit_rights = True
elif member.server_permissions.manage_server: elif member.guild_permissions.manage_guild:
edit_rights = True edit_rights = True
else: else:
result = await self.bot.db.config.find_one({'_id': str(server.id)}) result = await self.bot.db.config.find_one({'_id': str(server.id)})
@ -653,9 +691,9 @@ class PollControls:
# choices # choices
choices = 'You have not voted yet.' if vote_rights else 'You can\'t vote in this poll.' choices = 'You have not voted yet.' if vote_rights else 'You can\'t vote in this poll.'
if user.id in p.votes: if str(user.id) in p.votes:
if p.votes[user.id]['choices'].__len__() > 0: if p.votes[str(user.id)]['choices'].__len__() > 0:
choices = ', '.join([p.options_reaction[c] for c in p.votes[user.id]['choices']]) choices = ', '.join([p.options_reaction[c] for c in p.votes[str(user.id)]['choices']])
embed.add_field( 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:"}', 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) value=choices, inline=False)
@ -683,7 +721,7 @@ class PollControls:
embed.add_field(name='Time left in the poll:', value=time_left, inline=False) embed.add_field(name='Time left in the poll:', value=time_left, inline=False)
await self.bot.send_message(user, 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.anonymous and p.votes.__len__() > 0:
@ -697,41 +735,41 @@ class PollControls:
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(user_id)
if not member or i not in p.votes[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
msg += f'\n{name}' msg += f'\n{name}'
if p.votes[user_id]['weight'] != 1: if p.votes[str(user_id)]['weight'] != 1:
msg += f' (weight: {p.votes[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 msg.__len__() > 1500: if msg.__len__() > 1500:
await self.bot.send_message(user, msg) await user.send(msg)
msg = '' msg = ''
if c == 0: if c == 0:
msg += '\nNo votes for this option yet.' msg += '\nNo votes for this option yet.'
msg += '\n\n' msg += '\n\n'
if msg.__len__() > 0: if msg.__len__() > 0:
await self.bot.send_message(user, msg) await user.send(msg)
return return
# Assume: User wants to vote with reaction # Assume: User wants to vote with reaction
# no rights, terminate function # no rights, terminate function
if not await p.has_required_role(member): if not await p.has_required_role(member):
await self.bot.remove_reaction(message, emoji, user) await message.remove_reaction(emoji, user)
await self.bot.send_message(user, f'You are not allowed to vote in this poll. Only users with ' await member.send(f'You are not allowed to vote in this poll. Only users with '
f'at least one of these roles can vote:\n{", ".join(p.roles)}') f'at least one of these roles can vote:\n{", ".join(p.roles)}')
return return
# check if we need to remove reactions (this will trigger on_reaction_remove) # check if we need to remove reactions (this will trigger on_reaction_remove)
if str(channel.type) != 'private' and p.anonymous: if not isinstance(channel, discord.DMChannel) and p.anonymous:
# immediately remove reaction and to be safe, remove all reactions # 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) 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
@ -739,14 +777,13 @@ class PollControls:
# cant do this until we figure out how to see who removed the reaction? # cant do this until we figure out how to see who removed the reaction?
# for now MC 1 is like MC x # for now MC 1 is like MC x
if str(channel.type) != 'private' and p.multiple_choice == 1: # if isinstance(channel, discord.TextChannel) and p.multiple_choice == 1:
# remove all other reactions # # remove all other reactions
# if lock._waiters.__len__() == 0: # # if lock._waiters.__len__() == 0:
for r in message.reactions: # for r in message.reactions:
if r.emoji and r.emoji != emoji: # if r.emoji and r.emoji != emoji:
await self.bot.remove_reaction(message, r.emoji, user) # await message.remove_reaction(r.emoji, user)
pass # pass
def setup(bot): def setup(bot):
global logger global logger

View File

@ -1,5 +1,6 @@
"""Exception Classes for the Poll Wizard""" """Exception Classes for the Poll Wizard"""
class StopWizard(RuntimeError): class StopWizard(RuntimeError):
pass pass

View File

@ -0,0 +1,23 @@
import discord
class MessageCache:
def __init__(self, _bot):
self._bot = _bot
self._cache_dict = {}
def put(self, key, value: discord.Message):
self._cache_dict[key] = value
print("cache size:", self._cache_dict.__len__())
def get(self, key):
# Try to find it in this cache, then see if it is cached in the bots own message cache
message = self._cache_dict.get(key, None)
if message == None:
for m in self._bot._connection._messages:
if m.id == key:
return m
return message
def clear(self):
self._cache_dict = {}

View File

@ -1,14 +1,13 @@
import time import asyncio
import discord import discord
from essentials.settings import SETTINGS from essentials.settings import SETTINGS
from utils.paginator import embed_list_paginated
async def get_pre(bot, message): async def get_pre(bot, message):
'''Gets the prefix for a message.''' '''Gets the prefix for a message.'''
if str(message.channel.type) == 'private': if isinstance(message.channel, discord.abc.PrivateChannel):
shared_server_list = await get_servers(bot, message) shared_server_list = await get_servers(bot, message)
if shared_server_list.__len__() == 0: if shared_server_list.__len__() == 0:
return 'pm!' return 'pm!'
@ -18,7 +17,7 @@ async def get_pre(bot, message):
# return a tuple of all prefixes.. this will check them all! # return a tuple of all prefixes.. this will check them all!
return tuple([await get_server_pre(bot, s) for s in shared_server_list]) return tuple([await get_server_pre(bot, s) for s in shared_server_list])
else: else:
return await get_server_pre(bot, message.server) return await get_server_pre(bot, message.guild)
async def get_server_pre(bot, server): async def get_server_pre(bot, server):
@ -35,16 +34,16 @@ 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.server is None: if message.guild is None:
list_of_shared_servers = [] list_of_shared_servers = []
for s in bot.servers: for s in bot.guilds:
if message.author.id in [m.id for m in s.members]: if message.author.id in [m.id for m in s.members]:
list_of_shared_servers.append(s) list_of_shared_servers.append(s)
if short is not None: if short is not None:
query = bot.db.polls.find({'short': short}) query = bot.db.polls.find({'short': short})
if query is not None: if query is not None:
server_ids_with_short = [poll['server_id'] async for poll in query] server_ids_with_short = [poll['server_id'] async for poll in query]
servers_with_short = [bot.get_server(x) for x in server_ids_with_short] servers_with_short = [bot.get_guild(x) for x in server_ids_with_short]
shared_servers_with_short = list(set(servers_with_short).intersection(set(list_of_shared_servers))) shared_servers_with_short = list(set(servers_with_short).intersection(set(list_of_shared_servers)))
if shared_servers_with_short.__len__() >= 1: if shared_servers_with_short.__len__() >= 1:
return shared_servers_with_short return shared_servers_with_short
@ -55,7 +54,7 @@ async def get_servers(bot, message, short=None):
else: else:
return list_of_shared_servers return list_of_shared_servers
else: else:
return [message.server] return [message.guild]
async def ask_for_server(bot, message, short=None): async def ask_for_server(bot, message, short=None):
@ -63,7 +62,8 @@ async def ask_for_server(bot, message, short=None):
if server_list.__len__() == 0: if server_list.__len__() == 0:
if short == None: if short == None:
await bot.say( await bot.say(
'I could not find a common server where we can see eachother. If you think this is an error, please contact the developer.') 'I could not find a common server where we can see eachother. If you think this is an error, '
'please contact the developer.')
else: else:
await bot.say(f'I could not find a server where the poll {short} exists that we both can see.') await bot.say(f'I could not find a server where the poll {short} exists that we both can see.')
return None return None
@ -76,12 +76,18 @@ async def ask_for_server(bot, message, short=None):
text += f'\n**{i}** - {name}' text += f'\n**{i}** - {name}'
i += 1 i += 1
embed = discord.Embed(title="Select your server", description=text, color=SETTINGS.color) embed = discord.Embed(title="Select your server", description=text, color=SETTINGS.color)
server_msg = await bot.send_message(message.channel, embed=embed) server_msg = await message.channel.send(embed=embed)
valid_reply = False valid_reply = False
nr = 1 nr = 1
while valid_reply == False: while valid_reply == False:
reply = await bot.wait_for_message(timeout=60, author=message.author) def check(m):
return message.author == m.author
try:
reply = await bot.wait_for('message', timeout=60, check=check)
except asyncio.TimeoutError:
pass
else:
if reply and reply.content: if reply and reply.content:
if reply.content.startswith(await get_pre(bot, message)): if reply.content.startswith(await get_pre(bot, message)):
# await bot.say('You can\'t use bot commands while I am waiting for an answer.' # await bot.say('You can\'t use bot commands while I am waiting for an answer.'
@ -95,15 +101,15 @@ async def ask_for_server(bot, message, short=None):
return server_list[nr - 1] return server_list[nr - 1]
async def ask_for_channel(bot, server, message): async def ask_for_channel(ctx, bot, server, message):
# if performed from a channel, return that channel # if performed from a channel, return that channel
if str(message.channel.type) == 'text': if not isinstance(message.channel, discord.abc.PrivateChannel):
return message.channel return message.channel
# build channel list that the user is allowed to send messages to # build channel list that the user is allowed to send messages to
user = message.author user = message.author
member = server.get_member(user.id) 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] channel_list = [c for c in server.channels if isinstance(c, discord.TextChannel) and c.permissions_for(member).send_messages]
# if exactly 1 channel, return it # if exactly 1 channel, return it
if channel_list.__len__() == 1: if channel_list.__len__() == 1:
@ -111,32 +117,41 @@ async def ask_for_channel(bot, server, message):
# if no channels, display error # if no channels, display error
if channel_list.__len__() == 0: if channel_list.__len__() == 0:
embed = discord.Embed(title="Select a channel", description='No text channels found on this server. Make sure I can see them.', color=SETTINGS.color) embed = discord.Embed(title="Select a channel", description='No text channels found on this server. Make sure '
await bot.say(embed=embed) 'I can see them.', color=SETTINGS.color)
await ctx.send(embed=embed)
return False return False
# otherwise ask for a channel # otherwise ask for a channel
i = 1 i = 1
text = 'Polls are bound to a specific channel on a server. Please select the channel for this poll by typing the corresponding number.\n' text = 'Polls are bound to a specific channel on a server. Please select the channel for this poll by typing the ' \
'corresponding number.\n '
for name in [c.name for c in channel_list]: for name in [c.name for c in channel_list]:
to_add = f'\n**{i}** - {name}' to_add = f'\n**{i}** - {name}'
# check if length doesn't exceed allowed maximum or split it into multiple messages # check if length doesn't exceed allowed maximum or split it into multiple messages
if text.__len__() + to_add.__len__() > 2048: if text.__len__() + to_add.__len__() > 2048:
embed = discord.Embed(title="Select a channel", description=text, color=SETTINGS.color) embed = discord.Embed(title="Select a channel", description=text, color=SETTINGS.color)
await bot.say(embed=embed) await ctx.send(embed=embed)
text = 'Polls are bound to a specific channel on a server. Please select the channel for this poll by typing the corresponding number.\n' text = 'Polls are bound to a specific channel on a server. Please select the channel for this poll by ' \
'typing the corresponding number.\n '
else: else:
text += to_add text += to_add
i += 1 i += 1
embed = discord.Embed(title="Select a channel", description=text, color=SETTINGS.color) embed = discord.Embed(title="Select a channel", description=text, color=SETTINGS.color)
await bot.say(embed=embed) await ctx.send(embed=embed)
valid_reply = False valid_reply = False
nr = 1 nr = 1
while valid_reply == False: while not valid_reply:
reply = await bot.wait_for_message(timeout=60, author=message.author) def check(m):
return message.author.id == m.author.id
try:
reply = await bot.wait_for('message', timeout=60)
except asyncio.TimeoutError:
pass
else:
if reply and reply.content: if reply and reply.content:
if reply.content.startswith(await get_pre(bot, message)): if reply.content.startswith(await get_pre(bot, message)):
# await bot.say('You can\'t use bot commands while I am waiting for an answer.' # await bot.say('You can\'t use bot commands while I am waiting for an answer.'

View File

@ -1,17 +1,17 @@
import asyncio
import sys import sys
import traceback import traceback
import logging
import aiohttp import aiohttp
import discord import discord
import logging
from essentials.messagecache import MessageCache
from discord.ext import commands from discord.ext import commands
from motor.motor_asyncio import AsyncIOMotorClient from motor.motor_asyncio import AsyncIOMotorClient
from essentials.multi_server import get_pre from essentials.multi_server import get_pre
from essentials.settings import SETTINGS from essentials.settings import SETTINGS
from utils.asyncio_unique_queue import UniqueQueue
from utils.import_old_database import import_old_database
bot_config = { bot_config = {
'command_prefix': get_pre, 'command_prefix': get_pre,
@ -22,7 +22,7 @@ bot_config = {
'max_messages': 15000 'max_messages': 15000
} }
bot = commands.Bot(**bot_config) bot = commands.AutoShardedBot(**bot_config)
bot.remove_command('help') bot.remove_command('help')
# logger # logger
@ -50,18 +50,18 @@ for ext in extensions:
@bot.event @bot.event
async def on_ready(): async def on_ready():
bot.owner = await bot.get_user_info(str(SETTINGS.owner_id)) bot.owner = await bot.fetch_user(SETTINGS.owner_id)
mongo = AsyncIOMotorClient(SETTINGS.mongo_db) mongo = AsyncIOMotorClient(SETTINGS.mongo_db)
bot.db = mongo.pollmaster bot.db = mongo.pollmaster
bot.session = aiohttp.ClientSession() bot.session = aiohttp.ClientSession()
print(bot.db) print(bot.db)
await bot.change_presence(game=discord.Game(name=f'pm!help - v2.2')) await bot.change_presence(status=discord.Game(name=f'pm!help - v2.2'))
# check discord server configs # check discord server configs
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.servers: for server in bot.guilds:
if server.id not in db_server_ids: if 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(
@ -69,30 +69,20 @@ async def on_ready():
{'$set': {'prefix': 'pm!', 'admin_role': 'polladmin', 'user_role': 'polluser'}}, {'$set': {'prefix': 'pm!', 'admin_role': 'polladmin', 'user_role': 'polluser'}},
upsert=True upsert=True
) )
# stopping migration support.
# try:
# await import_old_database(bot, server)
# print(str(server), "updated.")
# except:
# print(str(server.id), "failed.")
except: except:
print("Problem verifying servers.") print("Problem verifying servers.")
# cache prefixes # cache prefixes
bot.pre = {entry['_id']: entry['prefix'] async for entry in bot.db.config.find({}, {'_id', 'prefix'})} bot.pre = {entry['_id']: entry['prefix'] async for entry in bot.db.config.find({}, {'_id', 'prefix'})}
# global locks and caches for performance when voting rapidly
bot.locks = {} bot.locks = {}
# bot.poll_cache = {} bot.message_cache = MessageCache(bot)
# bot.poll_refresh_q = UniqueQueue()
print("Servers verified. Bot running.") print("Servers verified. Bot running.")
@bot.event @bot.event
async def on_command_error(e, ctx): async def on_command_error(ctx, e):
if SETTINGS.log_errors: if SETTINGS.log_errors:
ignored_exceptions = ( ignored_exceptions = (
commands.MissingRequiredArgument, commands.MissingRequiredArgument,
@ -122,22 +112,9 @@ async def on_command_error(e, ctx):
f"\n\tAuthor: <@{ctx.message.author}>", f"\n\tAuthor: <@{ctx.message.author}>",
timestamp=ctx.message.timestamp timestamp=ctx.message.timestamp
) )
await bot.send_message(bot.owner, embed=e) await ctx.send(bot.owner, embed=e)
# if SETTINGS.mode == 'development': # if SETTINGS.mode == 'development':
raise e raise e
bot.run(SETTINGS.bot_token)
@bot.event
async def on_server_join(server):
result = await bot.db.config.find_one({'_id': str(server.id)})
if result is None:
await bot.db.config.update_one(
{'_id': str(server.id)},
{'$set': {'prefix': 'pm!', 'admin_role': 'polladmin', 'user_role': 'polluser'}},
upsert=True
)
bot.pre[str(server.id)] = 'pm!'
bot.run(SETTINGS.bot_token, reconnect=True)

View File

@ -1,4 +1,4 @@
# Pollmaster V 2.2 # Pollmaster V 2.3
## Overview ## Overview
@ -34,7 +34,7 @@ Here is how Pollmaster looks in action:
| pm!help | Shows an interactive help menu | | pm!help | Shows an interactive help menu |
| pm!new | Starts a new poll with all the settings | | pm!new | Starts a new poll with all the settings |
| pm!quick | Starts a new poll with just a question and options | | pm!quick | Starts a new poll with just a question and options |
| pm!show <label> | Shows poll in a specified channel (can be different from original channel| | | pm!show <label> | Shows poll in a specified channel (can be different from original channel) |
| pm!prefix <new prefix> | Change the prefix for this server | | pm!prefix <new prefix> | Change the prefix for this server |
| pm!userrole <any role> | Set the role that has the rights to use the bot | | pm!userrole <any role> | Set the role that has the rights to use the bot |

View File

@ -1,34 +0,0 @@
aiohttp==1.0.5
async-timeout==3.0.1
attrs==18.2.0
beautifulsoup4==4.7.1
bs4==0.0.1
certifi==2018.11.29
chardet==3.0.4
cycler==0.10.0
dateparser==0.7.0
dblpy==0.1.6
discord==0.16.12
discord.py==0.16.12
idna==2.8
idna-ssl==1.1.0
kiwisolver==1.0.1
matplotlib==3.0.2
motor==2.0.0
multidict==4.5.2
numpy==1.16.1
pymongo==3.7.2
pyparsing==2.3.1
python-dateutil==2.7.5
pytz==2018.9
ratelimiter==1.2.0.post0
regex==2019.2.7
requests==2.21.0
six==1.12.0
soupsieve==1.7.3
typing-extensions==3.7.2
tzlocal==1.5.1
Unidecode==1.0.23
urllib3==1.24.1
websockets==3.4
yarl==1.3.0

View File

@ -1,11 +0,0 @@
import asyncio
class UniqueQueue(asyncio.Queue):
async def put_unique_id(self, item):
if not item.get('id'):
return
if item.get('id') not in [v.get('id') for v in self._queue]:
await self.put(item)

View File

@ -1,96 +0,0 @@
import datetime
import json
import os
import pytz
async def import_old_database(bot, server):
"""try to import the old database"""
try:
clean_server = str(server).replace("/", "")
while clean_server.startswith("."):
clean_server = clean_server[1:]
fn = 'backup/' + clean_server + '.json'
with open(fn, 'r') as infile:
polls = json.load(infile)
for p in polls:
#print(polls[p]['short'])
wr = []
wn = []
for r, n in polls[p]['weights'].items():
wr.append(r)
try:
if n.is_integer():
n = int(n)
except:
pass
wn.append(n)
created = datetime.datetime.strptime(polls[p]['datestarted'], '%d-%m-%Y %H:%M').replace(tzinfo=pytz.utc)
if polls[p]['duration'] == 0:
duration = 0
else:
duration = created + datetime.timedelta(hours=float(polls[p]['duration']))
votes = {}
for u,o in polls[p]['votes'].items():
# get weight
user = server.get_member(u)
weight = 1
if wr.__len__() > 0:
valid_weights = [wn[wr.index(r)] for r in
list(set([n.name for n in user.roles]).intersection(set(wr)))]
if valid_weights.__len__() > 0:
#print(wr, wn)
weight = max(valid_weights)
choices = []
if o in polls[p]['options']:
choices = [polls[p]['options'].index(o)]
votes[u] = {'weight': weight, 'choices': choices}
new_format = {
'server_id': str(server.id),
'channel_id': str(polls[p]['channel']),
'author': str(polls[p]['author']),
'name': polls[p]['name'],
'short': polls[p]['short'],
'anonymous': polls[p]['anonymous'],
'reaction': True,
'multiple_choice': False,
'options_reaction': polls[p]['options'],
'reaction_default': False,
'roles': polls[p]['roles'],
'weights_roles': wr,
'weights_numbers': wn,
'duration': duration,
'duration_tz': 'UTC',
'time_created': created,
'open': polls[p]['open'],
'active': True,
'activation': 0,
'activation_tz': 'UTC',
'votes': votes
}
await bot.db.polls.update_one({'server_id': str(server.id), 'short': polls[p]['short']},
{'$set': new_format}, upsert=True)
os.remove(fn)
except FileNotFoundError:
pass
except Exception as e:
print(e)
# potential update message
# text = "**Dear Server Admin!**\n\n" \
# "After more than a year in the field, today Pollmaster received it's first big update and I am excited to present you the new Version!\n\n" \
# "**TL;DR: A massive overhaul of every function. The new (now customizable) prefix is `pm!` and you can find the rest of the commands with `pm!help`**\n\n" \
# "**Here are some more highlights:**\n" \
# "🔹 Voting is no longer done per text, but by using reactions\n" \
# "🔹 Creating new polls is now an interactive process instead of command lines\n" \
# "🔹 There is now a settings for multiple choice polls\n" \
# "🔹 You can use all the commands in a private message with Pollmaster to reduce spam in your channels\n\n" \
# "For the full changelog, please visit: https://github.com/matnad/pollmaster/blob/master/changelog.md\n" \
# "All your old polls should be converted to the new format and accessible with `pm!show` or `pm!show closed`.\n" \
# "If you have any problems or questions, please join the support discord: https://discord.gg/Vgk8Nve\n\n" \
# "**No action from you is required. But you might want to update permissions for your users. " \
# "See pm!help -> configuration**\n\n" \
# "I hope you enjoy the new Pollmaster!\n" \
# "Regards, Newti#0654"

View File

@ -1,4 +1,9 @@
async def embed_list_paginated(bot, pre, items, item_fct, base_embed, footer_prefix='', msg=None, start=0, per_page=10): import asyncio
import discord
async def embed_list_paginated(ctx, bot, pre, items, item_fct, base_embed, footer_prefix='', msg=None, start=0, per_page=10):
embed = base_embed embed = base_embed
# generate list # generate list
@ -21,27 +26,30 @@ async def embed_list_paginated(bot, pre, items, item_fct, base_embed, footer_pre
# post / edit message # post / edit message
if msg is not None: if msg is not None:
await bot.edit_message(msg, embed=embed) await msg.edit(embed=embed)
if str(msg.channel.type) != 'private': if not isinstance(msg.channel, discord.abc.PrivateChannel):
await bot.clear_reactions(msg) await msg.clear_reactions()
else: else:
msg = await bot.say(embed=embed) msg = await ctx.send(embed=embed)
# add reactions # add reactions
if start > 0: if start > 0:
await bot.add_reaction(msg, '') await msg.add_reaction('')
if items.__len__() > start+per_page: if items.__len__() > start+per_page:
await bot.add_reaction(msg, '') await msg.add_reaction('')
# wait for reactions (2 minutes) # wait for reactions (2 minutes)
def check(reaction, user): def check(reaction, user):
return reaction.emoji if user != bot.user else False return True if user != bot.user and str(reaction.emoji) in ['', ''] and reaction.message.id == msg.id else False
res = await bot.wait_for_reaction(emoji=['', ''], message=msg, timeout=120, check=check) try:
reaction, user = await bot.wait_for('reaction_add', timeout=120, check=check)
except asyncio.TimeoutError:
pass
else:
# redirect on reaction # redirect on reaction
if res is None: if reaction is None:
return return
elif res.reaction.emoji == '' and start > 0: elif reaction.emoji == '' and start > 0:
await embed_list_paginated(bot, pre, items, item_fct, base_embed, footer_prefix=footer_prefix, msg=msg, start=start-per_page, per_page=per_page) await embed_list_paginated(ctx, bot, pre, items, item_fct, base_embed, footer_prefix=footer_prefix, msg=msg, start=start-per_page, per_page=per_page)
elif res.reaction.emoji == '' and items.__len__() > start+per_page: elif reaction.emoji == '' and items.__len__() > start+per_page:
await embed_list_paginated(bot, pre, items, item_fct, base_embed, footer_prefix=footer_prefix, msg=msg, start=start+per_page, per_page=per_page) await embed_list_paginated(ctx, bot, pre, items, item_fct, base_embed, footer_prefix=footer_prefix, msg=msg, start=start+per_page, per_page=per_page)

View File

@ -6,9 +6,9 @@ animals = ['frog', 'newt', 'tadpole', 'toad', 'spider', 'biddy', 'canary', 'crow
async def generate_word(bot, server_id): async def generate_word(bot, server_id):
exists = 1 exists = 1
while exists is not None: while exists is not None:
ad = random.sample(adjs, 2) ad = random.sample(adjs, 1)
an = random.sample(animals, 1) an = random.sample(animals, 1)
short = ad[0].capitalize() + ad[1].capitalize() + an[0].capitalize() short = ad[0].capitalize() + an[0].capitalize()
exists = await bot.db.polls.find_one({'server_id': str(server_id), 'short': short}) exists = await bot.db.polls.find_one({'server_id': server_id, 'short': short})
return short return short