This commit is contained in:
matnad 2019-02-10 19:53:11 +01:00
parent 67ad573dd6
commit d94d0b976a
12 changed files with 1244 additions and 564 deletions

View File

@ -1,3 +1,5 @@
import logging
import discord
from discord.ext import commands
@ -74,4 +76,6 @@ class Config:
await self.bot.say(f'Server role `{role}` not found.')
def setup(bot):
global logger
logger = logging.getLogger('bot')
bot.add_cog(Config(bot))

33
cogs/db_api.py Normal file
View File

@ -0,0 +1,33 @@
import dbl
import asyncio
import logging
from essentials.settings import SETTINGS
class DiscordBotsOrgAPI:
"""Handles interactions with the discordbots.org API"""
def __init__(self, bot):
self.bot = bot
self.token = SETTINGS.dbl_token
self.dblpy = dbl.Client(self.bot, self.token)
self.bot.loop.create_task(self.update_stats())
async def update_stats(self):
"""This function runs every 30 minutes to automatically update your server count"""
while True:
logger.info('attempting to post server count')
try:
#await self.dblpy.post_server_count()
logger.info('posted server count ({})'.format(len(self.bot.servers)))
except Exception as e:
logger.exception('Failed to post server count\n{}: {}'.format(type(e).__name__, e))
await asyncio.sleep(1800)
def setup(bot):
global logger
logger = logging.getLogger('bot')
bot.add_cog(DiscordBotsOrgAPI(bot))

215
cogs/help.py Normal file
View File

