commit
dd1573798b
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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__/
|
||||
@ -1,26 +1,15 @@
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
class Config:
|
||||
|
||||
class Config(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.has_permissions(manage_server=True)
|
||||
@commands.command()
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def prefix(self, ctx, *, pre):
|
||||
'''Set a custom prefix for the server.'''
|
||||
server = ctx.message.server
|
||||
# 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)
|
||||
|
||||
server = ctx.message.guild
|
||||
if pre.endswith('\w'):
|
||||
pre = pre[:-2]+' '
|
||||
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)
|
||||
self.bot.pre[str(server.id)] = str(pre)
|
||||
await self.bot.say(msg)
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.has_permissions(manage_server=True)
|
||||
@commands.command()
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
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)'''
|
||||
server = ctx.message.server
|
||||
server = ctx.message.guild
|
||||
|
||||
if not role:
|
||||
result = await self.bot.db.config.find_one({'_id': str(server.id)})
|
||||
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'To change it type `{result.get("prefix")}adminrole <role name>`')
|
||||
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'To set one type `{result["prefix"]}adminrole <role name>`')
|
||||
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.say(f'Server role `{role}` can now manage all polls.')
|
||||
await ctx.send(f'Server role `{role}` can now manage all polls.')
|
||||
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.has_permissions(manage_server=True)
|
||||
@commands.command()
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
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)'''
|
||||
server = ctx.message.server
|
||||
server = ctx.message.guild
|
||||
|
||||
if not role:
|
||||
result = await self.bot.db.config.find_one({'_id': str(server.id)})
|
||||
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'To change it type `{result.get("prefix")}userrole <role name>`')
|
||||
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'To set one type `{result.get("prefix")}userrole <role name>`')
|
||||
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.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:
|
||||
await self.bot.say(f'Server role `{role}` not found.')
|
||||
await ctx.send(f'Server role `{role}` not found.')
|
||||
|
||||
def setup(bot):
|
||||
global logger
|
||||
|
||||
@ -2,10 +2,12 @@ import dbl
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
from essentials.settings import SETTINGS
|
||||
|
||||
|
||||
class DiscordBotsOrgAPI:
|
||||
class DiscordBotsOrgAPI(commands.Cog):
|
||||
"""Handles interactions with the discordbots.org API"""
|
||||
|
||||
def __init__(self, bot):
|
||||
@ -22,7 +24,7 @@ class DiscordBotsOrgAPI:
|
||||
try:
|
||||
if SETTINGS.mode == 'production':
|
||||
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:
|
||||
logger.exception('Failed to post server count\n{}: {}'.format(type(e).__name__, e))
|
||||
await asyncio.sleep(1800)
|
||||
|
||||
50
cogs/help.py
50
cogs/help.py
@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import discord
|
||||
@ -7,37 +8,38 @@ from essentials.multi_server import get_server_pre, ask_for_server
|
||||
from essentials.settings import SETTINGS
|
||||
|
||||
|
||||
class Help:
|
||||
class Help(commands.Cog):
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
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)
|
||||
|
||||
if msg is None:
|
||||
msg = await self.bot.say(embed=embed)
|
||||
msg = await ctx.send(embed=embed)
|
||||
# add reactions
|
||||
for emoji in self.pages:
|
||||
await self.bot.add_reaction(msg, emoji)
|
||||
await msg.add_reaction(emoji)
|
||||
else:
|
||||
await self.bot.edit_message(msg, embed=embed)
|
||||
await msg.edit(embed=embed)
|
||||
|
||||
# wait for reactions (2 minutes)
|
||||
def check(reaction, user):
|
||||
return reaction.emoji if user != self.bot.user else False
|
||||
# wait for reactions (3 minutes)
|
||||
def check(rct, usr):
|
||||
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)
|
||||
|
||||
# redirect on reaction
|
||||
if res is None:
|
||||
await self.bot.delete_message(msg)
|
||||
try:
|
||||
reaction, user = await self.bot.wait_for('reaction_add', timeout=180, check=check)
|
||||
except asyncio.TimeoutError:
|
||||
await msg.delete()
|
||||
return None
|
||||
else:
|
||||
if str(res.reaction.message.channel.type) != 'private':
|
||||
await self.bot.remove_reaction(res.reaction.message, res.reaction.emoji, res.user)
|
||||
return res
|
||||
if isinstance(reaction.message.channel, discord.TextChannel):
|
||||
await reaction.message.remove_reaction(reaction.emoji, user)
|
||||
return reaction
|
||||
|
||||
|
||||
|
||||
def get_help_embed(self, page, pre):
|
||||
|
||||
@ -202,22 +204,22 @@ class Help:
|
||||
|
||||
return embed
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.command()
|
||||
async def help(self, ctx, *, topic=None):
|
||||
server = await ask_for_server(self.bot, ctx.message)
|
||||
pre = await get_server_pre(self.bot, server)
|
||||
res = 1
|
||||
while res is not None:
|
||||
if res == 1:
|
||||
rct = 1
|
||||
while rct is not None:
|
||||
if rct == 1:
|
||||
page = '🏠'
|
||||
msg = None
|
||||
else:
|
||||
page = res.reaction.emoji
|
||||
msg = res.reaction.message
|
||||
res = await self.embed_list_reaction_handler(page, pre, msg)
|
||||
page = rct.emoji
|
||||
msg = rct.message
|
||||
rct = await self.embed_list_reaction_handler(ctx, page, pre, msg)
|
||||
# print(res.user, res.reaction, res.reaction.emoji)
|
||||
# cleanup
|
||||
await self.bot.delete_message(ctx.message)
|
||||
await ctx.message.delete()
|
||||
|
||||
|
||||
def setup(bot):
|
||||
|
||||
234
cogs/poll.py
234
cogs/poll.py
@ -4,18 +4,18 @@ import datetime
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from uuid import uuid4
|
||||
from string import ascii_lowercase, printable
|
||||
|
||||
import dateparser
|
||||
import pytz
|
||||
import regex
|
||||
import discord
|
||||
|
||||
from uuid import uuid4
|
||||
from string import ascii_lowercase
|
||||
from matplotlib import rcParams
|
||||
from matplotlib.afm import AFM
|
||||
from pytz import UnknownTimeZoneError
|
||||
from unidecode import unidecode
|
||||
|
||||
import discord
|
||||
from essentials.multi_server import get_pre
|
||||
from essentials.exceptions import *
|
||||
from essentials.settings import SETTINGS
|
||||
@ -32,8 +32,9 @@ with open(afm_fname, 'rb') as fh:
|
||||
## A-Z Emojis for Discord
|
||||
AZ_EMOJIS = [(b'\\U0001f1a'.replace(b'a', bytes(hex(224 + (6 + i))[2:], "utf-8"))).decode("unicode-escape") for i in
|
||||
range(26)]
|
||||
class Poll:
|
||||
|
||||
|
||||
class Poll:
|
||||
def __init__(self, bot, ctx=None, server=None, channel=None, load=False):
|
||||
|
||||
self.bot = bot
|
||||
@ -41,7 +42,7 @@ class Poll:
|
||||
|
||||
if not load and ctx:
|
||||
if server is None:
|
||||
server = ctx.message.server
|
||||
server = ctx.message.guild
|
||||
|
||||
if channel is None:
|
||||
channel = ctx.message.channel
|
||||
@ -97,41 +98,45 @@ class Poll:
|
||||
await self.save_to_db()
|
||||
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)
|
||||
if footer: embed.set_footer(text="Type `stop` to cancel the wizard.")
|
||||
return await self.bot.say(embed=embed)
|
||||
if footer:
|
||||
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):
|
||||
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.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):
|
||||
text = ''
|
||||
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)
|
||||
|
||||
async def add_vaild(self, message, string):
|
||||
text = ''
|
||||
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)
|
||||
|
||||
async def get_user_reply(self):
|
||||
async def get_user_reply(self, ctx):
|
||||
"""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.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}`',
|
||||
footer=False)
|
||||
raise StopWizard
|
||||
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
|
||||
|
||||
else:
|
||||
@ -149,7 +154,7 @@ class Poll:
|
||||
raise InvalidInput
|
||||
return string
|
||||
|
||||
async def set_name(self, force=None):
|
||||
async def set_name(self, ctx, force=None):
|
||||
"""Set the Question / Name of the Poll."""
|
||||
async def get_valid(in_reply):
|
||||
if not in_reply:
|
||||
@ -172,7 +177,7 @@ class Poll:
|
||||
|
||||
text = ("**What is the question of your poll?**\n"
|
||||
"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:
|
||||
try:
|
||||
@ -180,14 +185,14 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
self.name = await get_valid(reply)
|
||||
await self.add_vaild(message, self.name)
|
||||
break
|
||||
except InvalidInput:
|
||||
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."""
|
||||
async def get_valid(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.**
|
||||
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:
|
||||
try:
|
||||
@ -222,7 +227,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
self.short = await get_valid(reply)
|
||||
await self.add_vaild(message, self.short)
|
||||
break
|
||||
@ -235,7 +240,7 @@ class Poll:
|
||||
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."""
|
||||
async def get_valid(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 "
|
||||
"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` ")
|
||||
message = await self.wizard_says(text)
|
||||
message = await self.wizard_says(ctx, text)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -286,7 +291,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
dt = await get_valid(reply)
|
||||
self.activation = dt
|
||||
if self.activation == 0:
|
||||
@ -303,7 +308,7 @@ class Poll:
|
||||
except DateOutOfRange as e:
|
||||
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."""
|
||||
async def get_valid(in_reply):
|
||||
if not in_reply:
|
||||
@ -334,7 +339,7 @@ class Poll:
|
||||
"An anonymous poll has the following effects:\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)")
|
||||
message = await self.wizard_says(text)
|
||||
message = await self.wizard_says(ctx, text)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -342,40 +347,14 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
self.anonymous = await get_valid(reply)
|
||||
await self.add_vaild(message, f'{"Yes" if self.anonymous else "No"}')
|
||||
break
|
||||
except InvalidInput:
|
||||
await self.add_error(message, '**You can only answer with `yes` | `1` or `no` | `0`!**')
|
||||
|
||||
|
||||
# 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):
|
||||
async def set_multiple_choice(self, ctx, force=None):
|
||||
"""Determine if poll is multiple choice."""
|
||||
async def get_valid(in_reply):
|
||||
if not in_reply:
|
||||
@ -406,7 +385,7 @@ class Poll:
|
||||
"\n"
|
||||
"If the maximum choices are reached for a voter, they have to unvote an option before being able to "
|
||||
"vote for a different one.")
|
||||
message = await self.wizard_says(text)
|
||||
message = await self.wizard_says(ctx, text)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -414,7 +393,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
self.multiple_choice = await get_valid(reply)
|
||||
await self.add_vaild(message, f'{self.multiple_choice if self.multiple_choice > 0 else "No Limit"}')
|
||||
break
|
||||
@ -426,7 +405,7 @@ class Poll:
|
||||
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."""
|
||||
async def get_valid(in_reply):
|
||||
if not in_reply:
|
||||
@ -482,7 +461,7 @@ class Poll:
|
||||
"\n"
|
||||
"Example for custom options:\n"
|
||||
"**apple juice, banana ice cream, kiwi slices** ")
|
||||
message = await self.wizard_says(text)
|
||||
message = await self.wizard_says(ctx, text)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -490,7 +469,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
options = await get_valid(reply)
|
||||
self.options_reaction_default = False
|
||||
if isinstance(options, int):
|
||||
@ -510,7 +489,7 @@ class Poll:
|
||||
'**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."""
|
||||
async def get_valid(in_reply, roles):
|
||||
n_roles = roles.__len__()
|
||||
@ -563,7 +542,7 @@ class Poll:
|
||||
"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"
|
||||
"`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:
|
||||
try:
|
||||
@ -571,7 +550,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
self.roles = await get_valid(reply, roles)
|
||||
await self.add_vaild(message, f'{", ".join(self.roles)}')
|
||||
break
|
||||
@ -584,7 +563,7 @@ class Poll:
|
||||
except InvalidRoles as e:
|
||||
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."""
|
||||
async def get_valid(in_reply, server_roles):
|
||||
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"
|
||||
"To assign weights type the role, followed by a colon, followed by the weight like this:\n"
|
||||
"`moderator: 2, newbie: 0.5`")
|
||||
message = await self.wizard_says(text)
|
||||
message = await self.wizard_says(ctx, text)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -634,7 +613,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
w_n = await get_valid(reply, self.server.roles)
|
||||
self.weights_roles = w_n[0]
|
||||
self.weights_numbers = w_n[1]
|
||||
@ -654,7 +633,7 @@ class Poll:
|
||||
except WrongNumberOfArguments:
|
||||
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."""
|
||||
async def get_valid(in_reply):
|
||||
if not in_reply:
|
||||
@ -693,7 +672,7 @@ class Poll:
|
||||
"You can specify a timezone if you want.\n"
|
||||
"\n"
|
||||
"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:
|
||||
try:
|
||||
@ -701,7 +680,7 @@ class Poll:
|
||||
reply = force
|
||||
force = None
|
||||
else:
|
||||
reply = await self.get_user_reply()
|
||||
reply = await self.get_user_reply(ctx)
|
||||
dt = await get_valid(reply)
|
||||
self.duration = dt
|
||||
if self.duration == 0:
|
||||
@ -807,15 +786,15 @@ class Poll:
|
||||
|
||||
for user_id in self.votes:
|
||||
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
|
||||
name = member.nick
|
||||
if not name:
|
||||
name = member.name
|
||||
export += f'\n{name}'
|
||||
if self.votes[user_id]['weight'] != 1:
|
||||
export += f' (weight: {self.votes[user_id]["weight"]})'
|
||||
export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[user_id]['choices']])
|
||||
if self.votes[str(member.id)]['weight'] != 1:
|
||||
export += f' (weight: {self.votes[str(user_id)]["weight"]})'
|
||||
export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']])
|
||||
export += '\n'
|
||||
else:
|
||||
export += '--------------------------------------------\n' \
|
||||
@ -824,15 +803,15 @@ class Poll:
|
||||
|
||||
for user_id in self.votes:
|
||||
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
|
||||
name = member.nick
|
||||
if not name:
|
||||
name = member.name
|
||||
export += f'\n{name}'
|
||||
if self.votes[user_id]['weight'] != 1:
|
||||
export += f' (weight: {self.votes[user_id]["weight"]})'
|
||||
# export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[user_id]['choices']])
|
||||
if self.votes[str(user_id)]['weight'] != 1:
|
||||
export += f' (weight: {self.votes[str(user_id)]["weight"]})'
|
||||
# export += ': ' + ', '.join([self.options_reaction[c] for c in self.votes[str(user_id)]['choices']])
|
||||
export += '\n'
|
||||
|
||||
export += ('--------------------------------------------\n'
|
||||
@ -859,11 +838,10 @@ class Poll:
|
||||
async def from_dict(self, d):
|
||||
|
||||
self.id = d['_id']
|
||||
self.server = self.bot.get_server(str(d['server_id']))
|
||||
self.channel = self.bot.get_channel(str(d['channel_id']))
|
||||
# self.author = await self.bot.get_user_info(str(d['author']))
|
||||
self.server = self.bot.get_guild(int(d['server_id']))
|
||||
self.channel = self.bot.get_channel(int(d['channel_id']))
|
||||
if self.server:
|
||||
self.author = self.server.get_member(d['author'])
|
||||
self.author = self.server.get_member(int(d['author']))
|
||||
else:
|
||||
self.author = None
|
||||
self.name = d['name']
|
||||
@ -911,7 +889,7 @@ class Poll:
|
||||
|
||||
@staticmethod
|
||||
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:
|
||||
p = Poll(bot, ctx, load=True)
|
||||
await p.from_dict(query)
|
||||
@ -1018,34 +996,25 @@ class Poll:
|
||||
# else:
|
||||
# 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
|
||||
|
||||
async def post_embed(self, destination=None):
|
||||
if destination is None:
|
||||
msg = await self.bot.say(embed=await self.generate_embed())
|
||||
else:
|
||||
msg = await self.bot.send_message(destination=destination, embed= await self.generate_embed())
|
||||
async def post_embed(self, destination):
|
||||
msg = await destination.send(embed=await self.generate_embed())
|
||||
if self.reaction and await self.is_open() and await self.is_active():
|
||||
if self.options_reaction_default:
|
||||
for r in self.options_reaction:
|
||||
await self.bot.add_reaction(
|
||||
msg,
|
||||
r
|
||||
)
|
||||
await self.bot.add_reaction(msg, '❔')
|
||||
await msg.add_reaction(r)
|
||||
await msg.add_reaction('❔')
|
||||
return msg
|
||||
else:
|
||||
for i, r in enumerate(self.options_reaction):
|
||||
await self.bot.add_reaction(
|
||||
msg,
|
||||
AZ_EMOJIS[i]
|
||||
)
|
||||
await self.bot.add_reaction(msg, '❔')
|
||||
await msg.add_reaction(AZ_EMOJIS[i])
|
||||
await msg.add_reaction('❔')
|
||||
return msg
|
||||
elif not await self.is_open():
|
||||
await self.bot.add_reaction(msg, '❔')
|
||||
await self.bot.add_reaction(msg, '📎')
|
||||
await msg.add_reaction('❔')
|
||||
await msg.add_reaction('📎')
|
||||
else:
|
||||
return msg
|
||||
|
||||
@ -1135,8 +1104,8 @@ class Poll:
|
||||
async def vote(self, user, option, message, lock):
|
||||
if not await self.is_open():
|
||||
# refresh to show closed poll
|
||||
await self.bot.edit_message(message, embed=await self.generate_embed())
|
||||
await self.bot.clear_reactions(message)
|
||||
await message.edit(embed=await self.generate_embed())
|
||||
await message.clear_reactions()
|
||||
return
|
||||
elif not await self.is_active():
|
||||
return
|
||||
@ -1153,9 +1122,9 @@ class Poll:
|
||||
weight = max(valid_weights)
|
||||
|
||||
if str(user.id) not in self.votes:
|
||||
self.votes[user.id] = {'weight': weight, 'choices': []}
|
||||
self.votes[str(user.id)] = {'weight': weight, 'choices': []}
|
||||
else:
|
||||
self.votes[user.id]['weight'] = weight
|
||||
self.votes[str(user.id)]['weight'] = weight
|
||||
|
||||
if self.options_reaction_default:
|
||||
if option in self.options_reaction:
|
||||
@ -1166,23 +1135,23 @@ class Poll:
|
||||
|
||||
if choice != 'invalid':
|
||||
# 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:
|
||||
# anonymous multiple choice -> can't unreact so we toggle with react
|
||||
logger.warning("Unvoting, should not happen for non anon polls.")
|
||||
await self.unvote(user, option, message, lock)
|
||||
# refresh_poll = False
|
||||
else:
|
||||
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. ' \
|
||||
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.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
|
||||
else:
|
||||
self.votes[user.id]['choices'].append(choice)
|
||||
self.votes[user.id]['choices'] = list(set(self.votes[user.id]['choices']))
|
||||
self.votes[str(user.id)]['choices'].append(choice)
|
||||
self.votes[str(user.id)]['choices'] = list(set(self.votes[str(user.id)]['choices']))
|
||||
# else:
|
||||
# if [choice] == self.votes[user.id]['choices']:
|
||||
# # refresh_poll = False
|
||||
@ -1198,26 +1167,9 @@ class Poll:
|
||||
return
|
||||
|
||||
# commit
|
||||
#if lock._waiters.__len__() == 0:
|
||||
# updating DB, clearing cache and refresh if necessary
|
||||
await self.save_to_db()
|
||||
# await self.bot.poll_refresh_q.put_unique_id(
|
||||
# {'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock})
|
||||
# if self.bot.poll_cache.get(str(self.server.id) + self.short):
|
||||
# del self.bot.poll_cache[str(self.server.id) + self.short]
|
||||
# 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()))
|
||||
asyncio.ensure_future(message.edit(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):
|
||||
if not await self.is_open():
|
||||
# refresh to show closed poll
|
||||
await self.bot.edit_message(message, embed=await self.generate_embed())
|
||||
await self.bot.clear_reactions(message)
|
||||
await message.edit(embed=await self.generate_embed())
|
||||
if not isinstance(message.channel, discord.abc.PrivateChannel):
|
||||
await message.clear_reactions()
|
||||
return
|
||||
elif not await self.is_active():
|
||||
return
|
||||
|
||||
if str(user.id) not in self.votes: return
|
||||
if str(user.id) not in self.votes:
|
||||
return
|
||||
|
||||
choice = 'invalid'
|
||||
|
||||
@ -1242,24 +1196,14 @@ class Poll:
|
||||
if option in AZ_EMOJIS:
|
||||
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:
|
||||
self.votes[user.id]['choices'].remove(choice)
|
||||
self.votes[str(user.id)]['choices'].remove(choice)
|
||||
await self.save_to_db()
|
||||
asyncio.ensure_future(self.bot.edit_message(message, embed=await self.generate_embed()))
|
||||
# if lock._waiters.__len__() == 0:
|
||||
# # updating DB, clearing cache and refreshing message
|
||||
# await self.save_to_db()
|
||||
# await self.bot.poll_refresh_q.put_unique_id(
|
||||
# {'id': self.id, 'msg': message, 'sid': self.server.id, 'label': self.short, 'lock': lock})
|
||||
# if self.bot.poll_cache.get(str(self.server.id) + self.short):
|
||||
# del self.bot.poll_cache[str(self.server.id) + self.short]
|
||||
# # await self.bot.edit_message(message, embed=await self.generate_embed())
|
||||
# else:
|
||||
# # cache the poll until the queue is empty
|
||||
# self.bot.poll_cache[str(self.server.id) + self.short] = self
|
||||
asyncio.ensure_future(message.edit(embed=await self.generate_embed()))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
async def has_required_role(self, user):
|
||||
return not set([r.name for r in user.roles]).isdisjoint(self.roles)
|
||||
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import shlex
|
||||
import string
|
||||
import traceback
|
||||
|
||||
import discord
|
||||
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
|
||||
range(26)]
|
||||
|
||||
class PollControls:
|
||||
class PollControls(commands.Cog):
|
||||
def __init__(self, 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 = {}
|
||||
#
|
||||
# # 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):
|
||||
"""This function runs every 60 seconds to schedule prepared polls and close expired polls"""
|
||||
while True:
|
||||
@ -46,8 +54,8 @@ class PollControls:
|
||||
continue
|
||||
if p.active:
|
||||
try:
|
||||
await self.bot.send_message(p.channel, 'This poll has been scheduled and is active now!')
|
||||
await p.post_embed(destination=p.channel)
|
||||
await p.channel.send('This poll has been scheduled and is active now!')
|
||||
await p.post_embed(p.channel)
|
||||
except:
|
||||
continue
|
||||
|
||||
@ -60,8 +68,8 @@ class PollControls:
|
||||
continue
|
||||
if not p.open:
|
||||
try:
|
||||
await self.bot.send_message(p.channel, 'This poll has reached the deadline and is closed!')
|
||||
await p.post_embed(destination=p.channel)
|
||||
await p.channel.send('This poll has reached the deadline and is closed!')
|
||||
await p.post_embed(p.channel)
|
||||
except:
|
||||
continue
|
||||
except AttributeError as ae:
|
||||
@ -78,15 +86,15 @@ class PollControls:
|
||||
await asyncio.sleep(30)
|
||||
|
||||
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()
|
||||
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):
|
||||
member = server.get_member(ctx.message.author.id)
|
||||
if member.id == owner_id:
|
||||
return True
|
||||
elif member.server_permissions.manage_server:
|
||||
elif member.guild_permissions.manage_guild:
|
||||
return True
|
||||
else:
|
||||
result = await self.bot.db.config.find_one({'_id': str(server.id)})
|
||||
@ -94,7 +102,7 @@ class PollControls:
|
||||
return True
|
||||
else:
|
||||
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
|
||||
|
||||
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)
|
||||
if footer_text is not None:
|
||||
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):
|
||||
embed = discord.Embed(title='', description=say_text, colour=SETTINGS.color)
|
||||
embed.set_author(name=title, icon_url=SETTINGS.author_icon)
|
||||
if footer_text is not None:
|
||||
embed.set_footer(text=footer_text)
|
||||
await self.bot.say(embed=embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
# Commands
|
||||
@commands.command(pass_context=True)
|
||||
# # Commands
|
||||
@commands.command()
|
||||
async def activate(self, ctx, *, short=None):
|
||||
"""Activate a prepared poll. Parameter: <label>"""
|
||||
server = await ask_for_server(self.bot, ctx.message, short)
|
||||
@ -120,12 +128,12 @@ class PollControls:
|
||||
return
|
||||
|
||||
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' \
|
||||
f'`{pre}activate <poll_label>`'
|
||||
await self.say_error(ctx, error)
|
||||
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:
|
||||
# check if already active, then just do nothing
|
||||
if await p.is_active():
|
||||
@ -143,25 +151,25 @@ class PollControls:
|
||||
await p.save_to_db()
|
||||
await ctx.invoke(self.show, short)
|
||||
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)
|
||||
# footer = f'Type {pre}show to display all polls'
|
||||
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):
|
||||
'''Delete a poll. Parameter: <label>'''
|
||||
server = await ask_for_server(self.bot, ctx.message, short)
|
||||
if not server:
|
||||
return
|
||||
if short is None:
|
||||
pre = await get_server_pre(self.bot, ctx.message.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' \
|
||||
f'`{pre}delete <poll_label>`'
|
||||
await self.say_error(ctx, error)
|
||||
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:
|
||||
# Permission Check: Admin or Creator
|
||||
if not await self.is_admin_or_creator(
|
||||
@ -172,22 +180,22 @@ class PollControls:
|
||||
return False
|
||||
|
||||
# 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:
|
||||
say = f'Poll with label "{short}" was successfully deleted. This action can\'t be undone!'
|
||||
title = 'Poll deleted'
|
||||
await self.say_embed(ctx, say, title)
|
||||
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)
|
||||
|
||||
else:
|
||||
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'
|
||||
await self.say_error(ctx, error, footer)
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.command()
|
||||
async def close(self, ctx, *, short=None):
|
||||
'''Close a poll. Parameter: <label>'''
|
||||
server = await ask_for_server(self.bot, ctx.message, short)
|
||||
@ -195,12 +203,12 @@ class PollControls:
|
||||
return
|
||||
|
||||
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' \
|
||||
f'`{pre}close <poll_label>`'
|
||||
await self.say_error(ctx, error)
|
||||
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:
|
||||
# Permission Check: Admin or Creator
|
||||
if not await self.is_admin_or_creator(
|
||||
@ -215,13 +223,13 @@ class PollControls:
|
||||
await p.save_to_db()
|
||||
await ctx.invoke(self.show, short)
|
||||
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)
|
||||
# footer = f'Type {pre}show to display all polls'
|
||||
await self.say_error(ctx, error)
|
||||
await ctx.invoke(self.show)
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.command()
|
||||
async def export(self, ctx, *, short=None):
|
||||
'''Export a poll. Parameter: <label>'''
|
||||
server = await ask_for_server(self.bot, ctx.message, short)
|
||||
@ -229,26 +237,29 @@ class PollControls:
|
||||
return
|
||||
|
||||
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' \
|
||||
f'`{pre}export <poll_label>`'
|
||||
await self.say_error(ctx, error)
|
||||
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.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.'
|
||||
await self.say_error(ctx, error_text)
|
||||
else:
|
||||
# sending file
|
||||
file = await p.export()
|
||||
if file is not None:
|
||||
await self.bot.send_file(
|
||||
ctx.message.author,
|
||||
file,
|
||||
content='Sending you the requested export of "{}".'.format(p.short)
|
||||
)
|
||||
file_name = await p.export()
|
||||
if file_name is not None:
|
||||
await ctx.message.author.send('Sending you the requested export of "{}".'.format(p.short),
|
||||
file=discord.File(file_name)
|
||||
)
|
||||
# await self.bot.send_file(
|
||||
# ctx.message.author,
|
||||
# file_name,
|
||||
# content='Sending you the requested export of "{}".'.format(p.short)
|
||||
# )
|
||||
else:
|
||||
error_text = 'Could not export the requested poll. \nPlease report this to the developer.'
|
||||
await self.say_error(ctx, error_text)
|
||||
@ -259,7 +270,7 @@ class PollControls:
|
||||
await self.say_error(ctx, error)
|
||||
await ctx.invoke(self.show)
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.command()
|
||||
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>'''
|
||||
|
||||
@ -292,102 +303,102 @@ class PollControls:
|
||||
# msg = await self.embed_list_paginated(ctx, polls, item_fct, embed, per_page=8)
|
||||
pre = await get_server_pre(self.bot, server)
|
||||
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)
|
||||
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:
|
||||
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):
|
||||
return
|
||||
await p.post_embed()
|
||||
await p.post_embed(ctx)
|
||||
else:
|
||||
error = f'Poll with label {short} was not found.'
|
||||
pre = await get_server_pre(self.bot, server)
|
||||
footer = f'Type {pre}show to display all polls'
|
||||
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()
|
||||
# helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ")
|
||||
#
|
||||
# if cmd and cmd == 'help':
|
||||
# await self.say_embed(ctx, say_text=helpstring)
|
||||
# return
|
||||
#
|
||||
# try:
|
||||
# cmds = shlex.split(cmd)
|
||||
# except ValueError:
|
||||
# await self.say_error(ctx, error_text=helpstring)
|
||||
# return
|
||||
# except:
|
||||
# return
|
||||
#
|
||||
# try:
|
||||
# args, unknown_args = parser.parse_known_args(cmds)
|
||||
# except SystemExit:
|
||||
# await self.say_error(ctx, error_text=helpstring)
|
||||
# return
|
||||
# except:
|
||||
# return
|
||||
#
|
||||
# if unknown_args:
|
||||
# error_text = f'**There was an error reading the command line options!**.\n' \
|
||||
# f'Most likely this is because you didn\'t surround the arguments with double quotes like this: ' \
|
||||
# f'`{pre}cmd -q "question of the poll" -o "yes, no, maybe"`' \
|
||||
# f'\n\nHere are the arguments I could not understand:\n'
|
||||
# error_text += '`'+'\n'.join(unknown_args)+'`'
|
||||
# error_text += f'\n\nHere are the arguments which are ok:\n'
|
||||
# error_text += '`' + '\n'.join([f'{k}: {v}' for k, v in vars(args).items()]) + '`'
|
||||
#
|
||||
# await self.say_error(ctx, error_text=error_text, footer_text=f'type `{pre}cmd help` for details.')
|
||||
# return
|
||||
#
|
||||
# # pass arguments to the wizard
|
||||
# async def route(poll):
|
||||
# await poll.set_name(force=args.question)
|
||||
# await poll.set_short(force=args.label)
|
||||
# await poll.set_anonymous(force=f'{"yes" if args.anonymous else "no"}')
|
||||
# await poll.set_options_reaction(force=args.options)
|
||||
# await poll.set_multiple_choice(force=args.multiple_choice)
|
||||
# await poll.set_roles(force=args.roles)
|
||||
# await poll.set_weights(force=args.weights)
|
||||
# await poll.set_duration(force=args.deadline)
|
||||
#
|
||||
# poll = await self.wizard(ctx, route, server)
|
||||
# if poll:
|
||||
# await poll.post_embed(destination=poll.channel)
|
||||
# except Exception as error:
|
||||
# logger.error("ERROR IN pm!cmd")
|
||||
# logger.exception(error)
|
||||
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
#
|
||||
# @commands.command()
|
||||
# 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)
|
||||
#
|
||||
#
|
||||
@commands.command()
|
||||
async def quick(self, ctx, *, cmd=None):
|
||||
'''Create a quick poll with just a question and some options. Parameters: <Question> (optional)'''
|
||||
server = await ask_for_server(self.bot, ctx.message)
|
||||
@ -395,77 +406,76 @@ class PollControls:
|
||||
return
|
||||
|
||||
async def route(poll):
|
||||
await poll.set_name(force=cmd)
|
||||
await poll.set_short(force=str(await generate_word(self.bot, server.id)))
|
||||
await poll.set_anonymous(force='no')
|
||||
await poll.set_options_reaction()
|
||||
await poll.set_multiple_choice(force='1')
|
||||
await poll.set_roles(force='all')
|
||||
await poll.set_weights(force='none')
|
||||
await poll.set_duration(force='0')
|
||||
await poll.set_name(ctx, force=cmd)
|
||||
await poll.set_short(ctx, force=str(await generate_word(self.bot, server.id)))
|
||||
await poll.set_anonymous(ctx, force='no')
|
||||
await poll.set_options_reaction(ctx)
|
||||
await poll.set_multiple_choice(ctx, force='1')
|
||||
await poll.set_roles(ctx, force='all')
|
||||
await poll.set_weights(ctx, force='none')
|
||||
await poll.set_duration(ctx, force='0')
|
||||
|
||||
poll = await self.wizard(ctx, route, server)
|
||||
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):
|
||||
'''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)
|
||||
if not server:
|
||||
return
|
||||
|
||||
async def route(poll):
|
||||
await poll.set_name(force=cmd)
|
||||
await poll.set_short()
|
||||
await poll.set_preparation()
|
||||
await poll.set_anonymous()
|
||||
await poll.set_options_reaction()
|
||||
await poll.set_multiple_choice()
|
||||
await poll.set_roles()
|
||||
await poll.set_weights()
|
||||
await poll.set_duration()
|
||||
await poll.set_name(ctx, force=cmd)
|
||||
await poll.set_short(ctx)
|
||||
await poll.set_preparation(ctx)
|
||||
await poll.set_anonymous(ctx)
|
||||
await poll.set_options_reaction(ctx)
|
||||
await poll.set_multiple_choice(ctx)
|
||||
await poll.set_roles(ctx)
|
||||
await poll.set_weights(ctx)
|
||||
await poll.set_duration(ctx)
|
||||
|
||||
poll = await self.wizard(ctx, route, server)
|
||||
if poll:
|
||||
await poll.post_embed(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):
|
||||
'''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)
|
||||
if not server:
|
||||
return
|
||||
|
||||
async def route(poll):
|
||||
await poll.set_name(force=cmd)
|
||||
await poll.set_short()
|
||||
await poll.set_anonymous()
|
||||
await poll.set_options_reaction()
|
||||
await poll.set_multiple_choice()
|
||||
await poll.set_roles()
|
||||
await poll.set_weights()
|
||||
await poll.set_duration()
|
||||
await poll.set_name(ctx, force=cmd)
|
||||
await poll.set_short(ctx)
|
||||
await poll.set_anonymous(ctx)
|
||||
await poll.set_options_reaction(ctx)
|
||||
await poll.set_multiple_choice(ctx)
|
||||
await poll.set_roles(ctx)
|
||||
await poll.set_weights(ctx)
|
||||
await poll.set_duration(ctx)
|
||||
|
||||
poll = await self.wizard(ctx, route, server)
|
||||
if poll:
|
||||
await poll.post_embed(destination=poll.channel)
|
||||
await poll.post_embed(poll.channel)
|
||||
|
||||
# The Wizard!
|
||||
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:
|
||||
return
|
||||
|
||||
# Permission Check
|
||||
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)})
|
||||
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]:
|
||||
pre = await get_server_pre(self.bot, server)
|
||||
await self.bot.send_message(ctx.message.author,
|
||||
'You don\'t have sufficient rights to start new polls on this server. '
|
||||
await ctx.message.author.send('You don\'t have sufficient rights to start new polls on this server. '
|
||||
'A server administrator has to assign the user or admin role to you. '
|
||||
f'To view and set the permissions, an admin can use `{pre}userrole` and '
|
||||
f'`{pre}adminrole`')
|
||||
@ -485,126 +495,156 @@ class PollControls:
|
||||
await poll.save_to_db()
|
||||
return poll
|
||||
|
||||
# BOT EVENTS (@bot.event)
|
||||
async def on_socket_raw_receive(self, raw_msg):
|
||||
if not isinstance(raw_msg, str):
|
||||
return
|
||||
msg = json.loads(raw_msg)
|
||||
type = msg.get("t")
|
||||
data = msg.get("d")
|
||||
if not data:
|
||||
return
|
||||
emoji = data.get("emoji")
|
||||
user_id = data.get("user_id")
|
||||
message_id = data.get("message_id")
|
||||
if type == "MESSAGE_REACTION_ADD":
|
||||
await self.do_on_reaction_add(data)
|
||||
elif type == "MESSAGE_REACTION_REMOVE":
|
||||
await self.do_on_reaction_remove(data)
|
||||
# # BOT EVENTS (@bot.event)
|
||||
# async def on_socket_raw_receive(self, raw_msg):
|
||||
# print(raw_msg)
|
||||
# if not isinstance(raw_msg, str):
|
||||
# return
|
||||
# msg = json.loads(raw_msg)
|
||||
# type = msg.get("t")
|
||||
# data = msg.get("d")
|
||||
# if not data:
|
||||
# return
|
||||
# # emoji = data.get("emoji")
|
||||
# # user_id = data.get("user_id")
|
||||
# # message_id = data.get("message_id")
|
||||
# if type == "MESSAGE_REACTION_ADD":
|
||||
# await self.do_on_reaction_add(data)
|
||||
# elif type == "MESSAGE_REACTION_REMOVE":
|
||||
# #await self.do_on_reaction_remove(data)
|
||||
# pass
|
||||
|
||||
async def do_on_reaction_remove(self, data):
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_remove(self, data):
|
||||
# get emoji symbol
|
||||
emoji = data.get('emoji')
|
||||
emoji = data.emoji
|
||||
if emoji:
|
||||
emoji = emoji.get('name')
|
||||
emoji = emoji.name
|
||||
if not emoji:
|
||||
return
|
||||
|
||||
# check if removed by the bot.. this is a bit hacky but discord doesn't provide the correct info...
|
||||
message_id = data.get('message_id')
|
||||
user_id = data.get('user_id')
|
||||
message_id = data.message_id
|
||||
user_id = data.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)]
|
||||
return
|
||||
|
||||
# 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)
|
||||
# user = await self.bot.get_user_info(user_id) # only do this once
|
||||
|
||||
server_id = data.get('guild_id')
|
||||
if server_id:
|
||||
server = self.bot.get_server(server_id)
|
||||
if isinstance(channel, discord.TextChannel):
|
||||
server = channel.guild
|
||||
user = server.get_member(user_id)
|
||||
else:
|
||||
user = await self.bot.get_user_info(user_id) # only do this once, this is rate limited
|
||||
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)
|
||||
|
||||
if not channel:
|
||||
elif not channel:
|
||||
# discord rapidly closes dm channels by desing
|
||||
# put private channels back into the bots cache and try again
|
||||
await self.bot.start_private_message(user)
|
||||
user = await self.bot.fetch_user(user_id) # only do this once
|
||||
await user.create_dm()
|
||||
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:
|
||||
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
|
||||
|
||||
# create message object for the reaction sender, to get correct server
|
||||
if not server_id:
|
||||
user_msg = copy.deepcopy(message)
|
||||
user_msg.author = user
|
||||
server = await ask_for_server(self.bot, user_msg, label)
|
||||
server_id = server.id
|
||||
|
||||
# this is exclusive
|
||||
lock = self.get_lock(server_id)
|
||||
lock = self.get_lock(server.id)
|
||||
async with lock:
|
||||
p = await Poll.load_from_db(self.bot, server_id, label)
|
||||
p = await Poll.load_from_db(self.bot, server.id, label)
|
||||
if not isinstance(p, Poll):
|
||||
return
|
||||
if not p.anonymous:
|
||||
# for anonymous polls we can't unvote because we need to hide reactions
|
||||
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
|
||||
user_id = data.get('user_id')
|
||||
user_id = data.user_id
|
||||
if user_id == self.bot.user.id:
|
||||
return
|
||||
|
||||
# get emoji symbol
|
||||
emoji = data.get('emoji')
|
||||
emoji = data.emoji
|
||||
if emoji:
|
||||
emoji = emoji.get('name')
|
||||
emoji = emoji.name
|
||||
if not emoji:
|
||||
return
|
||||
|
||||
# check if we can find a poll label
|
||||
message_id = data.get('message_id')
|
||||
channel_id = data.get('channel_id')
|
||||
message_id = data.message_id
|
||||
channel_id = data.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
|
||||
# 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)
|
||||
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:
|
||||
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
|
||||
|
||||
# fetch poll
|
||||
# create message object for the reaction sender, to get correct server
|
||||
user_msg = copy.deepcopy(message)
|
||||
user_msg.author = user
|
||||
server = await ask_for_server(self.bot, user_msg, label)
|
||||
# user_msg = copy.deepcopy(message)
|
||||
# user_msg.author = user
|
||||
# server = await ask_for_server(self.bot, user_msg, label)
|
||||
|
||||
# this is exclusive to keep database access sequential
|
||||
# hopefully it will scale well enough or I need a different solution
|
||||
@ -617,13 +657,11 @@ class PollControls:
|
||||
# export
|
||||
if emoji == '📎':
|
||||
# sending file
|
||||
file = await p.export()
|
||||
if file is not None:
|
||||
await self.bot.send_file(
|
||||
user,
|
||||
file,
|
||||
content='Sending you the requested export of "{}".'.format(p.short)
|
||||
)
|
||||
file_name = await p.export()
|
||||
if file_name is not None:
|
||||
await user.send('Sending you the requested export of "{}".'.format(p.short),
|
||||
file=discord.File(file_name)
|
||||
)
|
||||
return
|
||||
|
||||
# info
|
||||
@ -643,7 +681,7 @@ class PollControls:
|
||||
edit_rights = False
|
||||
if str(member.id) == str(p.author):
|
||||
edit_rights = True
|
||||
elif member.server_permissions.manage_server:
|
||||
elif member.guild_permissions.manage_guild:
|
||||
edit_rights = True
|
||||
else:
|
||||
result = await self.bot.db.config.find_one({'_id': str(server.id)})
|
||||
@ -653,9 +691,9 @@ class PollControls:
|
||||
|
||||
# choices
|
||||
choices = 'You have not voted yet.' if vote_rights else 'You can\'t vote in this poll.'
|
||||
if user.id in p.votes:
|
||||
if p.votes[user.id]['choices'].__len__() > 0:
|
||||
choices = ', '.join([p.options_reaction[c] for c in p.votes[user.id]['choices']])
|
||||
if str(user.id) in p.votes:
|
||||
if p.votes[str(user.id)]['choices'].__len__() > 0:
|
||||
choices = ', '.join([p.options_reaction[c] for c in p.votes[str(user.id)]['choices']])
|
||||
embed.add_field(
|
||||
name=f'{"Your current votes (can be changed as long as the poll is open):" if is_open else "Your final votes:"}',
|
||||
value=choices, inline=False)
|
||||
@ -683,7 +721,7 @@ class PollControls:
|
||||
|
||||
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
|
||||
if not p.anonymous and p.votes.__len__() > 0:
|
||||
@ -697,41 +735,41 @@ class PollControls:
|
||||
c = 0
|
||||
for user_id in p.votes:
|
||||
member = server.get_member(user_id)
|
||||
if not member or i not in p.votes[user_id]['choices']:
|
||||
if not member or i not in p.votes[str(user_id)]['choices']:
|
||||
continue
|
||||
c += 1
|
||||
name = member.nick
|
||||
if not name:
|
||||
name = member.name
|
||||
msg += f'\n{name}'
|
||||
if p.votes[user_id]['weight'] != 1:
|
||||
msg += f' (weight: {p.votes[user_id]["weight"]})'
|
||||
if p.votes[str(user_id)]['weight'] != 1:
|
||||
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']])
|
||||
if msg.__len__() > 1500:
|
||||
await self.bot.send_message(user, msg)
|
||||
await user.send(msg)
|
||||
msg = ''
|
||||
if c == 0:
|
||||
msg += '\nNo votes for this option yet.'
|
||||
msg += '\n\n'
|
||||
|
||||
if msg.__len__() > 0:
|
||||
await self.bot.send_message(user, msg)
|
||||
|
||||
await user.send(msg)
|
||||
return
|
||||
|
||||
# Assume: User wants to vote with reaction
|
||||
# no rights, terminate function
|
||||
if not await p.has_required_role(member):
|
||||
await self.bot.remove_reaction(message, emoji, user)
|
||||
await self.bot.send_message(user, 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)}')
|
||||
await message.remove_reaction(emoji, user)
|
||||
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)}')
|
||||
return
|
||||
|
||||
# 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
|
||||
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
|
||||
# update database with vote
|
||||
@ -739,14 +777,13 @@ class PollControls:
|
||||
|
||||
# cant do this until we figure out how to see who removed the reaction?
|
||||
# for now MC 1 is like MC x
|
||||
if str(channel.type) != 'private' and p.multiple_choice == 1:
|
||||
# remove all other reactions
|
||||
# if lock._waiters.__len__() == 0:
|
||||
for r in message.reactions:
|
||||
if r.emoji and r.emoji != emoji:
|
||||
await self.bot.remove_reaction(message, r.emoji, user)
|
||||
pass
|
||||
|
||||
# if isinstance(channel, discord.TextChannel) and p.multiple_choice == 1:
|
||||
# # remove all other reactions
|
||||
# # if lock._waiters.__len__() == 0:
|
||||
# for r in message.reactions:
|
||||
# if r.emoji and r.emoji != emoji:
|
||||
# await message.remove_reaction(r.emoji, user)
|
||||
# pass
|
||||
|
||||
def setup(bot):
|
||||
global logger
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""Exception Classes for the Poll Wizard"""
|
||||
|
||||
|
||||
class StopWizard(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
23
essentials/messagecache.py
Normal file
23
essentials/messagecache.py
Normal 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 = {}
|
||||
@ -1,14 +1,13 @@
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
|
||||
from essentials.settings import SETTINGS
|
||||
from utils.paginator import embed_list_paginated
|
||||
|
||||
|
||||
async def get_pre(bot, 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)
|
||||
if shared_server_list.__len__() == 0:
|
||||
return 'pm!'
|
||||
@ -18,7 +17,7 @@ async def get_pre(bot, message):
|
||||
# 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])
|
||||
else:
|
||||
return await get_server_pre(bot, message.server)
|
||||
return await get_server_pre(bot, message.guild)
|
||||
|
||||
|
||||
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):
|
||||
'''Get best guess of relevant shared servers'''
|
||||
if message.server is None:
|
||||
if message.guild is None:
|
||||
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]:
|
||||
list_of_shared_servers.append(s)
|
||||
if short is not None:
|
||||
query = bot.db.polls.find({'short': short})
|
||||
if query is not None:
|
||||
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)))
|
||||
if shared_servers_with_short.__len__() >= 1:
|
||||
return shared_servers_with_short
|
||||
@ -55,7 +54,7 @@ async def get_servers(bot, message, short=None):
|
||||
else:
|
||||
return list_of_shared_servers
|
||||
else:
|
||||
return [message.server]
|
||||
return [message.guild]
|
||||
|
||||
|
||||
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 short == None:
|
||||
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:
|
||||
await bot.say(f'I could not find a server where the poll {short} exists that we both can see.')
|
||||
return None
|
||||
@ -76,34 +76,40 @@ async def ask_for_server(bot, message, short=None):
|
||||
text += f'\n**{i}** - {name}'
|
||||
i += 1
|
||||
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
|
||||
nr = 1
|
||||
while valid_reply == False:
|
||||
reply = await bot.wait_for_message(timeout=60, author=message.author)
|
||||
if reply and reply.content:
|
||||
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.'
|
||||
# '\n I\'ll stop waiting and execute your command.')
|
||||
return False
|
||||
if str(reply.content).isdigit():
|
||||
nr = int(reply.content)
|
||||
if 0 < nr <= server_list.__len__():
|
||||
valid_reply = True
|
||||
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.content.startswith(await get_pre(bot, message)):
|
||||
# await bot.say('You can\'t use bot commands while I am waiting for an answer.'
|
||||
# '\n I\'ll stop waiting and execute your command.')
|
||||
return False
|
||||
if str(reply.content).isdigit():
|
||||
nr = int(reply.content)
|
||||
if 0 < nr <= server_list.__len__():
|
||||
valid_reply = True
|
||||
|
||||
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 str(message.channel.type) == 'text':
|
||||
if not isinstance(message.channel, discord.abc.PrivateChannel):
|
||||
return message.channel
|
||||
|
||||
# build channel list that the user is allowed to send messages to
|
||||
user = message.author
|
||||
member = server.get_member(user.id)
|
||||
channel_list = [c for c in server.channels if str(c.type) == 'text' and c.permissions_for(member).send_messages]
|
||||
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 channel_list.__len__() == 1:
|
||||
@ -111,39 +117,48 @@ async def ask_for_channel(bot, server, message):
|
||||
|
||||
# if no channels, display error
|
||||
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)
|
||||
await bot.say(embed=embed)
|
||||
embed = discord.Embed(title="Select a channel", description='No text channels found on this server. Make sure '
|
||||
'I can see them.', color=SETTINGS.color)
|
||||
await ctx.send(embed=embed)
|
||||
return False
|
||||
|
||||
# otherwise ask for a channel
|
||||
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]:
|
||||
to_add = f'\n**{i}** - {name}'
|
||||
|
||||
# check if length doesn't exceed allowed maximum or split it into multiple messages
|
||||
if text.__len__() + to_add.__len__() > 2048:
|
||||
embed = discord.Embed(title="Select a channel", description=text, color=SETTINGS.color)
|
||||
await bot.say(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'
|
||||
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 '
|
||||
else:
|
||||
text += to_add
|
||||
i += 1
|
||||
|
||||
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
|
||||
nr = 1
|
||||
while valid_reply == False:
|
||||
reply = await bot.wait_for_message(timeout=60, author=message.author)
|
||||
if reply and reply.content:
|
||||
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.'
|
||||
# '\n I\'ll stop waiting and execute your command.')
|
||||
return False
|
||||
if str(reply.content).isdigit():
|
||||
nr = int(reply.content)
|
||||
if 0 < nr <= channel_list.__len__():
|
||||
valid_reply = True
|
||||
while not valid_reply:
|
||||
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.content.startswith(await get_pre(bot, message)):
|
||||
# await bot.say('You can\'t use bot commands while I am waiting for an answer.'
|
||||
# '\n I\'ll stop waiting and execute your command.')
|
||||
return False
|
||||
if str(reply.content).isdigit():
|
||||
nr = int(reply.content)
|
||||
if 0 < nr <= channel_list.__len__():
|
||||
valid_reply = True
|
||||
return channel_list[nr - 1]
|
||||
@ -1,17 +1,17 @@
|
||||
import asyncio
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
import logging
|
||||
|
||||
|
||||
from essentials.messagecache import MessageCache
|
||||
from discord.ext import commands
|
||||
from motor.motor_asyncio import AsyncIOMotorClient
|
||||
|
||||
from essentials.multi_server import get_pre
|
||||
from essentials.settings import SETTINGS
|
||||
from utils.asyncio_unique_queue import UniqueQueue
|
||||
from utils.import_old_database import import_old_database
|
||||
|
||||
bot_config = {
|
||||
'command_prefix': get_pre,
|
||||
@ -22,7 +22,7 @@ bot_config = {
|
||||
'max_messages': 15000
|
||||
}
|
||||
|
||||
bot = commands.Bot(**bot_config)
|
||||
bot = commands.AutoShardedBot(**bot_config)
|
||||
bot.remove_command('help')
|
||||
|
||||
# logger
|
||||
@ -50,18 +50,18 @@ for ext in extensions:
|
||||
|
||||
@bot.event
|
||||
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)
|
||||
bot.db = mongo.pollmaster
|
||||
bot.session = aiohttp.ClientSession()
|
||||
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
|
||||
try:
|
||||
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:
|
||||
# create new config entry
|
||||
await bot.db.config.update_one(
|
||||
@ -69,30 +69,20 @@ async def on_ready():
|
||||
{'$set': {'prefix': 'pm!', 'admin_role': 'polladmin', 'user_role': 'polluser'}},
|
||||
upsert=True
|
||||
)
|
||||
# stopping migration support.
|
||||
# try:
|
||||
# await import_old_database(bot, server)
|
||||
# print(str(server), "updated.")
|
||||
# except:
|
||||
# print(str(server.id), "failed.")
|
||||
|
||||
except:
|
||||
print("Problem verifying servers.")
|
||||
|
||||
# cache prefixes
|
||||
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.poll_cache = {}
|
||||
# bot.poll_refresh_q = UniqueQueue()
|
||||
|
||||
bot.message_cache = MessageCache(bot)
|
||||
print("Servers verified. Bot running.")
|
||||
|
||||
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_command_error(e, ctx):
|
||||
async def on_command_error(ctx, e):
|
||||
if SETTINGS.log_errors:
|
||||
ignored_exceptions = (
|
||||
commands.MissingRequiredArgument,
|
||||
@ -122,22 +112,9 @@ async def on_command_error(e, ctx):
|
||||
f"\n\tAuthor: <@{ctx.message.author}>",
|
||||
timestamp=ctx.message.timestamp
|
||||
)
|
||||
await bot.send_message(bot.owner, embed=e)
|
||||
await ctx.send(bot.owner, embed=e)
|
||||
|
||||
# if SETTINGS.mode == 'development':
|
||||
raise e
|
||||
|
||||
|
||||
@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)
|
||||
bot.run(SETTINGS.bot_token)
|
||||
@ -1,4 +1,4 @@
|
||||
# Pollmaster V 2.2
|
||||
# Pollmaster V 2.3
|
||||
|
||||
## Overview
|
||||
|
||||
@ -34,7 +34,7 @@ Here is how Pollmaster looks in action:
|
||||
| pm!help | Shows an interactive help menu |
|
||||
| pm!new | Starts a new poll with all the settings |
|
||||
| pm!quick | Starts a new poll with just a question and options |
|
||||
| pm!show <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!userrole <any role> | Set the role that has the rights to use the bot |
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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"
|
||||
@ -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
|
||||
|
||||
# generate list
|
||||
@ -21,27 +26,30 @@ async def embed_list_paginated(bot, pre, items, item_fct, base_embed, footer_pre
|
||||
|
||||
# post / edit message
|
||||
if msg is not None:
|
||||
await bot.edit_message(msg, embed=embed)
|
||||
if str(msg.channel.type) != 'private':
|
||||
await bot.clear_reactions(msg)
|
||||
await msg.edit(embed=embed)
|
||||
if not isinstance(msg.channel, discord.abc.PrivateChannel):
|
||||
await msg.clear_reactions()
|
||||
else:
|
||||
msg = await bot.say(embed=embed)
|
||||
msg = await ctx.send(embed=embed)
|
||||
|
||||
# add reactions
|
||||
if start > 0:
|
||||
await bot.add_reaction(msg, '⏪')
|
||||
await msg.add_reaction('⏪')
|
||||
if items.__len__() > start+per_page:
|
||||
await bot.add_reaction(msg, '⏩')
|
||||
await msg.add_reaction('⏩')
|
||||
|
||||
# wait for reactions (2 minutes)
|
||||
def check(reaction, user):
|
||||
return reaction.emoji if user != bot.user else False
|
||||
res = await bot.wait_for_reaction(emoji=['⏪', '⏩'], message=msg, timeout=120, check=check)
|
||||
|
||||
# redirect on reaction
|
||||
if res is None:
|
||||
return
|
||||
elif res.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)
|
||||
elif res.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)
|
||||
return True if user != bot.user and str(reaction.emoji) in ['⏪', '⏩'] and reaction.message.id == msg.id else False
|
||||
try:
|
||||
reaction, user = await bot.wait_for('reaction_add', timeout=120, check=check)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
else:
|
||||
# redirect on reaction
|
||||
if reaction is None:
|
||||
return
|
||||
elif reaction.emoji == '⏪' and start > 0:
|
||||
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 reaction.emoji == '⏩' and items.__len__() > start+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)
|
||||
@ -6,9 +6,9 @@ animals = ['frog', 'newt', 'tadpole', 'toad', 'spider', 'biddy', 'canary', 'crow
|
||||
async def generate_word(bot, server_id):
|
||||
exists = 1
|
||||
while exists is not None:
|
||||
ad = random.sample(adjs, 2)
|
||||
ad = random.sample(adjs, 1)
|
||||
an = random.sample(animals, 1)
|
||||
short = ad[0].capitalize() + ad[1].capitalize() + an[0].capitalize()
|
||||
exists = await bot.db.polls.find_one({'server_id': str(server_id), 'short': short})
|
||||
short = ad[0].capitalize() + an[0].capitalize()
|
||||
exists = await bot.db.polls.find_one({'server_id': server_id, 'short': short})
|
||||
|
||||
return short
|
||||
|
||||
Loading…
Reference in New Issue
Block a user