Initial commit
This commit is contained in:
commit
67ad573dd6
0
cogs/__init__.py
Normal file
0
cogs/__init__.py
Normal file
77
cogs/config.py
Normal file
77
cogs/config.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
@commands.has_permissions(manage_server=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)
|
||||||
|
|
||||||
|
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.'
|
||||||
|
else:
|
||||||
|
msg = f'The server prefix has been set to `{pre}` Use `{pre}prefix <prefix>` to change it again. ' \
|
||||||
|
f'If you would like to add a trailing whitespace to the prefix, use `{pre}prefix {pre}\w`.'
|
||||||
|
|
||||||
|
await self.bot.db.config.update_one({'_id': str(server.id)}, {'$set': {'prefix': str(pre)}}, upsert=True)
|
||||||
|
await self.bot.say(msg)
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
@commands.has_permissions(manage_server=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
|
||||||
|
|
||||||
|
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'
|
||||||
|
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'
|
||||||
|
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.')
|
||||||
|
else:
|
||||||
|
await self.bot.say(f'Server role `{role}` not found.')
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
@commands.has_permissions(manage_server=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
|
||||||
|
|
||||||
|
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'
|
||||||
|
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'
|
||||||
|
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.')
|
||||||
|
else:
|
||||||
|
await self.bot.say(f'Server role `{role}` not found.')
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(Config(bot))
|
||||||
906
cogs/poll.py
Normal file
906
cogs/poll.py
Normal file
@ -0,0 +1,906 @@
|
|||||||
|
import codecs
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from math import ceil
|
||||||
|
from uuid import uuid4
|
||||||
|
from string import ascii_lowercase
|
||||||
|
|
||||||
|
from matplotlib import rcParams
|
||||||
|
from matplotlib.afm import AFM
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from cogs.utils import get_pre
|
||||||
|
from utils.poll_name_generator import generate_word
|
||||||
|
|
||||||
|
## Helvetica is the closest font to Whitney (discord uses Whitney) in afm
|
||||||
|
## This is used to estimate text width and adjust the layout of the embeds'''
|
||||||
|
afm_fname = os.path.join(rcParams['datapath'], 'fonts', 'afm', 'phvr8a.afm')
|
||||||
|
with open(afm_fname, 'rb') as fh:
|
||||||
|
afm = AFM(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:
|
||||||
|
|
||||||
|
def __init__(self, bot, ctx=None, server=None, channel=None, load=False):
|
||||||
|
|
||||||
|
self.bot = bot
|
||||||
|
self.color = discord.Colour(int('7289da', 16))
|
||||||
|
self.cursor_pos = 0
|
||||||
|
|
||||||
|
if not load and ctx:
|
||||||
|
if server is None:
|
||||||
|
server = ctx.message.server
|
||||||
|
|
||||||
|
if channel is None:
|
||||||
|
channel = ctx.message.channel
|
||||||
|
|
||||||
|
self.author = ctx.message.author
|
||||||
|
self.stopped = False
|
||||||
|
|
||||||
|
self.server = server
|
||||||
|
self.channel = channel
|
||||||
|
|
||||||
|
self.name = "Quick Poll"
|
||||||
|
self.short = str(uuid4())
|
||||||
|
self.anonymous = False
|
||||||
|
self.reaction = True
|
||||||
|
self.multiple_choice = False
|
||||||
|
self.options_reaction = ['yes', 'no']
|
||||||
|
self.options_reaction_default = False
|
||||||
|
self.options_traditional = []
|
||||||
|
# self.options_traditional_default = False
|
||||||
|
self.roles = ['@everyone']
|
||||||
|
self.weights_roles = []
|
||||||
|
self.weights_numbers = []
|
||||||
|
self.duration = 0
|
||||||
|
self.time_created = time.time()
|
||||||
|
|
||||||
|
self.open = True
|
||||||
|
self.inactive = False
|
||||||
|
self.votes = {}
|
||||||
|
|
||||||
|
def is_open(self):
|
||||||
|
if open and self.duration > 0 and self.time_created + self.duration * 60 < time.time():
|
||||||
|
self.open = False
|
||||||
|
return self.open
|
||||||
|
|
||||||
|
async def wizard_says(self, text, footer=True):
|
||||||
|
embed = discord.Embed(title="Poll creation Wizard", description=text,
|
||||||
|
color=self.color)
|
||||||
|
if footer: embed.set_footer(text="Type `stop` to cancel the wizard.")
|
||||||
|
return await self.bot.say(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
|
||||||
|
embed = discord.Embed(title="Poll creation Wizard", description=text,
|
||||||
|
color=self.color)
|
||||||
|
embed.set_footer(text="Type `stop` to cancel the wizard.")
|
||||||
|
return await self.bot.edit_message(message, embed=embed)
|
||||||
|
|
||||||
|
async def add_error(self, message, error):
|
||||||
|
text = ''
|
||||||
|
if message.embeds.__len__() > 0:
|
||||||
|
text = message.embeds[0]['description'] + '\n\n:exclamation: ' + error
|
||||||
|
return await self.wizard_says_edit(message, text)
|
||||||
|
|
||||||
|
def check_reply(self, msg):
|
||||||
|
if msg and msg.content:
|
||||||
|
if msg.content.lower() == 'stop':
|
||||||
|
self.stopped = True
|
||||||
|
return msg
|
||||||
|
elif msg.content.__len__() > 0:
|
||||||
|
return msg
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_user_reply(self):
|
||||||
|
reply = await self.bot.wait_for_message(author=self.author, check=self.check_reply)
|
||||||
|
if self.stopped:
|
||||||
|
await self.wizard_says('Poll Wizard stopped.', footer=False)
|
||||||
|
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'
|
||||||
|
f'Stopping the Wizard and then executing the command:\n`{reply.content}`',
|
||||||
|
footer=False)
|
||||||
|
self.stopped = True
|
||||||
|
return 'stop'
|
||||||
|
return reply.content
|
||||||
|
|
||||||
|
async def set_name(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None:
|
||||||
|
self.name = force
|
||||||
|
return
|
||||||
|
text = """I will guide you step by step through the creation of your new poll.
|
||||||
|
We can do this in a text channel or you can PM me to keep the server clean.
|
||||||
|
**How would you like your poll to be called?**
|
||||||
|
Try to be descriptive without writing more than one sentence."""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply.__len__() < 2 or reply.__len__() > 200:
|
||||||
|
if reply != '':
|
||||||
|
await self.add_error(message, '**Keep the name between 3 and 200 letters**')
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
self.name = reply
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
async def set_short(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None:
|
||||||
|
self.short = force
|
||||||
|
return
|
||||||
|
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)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply.__len__() < 3 or reply.__len__() > 20 or reply.split(" ").__len__() != 1:
|
||||||
|
if reply != '':
|
||||||
|
await self.add_error(message, '**Only one word between 3 and 20 letters!**')
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
if await self.bot.db.polls.find_one({'server_id': str(self.server.id), 'short': reply}) is not None:
|
||||||
|
await self.add_error(message,
|
||||||
|
f'**The label `{reply}` is not unique on this server. Choose a different one!**')
|
||||||
|
reply = ''
|
||||||
|
if reply in ['open', 'closed', 'prepared']:
|
||||||
|
await self.add_error(message, '**Can\'t use reserved words (open, closed, prepared) as label!**')
|
||||||
|
reply = ''
|
||||||
|
|
||||||
|
self.short = reply
|
||||||
|
return self.short
|
||||||
|
|
||||||
|
async def set_anonymous(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None:
|
||||||
|
self.anonymous = force
|
||||||
|
return
|
||||||
|
text = """Next you need to decide: **Do you want your poll to be anonymous?**
|
||||||
|
|
||||||
|
`0 - No`
|
||||||
|
`1 - Yes`
|
||||||
|
|
||||||
|
An anonymous poll has the following effects:
|
||||||
|
:small_blue_diamond: You will never see who voted for which option
|
||||||
|
:small_blue_diamond: Once the poll is closed, you will see who participated (but not their choice)"""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply not in ['yes', 'no', '1', '0']:
|
||||||
|
if reply != '':
|
||||||
|
await self.add_error(message, '**You can only answer with `yes` | `1` or `no` | `0`!**')
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
if isinstance(reply, str):
|
||||||
|
reply = reply.lower()
|
||||||
|
|
||||||
|
self.anonymous = reply in ['yes', '1']
|
||||||
|
return self.anonymous
|
||||||
|
|
||||||
|
async def set_reaction(self, force=None):
|
||||||
|
''' Currently everything is reaction, this is not needed'''
|
||||||
|
if force is not None:
|
||||||
|
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):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None:
|
||||||
|
self.multiple_choice = force
|
||||||
|
return
|
||||||
|
text = """**Should users be able to vote for multiple options?**
|
||||||
|
|
||||||
|
`0 - No`
|
||||||
|
`1 - Yes`
|
||||||
|
|
||||||
|
If you type `0` or `no`, a new vote will override the old vote. Otherwise the users can vote for as many options as they like."""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply not in ['yes', 'no', '1', '0']:
|
||||||
|
if reply != '':
|
||||||
|
await self.add_error(message, '**You can only answer with `yes` | `1` or `no` | `0`!**')
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
if isinstance(reply, str):
|
||||||
|
reply = reply.lower()
|
||||||
|
|
||||||
|
self.multiple_choice = reply in ['yes', '1']
|
||||||
|
return self.multiple_choice
|
||||||
|
|
||||||
|
async def set_options_reaction(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None:
|
||||||
|
self.options_reaction = force
|
||||||
|
return
|
||||||
|
text = """**Choose the options/answers for your poll.**
|
||||||
|
Either type the corresponding number to a predefined option set,
|
||||||
|
or type your own options, separated by commas.
|
||||||
|
|
||||||
|
**1** - :white_check_mark: :negative_squared_cross_mark:
|
||||||
|
**2** - :thumbsup: :zipper_mouth: :thumbsdown:
|
||||||
|
**3** - :heart_eyes: :thumbsup: :zipper_mouth: :thumbsdown: :nauseated_face:
|
||||||
|
|
||||||
|
Example for custom options:
|
||||||
|
**apple juice, banana ice cream, kiwi slices** """
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply == '' or (reply.split(",").__len__() < 2 and reply not in ['1', '2', '3']) \
|
||||||
|
or (reply.split(",").__len__() > 26 and reply not in ['1', '2', '3']):
|
||||||
|
if reply != '':
|
||||||
|
await self.add_error(message,
|
||||||
|
'**Invalid entry. Type `1`, `2` or `3` or a comma separated list (max. 26 options).**')
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
|
||||||
|
if reply == '1':
|
||||||
|
self.options_reaction = ['✅', '❎']
|
||||||
|
self.options_reaction_default = True
|
||||||
|
elif reply == '2':
|
||||||
|
self.options_reaction = ['👍', '🤐', '👎']
|
||||||
|
self.options_reaction_default = True
|
||||||
|
elif reply == '3':
|
||||||
|
self.options_reaction = ['😍', '👍', '🤐', '👎', '🤢']
|
||||||
|
self.options_reaction_default = True
|
||||||
|
else:
|
||||||
|
self.options_reaction = [r.strip() for r in reply.split(",")]
|
||||||
|
self.options_reaction_default = False
|
||||||
|
return self.options_reaction
|
||||||
|
|
||||||
|
async def set_options_traditional(self, force=None):
|
||||||
|
'''Currently not used as everything is reaction based'''
|
||||||
|
if force is not None:
|
||||||
|
self.options_traditional = force
|
||||||
|
return
|
||||||
|
if self.stopped: return
|
||||||
|
text = """**Next you chose from the possible set of options/answers for your poll.**
|
||||||
|
Type the corresponding number or type your own options, separated by commas.
|
||||||
|
|
||||||
|
**1** - yes, no
|
||||||
|
**2** - in favour, against, abstain
|
||||||
|
**3** - love it, like it, don't care, meh, hate it
|
||||||
|
|
||||||
|
If you write your own options they will be listed and can be voted for.
|
||||||
|
To use your custom options type them like this:
|
||||||
|
**apple juice, banana ice cream, kiwi slices**"""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply == '' or (reply.split(",").__len__() < 2 and reply not in ['1', '2', '3']) \
|
||||||
|
or (reply.split(",").__len__() > 99 and reply not in ['1', '2', '3']):
|
||||||
|
if reply != '':
|
||||||
|
await self.add_error(message,
|
||||||
|
'**Invalid entry. Type `1` `2` or `3` or a comma separated list (max. 99 options).**')
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
|
||||||
|
if reply == '1':
|
||||||
|
self.options_traditional = ['yes, no']
|
||||||
|
elif reply == '2':
|
||||||
|
self.options_traditional = ['in favour', 'against', 'abstain']
|
||||||
|
elif reply == '3':
|
||||||
|
self.options_traditional = ['love it', 'like it', 'don\'t care', 'meh', 'hate it']
|
||||||
|
else:
|
||||||
|
self.options_traditional = [r.strip() for r in reply.split(",")]
|
||||||
|
return self.options_traditional
|
||||||
|
|
||||||
|
async def set_roles(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None:
|
||||||
|
self.roles = force
|
||||||
|
return
|
||||||
|
n_roles = self.server.roles.__len__()
|
||||||
|
if n_roles == 0:
|
||||||
|
text = "No roles found on this server, skipping to the next step."
|
||||||
|
self.roles = ['@everyone']
|
||||||
|
elif n_roles <= 15:
|
||||||
|
text = """**Choose which roles are allowed to vote.**
|
||||||
|
Type `0`, `all` or `everyone` to have no restrictions.
|
||||||
|
If you want multiple roles to be able to vote, separate the numbers with a comma.
|
||||||
|
"""
|
||||||
|
text += f'\n`{0} - no restrictions`'
|
||||||
|
i = 1
|
||||||
|
for role in [r.name for r in self.server.roles]:
|
||||||
|
text += f'\n`{i} - {role}`'
|
||||||
|
i += 1
|
||||||
|
text += """
|
||||||
|
|
||||||
|
Example: `2, 3`
|
||||||
|
"""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
while reply == '' or reply == 'error' or reply == 'toolarge':
|
||||||
|
if reply == 'error':
|
||||||
|
await self.add_error(message, f'**Only positive numbers separated by commas are allowed!**')
|
||||||
|
elif reply == 'toolarge':
|
||||||
|
await self.add_error(message, f'**Only use valid numbers......**')
|
||||||
|
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
|
||||||
|
if reply in ['0', 'all', 'everyone']:
|
||||||
|
break
|
||||||
|
if not all([r.strip().isdigit() for r in reply.split(",")]):
|
||||||
|
reply = 'error'
|
||||||
|
continue
|
||||||
|
elif any([int(r.strip()) > n_roles for r in reply.split(",")]):
|
||||||
|
reply = 'toolarge'
|
||||||
|
continue
|
||||||
|
if reply in ['0', 'all', 'everyone', 'stop']:
|
||||||
|
self.roles = ['@everyone']
|
||||||
|
else:
|
||||||
|
role_names = [r.name for r in self.server.roles]
|
||||||
|
self.roles = [role_names[i - 1] for i in [int(r.strip()) for r in reply.split(",")]]
|
||||||
|
|
||||||
|
else:
|
||||||
|
text = """**Choose which roles are allowed to vote.**
|
||||||
|
Type `0`, `all` or `everyone` to have no restrictions.
|
||||||
|
Type out the role names, separated by a comma, to restrict voting to specific roles:
|
||||||
|
`moderators, Editors, vips` (hint: role names are case sensitive!)
|
||||||
|
"""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
reply = ''
|
||||||
|
reply_roles = []
|
||||||
|
while reply == '':
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
if reply in ['0', 'all', 'everyone']:
|
||||||
|
break
|
||||||
|
reply_roles = [r.strip() for r in reply.split(",")]
|
||||||
|
invalid_roles = []
|
||||||
|
for r in reply_roles:
|
||||||
|
if not any([r == sr for sr in [x.name for x in self.server.roles]]):
|
||||||
|
invalid_roles.append(r)
|
||||||
|
if invalid_roles.__len__() > 0:
|
||||||
|
await self.add_error(message,
|
||||||
|
f'**Invalid roles found: {", ".join(invalid_roles)}. Roles are case-sensitive!**')
|
||||||
|
reply = ''
|
||||||
|
continue
|
||||||
|
|
||||||
|
if reply in ['0', 'all', 'everyone']:
|
||||||
|
self.roles = ['@everyone']
|
||||||
|
else:
|
||||||
|
self.roles = reply_roles
|
||||||
|
return self.roles
|
||||||
|
|
||||||
|
async def set_weights(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None and len(force) == 2:
|
||||||
|
self.weights_roles = force[0]
|
||||||
|
self.weights_numbers = force[1]
|
||||||
|
return
|
||||||
|
text = """Almost done.
|
||||||
|
**Weights allow you to give certain roles more or less effective votes.
|
||||||
|
Type `0` or `none` if you don't need any weights.**
|
||||||
|
A weight for the role `moderator` of `2` for example will automatically count the votes of all the moderators twice.
|
||||||
|
To assign weights type the role, followed by a colon, followed by the weight like this:
|
||||||
|
`moderator: 2, newbie: 0.5`"""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
status = 'start'
|
||||||
|
roles = []
|
||||||
|
weights = []
|
||||||
|
while status != 'ok':
|
||||||
|
if status != 'start':
|
||||||
|
await self.add_error(message, status)
|
||||||
|
status = 'ok'
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
roles = []
|
||||||
|
weights = []
|
||||||
|
if reply.lower() in ['0', 'none']:
|
||||||
|
break
|
||||||
|
for e in [r.strip() for r in reply.split(",")]:
|
||||||
|
if ':' in e:
|
||||||
|
c = [x.strip() for x in e.rsplit(":", 1)]
|
||||||
|
if not any([c[0] == sr for sr in [x.name for x in self.server.roles]]):
|
||||||
|
status = f'**Role `{c[0]}` not found.**'
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
c[1] = float(c[1])
|
||||||
|
except ValueError:
|
||||||
|
status = f'**Weight `{c[1]}` for `{c[0]}` is not a number.**'
|
||||||
|
break
|
||||||
|
c[1] = int(c[1]) if c[1].is_integer() else c[1]
|
||||||
|
roles.append(c[0])
|
||||||
|
weights.append(c[1])
|
||||||
|
if len(roles) > len(set(roles)):
|
||||||
|
status = f'**Only assign a weight to a role once. `{c[0]}` is assigned twice.**'
|
||||||
|
else:
|
||||||
|
status = f'**No colon found in `{e}`. Use `:` to separate role and weight.**'
|
||||||
|
break
|
||||||
|
|
||||||
|
self.weights_roles = roles
|
||||||
|
self.weights_numbers = weights
|
||||||
|
return [self.weights_roles, self.weights_numbers]
|
||||||
|
|
||||||
|
async def set_duration(self, force=None):
|
||||||
|
if self.stopped: return
|
||||||
|
if force is not None and isinstance(force, float):
|
||||||
|
self.duration = force
|
||||||
|
return
|
||||||
|
text = """Last step.
|
||||||
|
**How long should your poll be active?**
|
||||||
|
Type a number followed by `m`inutes, `h`ours or `d`ays.
|
||||||
|
If you want the poll to last indefinitely (until you close it), type `0`.
|
||||||
|
You can also type a UTC closing date in this format: dd.mm.yyyy hh:mm
|
||||||
|
|
||||||
|
Examples: `5m` or `1 h` or `7d` or `15.8.2019 12:15`"""
|
||||||
|
message = await self.wizard_says(text)
|
||||||
|
|
||||||
|
status = 'start'
|
||||||
|
duration = 0.0
|
||||||
|
while status != 'ok':
|
||||||
|
if status != 'start':
|
||||||
|
await self.add_error(message, status)
|
||||||
|
status = 'ok'
|
||||||
|
reply = await self.get_user_reply()
|
||||||
|
if self.stopped: break
|
||||||
|
if reply == '0': break
|
||||||
|
|
||||||
|
date = self.convert_user_date(reply)
|
||||||
|
if isinstance(date, float):
|
||||||
|
date = float(ceil(date))
|
||||||
|
if date < 0:
|
||||||
|
status = f'**The entered date is in the past. Use a date in the future.**'
|
||||||
|
else:
|
||||||
|
print(date)
|
||||||
|
duration = date
|
||||||
|
else:
|
||||||
|
unit = reply[-1].lower()
|
||||||
|
if unit not in ['m', 'h', 'd']:
|
||||||
|
status = f'**{reply} is not a valid format.**'
|
||||||
|
|
||||||
|
duration = reply[:-1].strip()
|
||||||
|
try:
|
||||||
|
duration = float(duration)
|
||||||
|
except ValueError:
|
||||||
|
status = f'**{reply} is not a valid format.**'
|
||||||
|
|
||||||
|
if unit == 'h':
|
||||||
|
duration *= 60
|
||||||
|
elif unit == 'd':
|
||||||
|
duration *= 60 * 24
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
return self.duration
|
||||||
|
|
||||||
|
def convert_user_date(self, date):
|
||||||
|
try:
|
||||||
|
dt = datetime.datetime.strptime(date, '%d.%m.%Y %H:%M')
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(dt)
|
||||||
|
print(datetime.datetime.utcnow())
|
||||||
|
return float((dt - datetime.datetime.utcnow()).total_seconds() / 60.0)
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
self.time_created = time.time()
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# poll_string = f'Poll by user {self.author}\n'
|
||||||
|
# poll_string += f'Name of the poll: {self.name}\n'
|
||||||
|
# poll_string += f'Short of the poll: {self.short}\n'
|
||||||
|
# poll_string += f'Short of the poll: {str(self.anonymous)}\n'
|
||||||
|
# poll_string += f'Options reaction: {",".join(self.options_reaction)}\n'
|
||||||
|
# poll_string += f'Options traditional: {",".join(self.options_traditional)}\n'
|
||||||
|
# poll_string += f'Roles: {",".join(self.roles)}\n'
|
||||||
|
# poll_string += f'WR: {",".join(self.weights_roles)}\n'
|
||||||
|
# poll_string += f'WN: {",".join([str(x) for x in self.weights_numbers])}\n'
|
||||||
|
# poll_string += f'duration: {self.duration} minutes\n'
|
||||||
|
# return poll_string
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'server_id': str(self.server.id),
|
||||||
|
'channel_id': str(self.channel.id),
|
||||||
|
'author': str(self.author.id),
|
||||||
|
'name': self.name,
|
||||||
|
'short': self.short,
|
||||||
|
'anonymous': self.anonymous,
|
||||||
|
'reaction': self.reaction,
|
||||||
|
'multiple_choice': self.multiple_choice,
|
||||||
|
'options_reaction': self.options_reaction,
|
||||||
|
'reaction_default': self.options_reaction_default,
|
||||||
|
'options_traditional': self.options_traditional,
|
||||||
|
'roles': self.roles,
|
||||||
|
'weights_roles': self.weights_roles,
|
||||||
|
'weights_numbers': self.weights_numbers,
|
||||||
|
'duration': self.duration,
|
||||||
|
'time_created': self.time_created,
|
||||||
|
'open': self.open,
|
||||||
|
'votes': self.votes
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_export(self):
|
||||||
|
# export to txt file
|
||||||
|
|
||||||
|
# build string for weights
|
||||||
|
weight_str = 'No weights'
|
||||||
|
if self.weights_roles.__len__() > 0:
|
||||||
|
weights = []
|
||||||
|
for r, n in zip(self.weights_roles, self.weights_numbers):
|
||||||
|
weights.append(f'{r}: {n}')
|
||||||
|
weight_str = ', '.join(weights)
|
||||||
|
|
||||||
|
# Determine the poll winner
|
||||||
|
winning_options = []
|
||||||
|
winning_votes = 0
|
||||||
|
for i, o in enumerate(self.options_reaction):
|
||||||
|
votes = self.count_votes(i, weighted=True)
|
||||||
|
if votes > winning_votes:
|
||||||
|
winning_options = [o]
|
||||||
|
winning_votes = votes
|
||||||
|
elif votes == winning_votes:
|
||||||
|
winning_options.append(o)
|
||||||
|
|
||||||
|
export = (f'--------------------------------------------\n'
|
||||||
|
f'POLLMASTER DISCORD EXPORT\n'
|
||||||
|
f'--------------------------------------------\n'
|
||||||
|
f'Server name (ID): {self.server.name} ({self.server.id})\n'
|
||||||
|
f'Owner of the poll: {self.author.name}\n'
|
||||||
|
f'Time of creation: {datetime.datetime.utcfromtimestamp(self.time_created).strftime("%d-%b-%Y %H:%M") + " UTC"}\n'
|
||||||
|
f'--------------------------------------------\n'
|
||||||
|
f'POLL SETTINGS\n'
|
||||||
|
f'--------------------------------------------\n'
|
||||||
|
f'Question / Name: {self.name}\n'
|
||||||
|
f'Label: {self.short}\n'
|
||||||
|
f'Anonymous: {"Yes" if self.anonymous else "No"}\n'
|
||||||
|
f'Multiple choice: {"Yes" if self.multiple_choice else "No"}\n'
|
||||||
|
f'Answer options: {", ".join(self.options_reaction)}\n'
|
||||||
|
f'Allowed roles: {", ".join(self.roles) if self.roles.__len__() > 0 else "@everyone"}\n'
|
||||||
|
f'Weights for roles: {weight_str}\n'
|
||||||
|
f'Deadline: {self.get_deadline(string=True)}\n'
|
||||||
|
f'--------------------------------------------\n'
|
||||||
|
f'POLL RESULTS\n'
|
||||||
|
f'--------------------------------------------\n'
|
||||||
|
f'Number of participants: {self.votes.__len__()}\n'
|
||||||
|
f'Raw results: {", ".join([str(o)+": "+str(self.count_votes(i, weighted=False)) for i,o in enumerate(self.options_reaction)])}\n'
|
||||||
|
f'Weighted results: {", ".join([str(o)+": "+str(self.count_votes(i, weighted=True)) for i,o in enumerate(self.options_reaction)])}\n'
|
||||||
|
f'Winning option{"s" if winning_options.__len__() > 1 else ""}: {", ".join(winning_options)} with {winning_votes} votes\n')
|
||||||
|
|
||||||
|
if not self.anonymous:
|
||||||
|
export += '--------------------------------------------\n' \
|
||||||
|
'DETAILED POLL RESULTS\n' \
|
||||||
|
'--------------------------------------------'
|
||||||
|
|
||||||
|
for user_id in self.votes:
|
||||||
|
member = self.server.get_member(user_id)
|
||||||
|
if self.votes[user_id]['choices'].__len__() == 0:
|
||||||
|
continue
|
||||||
|
export += f'\n{member.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']])
|
||||||
|
export += '\n'
|
||||||
|
else:
|
||||||
|
export += '--------------------------------------------\n' \
|
||||||
|
'LIST OF PARTICIPANTS\n' \
|
||||||
|
'--------------------------------------------'
|
||||||
|
|
||||||
|
for user_id in self.votes:
|
||||||
|
member = self.server.get_member(user_id)
|
||||||
|
if self.votes[user_id]['choices'].__len__() == 0:
|
||||||
|
continue
|
||||||
|
export += f'\n{member.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']])
|
||||||
|
export += '\n'
|
||||||
|
|
||||||
|
export += ('--------------------------------------------\n'
|
||||||
|
'BOT DETAILS\n'
|
||||||
|
'--------------------------------------------\n'
|
||||||
|
'Creator: Newti#0654\n'
|
||||||
|
'Link to invite, vote for or support Pollmaster:\n'
|
||||||
|
'https://discordbots.org/bot/444514223075360800\n'
|
||||||
|
'--------------------------------------------\n'
|
||||||
|
'END OF FILE\n'
|
||||||
|
'--------------------------------------------\n')
|
||||||
|
return export
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
if not self.open:
|
||||||
|
fn = 'export/' + str(self.server.id) + '_' + str(self.short) + '.txt'
|
||||||
|
with codecs.open(fn, 'w', 'utf-8') as outfile:
|
||||||
|
outfile.write(self.to_export())
|
||||||
|
return fn
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def from_dict(self, d):
|
||||||
|
self.server = self.bot.get_server(d['server_id'])
|
||||||
|
self.channel = self.bot.get_channel(d['channel_id'])
|
||||||
|
self.author = await self.bot.get_user_info(d['author'])
|
||||||
|
self.name = d['name']
|
||||||
|
self.short = d['short']
|
||||||
|
self.anonymous = d['anonymous']
|
||||||
|
self.reaction = d['reaction']
|
||||||
|
self.multiple_choice = d['multiple_choice']
|
||||||
|
self.options_reaction = d['options_reaction']
|
||||||
|
self.options_reaction_default = d['reaction_default']
|
||||||
|
self.options_traditional = d['options_traditional']
|
||||||
|
self.roles = d['roles']
|
||||||
|
self.weights_roles = d['weights_roles']
|
||||||
|
self.weights_numbers = d['weights_numbers']
|
||||||
|
self.duration = d['duration']
|
||||||
|
self.time_created = d['time_created']
|
||||||
|
self.open = d['open']
|
||||||
|
self.open = self.is_open() # ckeck for deadline since last call
|
||||||
|
self.cursor_pos = 0
|
||||||
|
self.votes = d['votes']
|
||||||
|
|
||||||
|
async def save_to_db(self):
|
||||||
|
await self.bot.db.polls.update_one({'server_id': str(self.server.id), 'short': str(self.short)},
|
||||||
|
{'$set': self.to_dict()}, upsert=True)
|
||||||
|
|
||||||
|
@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': str(short)})
|
||||||
|
if query is not None:
|
||||||
|
p = Poll(bot, ctx, load=True)
|
||||||
|
await p.from_dict(query)
|
||||||
|
return p
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def add_field_custom(self, name, value, embed):
|
||||||
|
## this is used to estimate the width of text and add empty embed fields for a cleaner report
|
||||||
|
## cursor_pos is used to track if we are at the start of a new line in the report. Each line has max 2 slots for info.
|
||||||
|
## If the line is short, we can fit a second field, if it is too long, we get an automatic linebreak.
|
||||||
|
## If it is in between, we create an empty field to prevent the inline from looking ugly
|
||||||
|
|
||||||
|
name = str(name)
|
||||||
|
value = str(value)
|
||||||
|
|
||||||
|
nwidth = afm.string_width_height(unidecode(name))
|
||||||
|
vwidth = afm.string_width_height(unidecode(value))
|
||||||
|
w = max(nwidth[0], vwidth[0])
|
||||||
|
|
||||||
|
embed.add_field(name=name, value=value, inline=False if w > 12500 and self.cursor_pos % 2 == 1 else True)
|
||||||
|
self.cursor_pos += 1
|
||||||
|
|
||||||
|
## create an empty field if we are at the second slot and the width of the first slot is between the critical values
|
||||||
|
if self.cursor_pos % 2 == 1 and w > 11600 and w < 20000:
|
||||||
|
embed.add_field(name='\u200b', value='\u200b', inline=True)
|
||||||
|
self.cursor_pos += 1
|
||||||
|
|
||||||
|
return embed
|
||||||
|
|
||||||
|
async def generate_embed(self):
|
||||||
|
self.cursor_pos = 0
|
||||||
|
embed = discord.Embed(title='', colour=self.color) # f'Status: {"Open" if self.is_open() else "Closed"}'
|
||||||
|
embed.set_author(name=f' >> {self.short} ',
|
||||||
|
icon_url="http://mnadler.ch/img/donat-chart-32.png")
|
||||||
|
embed.set_thumbnail(url="http://mnadler.ch/img/poll-topic-64.png")
|
||||||
|
|
||||||
|
# ## adding fields with custom, length sensitive function
|
||||||
|
embed = await self.add_field_custom(name='**Poll Question**', value=self.name, embed=embed)
|
||||||
|
|
||||||
|
embed = await self.add_field_custom(name='**Roles**', value=', '.join(self.roles), embed=embed)
|
||||||
|
if len(self.weights_roles) > 0:
|
||||||
|
weights = []
|
||||||
|
for r, n in zip(self.weights_roles, self.weights_numbers):
|
||||||
|
weights.append(f'{r}: {n}')
|
||||||
|
embed = await self.add_field_custom(name='**Weights**', value=', '.join(weights), embed=embed)
|
||||||
|
|
||||||
|
embed = await self.add_field_custom(name='**Anonymous**', value=self.anonymous, embed=embed)
|
||||||
|
|
||||||
|
# embed = await self.add_field_custom(name='**Multiple Choice**', value=self.multiple_choice, embed=embed)
|
||||||
|
embed = await self.add_field_custom(name='**Deadline**', value=self.get_poll_status(), embed=embed)
|
||||||
|
embed = await self.add_field_custom(name='**Author**', value=self.author.name, embed=embed)
|
||||||
|
|
||||||
|
if self.reaction:
|
||||||
|
if self.options_reaction_default:
|
||||||
|
if self.is_open():
|
||||||
|
text = f'*Vote by adding reactions to the poll*. '
|
||||||
|
text += '*You can vote for multiple options.*' if self.multiple_choice \
|
||||||
|
else '*You have 1 vote, but can change it.*'
|
||||||
|
else:
|
||||||
|
text = f'*Final Results of the {"multiple choice" if self.multiple_choice else "single choice"} Poll.*'
|
||||||
|
|
||||||
|
vote_display = []
|
||||||
|
for i, r in enumerate(self.options_reaction):
|
||||||
|
vote_display.append(f'{r} {self.count_votes(i)}')
|
||||||
|
embed = await self.add_field_custom(name='**Score**', value=' '.join(vote_display), embed=embed)
|
||||||
|
else:
|
||||||
|
embed.add_field(name='\u200b', value='\u200b', inline=False)
|
||||||
|
if self.is_open():
|
||||||
|
text = f'*Vote by adding reactions to the poll*. '
|
||||||
|
text += '*You can vote for multiple options.*' if self.multiple_choice \
|
||||||
|
else '*You have 1 vote, but can change it.*'
|
||||||
|
else:
|
||||||
|
text = f'*Final Results of the {"multiple choice" if self.multiple_choice else "single choice"} Poll.*'
|
||||||
|
embed = await self.add_field_custom(name='**Options**', value=text, embed=embed)
|
||||||
|
for i, r in enumerate(self.options_reaction):
|
||||||
|
embed = await self.add_field_custom(
|
||||||
|
name=f':regional_indicator_{ascii_lowercase[i]}: {self.count_votes(i)}',
|
||||||
|
value=r,
|
||||||
|
embed=embed
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = await self.add_field_custom(name='**Options**', value=', '.join(self.get_options()), embed=embed)
|
||||||
|
|
||||||
|
embed.set_footer(text='bot is in development')
|
||||||
|
|
||||||
|
return embed
|
||||||
|
|
||||||
|
async def post_embed(self, ctx):
|
||||||
|
msg = await self.bot.say(embed=await self.generate_embed())
|
||||||
|
if self.reaction and self.is_open():
|
||||||
|
if self.options_reaction_default:
|
||||||
|
for r in self.options_reaction:
|
||||||
|
await self.bot.add_reaction(
|
||||||
|
msg,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
return msg
|
||||||
|
else:
|
||||||
|
for i, r in enumerate(self.options_reaction):
|
||||||
|
await self.bot.add_reaction(
|
||||||
|
msg,
|
||||||
|
AZ_EMOJIS[i]
|
||||||
|
)
|
||||||
|
return msg
|
||||||
|
else:
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def get_options(self):
|
||||||
|
if self.reaction:
|
||||||
|
return self.options_reaction
|
||||||
|
else:
|
||||||
|
return self.options_traditional
|
||||||
|
|
||||||
|
def get_deadline(self, string=False):
|
||||||
|
deadline = float(self.time_created) + float(self.duration) * 60
|
||||||
|
if string:
|
||||||
|
if self.duration == 0:
|
||||||
|
return 'No deadline'
|
||||||
|
else:
|
||||||
|
return datetime.datetime.utcfromtimestamp(deadline).strftime('%d-%b-%Y %H:%M') + ' UTC'
|
||||||
|
else:
|
||||||
|
return deadline
|
||||||
|
|
||||||
|
def get_poll_status(self):
|
||||||
|
if self.is_open():
|
||||||
|
return self.get_deadline(string=True)
|
||||||
|
else:
|
||||||
|
return 'Poll is closed.'
|
||||||
|
|
||||||
|
def count_votes(self, option, weighted=True):
|
||||||
|
'''option: number from 0 to n'''
|
||||||
|
if weighted:
|
||||||
|
return sum([self.votes[c]['weight'] for c in [u for u in self.votes] if option in self.votes[c]['choices']])
|
||||||
|
else:
|
||||||
|
return sum([1 for c in [u for u in self.votes] if option in self.votes[c]['choices']])
|
||||||
|
|
||||||
|
# async def has_voted(self, user_id):
|
||||||
|
# query = await self.bot.db.polls.find_one({'server_id': str(self.server.id), 'short': self.short})
|
||||||
|
# if query is None:
|
||||||
|
# return False
|
||||||
|
# else:
|
||||||
|
# votes = query['votes']
|
||||||
|
# if user_id in votes:
|
||||||
|
# return votes[user_id]
|
||||||
|
# else:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
async def vote(self, user, option, message):
|
||||||
|
if not 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)
|
||||||
|
return
|
||||||
|
|
||||||
|
choice = 'invalid'
|
||||||
|
already_voted = False
|
||||||
|
|
||||||
|
# get weight
|
||||||
|
weight = 1
|
||||||
|
if self.weights_roles.__len__() > 0:
|
||||||
|
valid_weights = [self.weights_numbers[self.weights_roles.index(r)] for r in
|
||||||
|
list(set([n.name for n in user.roles]).intersection(set(self.weights_roles)))]
|
||||||
|
if valid_weights.__len__() > 0:
|
||||||
|
weight = max(valid_weights)
|
||||||
|
|
||||||
|
if str(user.id) not in self.votes:
|
||||||
|
self.votes[user.id] = {'weight': weight, 'choices': []}
|
||||||
|
else:
|
||||||
|
self.votes[user.id]['weight'] = weight
|
||||||
|
|
||||||
|
if self.reaction:
|
||||||
|
if self.options_reaction_default:
|
||||||
|
if option in self.options_reaction:
|
||||||
|
choice = self.options_reaction.index(option)
|
||||||
|
else:
|
||||||
|
if option in AZ_EMOJIS:
|
||||||
|
choice = AZ_EMOJIS.index(option)
|
||||||
|
|
||||||
|
if choice != 'invalid':
|
||||||
|
if self.multiple_choice:
|
||||||
|
if choice in self.votes[user.id]['choices'] and self.anonymous:
|
||||||
|
# anonymous multiple choice -> can't unreact so we toggle with react
|
||||||
|
await self.unvote(user, option, message)
|
||||||
|
return
|
||||||
|
self.votes[user.id]['choices'].append(choice)
|
||||||
|
# if len(self.votes[user.id]['choices']) > len(set(self.votes[user.id]['choices'])):
|
||||||
|
# already_voted = True
|
||||||
|
self.votes[user.id]['choices'] = list(set(self.votes[user.id]['choices']))
|
||||||
|
else:
|
||||||
|
if [choice] == self.votes[user.id]['choices']:
|
||||||
|
already_voted = True
|
||||||
|
else:
|
||||||
|
self.votes[user.id]['choices'] = [choice]
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# commit
|
||||||
|
await self.save_to_db()
|
||||||
|
|
||||||
|
# refresh
|
||||||
|
if not already_voted:
|
||||||
|
# edit message if there is a real change
|
||||||
|
await self.bot.edit_message(message, embed=await self.generate_embed())
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def unvote(self, user, option, message):
|
||||||
|
if not 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)
|
||||||
|
return
|
||||||
|
|
||||||
|
if str(user.id) not in self.votes: return
|
||||||
|
|
||||||
|
choice = 'invalid'
|
||||||
|
if self.reaction:
|
||||||
|
if self.options_reaction_default:
|
||||||
|
if option in self.options_reaction:
|
||||||
|
choice = self.options_reaction.index(option)
|
||||||
|
else:
|
||||||
|
if option in AZ_EMOJIS:
|
||||||
|
choice = AZ_EMOJIS.index(option)
|
||||||
|
|
||||||
|
if choice != 'invalid' and choice in self.votes[user.id]['choices']:
|
||||||
|
try:
|
||||||
|
self.votes[user.id]['choices'].remove(choice)
|
||||||
|
await self.save_to_db()
|
||||||
|
await self.bot.edit_message(message, 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)
|
||||||
397
cogs/poll_controls.py
Normal file
397
cogs/poll_controls.py
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
import copy
|
||||||
|
import pprint
|
||||||
|
import time
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from settings import *
|
||||||
|
from discord.ext import commands
|
||||||
|
from .poll import Poll
|
||||||
|
from .utils import ask_for_server, ask_for_channel, get_server_pre
|
||||||
|
from .utils import SETTINGS
|
||||||
|
from utils.poll_name_generator import generate_word
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PollControls:
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
## General Methods
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
result = await self.bot.db.config.find_one({'_id': str(server.id)})
|
||||||
|
if result and result.get('admin_role') in [r.name for r in member.roles]:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if error_msg is not None:
|
||||||
|
await self.bot.send_message(ctx.message.author, error_msg)
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def say_error(self, ctx, error_text, footer_text=None):
|
||||||
|
embed = discord.Embed(title='', description=error_text, colour=SETTINGS.color)
|
||||||
|
embed.set_author(name='Error', icon_url=SETTINGS.title_icon)
|
||||||
|
if footer_text is not None:
|
||||||
|
embed.set_footer(text=footer_text)
|
||||||
|
await self.bot.say(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.title_icon)
|
||||||
|
if footer_text is not None:
|
||||||
|
embed.set_footer(text=footer_text)
|
||||||
|
await self.bot.say(embed=embed)
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
if p is not None:
|
||||||
|
# Permission Check: Admin or Creator
|
||||||
|
if not await self.is_admin_or_creator(
|
||||||
|
ctx, server,
|
||||||
|
p.author.id,
|
||||||
|
'You don\'t have sufficient rights to delete this poll. Please talk to the server admin.'
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Delete Poll
|
||||||
|
result = await self.bot.db.polls.delete_one({'server_id': 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!`'
|
||||||
|
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)
|
||||||
|
footer = f'Type {pre}show to display all polls'
|
||||||
|
await self.say_error(ctx, error, footer)
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
async def close(self, ctx, *, short=None):
|
||||||
|
'''Close 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)
|
||||||
|
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)
|
||||||
|
if p is not None:
|
||||||
|
# Permission Check: Admin or Creator
|
||||||
|
if not await self.is_admin_or_creator(
|
||||||
|
ctx, server,
|
||||||
|
p.author.id,
|
||||||
|
'You don\'t have sufficient rights to close this poll. Please talk to the server admin.'
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Close Poll
|
||||||
|
p.open = False
|
||||||
|
await p.save_to_db()
|
||||||
|
await ctx.invoke(self.show, short)
|
||||||
|
else:
|
||||||
|
error = f'Poll with label "{short}" was not found.'
|
||||||
|
# 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)
|
||||||
|
async def export(self, ctx, *, short=None):
|
||||||
|
'''Export 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)
|
||||||
|
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)
|
||||||
|
if p is not None:
|
||||||
|
if p.open:
|
||||||
|
pre = await get_server_pre(self.bot, ctx.message.server)
|
||||||
|
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 = 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)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
error_text = 'Could not export the requested poll. \nPlease report this to the developer.'
|
||||||
|
await self.say_error(ctx, error_text)
|
||||||
|
else:
|
||||||
|
error = f'Poll with label "{short}" was not found.'
|
||||||
|
# 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)
|
||||||
|
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>'''
|
||||||
|
|
||||||
|
server = await ask_for_server(self.bot, ctx.message, short)
|
||||||
|
if not server:
|
||||||
|
return
|
||||||
|
|
||||||
|
if short in ['open', 'closed', 'prepared']:
|
||||||
|
query = None
|
||||||
|
if short == 'open':
|
||||||
|
query = self.bot.db.polls.find({'server_id': str(server.id), 'open': True})
|
||||||
|
elif short == 'closed':
|
||||||
|
query = self.bot.db.polls.find({'server_id': str(server.id), 'open': False})
|
||||||
|
elif short == 'prepared':
|
||||||
|
pass #TODO: prepared showw
|
||||||
|
|
||||||
|
if query is not None:
|
||||||
|
polls = [poll async for poll in query]
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
def item_fct(item):
|
||||||
|
return f':black_small_square: **{item["short"]}**: {item["name"]}'
|
||||||
|
|
||||||
|
title = f' Listing {short} polls'
|
||||||
|
embed = discord.Embed(title='', description='', colour=SETTINGS.color)
|
||||||
|
embed.set_author(name=title, icon_url=SETTINGS.title_icon)
|
||||||
|
# await self.bot.say(embed=await self.embed_list_paginated(polls, item_fct, embed))
|
||||||
|
msg = await self.embed_list_paginated(ctx, polls, item_fct, embed, per_page=8)
|
||||||
|
else:
|
||||||
|
p = await Poll.load_from_db(self.bot, str(server.id), short)
|
||||||
|
if p is not None:
|
||||||
|
msg = 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 quick(self, ctx, *, cmd=None):
|
||||||
|
'''Create a quick poll with just a question and some options. Parameters: <Question> (optional)'''
|
||||||
|
async def route(poll):
|
||||||
|
await poll.set_name(force=cmd)
|
||||||
|
await poll.set_short(force=str(await generate_word(self.bot, ctx.message.server.id)))
|
||||||
|
await poll.set_anonymous(force=False)
|
||||||
|
await poll.set_reaction(force=True)
|
||||||
|
await poll.set_multiple_choice(force=False)
|
||||||
|
await poll.set_options_reaction()
|
||||||
|
await poll.set_roles(force=['@everyone'])
|
||||||
|
await poll.set_weights(force=[[], []])
|
||||||
|
await poll.set_duration(force=0.0)
|
||||||
|
|
||||||
|
await self.wizard(ctx, route)
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
async def new(self, ctx, *, cmd=None):
|
||||||
|
'''Start the poll wizard to create a new poll step by step. Parameters: >Question> (optional) '''
|
||||||
|
async def route(poll):
|
||||||
|
await poll.set_name(force=cmd)
|
||||||
|
await poll.set_short()
|
||||||
|
await poll.set_anonymous()
|
||||||
|
# await poll.set_reaction()
|
||||||
|
await poll.set_multiple_choice()
|
||||||
|
if poll.reaction:
|
||||||
|
await poll.set_options_reaction()
|
||||||
|
else:
|
||||||
|
await poll.set_options_traditional()
|
||||||
|
await poll.set_roles()
|
||||||
|
await poll.set_weights()
|
||||||
|
await poll.set_duration()
|
||||||
|
|
||||||
|
await self.wizard(ctx, route)
|
||||||
|
|
||||||
|
## Other methods
|
||||||
|
async def embed_list_paginated(self, ctx, items, item_fct, base_embed, msg=None, start=0, per_page=10):
|
||||||
|
embed = base_embed
|
||||||
|
|
||||||
|
# generate list
|
||||||
|
embed.title = f'{items.__len__()} entries'
|
||||||
|
text = '\n'
|
||||||
|
for item in items[start:start+per_page]:
|
||||||
|
text += item_fct(item) + '\n'
|
||||||
|
embed.description = text
|
||||||
|
|
||||||
|
# footer text
|
||||||
|
pre = await get_server_pre(self.bot, ctx.message.server)
|
||||||
|
footer_text = f'Type {pre}show <label> to show a poll. '
|
||||||
|
if start > 0:
|
||||||
|
footer_text += f'React with ⏪ to show the last {per_page} entries. '
|
||||||
|
if items.__len__() > start+per_page:
|
||||||
|
footer_text += f'React with ⏩ to show the next {per_page} entries. '
|
||||||
|
if footer_text.__len__() > 0:
|
||||||
|
embed.set_footer(text=footer_text)
|
||||||
|
|
||||||
|
# post / edit message
|
||||||
|
if msg is not None:
|
||||||
|
await self.bot.edit_message(msg, embed=embed)
|
||||||
|
await self.bot.clear_reactions(msg)
|
||||||
|
else:
|
||||||
|
msg = await self.bot.say(embed=embed)
|
||||||
|
|
||||||
|
# add reactions
|
||||||
|
if start > 0:
|
||||||
|
await self.bot.add_reaction(msg, '⏪')
|
||||||
|
if items.__len__() > start+per_page:
|
||||||
|
await self.bot.add_reaction(msg, '⏩')
|
||||||
|
|
||||||
|
# wait for reactions (2 minutes)
|
||||||
|
def check(reaction, user):
|
||||||
|
return reaction.emoji if user != self.bot.user else False
|
||||||
|
res = await self.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 self.embed_list_paginated(ctx, items, item_fct, base_embed, msg=msg, start=start-per_page, per_page=per_page)
|
||||||
|
elif res.reaction.emoji == '⏩' and items.__len__() > start+per_page:
|
||||||
|
await self.embed_list_paginated(ctx, items, item_fct, base_embed, msg=msg, start=start+per_page, per_page=per_page)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def wizard(self, ctx, route):
|
||||||
|
server = await ask_for_server(self.bot, ctx.message)
|
||||||
|
if not server:
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = await ask_for_channel(self.bot, server, ctx.message)
|
||||||
|
if not channel:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Permission Check
|
||||||
|
member = server.get_member(ctx.message.author.id)
|
||||||
|
result = await self.bot.db.config.find_one({'_id': str(server.id)})
|
||||||
|
if result and result.get('admin_role') not in [r.name for r in member.roles] and result.get('user_role') not in [r.name for r in member.roles]:
|
||||||
|
await self.bot.send_message(ctx.message.author,
|
||||||
|
'You don\'t have sufficient rights to start new polls on this server. Please talk to the server admin.')
|
||||||
|
return
|
||||||
|
|
||||||
|
## Create object
|
||||||
|
poll = Poll(self.bot, ctx, server, channel)
|
||||||
|
|
||||||
|
## Route to define object, passed as argument for different constructors
|
||||||
|
await route(poll)
|
||||||
|
|
||||||
|
## Finalize
|
||||||
|
if poll.stopped:
|
||||||
|
print("Poll Wizard Stopped.")
|
||||||
|
else:
|
||||||
|
msg = await poll.post_embed(ctx)
|
||||||
|
await poll.save_to_db()
|
||||||
|
|
||||||
|
|
||||||
|
## BOT EVENTS (@bot.event)
|
||||||
|
async def on_reaction_add(self, reaction, user):
|
||||||
|
if user != self.bot.user:
|
||||||
|
|
||||||
|
if reaction.emoji.startswith(('⏪', '⏩')):
|
||||||
|
return
|
||||||
|
|
||||||
|
# only look at our polls
|
||||||
|
try:
|
||||||
|
short = reaction.message.embeds[0]['author']['name'][3:]
|
||||||
|
if not reaction.message.embeds[0]['author']['name'].startswith('>> ') or not short:
|
||||||
|
return
|
||||||
|
except IndexError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# create message object for the reaction
|
||||||
|
user_msg = copy.deepcopy(reaction.message)
|
||||||
|
user_msg.author = user
|
||||||
|
|
||||||
|
server = await ask_for_server(self.bot, user_msg, short)
|
||||||
|
if str(user_msg.channel.type) == 'private':
|
||||||
|
user = server.get_member(user.id)
|
||||||
|
user_msg.author = user
|
||||||
|
|
||||||
|
# fetch poll
|
||||||
|
p = await Poll.load_from_db(self.bot, server.id, short)
|
||||||
|
if p is None:
|
||||||
|
return
|
||||||
|
# no rights, terminate function
|
||||||
|
if not await p.has_required_role(user):
|
||||||
|
await self.bot.remove_reaction(reaction.message, reaction.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)}')
|
||||||
|
return
|
||||||
|
|
||||||
|
# order here is crucial since we can't determine if a reaction was removed by the bot or user
|
||||||
|
# update database with vote
|
||||||
|
await p.vote(user, reaction.emoji, reaction.message)
|
||||||
|
|
||||||
|
# check if we need to remove reactions (this will trigger on_reaction_remove)
|
||||||
|
if str(reaction.message.channel.type) != 'private':
|
||||||
|
if p.anonymous:
|
||||||
|
# immediately remove reaction
|
||||||
|
await self.bot.remove_reaction(reaction.message, reaction.emoji, user)
|
||||||
|
elif not p.multiple_choice:
|
||||||
|
# remove all other reactions
|
||||||
|
for r in reaction.message.reactions:
|
||||||
|
if r != reaction:
|
||||||
|
await self.bot.remove_reaction(reaction.message, r.emoji, user)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def on_reaction_remove(self, reaction, user):
|
||||||
|
if reaction.emoji.startswith(('⏪', '⏩')):
|
||||||
|
return
|
||||||
|
|
||||||
|
# only look at our polls
|
||||||
|
try:
|
||||||
|
short = reaction.message.embeds[0]['author']['name'][3:]
|
||||||
|
if not reaction.message.embeds[0]['author']['name'].startswith('>> ') or not short:
|
||||||
|
return
|
||||||
|
except IndexError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# create message object for the reaction
|
||||||
|
user_msg = copy.deepcopy(reaction.message)
|
||||||
|
user_msg.author = user
|
||||||
|
|
||||||
|
server = await ask_for_server(self.bot, user_msg, short)
|
||||||
|
if str(user_msg.channel.type) == 'private':
|
||||||
|
user = server.get_member(user.id)
|
||||||
|
user_msg.author = user
|
||||||
|
|
||||||
|
# fetch poll
|
||||||
|
p = await Poll.load_from_db(self.bot, server.id, short)
|
||||||
|
if p is None:
|
||||||
|
return
|
||||||
|
if not p.anonymous:
|
||||||
|
# for anonymous polls we can't unvote because we need to hide reactions
|
||||||
|
await p.unvote(user, reaction.emoji, reaction.message)
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(PollControls(bot))
|
||||||
136
cogs/utils.py
Normal file
136
cogs/utils.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import discord
|
||||||
|
|
||||||
|
class Settings:
|
||||||
|
def __init__(self):
|
||||||
|
self.color = discord.Colour(int('7289da', 16))
|
||||||
|
self.title_icon = "http://mnadler.ch/img/donat-chart-32.png"
|
||||||
|
|
||||||
|
SETTINGS = Settings()
|
||||||
|
|
||||||
|
async def get_pre(bot, message):
|
||||||
|
'''Gets the prefix for a message.'''
|
||||||
|
if str(message.channel.type) == 'private':
|
||||||
|
shared_server_list = await get_servers(bot, message)
|
||||||
|
if shared_server_list.__len__() == 0:
|
||||||
|
return '!'
|
||||||
|
elif shared_server_list.__len__() == 1:
|
||||||
|
return await get_server_pre(bot, shared_server_list[0])
|
||||||
|
else:
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
async def get_server_pre(bot, server):
|
||||||
|
'''Gets the prefix for a server.'''
|
||||||
|
try:
|
||||||
|
result = await bot.db.config.find_one({'_id': str(server.id)})
|
||||||
|
except AttributeError:
|
||||||
|
return '!'
|
||||||
|
if not result or not result.get('prefix'):
|
||||||
|
return '!'
|
||||||
|
return result.get('prefix')
|
||||||
|
|
||||||
|
|
||||||
|
async def get_servers(bot, message, short=None):
|
||||||
|
if message.server is None:
|
||||||
|
|
||||||
|
list_of_shared_servers = []
|
||||||
|
for s in bot.servers:
|
||||||
|
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]
|
||||||
|
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
|
||||||
|
|
||||||
|
# do this if no shared server with short is found
|
||||||
|
if list_of_shared_servers.__len__() == 0:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return list_of_shared_servers
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
return [message.server]
|
||||||
|
|
||||||
|
async def ask_for_server(bot, message, short=None):
|
||||||
|
server_list = await get_servers(bot, message, short)
|
||||||
|
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.')
|
||||||
|
else:
|
||||||
|
await bot.say(f'I could not find a server where the poll {short} exists that we both can see.')
|
||||||
|
return None
|
||||||
|
elif server_list.__len__() == 1:
|
||||||
|
return server_list[0]
|
||||||
|
else:
|
||||||
|
text = 'I\'m not sure which server you are referring to. Please tell me by typing the corresponding number.\n'
|
||||||
|
i = 1
|
||||||
|
for name in [s.name for s in server_list]:
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return server_list[nr - 1]
|
||||||
|
|
||||||
|
async def ask_for_channel(bot, server, message):
|
||||||
|
# if performed from a channel, return that channel
|
||||||
|
if str(message.channel.type) == 'text':
|
||||||
|
return message.channel
|
||||||
|
|
||||||
|
# if exactly 1 channel, return it
|
||||||
|
channel_list = [c for c in server.channels if str(c.type) == 'text']
|
||||||
|
if channel_list.__len__() == 1:
|
||||||
|
return channel_list[0]
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# otherwise ask for a channel
|
||||||
|
text = 'Polls are bound to a specific channel on a server. Please select the channel for this poll by typing the corresponding number.\n'
|
||||||
|
i = 1
|
||||||
|
for name in [c.name for c in channel_list]:
|
||||||
|
text += f'\n**{i}** - {name}'
|
||||||
|
i += 1
|
||||||
|
embed = discord.Embed(title="Select a channel", description=text, color=SETTINGS.color)
|
||||||
|
server_msg = await bot.say(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
|
||||||
|
return channel_list[nr - 1]
|
||||||
43
pollmaster.py
Normal file
43
pollmaster.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import os
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
|
from discord.ext import commands
|
||||||
|
from motor.motor_asyncio import AsyncIOMotorClient
|
||||||
|
|
||||||
|
#os.environ['dbltoken'] = 'ABC' #for website..
|
||||||
|
from cogs.utils import get_pre
|
||||||
|
|
||||||
|
os.environ['mongoDB'] = 'mongodb://localhost:27017/pollmaster'
|
||||||
|
|
||||||
|
# async def get_pre(bot, message):
|
||||||
|
# '''Gets the prefix for the server.'''
|
||||||
|
# print(str(message.content))
|
||||||
|
# try:
|
||||||
|
# result = await bot.db.config.find_one({'_id': str(message.server.id)})
|
||||||
|
# except AttributeError:
|
||||||
|
# return '!'
|
||||||
|
# if not result or not result.get('prefix'):
|
||||||
|
# return '!'
|
||||||
|
# return result.get('prefix')
|
||||||
|
|
||||||
|
bot = commands.Bot(command_prefix=get_pre)
|
||||||
|
dbltoken = os.environ.get('dbltoken')
|
||||||
|
|
||||||
|
extensions = ['cogs.config','cogs.poll_controls']
|
||||||
|
for ext in extensions:
|
||||||
|
bot.load_extension(ext)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_ready():
|
||||||
|
|
||||||
|
mongo = AsyncIOMotorClient(os.environ.get('mongodb'))
|
||||||
|
bot.db = mongo.pollmaster
|
||||||
|
bot.session = aiohttp.ClientSession()
|
||||||
|
print(bot.db)
|
||||||
|
# document = {'key': 'value'}
|
||||||
|
# result = await bot.db.test_collection.insert_one(document)
|
||||||
|
# print('result %s' % repr(result.inserted_id))
|
||||||
|
|
||||||
|
|
||||||
|
bot.run('NDQ0ODMxNzIwNjU5ODc3ODg5.DdhqZw.fsicJ8FffOYn670uPGuC4giXIlk')
|
||||||
4
settings.py
Normal file
4
settings.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import discord
|
||||||
|
|
||||||
|
|
||||||
|
PURPLE = discord.Colour(int('7289da', 16))
|
||||||
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
14
utils/poll_name_generator.py
Normal file
14
utils/poll_name_generator.py
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user