1
0
Fork 0
mirror of synced 2024-06-27 02:31:04 +12:00
This commit is contained in:
phxntxm 2017-02-13 02:01:50 -06:00
commit e3826ce3aa
12 changed files with 363 additions and 262 deletions

3
bot.py
View file

@ -5,6 +5,7 @@ import logging
import datetime
import pendulum
import os
import aiohttp
os.chdir(os.path.dirname(os.path.realpath(__file__)))
@ -88,6 +89,8 @@ async def on_command_error(error, ctx):
return
elif isinstance(error.original, discord.HTTPException) and 'empty message' in str(error.original):
return
elif isinstance(error.original, aiohttp.ClientOSError):
return
except AttributeError:
pass

View file

@ -86,7 +86,7 @@ class Blackjack:
@blackjack.command(pass_context=True, no_pm=True, name='forcestop', aliases=['stop'])
@utils.custom_perms(manage_server=True)
async def blackjack_stop(self, ctx):
"""Forces the game to stop, mostly for use if someone has gone afk
"""Forces the game to stop, mostly for use if someone has gone afk
EXAMPLE: !blackjack forcestop
RESULT: No more blackjack spam"""
@ -202,7 +202,7 @@ class Game:
# The channel we'll send messages to
self.channel = message.channel
# People can join in on this game, but lets make sure we don't go over the limit however
# People can join in on this game, but lets make sure we don't go over the limit however
self._max_players = 10
# Lets create our main deck, and shuffle it
@ -379,6 +379,11 @@ class Game:
for entry in self.players:
player = entry['player']
# Quick check here to ensure the player isn't someone who got added
# Specifically right after the betting phase
if not hasattr(player, 'bet'):
continue
hand = player.hand
count = max(player.count)

View file

@ -1,8 +1,7 @@
import discord
from discord.ext import commands
from .utils import checks
from .utils import config
from .utils import utilities
from . import utils
import subprocess
import glob
@ -24,119 +23,6 @@ class Core:
self.results_per_page = 10
self.commands = None
async def on_reaction_add(self, reaction, user):
# Make sure that this is a normal user who pressed the button
# Also make sure that this is even a message we should be paying attention to
if user.bot or reaction.message.id not in self.help_embeds:
return
# If right is clicked
if '\u27A1' in reaction.emoji:
embed = self.next_page(reaction.message.id)
# If left is clicked
elif '\u2B05' in reaction.emoji:
embed = self.prev_page(reaction.message.id)
else:
return
await self.bot.edit_message(reaction.message, embed=embed)
try:
await self.bot.remove_reaction(reaction.message, reaction.emoji, user)
except discord.Forbidden:
pass
async def on_reaction_remove(self, reaction, user):
# Make sure that this is a normal user who pressed the button
# Also make sure that this is even a message we should be paying attention to
# We need reaction_add and reaction_move for cases like PM's, where the bot cannot remove a reaction
# This causes an error when the bot tries to remove the reaction, leaving it in place
# So the next click from a user will remove it, not add it.
if user.bot or reaction.message.id not in self.help_embeds:
return
# If right is clicked
if '\u27A1' in reaction.emoji:
embed = self.next_page(reaction.message.id)
# If left is clicked
elif '\u2B05' in reaction.emoji:
embed = self.prev_page(reaction.message.id)
else:
return
await self.bot.edit_message(reaction.message, embed=embed)
try:
await self.bot.remove_reaction(reaction.message, reaction.emoji, user)
except discord.Forbidden:
pass
def determine_commands(self, page):
"""Returns the list of commands to use per page"""
end_index = self.results_per_page * page
start_index = end_index - self.results_per_page
return self.commands[start_index:end_index]
def prev_page(self, message_id):
"""Goes to the previus page"""
total_commands = len(self.commands)
# Increase the page count by one
page = self.help_embeds.get(message_id) - 1
total_pages = math.ceil(total_commands / self.results_per_page)
# If we hit the zeroith page, set to the very last page
if page <= 0:
page = total_pages
# Set the new page
self.help_embeds[message_id] = page
# Now create our new embed
return self.create_help_embed(message_id=message_id)
def next_page(self, message_id):
"""Goes to the next page for this message"""
total_commands = len(self.commands)
# Increase the page count by one
page = self.help_embeds.get(message_id) + 1
total_pages = math.ceil(total_commands / self.results_per_page)
# Make sure we don't reach past what we should; if we do, reset to page 1
if page > total_pages:
page = 1
# Set the new page
self.help_embeds[message_id] = page
# Now create our new embed
return self.create_help_embed(message_id=message_id)
def create_help_embed(self, message_id=None, page=1):
# If a message_id is provided, we need to get the new page (this is being sent by next/prev page buttons)
if message_id is not None:
page = self.help_embeds.get(message_id)
# Refresh our command list
self.commands = sorted(utilities.get_all_commands(self.bot))
# Calculate the total amount of pages needed
total_commands = len(self.commands)
total_pages = math.ceil(total_commands / self.results_per_page)
# Lets make sure that if a page was provided, it is within our range of pages available
if page < 1 or page > total_pages:
page = 1
# First create the embed object
opts = {"title": "Command List [{}/{}]".format(page, total_pages),
"description": "Run help on a specific command for more information on it!"}
embed = discord.Embed(**opts)
# Add each field for the commands for this page
fmt = "\n".join(self.determine_commands(page))
embed.add_field(name="Commands", value=fmt, inline=False)
return embed
def find_command(self, command):
# This method ensures the command given is valid. We need to loop through commands
# As self.bot.commands only includes parent commands
@ -161,7 +47,7 @@ class Core:
return cmd
@commands.command(pass_context=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def help(self, ctx, *, message=None):
"""This command is used to provide a link to the help URL.
This can be called on a command to provide more information about that command
@ -172,13 +58,6 @@ class Core:
cmd = None
page = 1
if ctx.message.server:
perms = ctx.message.server.me.permissions_in(ctx.message.channel)
if not (perms.embed_links and perms.add_reactions):
fmt = "I need the permissions `embed_links` and `add_reactions` to send my help message! " \
" Otherwise you can use this link to view available commands {}".format(
"http://bonfirebot.readthedocs.io/en/latest/")
await self.bot.say(fmt)
if message is not None:
# If something is provided, it can either be the page number or a command
@ -189,14 +68,12 @@ class Core:
cmd = self.find_command(message)
if cmd is None:
embed = self.create_help_embed(page=page)
msg = await self.bot.say(embed=embed)
# Add the arrows for previous and next page
await self.bot.add_reaction(msg, '\N{LEFTWARDS BLACK ARROW}')
await self.bot.add_reaction(msg, '\N{BLACK RIGHTWARDS ARROW}')
# The only thing we need to record about this message, is the page number, starting at 1
self.help_embeds[msg.id] = page
entries = sorted(utils.get_all_commands(self.bot))
try:
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
await pages.paginate()
except utils.CannotPaginate as e:
await self.bot.say(str(e))
else:
# Get the description for a command
description = cmd.help
@ -209,7 +86,7 @@ class Core:
example = None
result = None
# Also get the subcommands for this command, if they exist
subcommands = [x for x in utilities._get_all_commands(cmd) if x != cmd.qualified_name]
subcommands = [x for x in utils.get_subcommands(cmd) if x != cmd.qualified_name]
# The rest is simple, create the embed, set the thumbail to me, add all fields if they exist
embed = discord.Embed(title=cmd.qualified_name)
@ -226,7 +103,7 @@ class Core:
await self.bot.say(embed=embed)
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def motd(self, *, date=None):
"""This command can be used to print the current MOTD (Message of the day)
This will most likely not be updated every day, however messages will still be pushed to this every now and then
@ -234,7 +111,7 @@ class Core:
EXAMPLE: !motd
RESULT: 'This is an example message of the day!'"""
if date is None:
motd = await config.get_content('motd')
motd = await utils.get_content('motd')
try:
# Lets set this to the first one in the list first
latest_motd = motd[0]
@ -256,7 +133,7 @@ class Core:
else:
try:
r_filter = pendulum.parse(date)
motd = await config.get_content('motd', r_filter)
motd = await utils.get_content('motd', r_filter)
date = motd[0]['date']
motd = motd[0]['motd']
fmt = "Message of the day for {}:\n\n{}".format(date, motd)
@ -270,7 +147,7 @@ class Core:
await self.bot.say("Invalid date format! Try like {}".format(now))
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def calendar(self, month: str = None, year: int = None):
"""Provides a printout of the current month's calendar
Provide month and year to print the calendar of that year and month
@ -309,14 +186,14 @@ class Core:
await self.bot.say("```\n{}```".format(cal))
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def info(self):
"""This command can be used to print out some of my information"""
# fmt is a dictionary so we can set the key to it's output, then print both
# The only real use of doing it this way is easier editing if the info
# in this command is changed
bot_data = await config.get_content('bot_data')
bot_data = await utils.get_content('bot_data')
total_data = {'member_count': 0,
'server_count': 0}
for entry in bot_data:
@ -326,7 +203,7 @@ class Core:
# Create the original embed object
opts = {'title': 'Dev Server',
'description': 'Join the server above for any questions/suggestions about me.',
'url': config.dev_server}
'url': utils.dev_server}
embed = discord.Embed(**opts)
# Add the normal values
@ -359,7 +236,7 @@ class Core:
await self.bot.say(embed=embed)
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def uptime(self):
"""Provides a printout of the current bot's uptime
@ -368,7 +245,7 @@ class Core:
await self.bot.say("Uptime: ```\n{}```".format((pendulum.utcnow() - self.bot.uptime).in_words()))
@commands.command(aliases=['invite'])
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def addbot(self):
"""Provides a link that you can use to add me to a server
@ -389,7 +266,7 @@ class Core:
.format(discord.utils.oauth_url(app_info.id, perms)))
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def doggo(self):
"""Use this to print a random doggo image.
@ -401,7 +278,7 @@ class Core:
await self.bot.upload(f)
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def snek(self):
"""Use this to print a random snek image.
@ -413,7 +290,7 @@ class Core:
await self.bot.upload(f)
@commands.command()
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def joke(self):
"""Prints a random riddle
@ -432,7 +309,7 @@ class Core:
break
@commands.command(pass_context=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def roll(self, ctx, notation: str = "d6"):
"""Rolls a die based on the notation given
Format should be #d#

View file

@ -77,6 +77,8 @@ class Deviantart:
if result is None:
params['username'] = da_name
data = await utils.request(self.base_url, payload=params)
if data is None:
continue
result = data['results'][0]
cache[da_name] = result

View file

@ -142,6 +142,7 @@ class Interaction:
filename = 'avatar.gif'
else:
filename = 'avatar.webp'
file = utils.convert_to_jpeg(file)
await self.bot.upload(file, filename=filename)
else:
await self.bot.say(url)

View file

@ -1,6 +1,6 @@
from discord.ext import commands
from .utils import checks
from .utils import config
from . import utils
import discord
import re
@ -40,7 +40,7 @@ class Mod:
return cmd
@commands.command(pass_context=True, no_pm=True, aliases=['nick'])
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def nickname(self, ctx, *, name=None):
"""Used to set the nickname for Bonfire (provide no nickname and it will reset)
@ -50,7 +50,7 @@ class Mod:
await self.bot.say("\N{OK HAND SIGN}")
@commands.command(no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def kick(self, member: discord.Member):
"""Used to kick a member from this server
@ -63,7 +63,7 @@ class Mod:
await self.bot.say("But I can't, muh permissions >:c")
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(ban_members=True)
@utils.custom_perms(ban_members=True)
async def unban(self, ctx, member_id: int):
"""Used to unban a member from this server
Due to the fact that I cannot find a user without being in a server with them
@ -84,7 +84,7 @@ class Mod:
await self.bot.say("Sorry, I failed to unban that user!")
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(ban_members=True)
@utils.custom_perms(ban_members=True)
async def ban(self, ctx, *, member):
"""Used to ban a member
This can be used to ban someone preemptively as well.
@ -120,7 +120,7 @@ class Mod:
await self.bot.say("Sorry, I failed to ban that user!")
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def alerts(self, ctx, channel: discord.Channel):
"""This command is used to set a channel as the server's 'notifications' channel
Any notifications (like someone going live on Twitch, or Picarto) will go to that channel
@ -130,13 +130,13 @@ class Mod:
r_filter = {'server_id': ctx.message.server.id}
entry = {'server_id': ctx.message.server.id,
'channel_id': channel.id}
if not await config.add_content('server_alerts', entry, r_filter):
await config.update_content('server_alerts', entry, r_filter)
if not await utils.add_content('server_alerts', entry, r_filter):
await utils.update_content('server_alerts', entry, r_filter)
await self.bot.say("I have just changed this server's 'notifications' channel"
"\nAll notifications will now go to `{}`".format(channel))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def usernotify(self, ctx, on_off: str):
"""This command can be used to set whether or not you want user notificaitons to show
This will save what channel you run this command in, that will be the channel used to send the notification to
@ -151,8 +151,8 @@ class Mod:
r_filter = {'server_id': ctx.message.server.id}
entry = {'server_id': ctx.message.server.id,
'channel_id': on_off}
if not await config.add_content('user_notifications', entry, r_filter):
await config.update_content('user_notifications', entry, r_filter)
if not await utils.add_content('user_notifications', entry, r_filter):
await utils.update_content('user_notifications', entry, r_filter)
fmt = "notify" if on_off else "not notify"
await self.bot.say("This server will now {} if someone has joined or left".format(fmt))
@ -164,33 +164,33 @@ class Mod:
await self.bot.say('Invalid subcommand passed: {0.subcommand_passed}'.format(ctx))
@nsfw.command(name="add", pass_context=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def nsfw_add(self, ctx):
"""Registers this channel as a 'nsfw' channel
EXAMPLE: !nsfw add
RESULT: ;)"""
r_filter = {'channel_id': ctx.message.channel.id}
if await config.add_content('nsfw_channels', r_filter, r_filter):
if await utils.add_content('nsfw_channels', r_filter, r_filter):
await self.bot.say("This channel has just been registered as 'nsfw'! Have fun you naughties ;)")
else:
await self.bot.say("This channel is already registered as 'nsfw'!")
@nsfw.command(name="remove", aliases=["delete"], pass_context=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def nsfw_remove(self, ctx):
"""Removes this channel as a 'nsfw' channel
EXAMPLE: !nsfw remove
RESULT: ;("""
r_filter = {'channel_id': ctx.message.channel.id}
if await config.remove_content('nsfw_channels', r_filter):
if await utils.remove_content('nsfw_channels', r_filter):
await self.bot.say("This channel has just been unregistered as a nsfw channel")
else:
await self.bot.say("This channel is not registered as a ''nsfw' channel!")
@commands.command(pass_context=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def say(self, ctx, *, msg: str):
"""Tells the bot to repeat what you say
@ -204,7 +204,7 @@ class Mod:
pass
@commands.group(pass_context=True, invoke_without_command=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def perms(self, ctx, *, command: str = None):
"""This command can be used to print the current allowed permissions on a specific command
This supports groups as well as subcommands; pass no argument to print a list of available permissions
@ -217,7 +217,7 @@ class Mod:
return
r_filter = {'server_id': ctx.message.server.id}
server_perms = await config.get_content('custom_permissions', r_filter)
server_perms = await utils.get_content('custom_permissions', r_filter)
try:
server_perms = server_perms[0]
except TypeError:
@ -231,14 +231,14 @@ class Mod:
perms_value = server_perms.get(cmd.qualified_name)
if perms_value is None:
# If we don't find custom permissions, get the required permission for a command
# based on what we set in checks.custom_perms, if custom_perms isn't found, we'll get an IndexError
# based on what we set in utils.custom_perms, if custom_perms isn't found, we'll get an IndexError
try:
custom_perms = [func for func in cmd.checks if "custom_perms" in func.__qualname__][0]
custom_perms = [func for func in cmd.utils if "custom_perms" in func.__qualname__][0]
except IndexError:
# Loop through and check if there is a check called is_owner
# If we loop through and don't find one, this means that the only other choice is to be
# Able to manage the server (for the checks on perm commands)
for func in cmd.checks:
# Able to manage the server (for the utils on perm commands)
for func in cmd.utils:
if "is_owner" in func.__qualname__:
await self.bot.say("You need to own the bot to run this command")
return
@ -304,7 +304,7 @@ class Mod:
# The other case is if I'm using the default has_permissions case
# Which means I do not want to check custom permissions at all
# Currently the second case is only on adding and removing permissions, to avoid abuse on these
for check in cmd.checks:
for check in cmd.utils:
if "is_owner" == check.__name__ or re.search("has_permissions", str(check)) is not None:
await self.bot.say("This command cannot have custom permissions setup!")
return
@ -317,11 +317,11 @@ class Mod:
# In this case, I'm going the other way around, to make the least queries
# As custom permissions are probably going to be ran multiple times per server
# Whereas in most other cases, the command is probably going to be ran once/few times per server
if not await config.update_content('custom_permissions', entry, r_filter):
await config.add_content('custom_permissions', entry, r_filter)
if not await utils.update_content('custom_permissions', entry, r_filter):
await utils.add_content('custom_permissions', entry, r_filter)
# Same case as prefixes, for now, trigger a manual update
self.bot.loop.create_task(config.cache['custom_permissions'].update())
self.bot.loop.create_task(utils.cache['custom_permissions'].update())
await self.bot.say("I have just added your custom permissions; "
"you now need to have `{}` permissions to use the command `{}`".format(permissions, command))
@ -341,14 +341,14 @@ class Mod:
return
r_filter = {'server_id': ctx.message.server.id}
await config.replace_content('custom_permissions', r.row.without(cmd.qualified_name), r_filter)
await utils.replace_content('custom_permissions', r.row.without(cmd.qualified_name), r_filter)
await self.bot.say("I have just removed the custom permissions for {}!".format(cmd))
# Same case as prefixes, for now, trigger a manual update
self.bot.loop.create_task(config.cache['custom_permissions'].update())
self.bot.loop.create_task(utils.cache['custom_permissions'].update())
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(manage_server=True)
@utils.custom_perms(manage_server=True)
async def prefix(self, ctx, *, prefix: str):
"""This command can be used to set a custom prefix per server
@ -361,8 +361,8 @@ class Mod:
entry = {'server_id': ctx.message.server.id,
'prefix': prefix}
if not await config.add_content('prefixes', entry, r_filter):
await config.update_content('prefixes', entry, r_filter)
if not await utils.add_content('prefixes', entry, r_filter):
await utils.update_content('prefixes', entry, r_filter)
if prefix is None:
fmt = "I have just cleared your custom prefix, the default prefix will have to be used now"
@ -372,7 +372,7 @@ class Mod:
await self.bot.say(fmt)
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(manage_messages=True)
@utils.custom_perms(manage_messages=True)
async def purge(self, ctx, limit: int = 100):
"""This command is used to a purge a number of messages from the channel
@ -388,7 +388,7 @@ class Mod:
" I can only bulk delete messages that are under 14 days old.")
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(manage_messages=True)
@utils.custom_perms(manage_messages=True)
async def prune(self, ctx, limit: int = 100):
"""This command can be used to prune messages from certain members
Mention any user you want to prune messages from; if no members are mentioned, the messages removed will be mine
@ -430,14 +430,14 @@ class Mod:
pass
@commands.group(aliases=['rule'], pass_context=True, no_pm=True, invoke_without_command=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def rules(self, ctx, rule: int = None):
"""This command can be used to view the current rules on the server
EXAMPLE: !rules 5
RESULT: Rule 5 is printed"""
r_filter = {'server_id': ctx.message.server.id}
rules = await config.get_content('rules', r_filter)
rules = await utils.get_content('rules', r_filter)
try:
rules = rules[0]['rules']
except TypeError:
@ -448,9 +448,12 @@ class Mod:
return
if rule is None:
# Enumerate the list, so that we can print the number and the rule for each rule
fmt = "\n".join("{}) {}".format(num + 1, rule) for num, rule in enumerate(rules))
await self.bot.say('```\n{}```'.format(fmt))
try:
pages = utils.Pages(self.bot, message=ctx.message, entries=rules, per_page=5)
pages.title = "Rules for {}".format(ctx.message.server.name)
await pages.paginate()
except utils.CannotPaginate as e:
await self.bot.say(str(e))
else:
try:
fmt = rules[rule - 1]
@ -460,7 +463,7 @@ class Mod:
await self.bot.say("Rule {}: \"{}\"".format(rule, fmt))
@rules.command(name='add', aliases=['create'], pass_context=True, no_pm=True)
@checks.custom_perms(manage_server=True)
@utils.custom_perms(manage_server=True)
async def rules_add(self, ctx, *, rule: str):
"""Adds a rule to this server's rules
@ -470,13 +473,13 @@ class Mod:
entry = {'server_id': ctx.message.server.id,
'rules': [rule]}
update = {'rules': r.row['rules'].append(rule)}
if not await config.update_content('rules', update, r_filter):
await config.add_content('rules', entry, r_filter)
if not await utils.update_content('rules', update, r_filter):
await utils.add_content('rules', entry, r_filter)
await self.bot.say("I have just saved your new rule, use the rules command to view this server's current rules")
@rules.command(name='remove', aliases=['delete'], pass_context=True, no_pm=True)
@checks.custom_perms(manage_server=True)
@utils.custom_perms(manage_server=True)
async def rules_delete(self, ctx, rule: int):
"""Removes one of the rules from the list of this server's rules
Provide a number to delete that rule
@ -485,7 +488,7 @@ class Mod:
RESULT: Freedom from opression!"""
r_filter = {'server_id': ctx.message.server.id}
update = {'rules': r.row['rules'].delete_at(rule - 1)}
if not await config.update_content('rules', update, r_filter):
if not await utils.update_content('rules', update, r_filter):
await self.bot.say("That is not a valid rule number, try running the command again.")
else:
await self.bot.say("I have just removed that rule from your list of rules!")

View file

@ -1,9 +1,10 @@
from .utils import *
from .voice_utilities import *
import discord
from discord.ext import commands
from . import utils
import math
import time
import asyncio
@ -146,6 +147,8 @@ class Music:
async def create_voice_client(self, channel):
"""Creates a voice client and saves it"""
# First join the channel and get the VoiceClient that we'll use to save per server
await self.remove_voice_client(channel.server)
server = channel.server
state = self.get_voice_state(server)
voice = self.bot.voice_client_in(server)
@ -154,17 +157,22 @@ class Music:
try:
if voice is None:
state.voice = await self.bot.join_voice_channel(channel)
return True
if state.voice:
return True
elif voice.channel == channel:
state.voice = voice
return True
else:
# This shouldn't theoretically ever happen yet it does. Thanks Discord
await voice.disconnect()
state.voice = await self.bot.join_voice_channel(channel)
return True
except (discord.ClientException, socket.gaierror):
if state.voice:
return True
except (discord.ClientException, socket.gaierror, ConnectionResetError):
continue
return False
async def remove_voice_client(self, server):
"""Removes any voice clients from a server
@ -314,7 +322,7 @@ class Music:
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def progress(self, ctx):
"""Provides the progress of the current song"""
@ -338,7 +346,7 @@ class Music:
await self.bot.say(fmt)
@commands.command(no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def join(self, *, channel: discord.Channel):
"""Joins a voice channel."""
try:
@ -354,7 +362,7 @@ class Music:
await self.bot.say('Ready to play audio in ' + channel.name)
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def summon(self, ctx):
"""Summons the bot to join your voice channel."""
# This method will be invoked by other commands, so we should return True or False instead of just returning
@ -381,7 +389,7 @@ class Music:
return success
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def play(self, ctx, *, song: str):
"""Plays a song.
If there is a song currently in the queue, then it is
@ -399,7 +407,7 @@ class Music:
state = self.get_voice_state(ctx.message.server)
# First check if we are connected to a voice channel at all, if not summon to the channel the author is in
# Since summon checks if the author is in a channel, we don't need to handle that here, just return if it failed
# Since summon utils if the author is in a channel, we don't need to handle that here, just return if it failed
if state.voice is None:
success = await ctx.invoke(self.summon)
if not success:
@ -413,6 +421,12 @@ class Music:
author_channel = ctx.message.author.voice.voice_channel
my_channel = ctx.message.server.me.voice.voice_channel
if my_channel is None:
# If we're here this means that after 3 attempts...4 different "failsafes"...
# Discord has returned saying the connection was successful, and returned a None connection
await self.bot.say("I failed to connect to the channel! Please try again soon")
return
# To try to avoid some abuse, ensure the requester is actually in our channel
if my_channel != author_channel:
await self.bot.say("You are not currently in the channel; please join before trying to request a song.")
@ -466,7 +480,7 @@ class Music:
await self.bot.say('Enqueued ' + str(_entry))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def volume(self, ctx, value: int = None):
"""Sets the volume of the currently playing song."""
@ -485,7 +499,7 @@ class Music:
await self.bot.say('Set the volume to {:.0%}'.format(player.volume))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def pause(self, ctx):
"""Pauses the currently played song."""
state = self.get_voice_state(ctx.message.server)
@ -493,7 +507,7 @@ class Music:
state.player.pause()
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def resume(self, ctx):
"""Resumes the currently played song."""
state = self.get_voice_state(ctx.message.server)
@ -501,7 +515,7 @@ class Music:
state.player.resume()
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def stop(self, ctx):
"""Stops playing audio and leaves the voice channel.
This also clears the queue.
@ -525,7 +539,7 @@ class Music:
pass
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def eta(self, ctx):
"""Provides an ETA on when your next song will play"""
# Note: There is no way to tell how long a song has been playing, or how long there is left on a song
@ -565,7 +579,7 @@ class Music:
await self.bot.say("ETA till your next play is: {0[0]}m {0[1]}s".format(divmod(round(count, 0), 60)))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def queue(self, ctx):
"""Provides a printout of the songs that are in the queue"""
state = self.get_voice_state(ctx.message.server)
@ -581,14 +595,14 @@ class Music:
self.bot.loop.create_task(self.queue_embed_task(state, ctx.message.channel, ctx.message.author))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def queuelength(self, ctx):
"""Prints the length of the queue"""
await self.bot.say("There are a total of {} songs in the queue"
.format(len(self.get_voice_state(ctx.message.server).songs.entries)))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def skip(self, ctx):
"""Vote to skip a song. The song requester can automatically skip.
approximately 1/3 of the members in the voice channel
@ -620,7 +634,7 @@ class Music:
await self.bot.say('You have already voted to skip this song.')
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(kick_members=True)
@utils.custom_perms(kick_members=True)
async def modskip(self, ctx):
"""Forces a song skip, can only be used by a moderator"""
state = self.get_voice_state(ctx.message.server)
@ -632,7 +646,7 @@ class Music:
await self.bot.say('Song has just been skipped.')
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def playing(self, ctx):
"""Shows info about the currently played song."""

View file

@ -1,8 +1,6 @@
from discord.ext import commands
from .utils import config
from .utils import checks
from .utils import utilities
from . import utils
import re
import glob
@ -23,7 +21,7 @@ class Owner:
self.bot = bot
@commands.command()
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def motd_push(self, *, message):
"""Used to push a new message to the message of the day"""
date = pendulum.utcnow().to_date_string()
@ -31,12 +29,12 @@ class Owner:
entry = {'motd': message, 'date': date}
# Try to add this, if there's an entry for that date, lets update it to make sure only one motd is sent a day
# I should be managing this myself, more than one should not be sent in a day
if await config.add_content('motd', entry, r_filter):
await config.update_content('motd', entry, r_filter)
if await utils.add_content('motd', entry, r_filter):
await utils.update_content('motd', entry, r_filter)
await self.bot.say("New motd update for {}!".format(date))
@commands.command(pass_context=True)
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def debug(self, ctx):
"""Executes code"""
# Eval and exec have different useful purposes, so use both
@ -65,7 +63,7 @@ class Owner:
await self.bot.say(fmt.format(type(error).__name__, error))
@commands.command(pass_context=True)
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def shutdown(self, ctx):
"""Shuts the bot down"""
fmt = 'Shutting down, I will miss you {0.author.name}'
@ -74,21 +72,21 @@ class Owner:
await self.bot.close()
@commands.command()
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def name(self, newNick: str):
"""Changes the bot's name"""
await self.bot.edit_profile(username=newNick)
await self.bot.say('Changed username to ' + newNick)
@commands.command()
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def status(self, *, status: str):
"""Changes the bot's 'playing' status"""
await self.bot.change_status(discord.Game(name=status, type=0))
await self.bot.say("Just changed my status to '{0}'!".format(status))
@commands.command()
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def load(self, *, module: str):
"""Loads a module"""
@ -106,7 +104,7 @@ class Owner:
await self.bot.say(fmt.format(type(error).__name__, error))
@commands.command()
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def unload(self, *, module: str):
"""Unloads a module"""
@ -119,7 +117,7 @@ class Owner:
await self.bot.say("I have just unloaded the {} module".format(module))
@commands.command()
@commands.check(checks.is_owner)
@commands.check(utils.is_owner)
async def reload(self, *, module: str):
"""Reloads a module"""

View file

@ -1,9 +1,7 @@
import discord
from discord.ext import commands
from .utils import config
from .utils import checks
from .utils import images
from . import utils
import re
@ -30,7 +28,7 @@ class Stats:
return cmd
@commands.command(no_pm=True, pass_context=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def serverinfo(self, ctx):
"""Provides information about the server
@ -65,12 +63,12 @@ class Stats:
await self.bot.say(embed=embed)
@commands.group(no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def command(self):
pass
@command.command(no_pm=True, pass_context=True, name="stats")
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def command_stats(self, ctx, *, command):
"""This command can be used to view some usage stats about a specific command
@ -82,7 +80,7 @@ class Stats:
return
r_filter = {'command': cmd.qualified_name}
command_stats = await config.get_content('command_usage', r_filter)
command_stats = await utils.get_content('command_usage', r_filter)
try:
command_stats = command_stats[0]
except TypeError:
@ -98,7 +96,7 @@ class Stats:
("Total Usage", total_usage),
("Your Usage", member_usage),
("This Server's Usage", server_usage)]
banner = await images.create_banner(ctx.message.author, "Command Stats", data)
banner = await utils.create_banner(ctx.message.author, "Command Stats", data)
await self.bot.upload(banner)
except (FileNotFoundError, discord.Forbidden):
fmt = "The command {} has been used a total of {} times\n" \
@ -109,7 +107,7 @@ class Stats:
await self.bot.say(fmt)
@command.command(no_pm=True, pass_context=True, name="leaderboard")
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def command_leaderboard(self, ctx, option="server"):
"""This command can be used to print a leaderboard of commands
Provide 'server' to print a leaderboard for this server
@ -120,7 +118,7 @@ class Stats:
if re.search('(author|me)', option):
author = ctx.message.author
# First lets get all the command usage
command_stats = await config.get_content('command_usage')
command_stats = await utils.get_content('command_usage')
# Now use a dictionary comprehension to get just the command name, and usage
# Based on the author's usage of the command
stats = {data['command']: data['member_usage'].get(author.id) for data in command_stats
@ -133,7 +131,7 @@ class Stats:
# As this can include, for example, all 3 if there are only 3 entries
try:
top_5 = [(data[0], data[1]) for data in sorted_stats[:5]]
banner = await images.create_banner(ctx.message.author, "Your command usage", top_5)
banner = await utils.create_banner(ctx.message.author, "Your command usage", top_5)
await self.bot.upload(banner)
except (FileNotFoundError, discord.Forbidden):
top_5 = "\n".join("{}: {}".format(data[0], data[1]) for data in sorted_stats[:5])
@ -142,7 +140,7 @@ class Stats:
elif re.search('server', option):
# This is exactly the same as above, except server usage instead of member usage
server = ctx.message.server
command_stats = await config.get_content('command_usage')
command_stats = await utils.get_content('command_usage')
stats = {data['command']: data['server_usage'].get(server.id) for data in command_stats
if data['server_usage'].get(server.id, 0) > 0}
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)
@ -154,14 +152,14 @@ class Stats:
await self.bot.say("That is not a valid option, valid options are: `server` or `me`")
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def mostboops(self, ctx):
"""Shows the person you have 'booped' the most, as well as how many times
EXAMPLE: !mostboops
RESULT: You've booped @OtherPerson 351253897120935712093572193057310298 times!"""
r_filter = {'member_id': ctx.message.author.id}
boops = await config.get_content('boops', r_filter)
boops = await utils.get_content('boops', r_filter)
if boops is None:
await self.bot.say("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
return
@ -182,17 +180,17 @@ class Stats:
member = discord.utils.find(lambda m: m.id == most_id, self.bot.get_all_members())
await self.bot.say("{0} you have booped {1} the most amount of times, coming in at {2} times".format(
ctx.message.author.mention, member.mention, most_boops))
ctx.message.author.mention, member.display_name, most_boops))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def listboops(self, ctx):
"""Lists all the users you have booped and the amount of times
EXAMPLE: !listboops
RESULT: The list of your booped members!"""
r_filter = {'member_id': ctx.message.author.id}
boops = await config.get_content('boops', r_filter)
boops = await utils.get_content('boops', r_filter)
if boops is None:
await self.bot.say("You have not booped anyone {} Why the heck not...?".format(ctx.message.author.mention))
return
@ -210,7 +208,7 @@ class Stats:
try:
output = [("{0.display_name}".format(ctx.message.server.get_member(m_id)), amt)
for m_id, amt in sorted_booped_members]
banner = await images.create_banner(ctx.message.author, "Your booped victims", output)
banner = await utils.create_banner(ctx.message.author, "Your booped victims", output)
await self.bot.upload(banner)
except (FileNotFoundError, discord.Forbidden):
output = "\n".join(
@ -219,7 +217,7 @@ class Stats:
await self.bot.say("You have booped:```\n{}```".format(output))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def leaderboard(self, ctx):
"""Prints a leaderboard of everyone in the server's battling record
@ -227,32 +225,27 @@ class Stats:
RESULT: A leaderboard of this server's battle records"""
# Create a list of the ID's of all members in this server, for comparison to the records saved
server_member_ids = [member.id for member in ctx.message.server.members]
battles = await config.get_content('battle_records')
battles = await utils.get_content('battle_records')
battles = [battle for battle in battles if battle['member_id'] in server_member_ids]
# Sort the members based on their rating
sorted_members = sorted(battles, key=lambda k: k['rating'], reverse=True)
output = []
count = 1
for x in sorted_members:
member_id = x['member_id']
rating = x['rating']
member = ctx.message.server.get_member(member_id)
output.append((count, "{} (Rating: {})".format(member.display_name, rating)))
count += 1
if count >= 11:
break
output.append("{} (Rating: {})".format(member.display_name, rating))
try:
banner = await images.create_banner(ctx.message.author, "Battling Leaderboard", output)
await self.bot.upload(banner)
except (FileNotFoundError, discord.Forbidden):
fmt = "\n".join("#{}) {}".format(key, value) for key, value in output)
await self.bot.say("Battling leaderboard for this server:```\n{}```".format(fmt))
pages = utils.Pages(self.bot, message=ctx.message, entries=output)
await pages.paginate()
except utils.CannotPaginate as e:
await self.bot.say(str(e))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def stats(self, ctx, member: discord.Member = None):
"""Prints the battling stats for you, or the user provided
@ -262,7 +255,7 @@ class Stats:
# For this one, we don't want to pass a filter, as we do need all battle records
# We need this because we want to make a comparison for overall rank
all_members = await config.get_content('battle_records')
all_members = await utils.get_content('battle_records')
# Make a list comprehension to just check if the user has battled
if len([entry for entry in all_members if entry['member_id'] == member.id]) == 0:
@ -288,7 +281,7 @@ class Stats:
title = 'Stats for {}'.format(member.display_name)
fmt = [('Record', record), ('Server Rank', '{}/{}'.format(server_rank, len(server_members))),
('Overall Rank', '{}/{}'.format(total_rank, len(all_members))), ('Rating', rating)]
banner = await images.create_banner(member, title, fmt)
banner = await utils.create_banner(member, title, fmt)
await self.bot.upload(banner)
except (FileNotFoundError, discord.Forbidden):
fmt = 'Stats for {}:\n\tRecord: {}\n\tServer Rank: {}/{}\n\tOverall Rank: {}/{}\n\tRating: {}'

View file

@ -3,3 +3,4 @@ from .checks import is_owner, custom_perms, is_pm
from .config import *
from .utilities import *
from .images import create_banner
from .paginator import Pages, CannotPaginate

192
cogs/utils/paginator.py Normal file
View file

@ -0,0 +1,192 @@
import asyncio
import discord
class CannotPaginate(Exception):
pass
class Pages:
"""Implements a paginator that queries the user for the
pagination interface.
Pages are 1-index based, not 0-index based.
If the user does not reply within 2 minutes, the pagination
interface exits automatically.
"""
def __init__(self, bot, *, message, entries, per_page=10):
self.bot = bot
self.entries = entries
self.message = message
self.author = message.author
self.per_page = per_page
pages, left_over = divmod(len(self.entries), self.per_page)
if left_over:
pages += 1
self.maximum_pages = pages
self.embed = discord.Embed()
self.paginating = len(entries) > per_page
self.reaction_emojis = [
('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.first_page),
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.last_page),
('\N{INPUT SYMBOL FOR NUMBERS}', self.numbered_page ),
('\N{BLACK SQUARE FOR STOP}', self.stop_pages),
('\N{INFORMATION SOURCE}', self.show_help),
]
server = self.message.server
if server is not None:
self.permissions = self.message.channel.permissions_for(server.me)
else:
self.permissions = self.message.channel.permissions_for(self.bot.user)
if not self.permissions.embed_links:
raise CannotPaginate('Bot does not have embed links permission.')
def get_page(self, page):
base = (page - 1) * self.per_page
return self.entries[base:base + self.per_page]
async def show_page(self, page, *, first=False):
self.current_page = page
entries = self.get_page(page)
p = []
for t in enumerate(entries, 1 + ((page - 1) * self.per_page)):
p.append('%s. %s' % t)
self.embed.set_footer(text='Page %s/%s (%s entries)' % (page, self.maximum_pages, len(self.entries)))
if not self.paginating:
self.embed.description = '\n'.join(p)
return await self.bot.send_message(self.message.channel, embed=self.embed)
if not first:
self.embed.description = '\n'.join(p)
await self.bot.edit_message(self.message, embed=self.embed)
return
# verify we can actually use the pagination session
if not self.permissions.add_reactions:
raise CannotPaginate('Bot does not have add reactions permission.')
if not self.permissions.read_message_history:
raise CannotPaginate('Bot does not have Read Message History permission.')
p.append('')
p.append('Confused? React with \N{INFORMATION SOURCE} for more info.')
self.embed.description = '\n'.join(p)
self.message = await self.bot.send_message(self.message.channel, embed=self.embed)
for (reaction, _) in self.reaction_emojis:
if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'):
# no |<< or >>| buttons if we only have two pages
# we can't forbid it if someone ends up using it but remove
# it from the default set
continue
await self.bot.add_reaction(self.message, reaction)
async def checked_show_page(self, page):
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
async def first_page(self):
"""goes to the first page"""
await self.show_page(1)
async def last_page(self):
"""goes to the last page"""
await self.show_page(self.maximum_pages)
async def next_page(self):
"""goes to the next page"""
await self.checked_show_page(self.current_page + 1)
async def previous_page(self):
"""goes to the previous page"""
await self.checked_show_page(self.current_page - 1)
async def show_current_page(self):
if self.paginating:
await self.show_page(self.current_page)
async def numbered_page(self):
"""lets you type a page number to go to"""
to_delete = []
to_delete.append(await self.bot.send_message(self.message.channel, 'What page do you want to go to?'))
msg = await self.bot.wait_for_message(author=self.author, channel=self.message.channel,
check=lambda m: m.content.isdigit(), timeout=30.0)
if msg is not None:
page = int(msg.content)
to_delete.append(msg)
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
else:
to_delete.append(await self.bot.say('Invalid page given. (%s/%s)' % (page, self.maximum_pages)))
await asyncio.sleep(5)
else:
to_delete.append(await self.bot.send_message(self.message.channel, 'Took too long.'))
await asyncio.sleep(5)
try:
await self.bot.delete_messages(to_delete)
except Exception:
pass
async def show_help(self):
"""shows this message"""
e = discord.Embed()
messages = ['Welcome to the interactive paginator!\n']
messages.append('This interactively allows you to see pages of text by navigating with ' \
'reactions. They are as follows:\n')
for (emoji, func) in self.reaction_emojis:
messages.append('%s %s' % (emoji, func.__doc__))
e.description = '\n'.join(messages)
e.colour = 0x738bd7 # blurple
e.set_footer(text='We were on page %s before this message.' % self.current_page)
await self.bot.edit_message(self.message, embed=e)
async def go_back_to_current_page():
await asyncio.sleep(60.0)
await self.show_current_page()
self.bot.loop.create_task(go_back_to_current_page())
async def stop_pages(self):
"""stops the interactive pagination session"""
await self.bot.delete_message(self.message)
self.paginating = False
def react_check(self, reaction, user):
if user is None or user.id != self.author.id:
return False
for (emoji, func) in self.reaction_emojis:
if reaction.emoji == emoji:
self.match = func
return True
return False
async def paginate(self):
"""Actually paginate the entries and run the interactive loop if necessary."""
await self.show_page(1, first=True)
while self.paginating:
react = await self.bot.wait_for_reaction(message=self.message, check=self.react_check, timeout=120.0)
if react is None:
self.paginating = False
try:
await self.bot.clear_reactions(self.message)
except:
pass
finally:
break
try:
await self.bot.remove_reaction(self.message, react.reaction.emoji, react.user)
except:
pass # can't remove it so don't bother doing so
await self.match()

View file

@ -1,8 +1,20 @@
import aiohttp
import io
from io import BytesIO
import inspect
from . import config
from PIL import Image
def convert_to_jpeg(pfile):
# Open the file given
img = Image.open(pfile)
# Create the BytesIO object we'll use as our new "file"
new_file = BytesIO()
# Save to this file as jpeg
img.save(new_file, format='JPEG')
# In order to use the file, we need to seek back to the 0th position
new_file.seek(0)
return new_file
def get_all_commands(bot):
"""Returns a list of all command names for the bot"""
@ -14,17 +26,17 @@ def get_all_commands(bot):
# Only the command itself will be yielded if there are no children
for cmd_name in parent_command_names:
cmd = bot.commands.get(cmd_name)
for child_cmd in _get_all_commands(cmd):
for child_cmd in get_subcommands(cmd):
all_commands.append(child_cmd)
return all_commands
def _get_all_commands(command):
def get_subcommands(command):
yield command.qualified_name
try:
non_aliases = set(cmd.name for cmd in command.commands.values())
for cmd_name in non_aliases:
yield from _get_all_commands(command.commands[cmd_name])
yield from get_subcommands(command.commands[cmd_name])
except AttributeError:
pass
@ -61,7 +73,7 @@ async def download_image(url):
return None
# Then wrap it in a BytesIO object, to be used like an actual file
image = io.BytesIO(bts)
image = BytesIO(bts)
return image
async def request(url, *, headers=None, payload=None, method='GET', attr='json'):