@ -0,0 +1,215 @@
import logging
import discord
from discord.ext import commands
from essentials.multi_server import get_server_pre, ask_for_server
from essentials.settings import SETTINGS
class Help:
def __init__(self, bot):
self.bot = bot
self.pages = ['🏠', '🆕', '🔍', '🕹', '🛠', '💖']
async def embed_list_reaction_handler(self, page, pre, msg=None):
embed = self.get_help_embed(page, pre)
if msg is None:
msg = await self.bot.say(embed=embed)
# add reactions
for emoji in self.pages:
await self.bot.add_reaction(msg, emoji)
else:
await self.bot.edit_message(msg, embed=embed)
# 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=self.pages, message=msg, timeout=180, check=check)
# redirect on reaction
if res is None:
await self.bot.delete_message(msg)
return None
else:
await self.bot.remove_reaction(res.reaction.message, res.reaction.emoji, res.user)
return res
def get_help_embed(self, page, pre):
title = f' Pollmaster Help - React with an emoji to learn more about this topic!'
embed = discord.Embed(title='', description='', colour=SETTINGS.color)
embed.set_author(name=title, icon_url=SETTINGS.title_icon)
embed.set_footer(text='Use reactions to navigate the help. This message will self-destruct in 3 minutes.')
if page == '🏠':
## POLL CREATION SHORT
embed.add_field(name='🆕 Making New Polls',
value='There are 3 ways to create a new poll.', inline=False)
embed.add_field(name='Commands', value=f'`{pre}quick` | `{pre}new` | `{pre}prepared`', inline=False)
embed.add_field(name='Arguments', value=f'Arguments: `<poll question>` (optional)', inline=False)
embed.add_field(name='Examples', value=f'Examples: `{pre}new` | `{pre}quick What is the greenest color?`',
inline=False)
## POLL CONTROLS
embed.add_field(name='🔍 Show Polls',
value='Commands to list and display polls.', inline=False)
embed.add_field(name='Command', value=f'`{pre}show`', inline=False)
embed.add_field(name='Arguments', value=f'Arguments: `open` (default) | `closed` | `prepared` | '
f'`<poll_label>` (optional)', inline=False)
embed.add_field(name='Examples', value=f'Examples: `{pre}show` | `{pre}show closed` | `{pre}show mascot`',
inline=False)
## POLL CONTROLS
embed.add_field(name='🕹 Poll Controls',
value='You can use these commands to interact with polls.', inline=False)
embed.add_field(name='Commands', value=f'`{pre}close` | `{pre}export` | `{pre}delete` | `{pre}activate` ',
inline=False)
embed.add_field(name='Arguments', value=f'Arguments: <poll_label> (required)', inline=False)
embed.add_field(name='Examples', value=f'Examples: `{pre}close mascot` | `{pre}export proposal`',
inline=False)
## POLL CONTROLS
embed.add_field(name='🛠 Configuration',
value='Various Commands to personalize Pollmaster for this server.', inline=False)
embed.add_field(name='Commands',
value=f'`{pre}userrole <role>` | `{pre}adminrole <role>` | `{pre}prefix <new_prefix>` ',
inline=False)
## ABOUT
embed.add_field(name='💖 About Pollmaster',
value='More infos about Pollmaster, the developer, where to go for further help and how you can support us.',
inline=False)
elif page == '🆕':
embed.add_field(name='🆕 Making New Polls',
value='There are three ways to create a new poll. For all three commands you can either just '
'type the command or type the command followed by the question to skip that first step.'
'Your Members need the <admin> or <user> role to use these commands. More in 🛠 Configuration.',
inline=False)
embed.add_field(name='🔹 **Quick Poll:** `!quick <poll question>` (optional)',
value='If you just need a quick poll, this is the way to go. All you have to specify is the '
'question and your answers; the rest will be set to default values.',
inline=False)
embed.add_field(name='🔹 **All Features:** `!new <poll question>` (optional)',
value='This command gives you full control over your poll. A step by step wizard will guide '
'you through the process and you can specify options such as Multiple Choice, '
'Anonymous Voting, Role Restriction, Role Weights and Deadline.',
inline=False)
embed.add_field(name='🔹 **Prepare and Schedule:** `!prepare <poll question>` (optional)',
value='Similar to `!new`, this gives you all the options. But additionally, the poll will '
'be set to \'inactive\'. You can specify if the poll should activate at a certain time '
'and/or if you would like to manually `!activate` it. '
'Perfect if you are preparing for a team meeting!',
inline=False)
elif page == '🔍':
embed.add_field(name='🔍 Show Polls',
value='All users can display and list polls, with the exception of prepared polls. '
'Voting is done simply by using the reactions below the poll.',
inline=False)
embed.add_field(name='🔹 **Show a Poll:** `!show <poll_label>`',
value='This command will refresh and display a poll. The votes in the message will always '
'be up to date and accurate. The number of reactions can be different for a number '
'of reasons and you can safely disregard them.',
inline=False)
embed.add_field(name='🔹 **List Polls:** `!show <> | open | closed | prepared`',
value='If you just type `!show` without an argument it will default to `!show open`.'
'These commands will print a list of open, closed or prepared polls that exist on '
'the server. The first word in bold is the label of the poll and after the colon, '
'you can read the question. These lists are paginated and you can use the arrow '
'reactions to navigate larger lists.',
inline=False)
elif page == '🕹':
embed.add_field(name='🕹 Poll Controls',
value='All these commands can only be used by an <admin> or by the author of the poll. '
'Go to 🛠 Configuration for more info on the permissions.',
inline=False)
embed.add_field(name='🔹 **Close** `!close <poll_label>`',
value='Polls will close automatically when their deadline is reached. But you can always '
'close them manually by using this command. A closed poll will lock in the votes so '
'users can no longer change, add or remove votes. Once closed, you can export a poll.',
inline=False)
embed.add_field(name='🔹 **Delete** `!delete <poll_label>`',
value='This will *permanently and irreversibly* delete a poll from the database. '
'Once done, the label is freed up and can be assigned again.',
inline=False)
embed.add_field(name='🔹 **Export** `!export <poll_label>`',
value='You can use this command or react with 📎 to a closed poll to generate a report. '
'The report will then be sent to you in discord via the bot. This utf8-textfile '
'(make sure to open it in an utf8-ready editor) will contain all the infos about the '
'poll, including a detailed list of participants and their votes (just a list of names '
'for anonymous polls).',
inline=False)
embed.add_field(name='🔹 **Activate** `!activate <poll_label>`',
value='To see how you can prepare inactive polls read the `!prepare` command under Making '
'New Polls. This command is used to manually activate a prepared poll.',
inline=False)
elif page == '🛠':
embed.add_field(name='🛠 Configuration',
value='To run any of these commands you need the **\"Manage Server\"** permisson.',
inline=False)
embed.add_field(name='🔹 **Poll Admins** `!adminrole <role name> (optional)`',
value='This gives the rights to create polls and to control ALL polls on the server. '
'To see the current role for poll admin, run the command without an argument: `!adminrole`\n'
'If you want to change the admin role to any other role, use the name of the new role '
'as the argument: !adminrole moderators',
inline=False)
embed.add_field(name='🔹 **Poll Users** `!userrole <role name> (optional)`',
value='Everything here is identical to the admin role, except that Poll Users can only '
'control the polls which were created by themselves.',
inline=False)
embed.add_field(name='🔹 **Change Prefix** `!prefix <new_prefix>`',
value='This will change the bot prefix for your server. If you want to use a trailing '
'whitespace, use "\w" instead of " " (discord deletes trailing whitespaces).',
inline=False)
elif page == '💖':
embed.add_field(name='💖 Pollmaster 💖',
value='If you enjoy the bot, you can show your appreciation by giving him an upvote on Discordbots.',
inline=False)
embed.add_field(name='🔹 **Developer**',
value='Pollmaster is developed by Newti#0654',
inline=False)
embed.add_field(name='🔹 **Support**',
value='You can support Pollmaster by sending an upvote his way or by clicking the donate link '
'on the discordbots page:\n https://discordbots.org/bot/444514223075360800',
inline=False)
embed.add_field(name='🔹 **Support Server**',
value='If you need help with pollmaster, want to try him out or would like to give feedback '
'to the developer, feel free to join the support server: ',
inline=False)
embed.add_field(name='🔹 **Github**',
value='The full python source code is on my Github: https://github.com/matnad/pollmaster',
inline=False)
embed.add_field(name='**Thanks for using Pollmaster!** 💗', value='Newti', inline=False)
else:
return None
return embed
@commands.command(pass_context=True)
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:
page = '🏠'
msg = None
else:
page = res.reaction.emoji
msg = res.reaction.message
res = await self.embed_list_reaction_handler(page, pre, msg)
# print(res.user, res.reaction, res.reaction.emoji)
# cleanup
await self.bot.delete_message(ctx.message)
def setup(bot):
global logger
logger = logging.getLogger('bot')
bot.add_cog(Help(bot))

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,21 @@
import copy
import pprint
import time
import logging
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.paginator import embed_list_paginated
from essentials.multi_server import get_server_pre, ask_for_server, ask_for_channel
from essentials.settings import SETTINGS
from utils.poll_name_generator import generate_word
from essentials.exceptions import StopWizard
class PollControls:
def __init__(self, bot):
self.bot = bot
## General Methods
# 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:
@ -44,7 +43,47 @@ class PollControls:
embed.set_footer(text=footer_text)
await self.bot.say(embed=embed)
## Commands
# Commands
@commands.command(pass_context=True)
async def activate(self, ctx, *, short=None):
"""Activate a prepared 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}activate <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:
# check if already active, then just do nothing
if await p.is_active():
return
# 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 activate this poll. Please talk to the server admin.'
):
return
# Activate Poll
p.active = True
if p.duration_type == 'timespan':
# add the the time between creation and activation to the duration
# -> "restart" the duration
p.duration += (p.get_activation_date() - p.time_created) / 60
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 delete(self, ctx, *, short=None):
@ -62,9 +101,9 @@ class PollControls:
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.'
ctx, server,
p.author.id,
'You don\'t have sufficient rights to delete this poll. Please talk to the server admin.'
):
return False
@ -139,7 +178,7 @@ class PollControls:
await self.say_error(ctx, error_text)
else:
# sending file
file = p.export()
file = await p.export()
if file is not None:
await self.bot.send_file(
ctx.message.author,
@ -156,7 +195,6 @@ class PollControls:
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>'''
@ -168,11 +206,11 @@ class PollControls:
if short in ['open', 'closed', 'prepared']:
query = None
if short == 'open':
query = self.bot.db.polls.find({'server_id': str(server.id), 'open': True})
query = self.bot.db.polls.find({'server_id': str(server.id), 'open': True, 'active': True})
elif short == 'closed':
query = self.bot.db.polls.find({'server_id': str(server.id), 'open': False})
query = self.bot.db.polls.find({'server_id': str(server.id), 'open': False, 'active': True})
elif short == 'prepared':
pass #TODO: prepared showw
query = self.bot.db.polls.find({'server_id': str(server.id), 'active': False})
if query is not None:
polls = [poll async for poll in query]
@ -186,11 +224,18 @@ class PollControls:
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)
# 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,
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)
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()
else:
error = f'Poll with label {short} was not found.'
pre = await get_server_pre(self.bot, server)
@ -200,27 +245,30 @@ class PollControls:
@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_anonymous(force='no')
await poll.set_multiple_choice(force='no')
await poll.set_options_reaction()
await poll.set_roles(force=['@everyone'])
await poll.set_weights(force=[[], []])
await poll.set_duration(force=0.0)
await poll.set_roles(force='all')
await poll.set_weights(force='none')
await poll.set_duration(force='0')
await self.wizard(ctx, route)
poll = await self.wizard(ctx, route)
if poll:
await poll.post_embed()
@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 prepare(self, ctx, *, cmd=None):
'''Prepare a poll to use later. Parameters: <Question> (optional) '''
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_reaction()
await poll.set_multiple_choice()
if poll.reaction:
await poll.set_options_reaction()
@ -230,57 +278,32 @@ class PollControls:
await poll.set_weights()
await poll.set_duration()
await self.wizard(ctx, route)
poll = await self.wizard(ctx, route)
if poll:
await poll.post_embed(destination=ctx.message.author)
## 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)
@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_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()
poll = await self.wizard(ctx, route)
if poll:
await poll.post_embed()
# The Wizard!
async def wizard(self, ctx, route):
server = await ask_for_server(self.bot, ctx.message)
if not server:
@ -293,7 +316,8 @@ class PollControls:
# 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]:
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
@ -302,17 +326,17 @@ class PollControls:
poll = Poll(self.bot, ctx, server, channel)
## Route to define object, passed as argument for different constructors
await route(poll)
try:
await route(poll)
poll.finalize()
except StopWizard:
return
## Finalize
if poll.stopped:
print("Poll Wizard Stopped.")
else:
msg = await poll.post_embed(ctx)
await poll.save_to_db()
# Finalize
await poll.save_to_db()
return poll
## BOT EVENTS (@bot.event)
# BOT EVENTS (@bot.event)
async def on_reaction_add(self, reaction, user):
if user != self.bot.user:
@ -340,6 +364,19 @@ class PollControls:
p = await Poll.load_from_db(self.bot, server.id, short)
if p is None:
return
# export
if (reaction.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)
)
return
# no rights, terminate function
if not await p.has_required_role(user):
await self.bot.remove_reaction(reaction.message, reaction.emoji, user)
@ -362,8 +399,6 @@ class PollControls:
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
@ -393,5 +428,8 @@ class PollControls:
# for anonymous polls we can't unvote because we need to hide reactions
await p.unvote(user, reaction.emoji, reaction.message)
def setup(bot):
global logger
logger = logging.getLogger('bot')
bot.add_cog(PollControls(bot))

0
essentials/__init__.py Normal file
View File

47
essentials/exceptions.py Normal file
View File

@ -0,0 +1,47 @@
"""Exception Classes for the Poll Wizard"""
class StopWizard(RuntimeError):
pass
class InputError(RuntimeError):
pass
class InvalidInput(InputError):
pass
class ReservedInput(InputError):
pass
class DuplicateInput(InputError):
pass
class WrongNumberOfArguments(InputError):
pass
class ExpectedInteger(InputError):
pass
class ExpectedSeparator(InputError):
def __init__(self, separator):
self.separator = separator
class OutOfRange(InputError):
pass
class DateOutOfRange(InputError):
def __init__(self, date):
self.date = date
class InvalidRoles(InputError):
def __init__(self, roles):
self.roles = roles

View File

@ -1,11 +1,6 @@
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()
from essentials.settings import SETTINGS
async def get_pre(bot, message):
'''Gets the prefix for a message.'''
@ -21,6 +16,7 @@ async def get_pre(bot, message):
else:
return await get_server_pre(bot, message.server)
async def get_server_pre(bot, server):
'''Gets the prefix for a server.'''
try:
@ -33,13 +29,12 @@ 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:
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:
@ -54,11 +49,10 @@ async def get_servers(bot, message, short=None):
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:
@ -95,6 +89,7 @@ async def ask_for_server(bot, message, short=None):
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':

27
essentials/settings.py Normal file
View File

@ -0,0 +1,27 @@
import discord
from essentials.secrets import SECRETS
class Settings:
def __init__(self):
self.color = discord.Colour(int('7289da', 16))
self.title_icon = "http://mnadler.ch/img/donat-chart-32.png"
self.author_icon = "http://mnadler.ch/img/donat-chart-32.png"
self.report_icon = "http://mnadler.ch/img/poll-topic-64.png"
self.owner_id = 117687652278468610
self.msg_errors = False
self.log_errors = True
self.invite_link = \
'https://discordapp.com/api/oauth2/authorize?client_id=444831720659877889&permissions=126016&scope=bot'
self.load_secrets()
def load_secrets(self):
# secret
self.dbl_token = SECRETS.dbl_token
self.mongo_db = SECRETS.mongo_db
self.bot_token = SECRETS.bot_token
SETTINGS = Settings()

View File

@ -1,43 +1,99 @@
import datetime
import os
import traceback
import logging
import aiohttp
import discord
from discord.ext import commands
from motor.motor_asyncio import AsyncIOMotorClient
#os.environ['dbltoken'] = 'ABC' #for website..
from cogs.utils import get_pre
from essentials.multi_server import get_pre
from essentials.settings import SETTINGS
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_config = {
'command_prefix': get_pre,
'pm_help': False,
'status': discord.Status.online,
'owner_id': SETTINGS.owner_id,
'fetch_offline_members': False
}
bot = commands.Bot(command_prefix=get_pre)
dbltoken = os.environ.get('dbltoken')
bot = commands.Bot(**bot_config)
bot.remove_command('help')
extensions = ['cogs.config','cogs.poll_controls']
# logger
# create logger with 'spam_application'
logger = logging.getLogger('bot')
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('pollmaster.log')
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
extensions = ['cogs.config','cogs.poll_controls', 'cogs.help', 'cogs.db_api']
for ext in extensions:
bot.load_extension(ext)
@bot.event
async def on_ready():
bot.owner = await bot.get_user_info(str(SETTINGS.owner_id))
mongo = AsyncIOMotorClient(os.environ.get('mongodb'))
mongo = AsyncIOMotorClient(SETTINGS.mongo_db)
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))
await bot.change_presence(game=discord.Game(name=f'pm!help'))
bot.run('NDQ0ODMxNzIwNjU5ODc3ODg5.DdhqZw.fsicJ8FffOYn670uPGuC4giXIlk')
@bot.event
async def on_command_error(e, ctx):
if SETTINGS.log_errors:
ignored_exceptions = (
commands.MissingRequiredArgument,
commands.CommandNotFound,
commands.DisabledCommand,
commands.BadArgument,
commands.NoPrivateMessage,
commands.CheckFailure,
commands.CommandOnCooldown,
)
if isinstance(e, ignored_exceptions):
# log warnings
logger.warning(f'{type(e).__name__}: {e}\n{"".join(traceback.format_tb(e.__traceback__))}')
return
# log error
logger.error(f'{type(e).__name__}: {e}\n{"".join(traceback.format_tb(e.__traceback__))}')
if SETTINGS.msg_errors:
# send discord message for unexpected errors
e = discord.Embed(
title=f"Error With command: {ctx.command.name}",
description=f"```py\n{type(e).__name__}: {e}\n```\n\nContent:{ctx.message.content}"
f"\n\tServer: {ctx.message.server}\n\tChannel: <#{ctx.message.channel.id}>"
f"\n\tAuthor: <@{ctx.message.author.id}>",
timestamp=ctx.message.timestamp
)
await bot.send_message(bot.owner,embed=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.run(SETTINGS.bot_token)

View File

@ -1,4 +0,0 @@
import discord
PURPLE = discord.Colour(int('7289da', 16))

44
utils/paginator.py Normal file
View File

@ -0,0 +1,44 @@
async def embed_list_paginated(bot, pre, items, item_fct, base_embed, footer_prefix='', 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
#footer_text = f'Type {pre}show <label> to show a poll. '
if start > 0:
footer_prefix += f'React with ⏪ to show the last {per_page} entries. '
if items.__len__() > start+per_page:
footer_prefix += f'React with ⏩ to show the next {per_page} entries. '
if footer_prefix.__len__() > 0:
embed.set_footer(text=footer_prefix)
# post / edit message
if msg is not None:
await bot.edit_message(msg, embed=embed)
await bot.clear_reactions(msg)
else:
msg = await bot.say(embed=embed)
# add reactions
if start > 0:
await bot.add_reaction(msg, '')
if items.__len__() > start+per_page:
await bot.add_reaction(msg, '')
# 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, 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, msg=msg, start=start+per_page, per_page=per_page)