From d85454f6cf94abc68fe4170a195a56e9ab2f30cb Mon Sep 17 00:00:00 2001 From: Dan Hess Date: Sat, 10 Oct 2020 22:36:11 -0500 Subject: [PATCH] Big update, use ext.tasks, couple updates to handle chunking better, log errors in tasks to error channel --- bot.py | 33 ++---- cogs/admin.py | 249 +++++++++++++++++++++++++++++--------------- cogs/birthday.py | 106 +++++++++++-------- cogs/blackjack.py | 132 +++++++++++++---------- cogs/config.py | 173 +++++++++++++++++++++--------- cogs/events.py | 64 +++++++----- cogs/games.py | 5 +- cogs/hangman.py | 71 +++++++++---- cogs/images.py | 92 +++++++++------- cogs/interaction.py | 228 ++++++++++++++++++++++------------------ cogs/links.py | 88 +++++++++------- cogs/misc.py | 127 ++++++++++++++-------- cogs/mod.py | 26 +++-- cogs/overwatch.py | 99 ++++++++++++------ cogs/owner.py | 114 ++++++++++---------- cogs/picarto.py | 48 ++++----- cogs/polls.py | 5 +- cogs/raffle.py | 48 ++++++--- cogs/roles.py | 240 ++++++++++++++++++++++++++++-------------- cogs/roulette.py | 27 +++-- cogs/spotify.py | 18 +++- cogs/stats.py | 81 +++++++++----- cogs/tags.py | 93 ++++++++++++----- cogs/tictactoe.py | 149 ++++++++++++++++++-------- utils/utilities.py | 60 +++++++++-- 25 files changed, 1495 insertions(+), 881 deletions(-) diff --git a/bot.py b/bot.py index b609e54..b8695c8 100644 --- a/bot.py +++ b/bot.py @@ -1,13 +1,8 @@ import discord -import traceback import logging -import datetime import pendulum -import os import aiohttp -os.chdir(os.path.dirname(os.path.realpath(__file__))) - from discord.ext import commands import utils @@ -29,12 +24,17 @@ logging.basicConfig(level=logging.INFO, filename="bonfire.log") @bot.before_invoke -async def start_typing(ctx): +async def before_invocation(ctx): + # Start typing try: await ctx.trigger_typing() except (discord.Forbidden, discord.HTTPException): pass + # Ensure guild is chunked + if ctx.guild and not ctx.guild.chunked: + await ctx.guild.chunk() + @bot.event async def on_ready(): @@ -121,26 +121,7 @@ async def on_command_error(ctx, error): " recheck where your quotes are" ) else: - if isinstance(bot.error_channel, int): - bot.error_channel = bot.get_channel(bot.error_channel) - - if bot.error_channel is None: - now = datetime.datetime.now() - with open("error_log", "a") as f: - print( - "In server '{0.message.guild}' at {1}\n" - "Full command: `{0.message.content}`".format(ctx, str(now)), - file=f, - ) - traceback.print_tb(error.__traceback__, file=f) - print("{0.__class__.__name__}: {0}".format(error), file=f) - else: - await bot.error_channel.send( - f"""``` -Command = {discord.utils.escape_markdown(ctx.message.clean_content).strip()} -{''.join(traceback.format_tb(error.__traceback__)).strip()} -{error.__class__.__name__}: {error}```""" - ) + await utils.log_error(error, ctx.bot, ctx) except discord.HTTPException: pass diff --git a/cogs/admin.py b/cogs/admin.py index 2b45cdf..73d0fa9 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -26,7 +26,7 @@ class Admin(commands.Cog): await ctx.bot.db.execute( "INSERT INTO restrictions (source, destination, from_to, guild) VALUES ($1, 'everyone', 'from', $2)", cmd.qualified_name, - ctx.guild.id + ctx.guild.id, ) except UniqueViolationError: await ctx.send(f"{cmd.qualified_name} is already disabled") @@ -35,7 +35,7 @@ class Admin(commands.Cog): ctx.bot.cache.add_restriction( ctx.guild, "from", - {"source": cmd.qualified_name, "destination": "everyone"} + {"source": cmd.qualified_name, "destination": "everyone"}, ) @commands.command() @@ -48,7 +48,7 @@ class Admin(commands.Cog): await ctx.send("No command called `{}`".format(command)) return - query = f""" + query = """ DELETE FROM restrictions WHERE source=$1 AND from_to='from' AND @@ -57,9 +57,7 @@ guild=$2 """ await ctx.bot.db.execute(query, cmd.qualified_name, ctx.guild.id) ctx.bot.cache.remove_restriction( - ctx.guild, - "from", - {"source": cmd.qualified_name, "destination": "everyone"} + ctx.guild, "from", {"source": cmd.qualified_name, "destination": "everyone"} ) await ctx.send(f"{cmd.qualified_name} is no longer disabled") @@ -72,13 +70,17 @@ guild=$2 This sets the role to mentionable, mentions the role, then sets it back """ if not ctx.me.guild_permissions.manage_roles: - await ctx.send("I do not have permissions to edit roles (this is required to complete this command)") + await ctx.send( + "I do not have permissions to edit roles (this is required to complete this command)" + ) return try: await role.edit(mentionable=True) except discord.Forbidden: - await ctx.send("I do not have permissions to edit that role. " - "(I either don't have manage roles permissions, or it is higher on the hierarchy)") + await ctx.send( + "I do not have permissions to edit that role. " + "(I either don't have manage roles permissions, or it is higher on the hierarchy)" + ) else: fmt = f"{role.mention}\n{message}" await ctx.send(fmt) @@ -95,7 +97,7 @@ guild=$2 RESULT: All the current restrictions""" restrictions = await ctx.bot.db.fetch( "SELECT source, destination, from_to FROM restrictions WHERE guild=$1", - ctx.guild.id + ctx.guild.id, ) entries = [] @@ -106,7 +108,9 @@ guild=$2 dest = await utils.convert(ctx, restriction["destination"]) # If it doesn't exist, don't add it if dest: - entries.append(f"{restriction['source']} {'from' if restriction['from_to'] == 'from' else 'to'} {dest}") + entries.append( + f"{restriction['source']} {'from' if restriction['from_to'] == 'from' else 'to'} {dest}" + ) if entries: # Then paginate @@ -135,17 +139,23 @@ guild=$2 """ # First make sure we're given three options if len(options) != 3: - await ctx.send("You need to provide 3 options! Such as `command from @User`") + await ctx.send( + "You need to provide 3 options! Such as `command from @User`" + ) return elif ctx.message.mention_everyone: - await ctx.send("Please do not use this command to 'disable from everyone'. Use the `disable` command") + await ctx.send( + "Please do not use this command to 'disable from everyone'. Use the `disable` command" + ) return else: # Get the three arguments from this list, then make sure the 2nd is either from or to arg1, arg2, arg3 = options - if arg2.lower() not in ['from', 'to']: - await ctx.send("The 2nd option needs to be either \"to\" or \"from\". Such as: `command from @user` " - "or `command to Role`") + if arg2.lower() not in ["from", "to"]: + await ctx.send( + 'The 2nd option needs to be either "to" or "from". Such as: `command from @user` ' + "or `command to Role`" + ) return else: # Try to convert the other arguments @@ -153,7 +163,11 @@ guild=$2 option1 = await utils.convert(ctx, arg1) option2 = await utils.convert(ctx, arg3) if option1 is None or option2 is None: - await ctx.send("Sorry, but I don't know how to restrict {} {} {}".format(arg1, arg2, arg3)) + await ctx.send( + "Sorry, but I don't know how to restrict {} {} {}".format( + arg1, arg2, arg3 + ) + ) return from_to = arg2 @@ -173,7 +187,9 @@ guild=$2 # Channels - Command can't be ran in this channel # Roles - Command can't be ran by anyone in this role (least likely, but still possible uses) if arg2 == "from": - if isinstance(option2, (discord.Member, discord.Role, discord.TextChannel)): + if isinstance( + option2, (discord.Member, discord.Role, discord.TextChannel) + ): source = option1.qualified_name destination = str(option2.id) # To: @@ -189,16 +205,15 @@ guild=$2 # Command - Command cannot be used by this user if arg2 == "from": if isinstance(option2, (discord.TextChannel, discord.VoiceChannel)): - ov = discord.utils.find(lambda t: t[0] == option1, option2.overwrites) + ov = discord.utils.find( + lambda t: t[0] == option1, option2.overwrites + ) if ov: ov = ov[1] ov.update(read_messages=False) else: ov = discord.PermissionOverwrite(read_messages=False) - overwrites = { - 'channel': option2, - option1: ov - } + overwrites = {"channel": option2, option1: ov} elif isinstance(option2, (commands.core.Command, commands.core.Group)): source = option2.qualified_name destination = str(option1.id) @@ -209,46 +224,51 @@ guild=$2 # Role - Setup an overwrite for this channel so that this Role cannot read it if arg2 == "from": if isinstance(option2, (discord.Member, discord.Role)): - ov = discord.utils.find(lambda t: t[0] == option2, option1.overwrites) + ov = discord.utils.find( + lambda t: t[0] == option2, option1.overwrites + ) if ov: ov = ov[1] ov.update(read_messages=False) else: ov = discord.PermissionOverwrite(read_messages=False) - overwrites = { - 'channel': option1, - option2: ov - } - elif isinstance(option2, (commands.core.Command, commands.core.Group)) \ - and isinstance(option1, discord.TextChannel): + overwrites = {"channel": option1, option2: ov} + elif isinstance( + option2, (commands.core.Command, commands.core.Group) + ) and isinstance(option1, discord.TextChannel): source = option2.qualified_name destination = str(option1.id) # To: # Command - Command can only be used in this channel # Role - Setup an overwrite so only this role can read this channel else: - if isinstance(option2, (commands.core.Command, commands.core.Group)) \ - and isinstance(option1, discord.TextChannel): + if isinstance( + option2, (commands.core.Command, commands.core.Group) + ) and isinstance(option1, discord.TextChannel): source = option2.qualified_name destination = str(option1.id) elif isinstance(option2, (discord.Member, discord.Role)): - ov = discord.utils.find(lambda t: t[0] == option2, option1.overwrites) + ov = discord.utils.find( + lambda t: t[0] == option2, option1.overwrites + ) if ov: ov = ov[1] ov.update(read_messages=True) else: ov = discord.PermissionOverwrite(read_messages=True) - ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, - option1.overwrites) + ov2 = discord.utils.find( + lambda t: t[0] == ctx.message.guild.default_role, + option1.overwrites, + ) if ov2: ov2 = ov2[1] ov2.update(read_messages=False) else: ov2 = discord.PermissionOverwrite(read_messages=False) overwrites = { - 'channel': option1, + "channel": option1, option2: ov, - ctx.message.guild.default_role: ov2 + ctx.message.guild.default_role: ov2, } elif isinstance(option1, discord.Role): # From: @@ -259,38 +279,41 @@ guild=$2 source = option2.qualified_name destination = option1.id elif isinstance(option2, (discord.TextChannel, discord.VoiceChannel)): - ov = discord.utils.find(lambda t: t[0] == option1, option2.overwrites) + ov = discord.utils.find( + lambda t: t[0] == option1, option2.overwrites + ) if ov: ov = ov[1] ov.update(read_messages=False) else: ov = discord.PermissionOverwrite(read_messages=False) - overwrites = { - 'channel': option2, - option1: ov - } + overwrites = {"channel": option2, option1: ov} # To: # Command - You have to have this role to run this command # Channel - Setup an overwrite so you have to have this role to read this channel else: if isinstance(option2, (discord.TextChannel, discord.VoiceChannel)): - ov = discord.utils.find(lambda t: t[0] == option1, option2.overwrites) + ov = discord.utils.find( + lambda t: t[0] == option1, option2.overwrites + ) if ov: ov = ov[1] ov.update(read_messages=True) else: ov = discord.PermissionOverwrite(read_messages=True) - ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, - option2.overwrites) + ov2 = discord.utils.find( + lambda t: t[0] == ctx.message.guild.default_role, + option2.overwrites, + ) if ov2: ov2 = ov2[1] ov2.update(read_messages=False) else: ov2 = discord.PermissionOverwrite(read_messages=False) overwrites = { - 'channel': option2, + "channel": option2, option1: ov, - ctx.message.guild.default_role: ov2 + ctx.message.guild.default_role: ov2, } elif isinstance(option2, (commands.core.Command, commands.core.Group)): source = option2.qualified_name @@ -303,20 +326,26 @@ guild=$2 ctx.guild.id, source, destination, - from_to + from_to, ) except UniqueViolationError: # If it's already inserted, then nothing needs to be updated # It just means this particular restriction is already set pass else: - ctx.bot.cache.add_restriction(ctx.guild, from_to, {"source": source, "destination": destination}) + ctx.bot.cache.add_restriction( + ctx.guild, from_to, {"source": source, "destination": destination} + ) elif overwrites: - channel = overwrites.pop('channel') + channel = overwrites.pop("channel") for target, setting in overwrites.items(): await channel.set_permissions(target, overwrite=setting) else: - await ctx.send("Sorry but I don't know how to restrict {} {} {}".format(arg1, arg2, arg3)) + await ctx.send( + "Sorry but I don't know how to restrict {} {} {}".format( + arg1, arg2, arg3 + ) + ) return await ctx.send("I have just restricted {} {} {}".format(arg1, arg2, arg3)) @@ -336,14 +365,18 @@ guild=$2 """ # First make sure we're given three options if len(options) != 3: - await ctx.send("You need to provide 3 options! Such as `command from @User`") + await ctx.send( + "You need to provide 3 options! Such as `command from @User`" + ) return else: # Get the three arguments from this list, then make sure the 2nd is either from or to arg1, arg2, arg3 = options - if arg2.lower() not in ['from', 'to']: - await ctx.send("The 2nd option needs to be either \"to\" or \"from\". Such as: `command from @user` " - "or `command to Role`") + if arg2.lower() not in ["from", "to"]: + await ctx.send( + 'The 2nd option needs to be either "to" or "from". Such as: `command from @user` ' + "or `command to Role`" + ) return else: # Try to convert the other arguments @@ -351,11 +384,18 @@ guild=$2 option1 = await utils.convert(ctx, arg1) option2 = await utils.convert(ctx, arg3) if option1 is None or option2 is None: - await ctx.send("Sorry, but I don't know how to unrestrict {} {} {}".format(arg1, arg2, arg3)) + await ctx.send( + "Sorry, but I don't know how to unrestrict {} {} {}".format( + arg1, arg2, arg3 + ) + ) return # First check if this is a blacklist/whitelist (by checking if we are unrestricting commands) - if any(isinstance(x, (commands.core.Command, commands.core.Group)) for x in [option1, option2]): + if any( + isinstance(x, (commands.core.Command, commands.core.Group)) + for x in [option1, option2] + ): # The source should always be the command, so just set this based on which order is given (either is # allowed) if isinstance(option1, (commands.core.Command, commands.core.Group)): @@ -366,15 +406,23 @@ guild=$2 destination = str(option1.id) # Now just try to remove it - await ctx.bot.db.execute(""" + await ctx.bot.db.execute( + """ DELETE FROM restrictions WHERE source=$1 AND destination=$2 AND from_to=$3 AND - guild=$4""", source, destination, arg2, ctx.guild.id) - ctx.bot.cache.remove_restriction(ctx.guild, arg2, {"source": source, "destination": destination}) + guild=$4""", + source, + destination, + arg2, + ctx.guild.id, + ) + ctx.bot.cache.remove_restriction( + ctx.guild, arg2, {"source": source, "destination": destination} + ) # If this isn't a blacklist/whitelist, then we are attempting to remove an overwrite else: @@ -390,14 +438,21 @@ WHERE if arg2 == "from": # Get overwrites if they exist # If it doesn't, there's nothing to do here - ov = discord.utils.find(lambda t: t[0] == source, destination.overwrites) + ov = discord.utils.find( + lambda t: t[0] == source, destination.overwrites + ) if ov: ov = ov[1] ov.update(read_messages=True) await destination.set_permissions(source, overwrite=ov) else: - ov = discord.utils.find(lambda t: t[0] == source, destination.overwrites) - ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, destination.overwrites) + ov = discord.utils.find( + lambda t: t[0] == source, destination.overwrites + ) + ov2 = discord.utils.find( + lambda t: t[0] == ctx.message.guild.default_role, + destination.overwrites, + ) if ov: ov = ov[1] ov.update(read_messages=None) @@ -418,7 +473,7 @@ WHERE EXAMPLE: !perms help RESULT: Hopefully a result saying you just need send_messages permissions; otherwise lol - this server's admin doesn't like me """ + this server's admin doesn't like me""" cmd = ctx.bot.get_command(command) if cmd is None: @@ -444,7 +499,7 @@ WHERE result = await ctx.bot.db.fetchrow( "SELECT permission FROM custom_permissions WHERE guild = $1 AND command = $2", ctx.guild.id, - command + command, ) perms_value = result["permission"] if result else None @@ -452,7 +507,9 @@ WHERE # If we don't find custom permissions, get the required permission for a command # based on what we set in utils.can_run, if can_run isn't found, we'll get an IndexError try: - can_run = [func for func in cmd.checks if "can_run" in func.__qualname__][0] + can_run = [ + func for func in cmd.checks if "can_run" 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 @@ -461,23 +518,32 @@ WHERE if "is_owner" in func.__qualname__: await ctx.send("You need to own the bot to run this command") return - await ctx.send("You are required to have `manage_guild` permissions to run `{}`".format( - cmd.qualified_name - )) + await ctx.send( + "You are required to have `manage_guild` permissions to run `{}`".format( + cmd.qualified_name + ) + ) return # Perms will be an attribute if can_run is found no matter what, so no need to check this - perms = "\n".join(attribute for attribute, setting in can_run.perms.items() if setting) + perms = "\n".join( + attribute for attribute, setting in can_run.perms.items() if setting + ) await ctx.send( - "You are required to have `{}` permissions to run `{}`".format(perms, cmd.qualified_name)) + "You are required to have `{}` permissions to run `{}`".format( + perms, cmd.qualified_name + ) + ) else: # Permissions are saved as bit values, so create an object based on that value # Then check which permission is true, that is our required permission # There's no need to check for errors here, as we ensure a permission is valid when adding it permissions = discord.Permissions(perms_value) needed_perm = [perm[0] for perm in permissions if perm[1]][0] - await ctx.send("You need to have the permission `{}` " - "to use the command `{}` in this server".format(needed_perm, command)) + await ctx.send( + "You need to have the permission `{}` " + "to use the command `{}` in this server".format(needed_perm, command) + ) @perms.command(name="add", aliases=["setup,create"]) @commands.guild_only() @@ -493,15 +559,18 @@ WHERE # Since subcommands exist, base the last word in the list as the permission, and the rest of it as the command command, _, permission = msg.rpartition(" ") if command == "": - await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n" - "`perms add `") + await ctx.send( + "Please provide the permissions you want to setup, the format for this must be in:\n" + "`perms add `" + ) return cmd = ctx.bot.get_command(command) if cmd is None: await ctx.send( - "That command does not exist! You can't have custom permissions on a non-existant command....") + "That command does not exist! You can't have custom permissions on a non-existant command...." + ) return # If a user can run a command, they have to have send_messages permissions; so use this as the base @@ -514,8 +583,11 @@ WHERE try: setattr(perm_obj, permission, True) except AttributeError: - await ctx.send("{} does not appear to be a valid permission! Valid permissions are: ```\n{}```" - .format(permission, "\n".join(valid_perms))) + await ctx.send( + "{} does not appear to be a valid permission! Valid permissions are: ```\n{}```".format( + permission, "\n".join(valid_perms) + ) + ) return perm_value = perm_obj.value @@ -533,19 +605,23 @@ WHERE "INSERT INTO custom_permissions (guild, command, permission) VALUES ($1, $2, $3)", ctx.guild.id, cmd.qualified_name, - perm_value + perm_value, ) except UniqueViolationError: await ctx.bot.db.execute( "UPDATE custom_permissions SET permission = $1 WHERE guild = $2 AND command = $3", perm_value, ctx.guild.id, - cmd.qualified_name + cmd.qualified_name, ) ctx.bot.cache.update_custom_permission(ctx.guild, cmd, perm_value) - await ctx.send("I have just added your custom permissions; " - "you now need to have `{}` permissions to use the command `{}`".format(permission, command)) + await ctx.send( + "I have just added your custom permissions; " + "you now need to have `{}` permissions to use the command `{}`".format( + permission, command + ) + ) @perms.command(name="remove", aliases=["delete"]) @commands.guild_only() @@ -560,18 +636,21 @@ WHERE if cmd is None: await ctx.send( - "That command does not exist! You can't have custom permissions on a non-existant command....") + "That command does not exist! You can't have custom permissions on a non-existant command...." + ) return await ctx.bot.db.execute( - "DELETE FROM custom_permissions WHERE guild=$1 AND command=$2", ctx.guild.id, cmd.qualified_name + "DELETE FROM custom_permissions WHERE guild=$1 AND command=$2", + ctx.guild.id, + cmd.qualified_name, ) ctx.bot.cache.update_custom_permission(ctx.guild, cmd, None) await ctx.send("I have just removed the custom permissions for {}!".format(cmd)) - @commands.command(aliases=['nick']) + @commands.command(aliases=["nick"]) @commands.guild_only() @utils.can_run(kick_members=True) async def nickname(self, ctx, *, name=None): diff --git a/cogs/birthday.py b/cogs/birthday.py index 43ddf77..ce031de 100644 --- a/cogs/birthday.py +++ b/cogs/birthday.py @@ -1,11 +1,9 @@ import discord import datetime -import asyncio -import traceback import re import calendar -from discord.ext import commands +from discord.ext import commands, tasks from asyncpg import UniqueViolationError import utils @@ -40,7 +38,7 @@ def parse_string(date): "dec": 12, } - num_re = re.compile("^(\d+)[a-z]*$") + num_re = re.compile(r"^(\d+)[a-z]*$") for part in [x.lower() for x in date.split()]: match = num_re.match(part) @@ -62,17 +60,15 @@ class Birthday(commands.Cog): def __init__(self, bot): self.bot = bot - self.task = self.bot.loop.create_task(self.birthday_task()) async def get_birthdays_for_server(self, server, today=False): - members = ", ".join(f"{m.id}" for m in server.members) - query = f""" + query = """ SELECT id, birthday -FROM +FROM users WHERE - id IN ({members}) + id=ANY($1::bigint[]) """ if today: query += """ @@ -84,29 +80,16 @@ ORDER BY birthday """ - return await self.bot.db.fetch(query) - - async def birthday_task(self): - await self.bot.wait_until_ready() - - while not self.bot.is_closed(): - try: - await self.notify_birthdays() - except Exception as error: - with open("error_log", 'a') as f: - traceback.print_tb(error.__traceback__, file=f) - print(f"{error.__class__.__name__}: {error}", file=f) - finally: - # Every day - await asyncio.sleep(60 * 60 * 24) + return await self.bot.db.fetch(query, [m.id for m in server.members]) + @tasks.loop(hours=24) async def notify_birthdays(self): query = """ SELECT id, COALESCE(birthday_alerts, default_alerts) AS channel -FROM +FROM guilds -WHERE +WHERE birthday_notifications=True AND COALESCE(birthday_alerts, default_alerts) IS NOT NULL @@ -117,23 +100,33 @@ AND return for s in servers: + # Get guild + g = self.bot.get_guild(s["id"]) + if not g: + continue # Get the channel based on the birthday alerts, or default alerts channel - channel = self.bot.get_channel(s['channel']) + channel = g.get_channel(s["channel"]) if not channel: continue - bds = await self.get_birthdays_for_server(channel.guild, today=True) + # Make sure it's chunked + if not g.chunked: + await g.chunk() + + bds = await self.get_birthdays_for_server(g, today=True) # A list of the id's that will get updated for bd in bds: try: - member = channel.guild.get_member(bd["id"]) - await channel.send(f"It is {member.mention}'s birthday today! " - "Wish them a happy birthday! \N{SHORTCAKE}") + member = g.get_member(bd["id"]) + await channel.send( + f"It is {member.mention}'s birthday today! " + "Wish them a happy birthday! \N{SHORTCAKE}" + ) except (discord.Forbidden, discord.HTTPException): pass finally: - update_bds.append(bd['id']) + update_bds.append(bd["id"]) if not update_bds: return @@ -148,7 +141,11 @@ WHERE """ await self.bot.db.execute(query) - @commands.group(aliases=['birthdays'], invoke_without_command=True) + @notify_birthdays.error + async def notify_birthdays_errors(self, error): + await utils.log_error(error, self.bot) + + @commands.group(aliases=["birthdays"], invoke_without_command=True) @commands.guild_only() @utils.can_run(send_messages=True) async def birthday(self, ctx, *, member: discord.Member = None): @@ -156,13 +153,20 @@ WHERE EXAMPLE: !birthdays RESULT: A printout of the birthdays from everyone on this server""" + if not ctx.guild.chunked: + await ctx.guild.chunk() + if member: - date = await ctx.bot.db.fetchrow("SELECT birthday FROM users WHERE id=$1", member.id) + date = await ctx.bot.db.fetchrow( + "SELECT birthday FROM users WHERE id=$1", member.id + ) if date is None or date["birthday"] is None: await ctx.send(f"I do not have {member.display_name}'s birthday saved!") else: - date = date['birthday'] - await ctx.send(f"{member.display_name}'s birthday is {calendar.month_name[date.month]} {date.day}") + date = date["birthday"] + await ctx.send( + f"{member.display_name}'s birthday is {calendar.month_name[date.month]} {date.day}" + ) else: # Get this server's birthdays bds = await self.get_birthdays_for_server(ctx.guild) @@ -170,7 +174,7 @@ WHERE entries = [ f"{ctx.guild.get_member(bd['id']).display_name} ({bd['birthday'].strftime('%B %-d')})" for bd in bds - if bd['birthday'] + if bd["birthday"] ] if not entries: await ctx.send("I don't know anyone's birthday in this server!") @@ -184,7 +188,7 @@ WHERE except utils.CannotPaginate as e: await ctx.send(str(e)) - @birthday.command(name='add') + @birthday.command(name="add") @utils.can_run(send_messages=True) async def _add_bday(self, ctx, *, date): """Used to link your birthday to your account @@ -192,26 +196,36 @@ WHERE EXAMPLE: !birthday add December 1st RESULT: I now know your birthday is December 1st""" if len(date.split()) != 2: - await ctx.send("Please provide date in a valid format, such as December 1st!") + await ctx.send( + "Please provide date in a valid format, such as December 1st!" + ) return try: date = parse_string(date) except ValueError: - await ctx.send("Please provide date in a valid format, such as December 1st!") + await ctx.send( + "Please provide date in a valid format, such as December 1st!" + ) return if date is None: - await ctx.send("Please provide date in a valid format, such as December 1st!") + await ctx.send( + "Please provide date in a valid format, such as December 1st!" + ) return await ctx.send(f"I have just saved your birthday as {date}") try: - await ctx.bot.db.execute("INSERT INTO users (id, birthday) VALUES ($1, $2)", ctx.author.id, date) + await ctx.bot.db.execute( + "INSERT INTO users (id, birthday) VALUES ($1, $2)", ctx.author.id, date + ) except UniqueViolationError: - await ctx.bot.db.execute("UPDATE users SET birthday = $1 WHERE id = $2", date, ctx.author.id) + await ctx.bot.db.execute( + "UPDATE users SET birthday = $1 WHERE id = $2", date, ctx.author.id + ) - @birthday.command(name='remove') + @birthday.command(name="remove") @utils.can_run(send_messages=True) async def _remove_bday(self, ctx): """Used to unlink your birthday to your account @@ -219,7 +233,9 @@ WHERE EXAMPLE: !birthday remove RESULT: I have magically forgotten your birthday""" await ctx.send("I don't know your birthday anymore :(") - await ctx.bot.db.execute("UPDATE users SET birthday=NULL WHERE id=$1", ctx.author.id) + await ctx.bot.db.execute( + "UPDATE users SET birthday=NULL WHERE id=$1", ctx.author.id + ) def setup(bot): diff --git a/cogs/blackjack.py b/cogs/blackjack.py index b2a593a..e5736ce 100644 --- a/cogs/blackjack.py +++ b/cogs/blackjack.py @@ -24,7 +24,7 @@ class Blackjack(commands.Cog): game = Game(self.bot, message, self) self.games[message.guild.id] = game - @commands.group(aliases=['bj'], invoke_without_command=True) + @commands.group(aliases=["bj"], invoke_without_command=True) @commands.guild_only() @utils.can_run(send_messages=True) async def blackjack(self, ctx): @@ -49,9 +49,11 @@ class Blackjack(commands.Cog): if game.playing(ctx.message.author): await ctx.send("You are already playing! Wait for your turn!") else: - await ctx.send("There are already a max number of players playing/waiting to play!") + await ctx.send( + "There are already a max number of players playing/waiting to play!" + ) - @blackjack.command(name='leave', aliases=['quit']) + @blackjack.command(name="leave", aliases=["quit"]) @commands.guild_only() @utils.can_run(send_messages=True) async def blackjack_leave(self, ctx): @@ -69,11 +71,15 @@ class Blackjack(commands.Cog): status = game.leave(ctx.message.author) if status: - await ctx.send("You have left the game, and will be removed at the end of this round") + await ctx.send( + "You have left the game, and will be removed at the end of this round" + ) else: - await ctx.send("Either you have already bet, or you are not even playing right now!") + await ctx.send( + "Either you have already bet, or you are not even playing right now!" + ) - @blackjack.command(name='forcestop', aliases=['stop']) + @blackjack.command(name="forcestop", aliases=["stop"]) @commands.guild_only() @utils.can_run(manage_guild=True) async def blackjack_stop(self, ctx): @@ -140,10 +146,10 @@ class Player: value = card.value.value face = card.value.name - if face in ['queen', 'king', 'jack']: + if face in ["queen", "king", "jack"]: for index, t in enumerate(total): total[index] += 10 - elif face == 'ace': + elif face == "ace": total = FOIL(total, [1, 11]) else: for index, t in enumerate(total): @@ -162,7 +168,7 @@ class Player: def __eq__(self, other): if isinstance(other, Player): - if hasattr(other, 'member') and other.member == self.member: + if hasattr(other, "member") and other.member == self.member: return True return False @@ -179,7 +185,7 @@ class Game: player = Player(message.author) self.bj = bj self.bot = bot - self.players = [{'status': 'playing', 'player': player}] + self.players = [{"status": "playing", "player": player}] # Our buffer for players who want to join # People cannot join in the middle of a game, so we'll add them at the end self._added_players = [] @@ -202,7 +208,7 @@ class Game: del _deck2 self.deck.shuffle() # The dealer - self.dealer = Player('Dealer') + self.dealer = Player("Dealer") self.min_bet = 5 self.max_bet = 500 @@ -269,19 +275,23 @@ class Game: # Our check to make sure a valid 'command' was provided def check(m): if m.channel == self.channel and m.author == player.member: - return m.content.lower() in ['hit', 'stand', 'double'] + return m.content.lower() in ["hit", "stand", "double"] else: return False # First lets handle the blackjacks - for entry in [p for p in self.players if p['status'] == 'blackjack']: - player = entry['player'] + for entry in [p for p in self.players if p["status"] == "blackjack"]: + player = entry["player"] fmt = "You got a blackjack {0.member.mention}!\n\n{0}".format(player) await self.channel.send(fmt) # Loop through each player (as long as their status is playing) and they have bet chips - for entry in [p for p in self.players if p['status'] == 'playing' and hasattr(p['player'], 'bet')]: - player = entry['player'] + for entry in [ + p + for p in self.players + if p["status"] == "playing" and hasattr(p["player"], "bet") + ]: + player = entry["player"] # Let them know it's their turn to play fmt = "It is your turn to play {0.member.mention}\n\n{0}".format(player) @@ -290,7 +300,7 @@ class Game: # If they're not playing anymore (i.e. they busted, are standing, etc.) then we don't want to keep asking # them to hit or stand - while entry['status'] not in ['stand', 'bust']: + while entry["status"] not in ["stand", "bust"]: # Ask if they want to hit or stand if first: @@ -300,19 +310,19 @@ class Game: await self.channel.send(fmt) try: - msg = await self.bot.wait_for('message', timeout=60, check=check) + msg = await self.bot.wait_for("message", timeout=60, check=check) except asyncio.TimeoutError: await self.channel.send("Took to long! You're standing!") - entry['status'] = 'stand' + entry["status"] = "stand" else: # If they want to hit - if 'hit' in msg.content.lower(): + if "hit" in msg.content.lower(): self.hit(player) await self.channel.send(player) # If they want to stand - elif 'stand' in msg.content.lower(): + elif "stand" in msg.content.lower(): self.stand(player) - elif 'double' in msg.content.lower() and first: + elif "double" in msg.content.lower() and first: self.double(player) await self.channel.send(player) # TODO: Handle double, split @@ -327,14 +337,14 @@ class Game: # this, we'll loop 'infinitely', get a list of players who haven't bet yet, and then use the first person in # that list while True: - players = [p for p in self.players if p['status'] == 'playing'] + players = [p for p in self.players if p["status"] == "playing"] # If everyone has bet/there is no one playing anymore if len(players) == 0: break entry = players[0] - player = entry['player'] + player = entry["player"] def check(_msg): """Makes sure the message provided is within the min and max bets""" @@ -343,25 +353,33 @@ class Game: msg_length = int(_msg.content) return self.min_bet <= msg_length <= self.max_bet except ValueError: - return _msg.content.lower() == 'skip' + return _msg.content.lower() == "skip" else: return False - fmt = "Your turn to bet {0.member.mention}, your current chips are: {0.chips}\n" \ - "Current min bet is {1}, current max bet is {2}\n" \ - "Place your bet now (please provide only the number;" \ - "'skip' if you would like to leave this game)".format(player, self.min_bet, self.max_bet) + fmt = ( + "Your turn to bet {0.member.mention}, your current chips are: {0.chips}\n" + "Current min bet is {1}, current max bet is {2}\n" + "Place your bet now (please provide only the number;" + "'skip' if you would like to leave this game)".format( + player, self.min_bet, self.max_bet + ) + ) await self.channel.send(fmt) try: msg = await self.bot.wait_for("message", timeout=60, check=check) except asyncio.TimeoutError: - await self.channel.send("You took too long! You're sitting this round out") - entry['status'] = 'stand' + await self.channel.send( + "You took too long! You're sitting this round out" + ) + entry["status"] = "stand" else: - if msg.content.lower() == 'skip': - await self.channel.send("Alright, you've been removed from the game") + if msg.content.lower() == "skip": + await self.channel.send( + "Alright, you've been removed from the game" + ) self.leave(player.member) else: num = int(msg.content) @@ -369,7 +387,7 @@ class Game: if num <= player.chips: player.bet = num player.chips -= num - entry['status'] = 'bet' + entry["status"] = "bet" else: await self.channel.send("You can't bet more than you have!!") @@ -387,10 +405,10 @@ class Game: blackjack = [] for entry in self.players: - player = entry['player'] + 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'): + if not hasattr(player, "bet"): continue hand = player.hand @@ -400,7 +418,7 @@ class Game: # TODO: Handle blackjacks # First if is to check If we can possibly win (a bust is an automatic loss, no matter what) # Payouts for wins are 2 times the bet - if entry['status'] == 'blackjack': + if entry["status"] == "blackjack": if dealer_count != 21: player.chips += math.floor(player.bet * 2.5) blackjack.append(player) @@ -442,7 +460,7 @@ class Game: if player.chips <= 0: self._removed_players.append(player) - entry['status'] = 'playing' + entry["status"] = "playing" # Now that we've looped through everyone, send the message regarding the outcome fmt = "Round stats:\n" @@ -486,30 +504,32 @@ class Game: # What we want to do is remove any players that are in the game and have left the guild for entry in self.players: - m = entry['player'].member + m = entry["player"].member if m not in self.channel.guild.members: - self._removed_players.append(entry['player']) + self._removed_players.append(entry["player"]) # Remove the players who left - self.players = [p for p in self.players if p['player'] not in self._removed_players] + self.players = [ + p for p in self.players if p["player"] not in self._removed_players + ] self._removed_players.clear() def _get_player_index(self, player): """Provides the index of a certain player""" for i, entry in enumerate(self.players): - if entry['player'] == player: + if entry["player"] == player: return i def get_player(self, member): """Returns the player object for the discord member provided""" for entry in self.players: - if entry['player'].member == member: - return entry['player'] + if entry["player"].member == member: + return entry["player"] def playing(self, member): """Returns true if the member provided is currently in this game""" for entry in self.players: - if member == entry['player'].member: + if member == entry["player"].member: return True return False @@ -520,13 +540,13 @@ class Game: for i in range(2): for entry in self.players: card = list(self.deck.draw()) - entry['player'].hand.insert(card) + entry["player"].hand.insert(card) # Make sure we detect blackjack here, as this is when it matters - if 21 in entry['player'].count: - entry['status'] = 'blackjack' + if 21 in entry["player"].count: + entry["status"] = "blackjack" else: - entry['status'] = 'playing' + entry["status"] = "playing" # Also add a card to the dealer's hand card = list(self.deck.draw()) self.dealer.hand.insert(card) @@ -538,7 +558,7 @@ class Game: if len(self.players) + len(self._added_players) >= self._max_players: return False player = Player(member) - entry = {'status': 'playing', 'player': player} + entry = {"status": "playing", "player": player} self._added_players.append(entry) return True @@ -549,10 +569,10 @@ class Game: if player: # We need to make sure they haven't already bet index = self._get_player_index(player) - if self.players[index]['status'] == 'bet': + if self.players[index]["status"] == "bet": return False else: - self.players[index]['status'] = 'left' + self.players[index]["status"] = "left" self._removed_players.append(player) return True else: @@ -571,8 +591,8 @@ class Game: def stand(self, player): """Causes a player to stand""" for entry in self.players: - if entry['player'] == player: - entry['status'] = 'stand' + if entry["player"] == player: + entry["status"] = "stand" return def hit(self, player): @@ -588,10 +608,10 @@ class Game: if player.bust: index = self._get_player_index(player) - self.players[index]['status'] = 'bust' + self.players[index]["status"] = "bust" elif 21 in player.count: index = self._get_player_index(player) - self.players[index]['status'] = 'stand' + self.players[index]["status"] = "stand" def setup(bot): diff --git a/cogs/config.py b/cogs/config.py index a029464..ecb2a18 100644 --- a/cogs/config.py +++ b/cogs/config.py @@ -11,13 +11,11 @@ class ConfigException(Exception): class WrongSettingType(ConfigException): - def __init__(self, message): self.message = message class MessageFormatError(ConfigException): - def __init__(self, original, keys): self.original = original self.keys = keys @@ -27,9 +25,7 @@ class MessageFormatError(ConfigException): class GuildConfiguration(commands.Cog): """Handles configuring the different settings that can be used on the bot""" - keys = { - - } + keys = {} def _str_to_bool(self, opt, setting): setting = setting.title() @@ -48,27 +44,41 @@ class GuildConfiguration(commands.Cog): if opt == "prefix": ctx.bot.cache.update_prefix(ctx.guild, setting) try: - return await ctx.bot.db.execute(f"INSERT INTO guilds (id, \"{opt}\") VALUES ($1, $2)", ctx.guild.id, setting) + return await ctx.bot.db.execute( + f'INSERT INTO guilds (id, "{opt}") VALUES ($1, $2)', + ctx.guild.id, + setting, + ) except UniqueViolationError: - return await ctx.bot.db.execute(f"UPDATE guilds SET {opt} = $1 WHERE id = $2", setting, ctx.guild.id) + return await ctx.bot.db.execute( + f"UPDATE guilds SET {opt} = $1 WHERE id = $2", setting, ctx.guild.id + ) async def _show_bool_options(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT * FROM guilds WHERE id = $1", ctx.guild.id + ) return f"`{opt}` are currently {'enabled' if result is not None and result[opt] else 'disabled'}" async def _show_channel_options(self, ctx, opt): """For showing options that rely on a certain channel""" - result = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT * FROM guilds WHERE id = $1", ctx.guild.id + ) if result is None: return f"You do not have a channel set for {opt}" channel_id = result[opt] if channel_id: channel = ctx.guild.get_channel(channel_id) if channel: - return f"Your {opt} alerts channel is currently set to {channel.mention}" + return ( + f"Your {opt} alerts channel is currently set to {channel.mention}" + ) else: - return "It looks like you used to have a channel set for this," \ - "however the channel has since been deleted" + return ( + "It looks like you used to have a channel set for this," + "however the channel has since been deleted" + ) else: return f"You do not have a channel set for {opt}" @@ -89,23 +99,33 @@ class GuildConfiguration(commands.Cog): _handle_show_raffle_alerts = _show_channel_options async def _handle_show_welcome_msg(self, ctx, setting): - result = await ctx.bot.db.fetchrow("SELECT welcome_msg FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT welcome_msg FROM guilds WHERE id = $1", ctx.guild.id + ) try: - msg = result["welcome_msg"].format(server=ctx.guild.name, member=ctx.author.mention) - return f"Your current welcome message will appear like this:\n\n" + msg = result["welcome_msg"].format( + server=ctx.guild.name, member=ctx.author.mention + ) + return f"Your current welcome message will appear like this:\n\n{msg}" except (AttributeError, TypeError): return "You currently have no welcome message setup" async def _handle_show_goodbye_msg(self, ctx, setting): - result = await ctx.bot.db.fetchrow("SELECT goodbye_msg FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT goodbye_msg FROM guilds WHERE id = $1", ctx.guild.id + ) try: - msg = result["goodbye_msg"].format(server=ctx.guild.name, member=ctx.author.mention) - return f"Your current goodbye message will appear like this:\n\n" + msg = result["goodbye_msg"].format( + server=ctx.guild.name, member=ctx.author.mention + ) + return f"Your current goodbye message will appear like this:\n\n{msg}" except (AttributeError, TypeError): return "You currently have no goodbye message setup" async def _handle_show_prefix(self, ctx, setting): - result = await ctx.bot.db.fetchrow("SELECT prefix FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT prefix FROM guilds WHERE id = $1", ctx.guild.id + ) if result is not None: prefix = result["prefix"] @@ -115,7 +135,9 @@ class GuildConfiguration(commands.Cog): return "You do not have a custom prefix set, you are using the default prefix" async def _handle_show_followed_picarto_channels(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT followed_picarto_channels FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT followed_picarto_channels FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["followed_picarto_channels"]: try: @@ -127,7 +149,9 @@ class GuildConfiguration(commands.Cog): return "This server is not following any picarto channels" async def _handle_show_ignored_channels(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT ignored_channels FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT ignored_channels FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["ignored_channels"]: try: @@ -144,7 +168,9 @@ class GuildConfiguration(commands.Cog): return "This server is not ignoring any channels" async def _handle_show_ignored_members(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT ignored_members FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT ignored_members FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["ignored_members"]: try: @@ -161,7 +187,9 @@ class GuildConfiguration(commands.Cog): return "This server is not ignoring any members" async def _handle_show_rules(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT rules FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT rules FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["rules"]: try: @@ -173,7 +201,9 @@ class GuildConfiguration(commands.Cog): return "This server has no rules" async def _handle_show_assignable_roles(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["assignable_roles"]: try: @@ -190,7 +220,9 @@ class GuildConfiguration(commands.Cog): return "This server has no assignable roles" async def _handle_show_custom_battles(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT custom_battles FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT custom_battles FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["custom_battles"]: try: @@ -202,7 +234,9 @@ class GuildConfiguration(commands.Cog): return "This server has no custom battles" async def _handle_show_custom_hugs(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT custom_hugs FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT custom_hugs FROM guilds WHERE id = $1", ctx.guild.id + ) if result and result["custom_hugs"]: try: @@ -214,10 +248,12 @@ class GuildConfiguration(commands.Cog): return "This server has no custom hugs" async def _handle_show_join_role(self, ctx, opt): - result = await ctx.bot.db.fetchrow("SELECT join_role FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT join_role FROM guilds WHERE id = $1", ctx.guild.id + ) - if result and result['join_role']: - role = ctx.guild.get_role(result['join_role']) + if result and result["join_role"]: + role = ctx.guild.get_role(result["join_role"]) if role is None: return "You had a role set, but I can't find it...it's most likely been deleted afterwords!" else: @@ -257,7 +293,7 @@ class GuildConfiguration(commands.Cog): async def _handle_set_welcome_msg(self, ctx, setting): try: - setting.format(member='test', server='test') + setting.format(member="test", server="test") except KeyError as e: raise MessageFormatError(e, ["member", "server"]) else: @@ -265,7 +301,7 @@ class GuildConfiguration(commands.Cog): async def _handle_set_goodbye_msg(self, ctx, setting): try: - setting.format(member='test', server='test') + setting.format(member="test", server="test") except KeyError as e: raise MessageFormatError(e, ["member", "server"]) else: @@ -306,7 +342,9 @@ class GuildConfiguration(commands.Cog): async def _handle_set_followed_picarto_channels(self, ctx, setting): user = await utils.request(f"http://api.picarto.tv/v1/channel/name/{setting}") if user is None: - raise WrongSettingType(f"Could not find a picarto user with the username {setting}") + raise WrongSettingType( + f"Could not find a picarto user with the username {setting}" + ) query = """ UPDATE @@ -401,7 +439,7 @@ WHERE async def _handle_set_custom_hugs(self, ctx, setting): try: setting.format(user="user") - except (KeyError, ValueError)as e: + except (KeyError, ValueError) as e: raise MessageFormatError(e, ["user"]) else: query = """ @@ -480,7 +518,9 @@ where async def _handle_remove_followed_picarto_channels(self, ctx, setting=None): if setting is None: - raise WrongSettingType("Specifying which channel you want to remove is required") + raise WrongSettingType( + "Specifying which channel you want to remove is required" + ) query = """ UPDATE @@ -494,7 +534,9 @@ WHERE async def _handle_remove_ignored_channels(self, ctx, setting=None): if setting is None: - raise WrongSettingType("Specifying which channel you want to remove is required") + raise WrongSettingType( + "Specifying which channel you want to remove is required" + ) channel = await self._get_channel(ctx, setting) @@ -510,7 +552,9 @@ WHERE async def _handle_remove_ignored_members(self, ctx, setting=None): if setting is None: - raise WrongSettingType("Specifying which channel you want to remove is required") + raise WrongSettingType( + "Specifying which channel you want to remove is required" + ) # We want to make it possible to have members that aren't in the server ignored # So first check if it's a digit (the id) if not setting.isdigit(): @@ -532,7 +576,9 @@ WHERE async def _handle_remove_rules(self, ctx, setting=None): if setting is None or not setting.isdigit(): - raise WrongSettingType("Please provide the number of the rule you want to remove") + raise WrongSettingType( + "Please provide the number of the rule you want to remove" + ) query = """ UPDATE @@ -546,7 +592,9 @@ WHERE async def _handle_remove_assignable_roles(self, ctx, setting=None): if setting is None: - raise WrongSettingType("Specifying which channel you want to remove is required") + raise WrongSettingType( + "Specifying which channel you want to remove is required" + ) if not setting.isdigit(): converter = commands.converter.RoleConverter() role = await converter.convert(ctx, setting) @@ -566,7 +614,9 @@ WHERE async def _handle_remove_custom_battles(self, ctx, setting=None): if setting is None or not setting.isdigit(): - raise WrongSettingType("Please provide the number of the custom battle you want to remove") + raise WrongSettingType( + "Please provide the number of the custom battle you want to remove" + ) else: setting = int(setting) @@ -582,7 +632,9 @@ WHERE async def _handle_remove_custom_hugs(self, ctx, setting=None): if setting is None or not setting.isdigit(): - raise WrongSettingType("Please provide the number of the custom hug you want to remove") + raise WrongSettingType( + "Please provide the number of the custom hug you want to remove" + ) else: setting = int(setting) @@ -609,7 +661,9 @@ WHERE try: coro = getattr(self, f"_handle_show_{opt}") except AttributeError: - await ctx.send(f"{opt} is not a valid config option. Use {ctx.prefix}config to list all config options") + await ctx.send( + f"{opt} is not a valid config option. Use {ctx.prefix}config to list all config options" + ) else: try: msg = await coro(ctx, opt) @@ -620,16 +674,29 @@ WHERE else: return await ctx.send(msg) - settings = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id=$1", ctx.guild.id) + settings = await ctx.bot.db.fetchrow( + "SELECT * FROM guilds WHERE id=$1", ctx.guild.id + ) # For convenience, if it's None, just create it and return the default values if settings is None: - await ctx.bot.db.execute("INSERT INTO guilds (id) VALUES ($1)", ctx.guild.id) - settings = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id=$1", ctx.guild.id) + await ctx.bot.db.execute( + "INSERT INTO guilds (id) VALUES ($1)", ctx.guild.id + ) + settings = await ctx.bot.db.fetchrow( + "SELECT * FROM guilds WHERE id=$1", ctx.guild.id + ) alerts = {} # This is dirty I know, but oh well... - for alert_type in ["default", "welcome", "goodbye", "picarto", "birthday", "raffle"]: + for alert_type in [ + "default", + "welcome", + "goodbye", + "picarto", + "birthday", + "raffle", + ]: channel = ctx.guild.get_channel(settings.get(f"{alert_type}_alerts")) name = channel.name if channel else None alerts[alert_type] = name @@ -712,7 +779,9 @@ WHERE **{len(settings.get("custom_hugs"))}** """.strip() - embed = discord.Embed(title=f"Configuration for {ctx.guild.name}", description=fmt) + embed = discord.Embed( + title=f"Configuration for {ctx.guild.name}", description=fmt + ) embed.set_image(url=ctx.guild.icon_url) await ctx.send(embed=embed) @@ -724,11 +793,15 @@ WHERE try: coro = getattr(self, f"_handle_set_{option}") except AttributeError: - await ctx.send(f"{option} is not a valid config option. Use {ctx.prefix}config to list all config options") + await ctx.send( + f"{option} is not a valid config option. Use {ctx.prefix}config to list all config options" + ) else: # First make sure there's an entry for this guild before doing anything try: - await ctx.bot.db.execute("INSERT INTO guilds(id) VALUES ($1)", ctx.guild.id) + await ctx.bot.db.execute( + "INSERT INTO guilds(id) VALUES ($1)", ctx.guild.id + ) except UniqueViolationError: pass @@ -755,7 +828,9 @@ Extraneous args provided: {', '.join(k for k in exc.original.args)} try: coro = getattr(self, f"_handle_remove_{option}") except AttributeError: - await ctx.send(f"{option} is not a valid config option. Use {ctx.prefix}config to list all config options") + await ctx.send( + f"{option} is not a valid config option. Use {ctx.prefix}config to list all config options" + ) else: try: await coro(ctx, setting=setting) diff --git a/cogs/events.py b/cogs/events.py index 11aa856..3efc2d7 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -8,9 +8,9 @@ from discord.ext import commands log = logging.getLogger() -discord_bots_url = 'https://bots.discord.pw/api/bots/{}/stats' +discord_bots_url = "https://bots.discord.pw/api/bots/{}/stats" discordbots_url = "https://discordbots.org/api/bots/{}/stats" -carbonitex_url = 'https://www.carbonitex.net/discord/data/botdata.php' +carbonitex_url = "https://www.carbonitex.net/discord/data/botdata.php" class StatsUpdate(commands.Cog): @@ -27,39 +27,42 @@ class StatsUpdate(commands.Cog): server_count = len(self.bot.guilds) # Carbonitex request - carbon_payload = { - 'key': config.carbon_key, - 'servercount': server_count - } + carbon_payload = {"key": config.carbon_key, "servercount": server_count} async with self.session.post(carbonitex_url, data=carbon_payload) as resp: - log.info('Carbonitex statistics returned {} for {}'.format(resp.status, carbon_payload)) + log.info( + "Carbonitex statistics returned {} for {}".format( + resp.status, carbon_payload + ) + ) # Discord.bots.pw request - payload = json.dumps({ - 'server_count': server_count - }) + payload = json.dumps({"server_count": server_count}) headers = { - 'authorization': config.discord_bots_key, - 'content-type': 'application/json' + "authorization": config.discord_bots_key, + "content-type": "application/json", } url = discord_bots_url.format(self.bot.user.id) async with self.session.post(url, data=payload, headers=headers) as resp: - log.info('bots.discord.pw statistics returned {} for {}'.format(resp.status, payload)) + log.info( + "bots.discord.pw statistics returned {} for {}".format( + resp.status, payload + ) + ) # discordbots.com request url = discordbots_url.format(self.bot.user.id) - payload = { - "server_count": server_count - } + payload = {"server_count": server_count} - headers = { - "Authorization": config.discordbots_key - } + headers = {"Authorization": config.discordbots_key} async with self.session.post(url, data=payload, headers=headers) as resp: - log.info('discordbots.com statistics retruned {} for {}'.format(resp.status, payload)) + log.info( + "discordbots.com statistics retruned {} for {}".format( + resp.status, payload + ) + ) @commands.Cog.listener() async def on_guild_join(self, _): @@ -88,11 +91,13 @@ WHERE """ settings = await self.bot.db.fetchrow(query, member.guild.id) if settings: - message = settings['msg'] or "Welcome to the '{server}' server {member}!" + message = settings["msg"] or "Welcome to the '{server}' server {member}!" if settings["notify"]: try: - channel = member.guild.get_channel(settings['channel']) - await channel.send(message.format(server=member.guild.name, member=member.mention)) + channel = member.guild.get_channel(settings["channel"]) + await channel.send( + message.format(server=member.guild.name, member=member.mention) + ) # Forbidden for if the channel has send messages perms off # HTTP Exception to catch any weird happenings # Attribute Error catches when a channel is set, but that channel doesn't exist any more @@ -100,7 +105,7 @@ WHERE pass try: - role = member.guild.get_role(settings['role']) + role = member.guild.get_role(settings["role"]) await member.add_roles(role) except (discord.Forbidden, discord.HTTPException, AttributeError): pass @@ -122,10 +127,15 @@ AND """ settings = await self.bot.db.fetchrow(query, member.guild.id) if settings: - message = settings['msg'] or "{member} has left the server, I hope it wasn't because of something I said :c" - channel = member.guild.get_channel(settings['channel']) + message = ( + settings["msg"] + or "{member} has left the server, I hope it wasn't because of something I said :c" + ) + channel = member.guild.get_channel(settings["channel"]) try: - await channel.send(message.format(server=member.guild.name, member=member.mention)) + await channel.send( + message.format(server=member.guild.name, member=member.mention) + ) except (discord.Forbidden, discord.HTTPException, AttributeError): pass diff --git a/cogs/games.py b/cogs/games.py index fce2c7d..30b5bc7 100644 --- a/cogs/games.py +++ b/cogs/games.py @@ -4,7 +4,6 @@ import utils class Games(commands.Cog): - @commands.guild_only() @utils.can_run(send_messages=True) @commands.command(aliases=["word_chain", "しりとり"]) @@ -18,6 +17,7 @@ class Games(commands.Cog): The kana ん cannot be used, as no word in Japanese starts with this The word used cannot be a previously given word """ + def grab_letter(word, last=True): iterator = reversed(word) if last else iter(word) @@ -41,6 +41,7 @@ class Games(commands.Cog): # Setup the info needed for the game message = ctx.message message.content = start_word + last_letter = None words_used = [] while True: @@ -69,7 +70,7 @@ class Games(commands.Cog): # If we're here, game over, someone messed up await message.add_reaction("❌") if last_letter in ("ん", "ン"): - await ctx.send(f"Wrong! ん cannot be used as the last kana!") + await ctx.send("Wrong! ん cannot be used as the last kana!") else: await ctx.send(f"Wrong! {message.author.mention} is a loser!") diff --git a/cogs/hangman.py b/cogs/hangman.py index 6a64534..350d47a 100644 --- a/cogs/hangman.py +++ b/cogs/hangman.py @@ -12,7 +12,9 @@ class Game: def __init__(self, word): self.word = word # This converts everything but spaces to a blank - self.blanks = "".join(letter if not re.search("[a-zA-Z0-9]", letter) else "_" for letter in word) + self.blanks = "".join( + letter if not re.search("[a-zA-Z0-9]", letter) else "_" for letter in word + ) self.failed_letters = [] self.guessed_letters = [] self.fails = 0 @@ -24,8 +26,9 @@ class Game: # Replace every occurence of the guessed letter, with the correct letter # Use the one in the word instead of letter, due to capitalization self.blanks = "".join( - word_letter if letter.lower() == word_letter.lower() else self.blanks[i] for i, word_letter in - enumerate(self.word)) + word_letter if letter.lower() == word_letter.lower() else self.blanks[i] + for i, word_letter in enumerate(self.word) + ) return True else: self.fails += 1 @@ -52,15 +55,22 @@ class Game: man = " ——\n" man += " | |\n" man += " {} |\n".format("o" if self.fails > 0 else " ") - man += " {}{}{} |\n".format("/" if self.fails > 1 else " ", "|" if self.fails > 2 else " ", - "\\" if self.fails > 3 else " ") + man += " {}{}{} |\n".format( + "/" if self.fails > 1 else " ", + "|" if self.fails > 2 else " ", + "\\" if self.fails > 3 else " ", + ) man += " {} |\n".format("|" if self.fails > 4 else " ") - man += " {} {} |\n".format("/" if self.fails > 5 else " ", "\\" if self.fails > 6 else " ") + man += " {} {} |\n".format( + "/" if self.fails > 5 else " ", "\\" if self.fails > 6 else " " + ) man += " |\n" man += " ———————\n" fmt = "```\n{}```".format(man) # Then just add the guesses and the blanks to the string - fmt += "```\nGuesses: {}\nWord: {}```".format(", ".join(self.failed_letters), " ".join(self.blanks)) + fmt += "```\nGuesses: {}\nWord: {}```".format( + ", ".join(self.failed_letters), " ".join(self.blanks) + ) return fmt @@ -77,7 +87,7 @@ class Hangman(commands.Cog): game.author = ctx.message.author.id return game - @commands.group(aliases=['hm'], invoke_without_command=True) + @commands.group(aliases=["hm"], invoke_without_command=True) @commands.guild_only() @commands.cooldown(1, 7, BucketType.user) @checks.can_run(send_messages=True) @@ -126,7 +136,7 @@ class Hangman(commands.Cog): await ctx.send(fmt) - @hangman.command(name='create', aliases=['start']) + @hangman.command(name="create", aliases=["start"]) @commands.guild_only() @checks.can_run(send_messages=True) async def create_hangman(self, ctx): @@ -142,49 +152,62 @@ class Hangman(commands.Cog): await ctx.send("Sorry but only one Hangman game can be running per server!") return if ctx.guild.id in self.pending_games: - await ctx.send("Someone has already started one, and I'm now waiting for them...") + await ctx.send( + "Someone has already started one, and I'm now waiting for them..." + ) return try: msg = await ctx.message.author.send( "Please respond with a phrase you would like to use for your hangman game in **{}**\n\nPlease keep " - "phrases less than 31 characters".format( - ctx.message.guild.name)) + "phrases less than 31 characters".format(ctx.message.guild.name) + ) except discord.Forbidden: await ctx.send( "I can't message you {}! Please allow DM's so I can message you and ask for the hangman phrase you " - "want to use!".format(ctx.message.author.display_name)) + "want to use!".format(ctx.message.author.display_name) + ) return - await ctx.send("I have DM'd you {}, please respond there with the phrase you would like to setup".format( - ctx.message.author.display_name)) + await ctx.send( + "I have DM'd you {}, please respond there with the phrase you would like to setup".format( + ctx.message.author.display_name + ) + ) def check(m): return m.channel == msg.channel and len(m.content) <= 30 self.pending_games.append(ctx.guild.id) try: - msg = await ctx.bot.wait_for('message', check=check, timeout=60) + msg = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: self.pending_games.remove(ctx.guild.id) await ctx.send( - "You took too long! Please look at your DM's as that's where I'm asking for the phrase you want to use") + "You took too long! Please look at your DM's as that's where I'm asking for the phrase you want to use" + ) return else: self.pending_games.remove(ctx.guild.id) - forbidden_phrases = ['stop', 'delete', 'remove', 'end', 'create', 'start'] + forbidden_phrases = ["stop", "delete", "remove", "end", "create", "start"] if msg.content in forbidden_phrases: - await ctx.send("Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format( - "\n".join(forbidden_phrases))) + await ctx.send( + "Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format( + "\n".join(forbidden_phrases) + ) + ) return game = self.create(msg.content, ctx) # Let them know the game has started, then print the current game so that the blanks are shown await ctx.send( - "Alright, a hangman game has just started, you can start guessing now!\n{}".format(str(game))) + "Alright, a hangman game has just started, you can start guessing now!\n{}".format( + str(game) + ) + ) - @hangman.command(name='delete', aliases=['stop', 'remove', 'end']) + @hangman.command(name="delete", aliases=["stop", "remove", "end"]) @commands.guild_only() @checks.can_run(kick_members=True) async def stop_game(self, ctx): @@ -199,7 +222,9 @@ class Hangman(commands.Cog): return del self.games[ctx.message.guild.id] - await ctx.send("I have just stopped the game of Hangman, a new should be able to be started now!") + await ctx.send( + "I have just stopped the game of Hangman, a new should be able to be started now!" + ) def setup(bot): diff --git a/cogs/images.py b/cogs/images.py index 4827e08..57326e7 100644 --- a/cogs/images.py +++ b/cogs/images.py @@ -18,7 +18,9 @@ class Images(commands.Cog): url = data["data"]["file_url_size_large"] filename = data["data"]["file_name"] except (KeyError, TypeError): - return await ctx.send(f"I couldn't connect! Sorry no {animal}s right now ;w;") + return await ctx.send( + f"I couldn't connect! Sorry no {animal}s right now ;w;" + ) else: image = await utils.download_image(url) f = discord.File(image, filename=filename) @@ -29,7 +31,7 @@ class Images(commands.Cog): f"File to large to send as attachment, here is the URL: {url}" ) - @commands.command(aliases=['rc']) + @commands.command(aliases=["rc"]) @utils.can_run(send_messages=True) async def cat(self, ctx): """Use this to print a random cat image. @@ -38,7 +40,7 @@ class Images(commands.Cog): RESULT: A beautiful picture of a cat o3o""" url = "http://thecatapi.com/api/images/get" opts = {"format": "src"} - result = await utils.request(url, attr='url', payload=opts) + result = await utils.request(url, attr="url", payload=opts) try: image = await utils.download_image(result) @@ -53,14 +55,14 @@ class Images(commands.Cog): f"File to large to send as attachment, here is the URL: {url}" ) - @commands.command(aliases=['dog', 'rd']) + @commands.command(aliases=["dog", "rd"]) @utils.can_run(send_messages=True) async def doggo(self, ctx): """Use this to print a random doggo image. EXAMPLE: !doggo RESULT: A beautiful picture of a dog o3o""" - result = await utils.request('https://random.dog/woof.json') + result = await utils.request("https://random.dog/woof.json") try: url = result.get("url") filename = re.match("https://random.dog/(.*)", url).group(1) @@ -77,7 +79,7 @@ class Images(commands.Cog): f"File to large to send as attachment, here is the URL: {url}" ) - @commands.command(aliases=['snake']) + @commands.command(aliases=["snake"]) @utils.can_run(send_messages=True) async def snek(self, ctx): """Use this to print a random snek image. @@ -144,11 +146,11 @@ class Images(commands.Cog): member = ctx.message.author url = str(member.avatar_url) - if '.gif' not in url: - url = str(member.avatar_url_as(format='png')) - filename = 'avatar.png' + if ".gif" not in url: + url = str(member.avatar_url_as(format="png")) + filename = "avatar.png" else: - filename = 'avatar.gif' + filename = "avatar.gif" if ctx.message.guild.me.permissions_in(ctx.message.channel).attach_files: filedata = await utils.download_image(url) if filedata is None: @@ -171,23 +173,25 @@ class Images(commands.Cog): RESULT: A picture of Rainbow Dash!""" if len(search) > 0: - url = 'https://derpibooru.org/api/v1/json/search/images' + url = "https://derpibooru.org/api/v1/json/search/images" # Ensure a filter was not provided, as we either want to use our own, or none (for safe pics) - query = ' '.join(value for value in search if not re.search('&?filter_id=[0-9]+', value)) - params = {'q': query} + query = " ".join( + value for value in search if not re.search("&?filter_id=[0-9]+", value) + ) + params = {"q": query} nsfw = utils.channel_is_nsfw(ctx.message.channel) # If this is a nsfw channel, we just need to tack on 'explicit' to the terms # Also use the custom filter that I have setup, that blocks some certain tags # If the channel is not nsfw, we don't need to do anything, as the default filter blocks explicit if nsfw: - params['q'] += ", (explicit OR suggestive)" - params['filter_id'] = 95938 + params["q"] += ", (explicit OR suggestive)" + params["filter_id"] = 95938 else: - params['q'] += ", safe" + params["q"] += ", safe" # Lets filter out some of the "crap" that's on derpibooru by requiring an image with a score higher than 15 - params['q'] += ', score.gt:15' + params["q"] += ", score.gt:15" try: # Get the response from derpibooru and parse the 'search' result from it @@ -196,37 +200,47 @@ class Images(commands.Cog): if data is None: await ctx.send("Sorry but I failed to connect to Derpibooru!") return - results = data['images'] + results = data["images"] except KeyError: - await ctx.send("No results with that search term, {0}!".format(ctx.message.author.mention)) + await ctx.send( + "No results with that search term, {0}!".format( + ctx.message.author.mention + ) + ) return # The first request we've made ensures there are results # Now we can get the total count from that, and make another request based on the number of pages as well if len(results) > 0: # Get the total number of pages - pages = math.ceil(data['total'] / len(results)) + pages = math.ceil(data["total"] / len(results)) # Set a new paramater to set which page to use, randomly based on the number of pages - params['page'] = random.SystemRandom().randint(1, pages) + params["page"] = random.SystemRandom().randint(1, pages) data = await utils.request(url, payload=params) if data is None: await ctx.send("Sorry but I failed to connect to Derpibooru!") return # Now get the results again - results = data['images'] + results = data["images"] # Get the image link from the now random page'd and random result from that page index = random.SystemRandom().randint(0, len(results) - 1) # image_link = 'https://derpibooru.org/{}'.format(results[index]['id']) - image_link = results[index]['view_url'] + image_link = results[index]["view_url"] else: - await ctx.send("No results with that search term, {0}!".format(ctx.message.author.mention)) + await ctx.send( + "No results with that search term, {0}!".format( + ctx.message.author.mention + ) + ) return else: # If no search term was provided, search for a random image # .url will be the URL we end up at, not the one requested. # https://derpibooru.org/images/random redirects to a random image, so this is exactly what we want - image_link = await utils.request('https://derpibooru.org/images/random', attr='url') + image_link = await utils.request( + "https://derpibooru.org/images/random", attr="url" + ) await ctx.send(image_link) @commands.command() @@ -242,29 +256,31 @@ class Images(commands.Cog): # This changes the formatting for queries, so we don't # Have to use e621's stupid formatting when using the command - tags = tags.replace(' ', '_') - tags = tags.replace(',_', ' ') + tags = tags.replace(" ", "_") + tags = tags.replace(",_", " ") - url = 'https://e621.net/posts.json' + url = "https://e621.net/posts.json" params = { - 'login': utils.config.e621_user, - 'api_key': utils.config.e621_key, - 'limit': 5, - 'tags': tags + "login": utils.config.e621_user, + "api_key": utils.config.e621_key, + "limit": 5, + "tags": tags, } - headers = {'User-Agent': utils.config.user_agent} + headers = {"User-Agent": utils.config.user_agent} nsfw = utils.channel_is_nsfw(ctx.message.channel) # e621 by default does not filter explicit content, so tack on # safe/explicit based on if this channel is nsfw or not - params['tags'] += " rating:explicit" if nsfw else " rating:safe" + params["tags"] += " rating:explicit" if nsfw else " rating:safe" # Tack on a random order - params['tags'] += " order:random" + params["tags"] += " order:random" data = await utils.request(url, payload=params, headers=headers) if data is None: - await ctx.send("Sorry, I had trouble connecting at the moment; please try again later") + await ctx.send( + "Sorry, I had trouble connecting at the moment; please try again later" + ) return # Try to find an image from the list. If there were no results, we're going to attempt to find @@ -283,7 +299,9 @@ class Images(commands.Cog): await ctx.send(image["file"]["url"]) return except (ValueError, KeyError): - await ctx.send("No results with that tag {}".format(ctx.message.author.mention)) + await ctx.send( + "No results with that tag {}".format(ctx.message.author.mention) + ) return # If we're here then there was nothing in the posts, or nothing found that's not blacklisted diff --git a/cogs/interaction.py b/cogs/interaction.py index 5d4698e..3b45550 100644 --- a/cogs/interaction.py +++ b/cogs/interaction.py @@ -8,81 +8,83 @@ import discord import random import functools -battle_outcomes = \ - ["A meteor fell on {loser}, {winner} is left standing and has been declared the victor!", - "{loser} was shot through the heart, and {winner} is to blame", - "{winner} has bucked {loser} into a tree, even Big Mac would be impressed at that kick!", - "As they were battling, {loser} was struck by lightning! {winner} you lucked out this time!", - "{loser} tried to dive at {winner} while fighting, somehow they missed and landed in quicksand." - "Try paying more attention next time {loser}", - "{loser} got a little...heated during the battle and ended up getting set on fire. " - "{winner} wins by remaining cool", - "Princess Celestia came in and banished {loser} to the moon. Good luck getting into any battles up there", - "{loser} took an arrow to the knee, they are no longer an adventurer. Keep on adventuring {winner}", - "Common sense should make it obvious not to get into battle with {winner}. Apparently {loser} didn't get the memo", - "{winner} had a nice cup of tea with {loser} over their conflict, and mutually agreed that {winner} was Best Pony", - "{winner} and {loser} had an intense staring contest. " - "Sadly, {loser} forgot to breathe and lost much morethan the staring contest", - "It appears {loser} is actually a pacifist, they ran away screaming and crying. " - "Maybe you should have thought of that before getting in a fight?", - "A bunch of parasprites came in and ate up the jetpack while {loser} was flying with it. Those pesky critters...", - "{winner} used their charm to seduce {loser} to surrender.", - "{loser} slipped on a banana peel and fell into a pit of spikes. That's actually impressive.", - "{winner} realized it was high noon, {loser} never even saw it coming.", - "{loser} spontaneously combusted...lol rip", - "after many turns {winner} summons exodia and {loser} is sent to the shadow realm", - "{winner} and {loser} sit down for an intense game of chess, " - "in the heat of the moment {winner} forgot they were playing a " - "game and summoned a real knight", - "{winner} challenges {loser} to rock paper scissors, " - "unfortunately for {loser}, {winner} chose scissors and stabbed them", - "{winner} goes back in time and becomes {loser}'s best friend, winning without ever throwing a punch", - "{loser} trips down some stairs on their way to the battle with {winner}", - "{winner} books {loser} a one way ticket to Flugendorf prison", - "{loser} was already dead", - "{loser} was crushed under the weight of expectations", - "{loser} was wearing a redshirt and it was their first day", - "{winner} and {loser} were walking along when suddenly {loser} " - "got kidnapped by a flying monkey; hope they had water with them", - "{winner} brought an army to a fist fight, {loser} never saw their opponent once", - "{winner} used multiple simultaneous devestating defensive deep strikes to overwhelm {loser}", - "{winner} and {loser} engage in a dance off; {winner} wiped the floor with {loser}", - "{loser} tried to hide in the sand to catch {winner} off guard, " - "unfortunately looks like a Giant Antlion had the same " - "idea for him", - "{loser} was busy playing trash videogames the night before the fight and collapsed before {winner}", - "{winner} threw a sick meme and {loser} totally got PRANK'D", - "{winner} and {loser} go on a skiing trip together, turns out {loser} forgot how to pizza french-fry", - "{winner} is the cure and {loser} is the disease....well {loser} was the disease", - "{loser} talked their mouth off at {winner}...literally...", - "Looks like {loser} didn't put enough points into kazoo playing, who knew they would have needed it", - "{loser} was too scared by the illuminati and extra-dimensional talking horses to show up", - "{loser} didn't press x enough to not die", - "{winner} and {loser} go fishing to settle their debate, " - "{winner} caught a sizeable fish and {loser} caught a boot older than time", - "{winner} did a hero landing and {loser} was so surprised they gave up immediately"] +battle_outcomes = [ + "A meteor fell on {loser}, {winner} is left standing and has been declared the victor!", + "{loser} was shot through the heart, and {winner} is to blame", + "{winner} has bucked {loser} into a tree, even Big Mac would be impressed at that kick!", + "As they were battling, {loser} was struck by lightning! {winner} you lucked out this time!", + "{loser} tried to dive at {winner} while fighting, somehow they missed and landed in quicksand." + "Try paying more attention next time {loser}", + "{loser} got a little...heated during the battle and ended up getting set on fire. " + "{winner} wins by remaining cool", + "Princess Celestia came in and banished {loser} to the moon. Good luck getting into any battles up there", + "{loser} took an arrow to the knee, they are no longer an adventurer. Keep on adventuring {winner}", + "Common sense should make it obvious not to get into battle with {winner}. Apparently {loser} didn't get the memo", + "{winner} had a nice cup of tea with {loser} over their conflict, and mutually agreed that {winner} was Best Pony", + "{winner} and {loser} had an intense staring contest. " + "Sadly, {loser} forgot to breathe and lost much morethan the staring contest", + "It appears {loser} is actually a pacifist, they ran away screaming and crying. " + "Maybe you should have thought of that before getting in a fight?", + "A bunch of parasprites came in and ate up the jetpack while {loser} was flying with it. Those pesky critters...", + "{winner} used their charm to seduce {loser} to surrender.", + "{loser} slipped on a banana peel and fell into a pit of spikes. That's actually impressive.", + "{winner} realized it was high noon, {loser} never even saw it coming.", + "{loser} spontaneously combusted...lol rip", + "after many turns {winner} summons exodia and {loser} is sent to the shadow realm", + "{winner} and {loser} sit down for an intense game of chess, " + "in the heat of the moment {winner} forgot they were playing a " + "game and summoned a real knight", + "{winner} challenges {loser} to rock paper scissors, " + "unfortunately for {loser}, {winner} chose scissors and stabbed them", + "{winner} goes back in time and becomes {loser}'s best friend, winning without ever throwing a punch", + "{loser} trips down some stairs on their way to the battle with {winner}", + "{winner} books {loser} a one way ticket to Flugendorf prison", + "{loser} was already dead", + "{loser} was crushed under the weight of expectations", + "{loser} was wearing a redshirt and it was their first day", + "{winner} and {loser} were walking along when suddenly {loser} " + "got kidnapped by a flying monkey; hope they had water with them", + "{winner} brought an army to a fist fight, {loser} never saw their opponent once", + "{winner} used multiple simultaneous devestating defensive deep strikes to overwhelm {loser}", + "{winner} and {loser} engage in a dance off; {winner} wiped the floor with {loser}", + "{loser} tried to hide in the sand to catch {winner} off guard, " + "unfortunately looks like a Giant Antlion had the same " + "idea for him", + "{loser} was busy playing trash videogames the night before the fight and collapsed before {winner}", + "{winner} threw a sick meme and {loser} totally got PRANK'D", + "{winner} and {loser} go on a skiing trip together, turns out {loser} forgot how to pizza french-fry", + "{winner} is the cure and {loser} is the disease....well {loser} was the disease", + "{loser} talked their mouth off at {winner}...literally...", + "Looks like {loser} didn't put enough points into kazoo playing, who knew they would have needed it", + "{loser} was too scared by the illuminati and extra-dimensional talking horses to show up", + "{loser} didn't press x enough to not die", + "{winner} and {loser} go fishing to settle their debate, " + "{winner} caught a sizeable fish and {loser} caught a boot older than time", + "{winner} did a hero landing and {loser} was so surprised they gave up immediately", +] -hugs = \ - ["*hugs {user}.*", - "*tackles {user} for a hug.*", - "*drags {user} into her dungeon where hugs ensue*", - "*pulls {user} to the side for a warm hug*", - "*goes out to buy a big enough blanket to embrace {user}*", - "*hard codes an electric hug to {user}*", - "*hires mercenaries to take {user} out....to a nice dinner*", - "*pays $10 to not touch {user}*", - "*clones herself to create a hug pile with {user}*", - "*orders an airstrike of hugs {user}*", - "*glomps {user}*", - "*hears a knock at her door, opens it, finds {user} and hugs them excitedly*", - "*goes in for a punch but misses and ends up hugging {user}*", - "*hugs {user} from behind*", - "*denies a hug from {user}*", - "*does a hug to {user}*", - "*lets {user} cuddle nonchalantly*", - "*cuddles {user}*", - "*burrows underground and pops up underneath {user} she hugs their legs.*", - "*approaches {user} after having gone to the gym for several months and almost crushes them.*"] +hugs = [ + "*hugs {user}.*", + "*tackles {user} for a hug.*", + "*drags {user} into her dungeon where hugs ensue*", + "*pulls {user} to the side for a warm hug*", + "*goes out to buy a big enough blanket to embrace {user}*", + "*hard codes an electric hug to {user}*", + "*hires mercenaries to take {user} out....to a nice dinner*", + "*pays $10 to not touch {user}*", + "*clones herself to create a hug pile with {user}*", + "*orders an airstrike of hugs {user}*", + "*glomps {user}*", + "*hears a knock at her door, opens it, finds {user} and hugs them excitedly*", + "*goes in for a punch but misses and ends up hugging {user}*", + "*hugs {user} from behind*", + "*denies a hug from {user}*", + "*does a hug to {user}*", + "*lets {user} cuddle nonchalantly*", + "*cuddles {user}*", + "*burrows underground and pops up underneath {user} she hugs their legs.*", + "*approaches {user} after having gone to the gym for several months and almost crushes them.*", +] class Interaction(commands.Cog): @@ -142,7 +144,7 @@ class Interaction(commands.Cog): settings = await ctx.bot.db.fetchrow( "SELECT custom_hugs, include_default_hugs FROM guilds WHERE id = $1", - ctx.guild.id + ctx.guild.id, ) msgs = hugs.copy() if settings: @@ -158,7 +160,7 @@ class Interaction(commands.Cog): fmt = random.SystemRandom().choice(msgs) await ctx.send(fmt.format(user=user.display_name)) - @commands.command(aliases=['1v1']) + @commands.command(aliases=["1v1"]) @commands.guild_only() @commands.cooldown(1, 20, BucketType.user) @utils.can_run(send_messages=True) @@ -169,8 +171,10 @@ class Interaction(commands.Cog): RESULT: A battle to the death""" # First check if everyone was mentioned if ctx.message.mention_everyone: - await ctx.send("You want to battle {} people? Good luck with that...".format( - len(ctx.channel.members) - 1) + await ctx.send( + "You want to battle {} people? Good luck with that...".format( + len(ctx.channel.members) - 1 + ) ) return # Then check if nothing was provided @@ -188,7 +192,9 @@ class Interaction(commands.Cog): # Then check if the person used is the author if ctx.author.id == player2.id: ctx.command.reset_cooldown(ctx) - await ctx.send("Why would you want to battle yourself? Suicide is not the answer") + await ctx.send( + "Why would you want to battle yourself? Suicide is not the answer" + ) return # Check if the person battled is me if ctx.bot.user.id == player2.id: @@ -202,14 +208,18 @@ class Interaction(commands.Cog): return if not self.can_receive_battle(player2): ctx.command.reset_cooldown(ctx) - await ctx.send("{} is already being challenged to a battle!".format(player2)) + await ctx.send( + "{} is already being challenged to a battle!".format(player2) + ) return # Add the author and player provided in a new battle battle = self.start_battle(ctx.author, player2) - fmt = f"{ctx.author.mention} has challenged you to a battle {player2.mention}\n" \ - f"{ctx.prefix}accept or {ctx.prefix}decline" + fmt = ( + f"{ctx.author.mention} has challenged you to a battle {player2.mention}\n" + f"{ctx.prefix}accept or {ctx.prefix}decline" + ) # Add a call to turn off battling, if the battle is not accepted/declined in 3 minutes part = functools.partial(self.battling_off, battle) ctx.bot.loop.call_later(180, part) @@ -231,14 +241,16 @@ class Interaction(commands.Cog): return if ctx.guild.get_member(battle.initiator.id) is None: - await ctx.send("The person who challenged you to a battle has apparently left the server....why?") + await ctx.send( + "The person who challenged you to a battle has apparently left the server....why?" + ) self.battling_off(battle) return # Lets get the settings settings = await ctx.bot.db.fetchrow( "SELECT custom_battles, include_default_battles FROM guilds WHERE id = $1", - ctx.guild.id + ctx.guild.id, ) msgs = battle_outcomes if settings: @@ -280,7 +292,7 @@ WHERE id = any($2) old_winner = old_loser = None for result in results: - if result['id'] == loser.id: + if result["id"] == loser.id: old_loser = result else: old_winner = result @@ -310,9 +322,9 @@ VALUES await ctx.bot.db.execute( update_query, loser_rating, - old_loser['battle_wins'], - old_loser['battle_losses'] + 1, - loser.id + old_loser["battle_wins"], + old_loser["battle_losses"] + 1, + loser.id, ) else: await ctx.bot.db.execute(insert_query, loser.id, loser_rating, 0, 1) @@ -320,9 +332,9 @@ VALUES await ctx.bot.db.execute( update_query, winner_rating, - old_winner['battle_wins'] + 1, - old_winner['battle_losses'], - winner.id + old_winner["battle_wins"] + 1, + old_winner["battle_losses"], + winner.id, ) else: await ctx.bot.db.execute(insert_query, winner.id, winner_rating, 1, 0) @@ -331,15 +343,17 @@ VALUES new_winner_rank = new_loser_rank = None for result in results: - if result['id'] == loser.id: - new_loser_rank = result['rank'] + if result["id"] == loser.id: + new_loser_rank = result["rank"] else: - new_winner_rank = result['rank'] + new_winner_rank = result["rank"] fmt = fmt.format(winner=winner.display_name, loser=loser.display_name) if old_winner: fmt += "\n{} - Rank: {} ( +{} )".format( - winner.display_name, new_winner_rank, old_winner["rank"] - new_winner_rank + winner.display_name, + new_winner_rank, + old_winner["rank"] - new_winner_rank, ) else: fmt += "\n{} - Rank: {}".format(winner.display_name, new_winner_rank) @@ -400,27 +414,37 @@ VALUES amount = await ctx.bot.db.fetchrow(query, booper.id, boopee.id) if amount is None: amount = 1 - replacement_query = "INSERT INTO boops (booper, boopee, amount) VALUES($1, $2, $3)" + replacement_query = ( + "INSERT INTO boops (booper, boopee, amount) VALUES($1, $2, $3)" + ) else: - replacement_query = "UPDATE boops SET amount=$3 WHERE booper=$1 AND boopee=$2" - amount = amount['amount'] + 1 + replacement_query = ( + "UPDATE boops SET amount=$3 WHERE booper=$1 AND boopee=$2" + ) + amount = amount["amount"] + 1 - await ctx.send(f"{booper.mention} has just booped {boopee.mention}{message}! That's {amount} times now!") + await ctx.send( + f"{booper.mention} has just booped {boopee.mention}{message}! That's {amount} times now!" + ) await ctx.bot.db.execute(replacement_query, booper.id, boopee.id, amount) class Battle: - def __init__(self, initiator, receiver): self.initiator = initiator self.receiver = receiver self.rand = random.SystemRandom() def is_initiator(self, player): - return player.id == self.initiator.id and player.guild.id == self.initiator.guild.id + return ( + player.id == self.initiator.id + and player.guild.id == self.initiator.guild.id + ) def is_receiver(self, player): - return player.id == self.receiver.id and player.guild.id == self.receiver.guild.id + return ( + player.id == self.receiver.id and player.guild.id == self.receiver.guild.id + ) def is_battling(self, player): return self.is_initiator(player) or self.is_receiver(player) diff --git a/cogs/links.py b/cogs/links.py index 79dca77..a2e3e9b 100644 --- a/cogs/links.py +++ b/cogs/links.py @@ -12,7 +12,7 @@ class Links(commands.Cog): """This class contains all the commands that make HTTP requests In other words, all commands here rely on other URL's to complete their requests""" - @commands.command(aliases=['g']) + @commands.command(aliases=["g"]) @utils.can_run(send_messages=True) async def google(self, ctx, *, query: str): """Searches google for a provided query @@ -23,48 +23,49 @@ class Links(commands.Cog): # Turn safe filter on or off, based on whether or not this is a nsfw channel nsfw = utils.channel_is_nsfw(ctx.message.channel) - safe = 'off' if nsfw else 'on' + safe = "off" if nsfw else "on" - params = {'q': query, - 'safe': safe, - 'hl': 'en', - 'cr': 'countryUS'} + params = {"q": query, "safe": safe, "hl": "en", "cr": "countryUS"} # Our format we'll end up using to send to the channel fmt = "" # First make the request to google to get the results - data = await utils.request(url, payload=params, attr='text') + data = await utils.request(url, payload=params, attr="text") if data is None: await ctx.send("I failed to connect to google! (That can happen??)") return # Convert to a BeautifulSoup element and loop through each result clasified by h3 tags with a class of 'r' - soup = bs(data, 'html.parser') + soup = bs(data, "html.parser") - for element in soup.find_all('h3', class_='r')[:3]: + for element in soup.find_all("h3", class_="r")[:3]: # Get the link's href tag, which looks like q=[url here]&sa # Use a lookahead and lookbehind to find this url exactly try: - result_url = re.search('(?<=q=).*(?=&sa=)', element.find('a').get('href')).group(0) + result_url = re.search( + "(?<=q=).*(?=&sa=)", element.find("a").get("href") + ).group(0) except AttributeError: await ctx.send("I couldn't find any results for {}!".format(query)) return # Get the next sibling, find the span where the description is, and get the text from this try: - description = element.next_sibling.find('span', class_='st').text + description = element.next_sibling.find("span", class_="st").text except Exception: description = "" # Add this to our text we'll use to send - fmt += '\n\n**URL**: <{}>\n**Description**: {}'.format(result_url, description) + fmt += "\n\n**URL**: <{}>\n**Description**: {}".format( + result_url, description + ) fmt = "**Top 3 results for the query** _{}_:{}".format(query, fmt) await ctx.send(fmt) - @commands.command(aliases=['yt']) + @commands.command(aliases=["yt"]) @utils.can_run(send_messages=True) async def youtube(self, ctx, *, query: str): """Searches youtube for a provided query @@ -73,10 +74,7 @@ class Links(commands.Cog): RESULT: Cat videos!""" key = utils.youtube_key url = "https://www.googleapis.com/youtube/v3/search" - params = {'key': key, - 'part': 'snippet, id', - 'type': 'video', - 'q': query} + params = {"key": key, "part": "snippet, id", "type": "video", "q": query} data = await utils.request(url, payload=params) @@ -85,16 +83,20 @@ class Links(commands.Cog): return try: - result = data['items'][0] + result = data["items"][0] except IndexError: - await ctx.send("I could not find any results with the search term {}".format(query)) + await ctx.send( + "I could not find any results with the search term {}".format(query) + ) return - result_url = "https://youtube.com/watch?v={}".format(result['id']['videoId']) - title = result['snippet']['title'] - description = result['snippet']['description'] + result_url = "https://youtube.com/watch?v={}".format(result["id"]["videoId"]) + title = result["snippet"]["title"] + description = result["snippet"]["description"] - fmt = "**Title:** {}\n\n**Description:** {}\n\n**URL:** <{}>".format(title, description, result_url) + fmt = "**Title:** {}\n\n**Description:** {}\n\n**URL:** <{}>".format( + title, description, result_url + ) await ctx.send(fmt) @commands.command() @@ -106,10 +108,12 @@ class Links(commands.Cog): RESULT: A link to the wikipedia article for the word test""" # All we need to do is search for the term provided, so the action, list, and format never need to change base_url = "https://en.wikipedia.org/w/api.php" - params = {"action": "query", - "list": "search", - "format": "json", - "srsearch": query} + params = { + "action": "query", + "list": "search", + "format": "json", + "srsearch": query, + } data = await utils.request(base_url, payload=params) @@ -117,22 +121,28 @@ class Links(commands.Cog): await ctx.send("Sorry but I failed to connect to Wikipedia!") return - if len(data['query']['search']) == 0: - await ctx.send("I could not find any results with that term, I tried my best :c") + if len(data["query"]["search"]) == 0: + await ctx.send( + "I could not find any results with that term, I tried my best :c" + ) return # Wiki articles' URLs are in the format https://en.wikipedia.org/wiki/[Titlehere] # Replace spaces with %20 - url = "https://en.wikipedia.org/wiki/{}".format(data['query']['search'][0]['title'].replace(' ', '%20')) - snippet = data['query']['search'][0]['snippet'] + url = "https://en.wikipedia.org/wiki/{}".format( + data["query"]["search"][0]["title"].replace(" ", "%20") + ) + snippet = data["query"]["search"][0]["snippet"] # The next part replaces some of the HTML formatting that's provided # These are the only ones I've encountered so far through testing, there may be more though - snippet = re.sub('', '', snippet) - snippet = re.sub('', '', snippet) - snippet = re.sub('"', '"', snippet) + snippet = re.sub('', "", snippet) + snippet = re.sub("", "", snippet) + snippet = re.sub(""", '"', snippet) await ctx.send( - "Here is the best match I found with the query `{}`:\nURL: <{}>\nSnippet: \n```\n{}```".format(query, url, - snippet)) + "Here is the best match I found with the query `{}`:\nURL: <{}>\nSnippet: \n```\n{}```".format( + query, url, snippet + ) + ) @commands.command() @utils.can_run(send_messages=True) @@ -151,11 +161,11 @@ class Links(commands.Cog): return # List is the list of definitions found, if it's empty then nothing was found - if len(data['list']) == 0: + if len(data["list"]) == 0: await ctx.send("No result with that term!") # If the list is not empty, use the first result and print it's defintion else: - entries = [x['definition'] for x in data['list']] + entries = [x["definition"] for x in data["list"]] try: pages = utils.Pages(ctx, entries=entries[:5], per_page=1) await pages.paginate() @@ -163,7 +173,7 @@ class Links(commands.Cog): await ctx.send(str(e)) # Urban dictionary has some long definitions, some might not be able to be sent except discord.HTTPException: - await ctx.send('```\nError: Definition is too long for me to send```') + await ctx.send("```\nError: Definition is too long for me to send```") except KeyError: await ctx.send("Sorry but I failed to connect to urban dictionary!") else: diff --git a/cogs/misc.py b/cogs/misc.py index bac8f52..86a0419 100644 --- a/cogs/misc.py +++ b/cogs/misc.py @@ -15,31 +15,36 @@ def _command_signature(cmd): result = [cmd.qualified_name] if cmd.usage: result.append(cmd.usage) - return ' '.join(result) + return " ".join(result) params = cmd.clean_params if not params: - return ' '.join(result) + return " ".join(result) for name, param in params.items(): if param.default is not param.empty: # We don't want None or '' to trigger the [name=value] case and instead it should # do [name] since [name=None] or [name=] are not exactly useful for the user. - should_print = param.default if isinstance(param.default, str) else param.default is not None + should_print = ( + param.default + if isinstance(param.default, str) + else param.default is not None + ) if should_print: - result.append(f'[{name}={param.default!r}]') + result.append(f"[{name}={param.default!r}]") else: - result.append(f'[{name}]') + result.append(f"[{name}]") elif param.kind == param.VAR_POSITIONAL: - result.append(f'[{name}...]') + result.append(f"[{name}...]") else: - result.append(f'<{name}>') + result.append(f"<{name}>") - return ' '.join(result) + return " ".join(result) class Miscellaneous(commands.Cog): """Core commands, these are the miscallaneous commands that don't fit into other categories'""" + process = psutil.Process() process.cpu_percent() @@ -56,7 +61,7 @@ class Miscellaneous(commands.Cog): entity = ctx.bot.get_cog(command) or ctx.bot.get_command(command) if entity is None: - clean = command.replace('@', '@\u200b') + clean = command.replace("@", "@\u200b") return await ctx.send(f'Command or category "{clean}" not found.') elif isinstance(entity, commands.Command): p = await utils.HelpPaginator.from_command(ctx, entity) @@ -73,14 +78,18 @@ class Miscellaneous(commands.Cog): if entity: entity = ctx.bot.get_cog(entity) or ctx.bot.get_command(entity) if entity is None: - fmt = "Hello! Here is a list of the sections of commands that I have " \ - "(there are a lot of commands so just start with the sections...I know, I'm pretty great)\n" + fmt = ( + "Hello! Here is a list of the sections of commands that I have " + "(there are a lot of commands so just start with the sections...I know, I'm pretty great)\n" + ) fmt += "To use a command's paramaters, you need to know the notation for them:\n" fmt += "\t This means the argument is __**required**__.\n" fmt += "\t[argument] This means the argument is __**optional**__.\n" fmt += "\t[A|B] This means the it can be __**either A or B**__.\n" fmt += "\t[argument...] This means you can have multiple arguments.\n" - fmt += "\n**Type `{}help section` to get help on a specific section**\n".format(ctx.prefix) + fmt += "\n**Type `{}help section` to get help on a specific section**\n".format( + ctx.prefix + ) fmt += "**CASE MATTERS** Sections are in `Title Case` and commands are in `lower case`\n\n" chunks.append(fmt) @@ -100,14 +109,20 @@ class Miscellaneous(commands.Cog): chunks.append(tmp) else: cmds = sorted(entity.get_commands(), key=lambda c: c.name) - fmt = "Here are a list of commands under the section {}\n".format(entity.__class__.__name__) - fmt += "Type `{}help command` to get more help on a specific command\n\n".format(ctx.prefix) + fmt = "Here are a list of commands under the section {}\n".format( + entity.__class__.__name__ + ) + fmt += "Type `{}help command` to get more help on a specific command\n\n".format( + ctx.prefix + ) chunks.append(fmt) for command in cmds: for subcommand in command.walk_commands(): - tmp = "**{}**\n\t{}\n".format(subcommand.qualified_name, subcommand.short_doc) + tmp = "**{}**\n\t{}\n".format( + subcommand.qualified_name, subcommand.short_doc + ) if len(chunks[len(chunks) - 1] + tmp) > 2000: chunks.append(tmp) else: @@ -115,7 +130,8 @@ class Miscellaneous(commands.Cog): if utils.dev_server: tmp = "\n\nIf I'm having issues, then please visit the dev server and ask for help. {}".format( - utils.dev_server) + utils.dev_server + ) if len(chunks[len(chunks) - 1] + tmp) > 2000: chunks.append(tmp) else: @@ -130,7 +146,9 @@ class Miscellaneous(commands.Cog): for chunk in chunks: await destination.send(chunk) except (discord.Forbidden, discord.HTTPException): - await ctx.send("I cannot DM you, please allow DM's from this server to run this command") + await ctx.send( + "I cannot DM you, please allow DM's from this server to run this command" + ) else: if ctx.guild and destination == ctx.author: await ctx.send("I have just DM'd you some information about me!") @@ -140,7 +158,9 @@ class Miscellaneous(commands.Cog): async def ping(self, ctx): """Returns the latency between the server websocket, and between reading messages""" msg_latency = datetime.datetime.utcnow() - ctx.message.created_at - fmt = "Message latency {0:.2f} seconds".format(msg_latency.seconds + msg_latency.microseconds / 1000000) + fmt = "Message latency {0:.2f} seconds".format( + msg_latency.seconds + msg_latency.microseconds / 1000000 + ) fmt += "\nWebsocket latency {0:.2f} seconds".format(ctx.bot.latency) await ctx.send(fmt) @@ -191,7 +211,7 @@ class Miscellaneous(commands.Cog): "september": 9, "october": 10, "november": 11, - "december": 12 + "december": 12, } # In month was not passed, use the current month if month is None: @@ -208,7 +228,7 @@ class Miscellaneous(commands.Cog): cal = calendar.TextCalendar().formatmonth(year, month) await ctx.send("```\n{}```".format(cal)) - @commands.command(aliases=['about']) + @commands.command(aliases=["about"]) @utils.can_run(send_messages=True) async def info(self, ctx): """This command can be used to print out some of my information""" @@ -222,13 +242,15 @@ class Miscellaneous(commands.Cog): if utils.patreon_link: description += "\n[Patreon]({})".format(utils.patreon_link) # Now creat the object - opts = {'title': 'Bonfire', - 'description': description, - 'colour': discord.Colour.green()} + opts = { + "title": "Bonfire", + "description": description, + "colour": discord.Colour.green(), + } # Set the owner embed = discord.Embed(**opts) - if hasattr(ctx.bot, 'owner'): + if hasattr(ctx.bot, "owner"): embed.set_author(name=str(ctx.bot.owner), icon_url=ctx.bot.owner.avatar_url) # Setup the process statistics @@ -237,10 +259,12 @@ class Miscellaneous(commands.Cog): memory_usage = self.process.memory_full_info().uss / 1024 ** 2 cpu_usage = self.process.cpu_percent() - value += 'Memory: {:.2f} MiB'.format(memory_usage) - value += '\nCPU: {}%'.format(cpu_usage) - if hasattr(ctx.bot, 'uptime'): - value += "\nUptime: {}".format((pendulum.now(tz="UTC") - ctx.bot.uptime).in_words()) + value += "Memory: {:.2f} MiB".format(memory_usage) + value += "\nCPU: {}%".format(cpu_usage) + if hasattr(ctx.bot, "uptime"): + value += "\nUptime: {}".format( + (pendulum.now(tz="UTC") - ctx.bot.uptime).in_words() + ) embed.add_field(name=name, value=value, inline=False) # Setup the user and guild statistics @@ -258,10 +282,10 @@ class Miscellaneous(commands.Cog): # Lets make this one a list and join it at the end value = [] - hm = ctx.bot.get_cog('Hangman') - ttt = ctx.bot.get_cog('TicTacToe') - bj = ctx.bot.get_cog('Blackjack') - interaction = ctx.bot.get_cog('Interaction') + hm = ctx.bot.get_cog("Hangman") + ttt = ctx.bot.get_cog("TicTacToe") + bj = ctx.bot.get_cog("Blackjack") + interaction = ctx.bot.get_cog("Interaction") if hm: value.append("Hangman games: {}".format(len(hm.games))) @@ -271,7 +295,7 @@ class Miscellaneous(commands.Cog): value.append("Blackjack games: {}".format(len(bj.games))) if interaction: count_battles = 0 - for battles in ctx.bot.get_cog('Interaction').battles.values(): + for battles in ctx.bot.get_cog("Interaction").battles.values(): count_battles += len(battles) value.append("Battles running: {}".format(len(bj.games))) embed.add_field(name=name, value="\n".join(value), inline=False) @@ -285,12 +309,18 @@ class Miscellaneous(commands.Cog): EXAMPLE: !uptime RESULT: A BAJILLION DAYS""" - if hasattr(ctx.bot, 'uptime'): - await ctx.send("Uptime: ```\n{}```".format((pendulum.now(tz="UTC") - ctx.bot.uptime).in_words())) + if hasattr(ctx.bot, "uptime"): + await ctx.send( + "Uptime: ```\n{}```".format( + (pendulum.now(tz="UTC") - ctx.bot.uptime).in_words() + ) + ) else: - await ctx.send("I've just restarted and not quite ready yet...gimme time I'm not a morning pony :c") + await ctx.send( + "I've just restarted and not quite ready yet...gimme time I'm not a morning pony :c" + ) - @commands.command(aliases=['invite']) + @commands.command(aliases=["invite"]) @utils.can_run(send_messages=True) async def addbot(self, ctx): """Provides a link that you can use to add me to a server @@ -312,8 +342,11 @@ class Miscellaneous(commands.Cog): perms.attach_files = True perms.add_reactions = True app_info = await ctx.bot.application_info() - await ctx.send("Use this URL to add me to a server that you'd like!\n<{}>" - .format(discord.utils.oauth_url(app_info.id, perms))) + await ctx.send( + "Use this URL to add me to a server that you'd like!\n<{}>".format( + discord.utils.oauth_url(app_info.id, perms) + ) + ) @commands.command(enabled=False) @utils.can_run(send_messages=True) @@ -337,11 +370,11 @@ class Miscellaneous(commands.Cog): try: # We do not want to try to convert the dice, because we want d# to # be a valid notation - dice = re.search("(\d*)d(\d*)", notation).group(1) - num = int(re.search("(\d*)d(\d*)", notation).group(2)) + dice = re.search(r"(\d*)d(\d*)", notation).group(1) + num = int(re.search(r"(\d*)d(\d*)", notation).group(2)) # Attempt to get addition/subtraction - add = re.search("\+ ?(\d+)", notation) - subtract = re.search("- ?(\d+)", notation) + add = re.search(r"\+ ?(\d+)", notation) + subtract = re.search(r"- ?(\d+)", notation) # Check if something like ed3 was provided, or something else entirely # was provided except (AttributeError, ValueError): @@ -360,7 +393,9 @@ class Miscellaneous(commands.Cog): await ctx.send("What die has more than 100 sides? Please, calm down") return if num <= 1: - await ctx.send("A {} sided die? You know that's impossible right?".format(num)) + await ctx.send( + "A {} sided die? You know that's impossible right?".format(num) + ) return nums = [random.SystemRandom().randint(1, num) for _ in range(0, int(dice))] @@ -375,7 +410,7 @@ class Miscellaneous(commands.Cog): value_str = ", ".join("{}".format(x) for x in nums) if dice == 1: - fmt = '{0.message.author.name} has rolled a {1} sided die and got the number {2}!'.format( + fmt = "{0.message.author.name} has rolled a {1} sided die and got the number {2}!".format( ctx, num, value_str ) if add or subtract: @@ -386,7 +421,7 @@ class Miscellaneous(commands.Cog): fmt += " - {}".format(subtract) fmt += ")" else: - fmt = '{0.message.author.name} has rolled {1}, {2} sided dice and got the numbers {3}!'.format( + fmt = "{0.message.author.name} has rolled {1}, {2} sided dice and got the numbers {3}!".format( ctx, dice, num, value_str ) if add or subtract: diff --git a/cogs/mod.py b/cogs/mod.py index 61c298b..9ea0be5 100644 --- a/cogs/mod.py +++ b/cogs/mod.py @@ -48,7 +48,9 @@ class Moderation(commands.Cog): @commands.command() @commands.guild_only() @utils.can_run(ban_members=True) - async def ban(self, ctx, member: typing.Union[discord.Member, discord.Object], *, reason=None): + async def ban( + self, ctx, member: typing.Union[discord.Member, discord.Object], *, reason=None + ): """Used to ban a member This can be used to ban someone preemptively as well. Provide the ID of the user and this should ban them without them being in the server @@ -72,7 +74,9 @@ class Moderation(commands.Cog): EXAMPLE: !purge 50 RESULT: -50 messages in this channel""" - if not ctx.message.channel.permissions_for(ctx.message.guild.me).manage_messages: + if not ctx.message.channel.permissions_for( + ctx.message.guild.me + ).manage_messages: await ctx.send("I do not have permission to delete messages...") return try: @@ -80,10 +84,12 @@ class Moderation(commands.Cog): await ctx.message.delete() except discord.HTTPException: try: - await ctx.message.channel.send("Detected messages that are too far " - "back for me to delete; I can only bulk delete messages" - " that are under 14 days old.") - except: + await ctx.message.channel.send( + "Detected messages that are too far " + "back for me to delete; I can only bulk delete messages" + " that are under 14 days old." + ) + except discord.Forbidden: pass @commands.command() @@ -125,7 +131,9 @@ class Moderation(commands.Cog): # If we're not setting the user to the bot, then we're deleting someone elses messages # To do so, we need manage_messages permission, so check if we have that - if not ctx.message.channel.permissions_for(ctx.message.guild.me).manage_messages: + if not ctx.message.channel.permissions_for( + ctx.message.guild.me + ).manage_messages: await ctx.send("I do not have permission to delete messages...") return @@ -137,7 +145,7 @@ class Moderation(commands.Cog): try: await msg.delete() count += 1 - except: + except discord.HTTPException: pass if count >= limit: break @@ -147,7 +155,7 @@ class Moderation(commands.Cog): try: await msg.delete() await ctx.message.delete() - except: + except discord.HTTPException: pass diff --git a/cogs/overwatch.py b/cogs/overwatch.py index 3b3764e..354c8fe 100644 --- a/cogs/overwatch.py +++ b/cogs/overwatch.py @@ -9,9 +9,19 @@ BASE_URL = "https://api.owapi.net/api/v3/u/" # The API returns something if it exists, and leaves it out of the data returned entirely if it does not # For example if you have not won with a character, wins will not exist in the list # This sets an easy way to use list comprehension later, to print all possible things we want, if it exists -check_g_stats = ["eliminations", "deaths", 'kpd', 'wins', 'losses', 'time_played', - 'cards', 'damage_done', 'healing_done', 'multikills'] -check_o_stats = ['wins'] +check_g_stats = [ + "eliminations", + "deaths", + "kpd", + "wins", + "losses", + "time_played", + "cards", + "damage_done", + "healing_done", + "multikills", +] +check_o_stats = ["wins"] log = logging.getLogger() @@ -36,7 +46,7 @@ class Overwatch(commands.Cog): EXAMPLE: !ow stats @OtherPerson Junkrat RESULT: Whether or not you should unfriend this person because they're a dirty rat""" user = user or ctx.message.author - bt = ctx.bot.db.load('overwatch', key=str(user.id), pluck='battletag') + bt = ctx.bot.db.load("overwatch", key=str(user.id), pluck="battletag") if bt is None: await ctx.send("I do not have this user's battletag saved!") @@ -52,40 +62,60 @@ class Overwatch(commands.Cog): log.info(data) - region = [x for x in data.keys() if data[x] is not None and x in ['us', 'any', 'kr', 'eu']][0] - stats = data[region]['stats']['quickplay'] + region = [ + x + for x in data.keys() + if data[x] is not None and x in ["us", "any", "kr", "eu"] + ][0] + stats = data[region]["stats"]["quickplay"] - output_data = [(k.title().replace("_", " "), r) for k, r in stats['game_stats'].items() if - k in check_g_stats] + output_data = [ + (k.title().replace("_", " "), r) + for k, r in stats["game_stats"].items() + if k in check_g_stats + ] else: # If there was a hero provided, search for a user's data on that hero - hero = hero.lower().replace('-', '') + hero = hero.lower().replace("-", "") url = BASE_URL + "{}/heroes".format(bt) data = await utils.request(url) if data is None: await ctx.send("I couldn't connect to overwatch at the moment!") return - region = [x for x in data.keys() if data[x] is not None and x in ['us', 'any', 'kr', 'eu']][0] - stats = data[region]['heroes']['stats']['quickplay'].get(hero) + region = [ + x + for x in data.keys() + if data[x] is not None and x in ["us", "any", "kr", "eu"] + ][0] + stats = data[region]["heroes"]["stats"]["quickplay"].get(hero) if stats is None: - fmt = "I couldn't find data with that hero, make sure that is a valid hero, " \ - "otherwise {} has never used the hero {} before!".format(user.display_name, hero) + fmt = ( + "I couldn't find data with that hero, make sure that is a valid hero, " + "otherwise {} has never used the hero {} before!".format( + user.display_name, hero + ) + ) await ctx.send(fmt) return # Same list comprehension as before - output_data = [(k.title().replace("_", " "), r) for k, r in stats['general_stats'].items() if - k in check_g_stats] - for k, r in stats['hero_stats'].items(): + output_data = [ + (k.title().replace("_", " "), r) + for k, r in stats["general_stats"].items() + if k in check_g_stats + ] + for k, r in stats["hero_stats"].items(): output_data.append((k.title().replace("_", " "), r)) try: banner = await utils.create_banner(user, "Overwatch", output_data) - await ctx.send(file=discord.File(banner, filename='banner.png')) + await ctx.send(file=discord.File(banner, filename="banner.png")) except (FileNotFoundError, discord.Forbidden): fmt = "\n".join("{}: {}".format(k, r) for k, r in output_data) - await ctx.send("Overwatch stats for {}: ```py\n{}```".format(user.name, fmt)) + await ctx.send( + "Overwatch stats for {}: ```py\n{}```".format(user.name, fmt) + ) @ow.command(name="add") @utils.can_run(send_messages=True) @@ -95,7 +125,6 @@ class Overwatch(commands.Cog): EXAMPLE: !ow add Username#1234 RESULT: Your battletag is now saved""" - # Battletags are normally provided like name#id # However the API needs this to be a -, so repliace # with - if it exists bt = bt.replace("#", "-") @@ -106,32 +135,34 @@ class Overwatch(commands.Cog): url = BASE_URL + "{}/stats".format(bt) data = await utils.request(url) if data is None: - await ctx.send("Profile does not exist! Battletags are picky, " - "format needs to be `user#xxxx`. Capitalization matters") + await ctx.send( + "Profile does not exist! Battletags are picky, " + "format needs to be `user#xxxx`. Capitalization matters" + ) return # Now just save the battletag - entry = { - 'member_id': key, - 'battletag': bt - } + entry = {"member_id": key, "battletag": bt} - await ctx.bot.db.save('overwatch', entry) - await ctx.send("I have just saved your battletag {}".format(ctx.message.author.mention)) + await ctx.bot.db.save("overwatch", entry) + await ctx.send( + "I have just saved your battletag {}".format(ctx.message.author.mention) + ) - @ow.command(name="delete", aliases=['remove']) + @ow.command(name="delete", aliases=["remove"]) @utils.can_run(send_messages=True) async def delete(self, ctx): """Removes your battletag from the records EXAMPLE: !ow delete RESULT: Your battletag is no longer saved""" - entry = { - 'member_id': str(ctx.message.author.id), - 'battletag': None - } - await ctx.bot.db.save('overwatch', entry) - await ctx.send("I no longer have your battletag saved {}".format(ctx.message.author.mention)) + entry = {"member_id": str(ctx.message.author.id), "battletag": None} + await ctx.bot.db.save("overwatch", entry) + await ctx.send( + "I no longer have your battletag saved {}".format( + ctx.message.author.mention + ) + ) def setup(bot): diff --git a/cogs/owner.py b/cogs/owner.py index 71475ec..282b42a 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -5,19 +5,21 @@ import discord import inspect import textwrap import traceback -import subprocess import io from contextlib import redirect_stdout def get_syntax_error(e): if e.text is None: - return '```py\n{0.__class__.__name__}: {0}\n```'.format(e) - return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(e, '^', type(e).__name__) + return "```py\n{0.__class__.__name__}: {0}\n```".format(e) + return "```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```".format( + e, "^", type(e).__name__ + ) class Owner(commands.Cog): """Commands that can only be used by the owner of the bot, bot management commands""" + _last_result = None sessions = set() @@ -28,62 +30,68 @@ class Owner(commands.Cog): def cleanup_code(content): """Automatically removes code blocks from the code.""" # remove ```py\n``` - if content.startswith('```') and content.endswith('```'): - return '\n'.join(content.split('\n')[1:-1]) + if content.startswith("```") and content.endswith("```"): + return "\n".join(content.split("\n")[1:-1]) # remove `foo` - return content.strip('` \n') + return content.strip("` \n") @commands.command(hidden=True) async def repl(self, ctx): msg = ctx.message variables = { - 'ctx': ctx, - 'bot': ctx.bot, - 'message': msg, - 'guild': msg.guild, - 'server': msg.guild, - 'channel': msg.channel, - 'author': msg.author, - 'self': self, - '_': None, + "ctx": ctx, + "bot": ctx.bot, + "message": msg, + "guild": msg.guild, + "server": msg.guild, + "channel": msg.channel, + "author": msg.author, + "self": self, + "_": None, } if msg.channel.id in self.sessions: - await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.') + await ctx.send( + "Already running a REPL session in this channel. Exit it with `quit`." + ) return self.sessions.add(msg.channel.id) - await ctx.send('Enter code to execute or evaluate. `exit()` or `quit` to exit.') + await ctx.send("Enter code to execute or evaluate. `exit()` or `quit` to exit.") def check(m): - return m.author.id == msg.author.id and \ - m.channel.id == msg.channel.id and \ - m.content.startswith('`') + return ( + m.author.id == msg.author.id + and m.channel.id == msg.channel.id + and m.content.startswith("`") + ) code = None while True: try: - response = await ctx.bot.wait_for('message', check=check, timeout=10.0 * 60.0) + response = await ctx.bot.wait_for( + "message", check=check, timeout=10.0 * 60.0 + ) except asyncio.TimeoutError: - await ctx.send('Exiting REPL session.') + await ctx.send("Exiting REPL session.") self.sessions.remove(msg.channel.id) break cleaned = self.cleanup_code(response.content) - if cleaned in ('quit', 'exit', 'exit()'): - await ctx.send('Exiting.') + if cleaned in ("quit", "exit", "exit()"): + await ctx.send("Exiting.") self.sessions.remove(msg.channel.id) return executor = exec - if cleaned.count('\n') == 0: + if cleaned.count("\n") == 0: # single statement, potentially 'eval' try: - code = compile(cleaned, '', 'eval') + code = compile(cleaned, "", "eval") except SyntaxError: pass else: @@ -91,12 +99,12 @@ class Owner(commands.Cog): if executor is exec: try: - code = compile(cleaned, '', 'exec') + code = compile(cleaned, "", "exec") except SyntaxError as e: await ctx.send(get_syntax_error(e)) continue - variables['message'] = response + variables["message"] = response fmt = None stdout = io.StringIO() @@ -108,25 +116,25 @@ class Owner(commands.Cog): result = await result except Exception: value = stdout.getvalue() - fmt = '```py\n{}{}\n```'.format(value, traceback.format_exc()) + fmt = "```py\n{}{}\n```".format(value, traceback.format_exc()) else: value = stdout.getvalue() if result is not None: - fmt = '```py\n{}{}\n```'.format(value, result) - variables['_'] = result + fmt = "```py\n{}{}\n```".format(value, result) + variables["_"] = result elif value: - fmt = '```py\n{}\n```'.format(value) + fmt = "```py\n{}\n```".format(value) try: if fmt is not None: if len(fmt) > 2000: - await ctx.send('Content too big to be printed.') + await ctx.send("Content too big to be printed.") else: await ctx.send(fmt) except discord.Forbidden: pass except discord.HTTPException as e: - await ctx.send('Unexpected error: `{}`'.format(e)) + await ctx.send("Unexpected error: `{}`".format(e)) @commands.command() async def sendtochannel(self, ctx, cid: int, *, message): @@ -141,15 +149,15 @@ class Owner(commands.Cog): @commands.command() async def debug(self, ctx, *, body: str): env = { - 'bot': ctx.bot, - 'ctx': ctx, - 'channel': ctx.message.channel, - 'author': ctx.message.author, - 'server': ctx.message.guild, - 'guild': ctx.message.guild, - 'message': ctx.message, - 'self': self, - '_': self._last_result + "bot": ctx.bot, + "ctx": ctx, + "channel": ctx.message.channel, + "author": ctx.message.author, + "server": ctx.message.guild, + "guild": ctx.message.guild, + "message": ctx.message, + "self": self, + "_": self._last_result, } env.update(globals()) @@ -157,14 +165,14 @@ class Owner(commands.Cog): body = self.cleanup_code(body) stdout = io.StringIO() - to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ') + to_compile = "async def func():\n%s" % textwrap.indent(body, " ") try: exec(to_compile, env) except SyntaxError as e: return await ctx.send(get_syntax_error(e)) - func = env['func'] + func = env["func"] try: with redirect_stdout(stdout): ret = await func() @@ -174,7 +182,7 @@ class Owner(commands.Cog): else: value = stdout.getvalue() try: - await ctx.message.add_reaction('\u2705') + await ctx.message.add_reaction("\u2705") except Exception: pass @@ -189,20 +197,18 @@ class Owner(commands.Cog): async def bash(self, ctx, *, cmd: str): """Runs a bash command""" proc = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.STDOUT + cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT ) stdout = (await proc.communicate())[0] if stdout: - await ctx.send(f'[stdout]\n{stdout.decode()}') + await ctx.send(f"[stdout]\n{stdout.decode()}") else: await ctx.send("Process finished, no output") @commands.command() async def shutdown(self, ctx): """Shuts the bot down""" - fmt = 'Shutting down, I will miss you {0.author.name}' + fmt = "Shutting down, I will miss you {0.author.name}" await ctx.send(fmt.format(ctx.message)) await ctx.bot.logout() await ctx.bot.close() @@ -211,7 +217,7 @@ class Owner(commands.Cog): async def name(self, ctx, new_nick: str): """Changes the bot's name""" await ctx.bot.user.edit(username=new_nick) - await ctx.send('Changed username to ' + new_nick) + await ctx.send("Changed username to " + new_nick) @commands.command() async def status(self, ctx, *, status: str): @@ -233,7 +239,7 @@ class Owner(commands.Cog): ctx.bot.load_extension(module) await ctx.send("I have just loaded the {} module".format(module)) except Exception as error: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + fmt = "An error occurred while processing this request: ```py\n{}: {}\n```" await ctx.send(fmt.format(type(error).__name__, error)) @commands.command() @@ -263,7 +269,7 @@ class Owner(commands.Cog): ctx.bot.load_extension(module) await ctx.send("I have just reloaded the {} module".format(module)) except Exception as error: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + fmt = "An error occurred while processing this request: ```py\n{}: {}\n```" await ctx.send(fmt.format(type(error).__name__, error)) diff --git a/cogs/picarto.py b/cogs/picarto.py index 64e6899..6150ea6 100644 --- a/cogs/picarto.py +++ b/cogs/picarto.py @@ -3,9 +3,9 @@ import discord import traceback import utils -from discord.ext import commands +from discord.ext import commands, tasks -BASE_URL = 'https://api.picarto.tv/v1' +BASE_URL = "https://api.picarto.tv/v1" def produce_embed(*channels): @@ -19,7 +19,9 @@ def produce_embed(*channels): **Gaming:** {"Yes" if channel.get("gaming") else "No"} **Commissions:** {"Yes" if channel.get("commissions") else "No"}""" - return discord.Embed(title="Channels that have gone online!", description=description.strip()) + return discord.Embed( + title="Channels that have gone online!", description=description.strip() + ) class Picarto(commands.Cog): @@ -27,17 +29,13 @@ class Picarto(commands.Cog): def __init__(self, bot): self.bot = bot - self.task = self.bot.loop.create_task(self.picarto_task()) self.channel_info = {} # noinspection PyAttributeOutsideInit async def get_online_users(self): # This method is in place to just return all online users so we can compare against it - url = BASE_URL + '/online' - payload = { - 'adult': 'true', - 'gaming': 'true' - } + url = BASE_URL + "/online" + payload = {"adult": "true", "gaming": "true"} channel_info = {} channels = await utils.request(url, payload=payload) if channels and isinstance(channels, (list, set, tuple)) and len(channels) > 0: @@ -58,24 +56,7 @@ class Picarto(commands.Cog): # After loop has finished successfully, we want to override the statuses of the channels self.channel_info = channel_info - async def picarto_task(self): - # The first time we setup this task, if we leave an empty dict as channel_info....it will announce anything - # So what we want to do here, is get everyone who is online now before starting the actual check - # Also wait a little before starting - await self.bot.wait_until_ready() - await self.get_online_users() - await asyncio.sleep(30) - # Now that we've done the initial setup, start the actual loop we'll use - try: - while not self.bot.is_closed(): - await self.check_channels() - await asyncio.sleep(30) - except Exception as error: - with open("error_log", 'a') as f: - traceback.print_tb(error.__traceback__, file=f) - print('{0.__class__.__name__}: {0}'.format(error), file=f) - await asyncio.sleep(30) - + @tasks.loop(seconds=30) async def check_channels(self): query = """ SELECT @@ -99,13 +80,24 @@ WHERE # If they've gone online, produce the embed for them and send it if gone_online: embed = produce_embed(*gone_online) - channel = self.bot.get_channel(result["channel"]) + g = self.bot.get_guild(result["id"]) + channel = g.get_channel(result["channel"]) if channel is not None: try: await channel.send(embed=embed) except (discord.Forbidden, discord.HTTPException, AttributeError): pass + @check_channels.before_task + async def before_check_channels(self): + await self.get_online_users() + await asyncio.sleep(30) + + @check_channels.error + async def picarto_error(self, error): + await utils.log_error(error, self.bot) + await self.check_channels.restart() + def setup(bot): bot.add_cog(Picarto(bot)) diff --git a/cogs/polls.py b/cogs/polls.py index 004104b..459c5cc 100644 --- a/cogs/polls.py +++ b/cogs/polls.py @@ -6,7 +6,7 @@ import utils def to_keycap(c): - return '\N{KEYCAP TEN}' if c == 10 else str(c) + '\u20e3' + return "\N{KEYCAP TEN}" if c == 10 else str(c) + "\u20e3" class Poll: @@ -55,8 +55,7 @@ class Polls(commands.Cog): else: fmt = "{} asked: {}\n".format(ctx.message.author.display_name, question) fmt += "\n".join( - "{}: {}".format(to_keycap(i + 1), opt) - for i, opt in enumerate(options) + "{}: {}".format(to_keycap(i + 1), opt) for i, opt in enumerate(options) ) msg = await ctx.send(fmt) diff --git a/cogs/raffle.py b/cogs/raffle.py index 55612f9..530b4da 100644 --- a/cogs/raffle.py +++ b/cogs/raffle.py @@ -11,6 +11,7 @@ import random class Raffle(commands.Cog): """Used to hold custom raffles""" + raffles = defaultdict(list) def create_raffle(self, ctx, title, num): @@ -37,9 +38,9 @@ class Raffle(commands.Cog): embed.add_field( name=f"Raffle {num + 1}", value=f"Title: {raffle.title}\n" - f"Total Entrants: {len(raffle.entrants)}\n" - f"Ends in {raffle.remaining}", - inline=False + f"Total Entrants: {len(raffle.entrants)}\n" + f"Ends in {raffle.remaining}", + inline=False, ) await ctx.send(embed=embed) @@ -63,7 +64,7 @@ class Raffle(commands.Cog): else: await ctx.send("You have already entered this raffle!") - @raffle.command(name='create', aliases=['start', 'begin', 'add']) + @raffle.command(name="create", aliases=["start", "begin", "add"]) @commands.guild_only() @utils.can_run(kick_members=True) async def raffle_create(self, ctx): @@ -76,37 +77,48 @@ class Raffle(commands.Cog): channel = ctx.channel await ctx.send( - "Ready to start a new raffle! Please respond with the title you would like to use for this raffle!") + "Ready to start a new raffle! Please respond with the title you would like to use for this raffle!" + ) - check = lambda m: m.author == author and m.channel == channel try: - msg = await ctx.bot.wait_for('message', check=check, timeout=120) + msg = await ctx.bot.wait_for( + "message", + check=lambda m: m.author == author and m.channel == channel, + timeout=120, + ) except asyncio.TimeoutError: await ctx.send("You took too long! >:c") return title = msg.content - fmt = "Alright, your new raffle will be titled:\n\n{}\n\nHow long would you like this raffle to run for? " \ - "The format should be [number] [length] for example, `2 days` or `1 hour` or `30 minutes` etc. " \ - "The minimum for this is 10 minutes, and the maximum is 3 days" + fmt = ( + "Alright, your new raffle will be titled:\n\n{}\n\nHow long would you like this raffle to run for? " + "The format should be [number] [length] for example, `2 days` or `1 hour` or `30 minutes` etc. " + "The minimum for this is 10 minutes, and the maximum is 3 days" + ) await ctx.send(fmt.format(title)) # Our check to ensure that a proper length of time was passed def check(m): if m.author == author and m.channel == channel: - return re.search("\d+ (minutes?|hours?|days?)", m.content.lower()) is not None + return ( + re.search(r"\d+ (minutes?|hours?|days?)", m.content.lower()) + is not None + ) else: return False try: - msg = await ctx.bot.wait_for('message', timeout=120, check=check) + msg = await ctx.bot.wait_for("message", timeout=120, check=check) except asyncio.TimeoutError: await ctx.send("You took too long! >:c") return # Lets get the length provided, based on the number and type passed - num, term = re.search("(\d+) (minutes?|hours?|days?)", msg.content.lower()).groups() + num, term = re.search( + r"(\d+) (minutes?|hours?|days?)", msg.content.lower() + ).groups() # This should be safe to convert, we already made sure with our check earlier this would match num = int(num) @@ -120,7 +132,8 @@ class Raffle(commands.Cog): if not 60 < num < 259200: await ctx.send( - "Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 days") + "Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 days" + ) return self.create_raffle(ctx, title, num) @@ -132,7 +145,6 @@ def setup(bot): class GuildRaffle: - def __init__(self, ctx, title, expires): self._ctx = ctx self.title = title @@ -178,13 +190,15 @@ AND result = await self.db.fetch(query, self.guild.id) if result: - channel = self.guild.get_channel(result['channel']) + channel = self.guild.get_channel(result["channel"]) if channel is None: return if entrants: winner = random.SystemRandom().choice(self.entrants) - await channel.send(f"The winner of the raffle `{self.title}` is {winner.mention}! Congratulations!") + await channel.send( + f"The winner of the raffle `{self.title}` is {winner.mention}! Congratulations!" + ) else: await channel.send( f"There were no entrants to the raffle `{self.title}`, who are in this server currently!" diff --git a/cogs/roles.py b/cogs/roles.py index 463d98a..7b9907f 100644 --- a/cogs/roles.py +++ b/cogs/roles.py @@ -10,7 +10,7 @@ import asyncio class Roles(commands.Cog): """Class to handle management of roles on the server""" - @commands.command(aliases=['color']) + @commands.command(aliases=["color"]) @commands.guild_only() @utils.can_run(send_messages=True) async def colour(self, ctx, role_colour: discord.Colour): @@ -20,10 +20,14 @@ class Roles(commands.Cog): EXAMPLE: !colour red RESULT: A role that matches red (#e74c3c) will be given to you""" - result = await ctx.bot.db.fetchrow("SELECT colour_roles FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT colour_roles FROM guilds WHERE id = $1", ctx.guild.id + ) if result and not result["colour_roles"]: - await ctx.send("Colour roles not allowed on this server! " - "The command `allowcolours` must be ran to enable them!") + await ctx.send( + "Colour roles not allowed on this server! " + "The command `allowcolours` must be ran to enable them!" + ) return if not ctx.me.guild_permissions.manage_roles: @@ -37,16 +41,15 @@ class Roles(commands.Cog): role = discord.utils.get(ctx.guild.roles, name=name, colour=role_colour) # The colour roles they currently have, we need to remove them if they want a new colour - old_roles = [r for r in ctx.author.roles if re.match(r'Bonfire #[0-9a-zA-Z]+', r.name)] + old_roles = [ + r for r in ctx.author.roles if re.match(r"Bonfire #[0-9a-zA-Z]+", r.name) + ] if old_roles: await ctx.author.remove_roles(*old_roles) # If the role doesn't exist, we need to create it if not role: - opts = { - "name": name, - "colour": role_colour - } + opts = {"name": name, "colour": role_colour} try: role = await ctx.guild.create_role(**opts) except discord.HTTPException: @@ -57,10 +60,10 @@ class Roles(commands.Cog): await ctx.send("I have just given you your requested colour!") - @commands.group(aliases=['roles'], invoke_without_command=True) + @commands.group(aliases=["roles"], invoke_without_command=True) @commands.guild_only() @utils.can_run(send_messages=True) - async def role(self, ctx, *, role: discord.Role=None): + async def role(self, ctx, *, role: discord.Role = None): """This command can be used to modify the roles on the server. Pass no subcommands and this will print the roles currently available on this server If you give a role as the argument then it will give some information about that role @@ -79,20 +82,29 @@ class Roles(commands.Cog): embed = discord.Embed(**opts) # Add details to it embed.add_field(name="Created", value=role.created_at.date()) - embed.add_field(name="Mentionable", value="Yes" if role.mentionable else "No") + embed.add_field( + name="Mentionable", value="Yes" if role.mentionable else "No" + ) total_members = len(role.members) embed.add_field(name="Total members", value=str(total_members)) # If there are only a few members in this role, display them if 5 >= total_members > 0: - embed.add_field(name="Members", value="\n".join(m.display_name for m in role.members)) + embed.add_field( + name="Members", + value="\n".join(m.display_name for m in role.members), + ) await ctx.send(embed=embed) else: # Don't include the colour roles colour_role = re.compile("Bonfire #.+") # Simply get a list of all roles in this server and send them - entries = [r.name for r in ctx.guild.roles[1:] if not colour_role.match(r.name)] + entries = [ + r.name for r in ctx.guild.roles[1:] if not colour_role.match(r.name) + ] if len(entries) == 0: - await ctx.send("You do not have any roles setup on this server, other than the default role!") + await ctx.send( + "You do not have any roles setup on this server, other than the default role!" + ) return try: @@ -101,7 +113,7 @@ class Roles(commands.Cog): except utils.CannotPaginate as e: await ctx.send(str(e)) - @role.command(name='remove') + @role.command(name="remove") @commands.guild_only() @utils.can_run(manage_roles=True) async def remove_role(self, ctx): @@ -111,38 +123,51 @@ class Roles(commands.Cog): RESULT: A follow-along to remove the role(s) you want to, from these 3 members""" # No use in running through everything if the bot cannot manage roles if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles: - await ctx.send("I can't manage roles in this server, do you not trust me? :c") + await ctx.send( + "I can't manage roles in this server, do you not trust me? :c" + ) return - check = lambda m: m.author == ctx.message.author and m.channel == ctx.message.channel + check = ( + lambda m: m.author == ctx.message.author + and m.channel == ctx.message.channel + ) - server_roles = [role for role in ctx.message.guild.roles if not role.is_default()] + server_roles = [ + role for role in ctx.message.guild.roles if not role.is_default() + ] # First get the list of all mentioned users members = ctx.message.mentions # If no users are mentioned, ask the author for a list of the members they want to remove the role from if len(members) == 0: - await ctx.send("Please provide the list of members you want to remove a role from") + await ctx.send( + "Please provide the list of members you want to remove a role from" + ) try: - msg = await ctx.bot.wait_for('message', check=check, timeout=60) + msg = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return if len(msg.mentions) == 0: - await ctx.send("I cannot remove a role from someone if you don't provide someone...") + await ctx.send( + "I cannot remove a role from someone if you don't provide someone..." + ) return # Override members if everything has gone alright, and then continue members = msg.mentions # This allows the user to remove multiple roles from the list of users, if they want. - await ctx.send("Alright, please provide the roles you would like to remove from this member. " - "Make sure the roles, if more than one is provided, are separate by commas. ") + await ctx.send( + "Alright, please provide the roles you would like to remove from this member. " + "Make sure the roles, if more than one is provided, are separate by commas. " + ) try: - msg = await ctx.bot.wait_for('message', check=check, timeout=60) + msg = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return # Split the content based on commas, using regex so we can split if a space was not provided or if it was - role_names = re.split(', ?', msg.content) + role_names = re.split(", ?", msg.content) roles = [] # This loop is just to get the actual role objects based on the name for role in role_names: @@ -158,10 +183,14 @@ class Roles(commands.Cog): # Otherwise, remove the roles from each member given for member in members: await member.remove_roles(*roles) - await ctx.send("I have just removed the following roles:```\n{}``` from the following members:" - "```\n{}```".format("\n".join(role_names), "\n".join([m.display_name for m in members]))) + await ctx.send( + "I have just removed the following roles:```\n{}``` from the following members:" + "```\n{}```".format( + "\n".join(role_names), "\n".join([m.display_name for m in members]) + ) + ) - @role.command(name='add', aliases=['give', 'assign']) + @role.command(name="add", aliases=["give", "assign"]) @commands.guild_only() @utils.can_run(manage_roles=True) async def add_role(self, ctx): @@ -173,33 +202,46 @@ class Roles(commands.Cog): RESULT: A follow along to add the roles you want to these 3""" # No use in running through everything if the bot cannot manage roles if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles: - await ctx.send("I can't manage roles in this server, do you not trust me? :c") + await ctx.send( + "I can't manage roles in this server, do you not trust me? :c" + ) return - check = lambda m: m.author == ctx.message.author and m.channel == ctx.message.channel + check = ( + lambda m: m.author == ctx.message.author + and m.channel == ctx.message.channel + ) # This is exactly the same as removing roles, except we call add_roles instead. - server_roles = [role for role in ctx.message.guild.roles if not role.is_default()] + server_roles = [ + role for role in ctx.message.guild.roles if not role.is_default() + ] members = ctx.message.mentions if len(members) == 0: - await ctx.send("Please provide the list of members you want to add a role to") + await ctx.send( + "Please provide the list of members you want to add a role to" + ) try: - msg = await ctx.bot.wait_for('message', check=check, timeout=60) + msg = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return if len(msg.mentions) == 0: - await ctx.send("I cannot add a role to someone if you don't provide someone...") + await ctx.send( + "I cannot add a role to someone if you don't provide someone..." + ) return members = msg.mentions - await ctx.send("Alright, please provide the roles you would like to add to this member. " - "Make sure the roles, if more than one is provided, are separate by commas. ") + await ctx.send( + "Alright, please provide the roles you would like to add to this member. " + "Make sure the roles, if more than one is provided, are separate by commas. " + ) try: - msg = await ctx.bot.wait_for('message', check=check, timeout=60) + msg = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return - role_names = re.split(', ?', msg.content) + role_names = re.split(", ?", msg.content) roles = [] for role in role_names: _role = discord.utils.get(server_roles, name=role) @@ -212,10 +254,14 @@ class Roles(commands.Cog): for member in members: await member.add_roles(*roles) - await ctx.send("I have just added the following roles:```\n{}``` to the following members:" - "```\n{}```".format("\n".join(role_names), "\n".join([m.display_name for m in members]))) + await ctx.send( + "I have just added the following roles:```\n{}``` to the following members:" + "```\n{}```".format( + "\n".join(role_names), "\n".join([m.display_name for m in members]) + ) + ) - @role.command(name='delete') + @role.command(name="delete") @commands.guild_only() @utils.can_run(manage_roles=True) async def delete_role(self, ctx, *, role: discord.Role = None): @@ -225,16 +271,21 @@ class Roles(commands.Cog): RESULT: No more role called StupidRole""" # No use in running through everything if the bot cannot manage roles if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles: - await ctx.send("I can't delete roles in this server, do you not trust me? :c") + await ctx.send( + "I can't delete roles in this server, do you not trust me? :c" + ) return # If no role was given, get the current roles on the server and ask which ones they'd like to remove if role is None: - server_roles = [role for role in ctx.message.guild.roles if not role.is_default()] + server_roles = [ + role for role in ctx.message.guild.roles if not role.is_default() + ] await ctx.send( "Which role would you like to remove from the server? Here is a list of this server's roles:" - "```\n{}```".format("\n".join([r.name for r in server_roles]))) + "```\n{}```".format("\n".join([r.name for r in server_roles])) + ) # For this method we're only going to delete one role at a time # This check attempts to find a role based on the content provided, if it can't find one it returns None @@ -246,7 +297,7 @@ class Roles(commands.Cog): return False try: - msg = await ctx.bot.wait_for('message', timeout=60, check=check) + msg = await ctx.bot.wait_for("message", timeout=60, check=check) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return @@ -255,9 +306,11 @@ class Roles(commands.Cog): role = discord.utils.get(server_roles, name=msg.content) await role.delete() - await ctx.send("I have just removed the role {} from this server".format(role.name)) + await ctx.send( + "I have just removed the role {} from this server".format(role.name) + ) - @role.command(name='create') + @role.command(name="create") @commands.guild_only() @utils.can_run(manage_roles=True) async def create_role(self, ctx): @@ -269,7 +322,9 @@ class Roles(commands.Cog): RESULT: A follow along in order to create a new role""" # No use in running through everything if the bot cannot create the role if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles: - await ctx.send("I can't create roles in this server, do you not trust me? :c") + await ctx.send( + "I can't create roles in this server, do you not trust me? :c" + ) return # Save a couple variables that will be used repeatedly @@ -280,7 +335,7 @@ class Roles(commands.Cog): # A couple checks that will be used in the wait_for_message's def num_seperated_check(m): if m.author == author and m.channel == channel: - return re.search("(\d(, ?| )?|[nN]one)", m.content) is not None + return re.search(r"(\d(, ?| )?|[nN]one)", m.content) is not None else: return False @@ -296,13 +351,16 @@ class Roles(commands.Cog): else: return False - author_check = lambda m: m.author == author and m.channel == channel - # Start the checks for the role, get the name of the role first await ctx.send( - "Alright! I'm ready to create a new role, please respond with the name of the role you want to create") + "Alright! I'm ready to create a new role, please respond with the name of the role you want to create" + ) try: - msg = await ctx.bot.wait_for('message', timeout=60.0, check=author_check) + msg = await ctx.bot.wait_for( + "message", + timeout=60.0, + check=lambda m: m.author == author and m.channel == channel, + ) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return @@ -311,24 +369,34 @@ class Roles(commands.Cog): # Print a list of all the permissions available, then ask for which ones need to be active on this new role all_perms = list(discord.Permissions.VALID_FLAGS.keys()) fmt = "\n".join("{}) {}".format(i, perm) for i, perm in enumerate(all_perms)) - await ctx.send("Sounds fancy! Here is a list of all the permissions available. Please respond with just " - "the numbers, seperated by commas, of the permissions you want this role to have.\n" - "```\n{}```".format(fmt)) + await ctx.send( + "Sounds fancy! Here is a list of all the permissions available. Please respond with just " + "the numbers, seperated by commas, of the permissions you want this role to have.\n" + "```\n{}```".format(fmt) + ) # For this we're going to give a couple extra minutes before we timeout # as it might take a bit to figure out which permissions they want try: - msg = await ctx.bot.wait_for('message', timeout=180.0, check=num_seperated_check) + msg = await ctx.bot.wait_for( + "message", timeout=180.0, check=num_seperated_check + ) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return # Check if any integer's were provided that are within the length of the list of permissions - num_permissions = [int(i) for i in re.split(' ?,?', msg.content) if i.isdigit() and int(i) < len(all_perms)] + num_permissions = [ + int(i) + for i in re.split(" ?,?", msg.content) + if i.isdigit() and int(i) < len(all_perms) + ] # Check if this role should be in a separate section on the sidebard, i.e. hoisted - await ctx.send("Do you want this role to be in a separate section on the sidebar? (yes or no)") + await ctx.send( + "Do you want this role to be in a separate section on the sidebar? (yes or no)" + ) try: - msg = await ctx.bot.wait_for('message', timeout=60.0, check=yes_no_check) + msg = await ctx.bot.wait_for("message", timeout=60.0, check=yes_no_check) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return @@ -337,7 +405,7 @@ class Roles(commands.Cog): # Check if this role should be able to be mentioned await ctx.send("Do you want this role to be mentionable? (yes or no)") try: - msg = await ctx.bot.wait_for('message', timeout=60.0, check=yes_no_check) + msg = await ctx.bot.wait_for("message", timeout=60.0, check=yes_no_check) except asyncio.TimeoutError: await ctx.send("You took too long. I'm impatient, don't make me wait") return @@ -350,18 +418,20 @@ class Roles(commands.Cog): setattr(perms, all_perms[index], True) payload = { - 'name': name, - 'permissions': perms, - 'hoist': hoist, - 'mentionable': mentionable + "name": name, + "permissions": perms, + "hoist": hoist, + "mentionable": mentionable, } # Create the role, and wait a second, sometimes it goes too quickly and we get a role with 'new role' to print role = await server.create_role(**payload) await asyncio.sleep(1) - await ctx.send("We did it! You just created the new role {}\nIf you want to add this role" - " to some people, mention them now".format(role.name)) + await ctx.send( + "We did it! You just created the new role {}\nIf you want to add this role" + " to some people, mention them now".format(role.name) + ) try: - msg = await ctx.bot.wait_for('message', timeout=60.0, check=members_check) + msg = await ctx.bot.wait_for("message", timeout=60.0, check=members_check) except asyncio.TimeoutError: # There's no need to mention the users, so don't send a failure message if they didn't, just return return @@ -386,7 +456,9 @@ class Roles(commands.Cog): return author = ctx.message.author - result = await ctx.bot.db.fetchrow("SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id + ) if result is None: await ctx.send("There are no self-assignable roles on this server") @@ -399,10 +471,14 @@ class Roles(commands.Cog): fmt = "" roles = [r for r in role if r.id in self_assignable_roles] - fmt += "\n".join(["Successfully added {}".format(r.name) - if r.id in self_assignable_roles else - "{} is not available to be self-assigned".format(r.name) - for r in role]) + fmt += "\n".join( + [ + "Successfully added {}".format(r.name) + if r.id in self_assignable_roles + else "{} is not available to be self-assigned".format(r.name) + for r in role + ] + ) try: await author.add_roles(*roles) @@ -423,7 +499,9 @@ class Roles(commands.Cog): return author = ctx.message.author - result = await ctx.bot.db.fetchrow("SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id) + result = await ctx.bot.db.fetchrow( + "SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id + ) if result is None: await ctx.send("There are no self-assignable roles on this server") @@ -436,10 +514,14 @@ class Roles(commands.Cog): fmt = "" roles = [r for r in role if str(r.id) in self_assignable_roles] - fmt += "\n".join(["Successfully removed {}".format(r.name) - if str(r.id) in self_assignable_roles else - "{} is not available to be self-assigned".format(r.name) - for r in role]) + fmt += "\n".join( + [ + "Successfully removed {}".format(r.name) + if str(r.id) in self_assignable_roles + else "{} is not available to be self-assigned".format(r.name) + for r in role + ] + ) try: await author.remove_roles(*roles) diff --git a/cogs/roulette.py b/cogs/roulette.py index 8c5b8e8..0df34e9 100644 --- a/cogs/roulette.py +++ b/cogs/roulette.py @@ -10,6 +10,7 @@ import utils class Roulette(commands.Cog): """A fun game that ends in someone getting kicked!""" + roulettes = [] def get_game(self, server): @@ -47,14 +48,17 @@ class Roulette(commands.Cog): result = r.join(ctx.message.author) time_left = r.time_left if result: - await ctx.send("You have joined this roulette game! Good luck~ This roulette will end in " + time_left) + await ctx.send( + "You have joined this roulette game! Good luck~ This roulette will end in " + + time_left + ) else: await ctx.send("This roulette will end in " + time_left) - @roulette.command(name='start', aliases=['create']) + @roulette.command(name="start", aliases=["create"]) @commands.guild_only() @utils.can_run(kick_members=True) - async def roulette_start(self, ctx, time: int=5): + async def roulette_start(self, ctx, time: int = 5): """Starts a roulette, that will end in one of the entrants being kicked from the server By default, the roulette will end in 5 minutes; provide a number (up to 30) to change how many minutes until it ends @@ -62,15 +66,23 @@ class Roulette(commands.Cog): EXAMPLE: !roulette start RESULT: A new roulette game!""" if time < 1 or time > 30: - await ctx.send("Invalid time! The roulette must be set to run between 1 and 30 minutes") + await ctx.send( + "Invalid time! The roulette must be set to run between 1 and 30 minutes" + ) return else: game = self.start_game(ctx.message.guild, time) if game: - await ctx.send("A new roulette game has just started! A random entrant will be kicked in {} minutes." - " Type {}roulette to join this roulette...good luck~".format(game.time_left, ctx.prefix)) + await ctx.send( + "A new roulette game has just started! A random entrant will be kicked in {} minutes." + " Type {}roulette to join this roulette...good luck~".format( + game.time_left, ctx.prefix + ) + ) else: - await ctx.send("There is already a roulette game running on this server!") + await ctx.send( + "There is already a roulette game running on this server!" + ) return await asyncio.sleep(time * 60) @@ -94,7 +106,6 @@ class Roulette(commands.Cog): class Game: - def __init__(self, guild, time): self.entrants = [] self.server = guild diff --git a/cogs/spotify.py b/cogs/spotify.py index 75891c5..0023b9e 100644 --- a/cogs/spotify.py +++ b/cogs/spotify.py @@ -31,9 +31,9 @@ class Spotify(commands.Cog): try: delay = await self.get_api_token() except Exception as error: - with open("error_log", 'a') as f: + with open("error_log", "a") as f: traceback.print_tb(error.__traceback__, file=f) - print('{0.__class__.__name__}: {0}'.format(error), file=f) + print("{0.__class__.__name__}: {0}".format(error), file=f) finally: await asyncio.sleep(delay) @@ -61,7 +61,12 @@ class Spotify(commands.Cog): url = "https://api.spotify.com/v1/search" response = await utils.request(url, headers=headers, payload=opts) try: - await ctx.send(response.get("tracks").get("items")[0].get("external_urls").get("spotify")) + await ctx.send( + response.get("tracks") + .get("items")[0] + .get("external_urls") + .get("spotify") + ) except (KeyError, AttributeError, IndexError): await ctx.send("Couldn't find a song for:\n{}".format(query)) @@ -79,7 +84,12 @@ class Spotify(commands.Cog): url = "https://api.spotify.com/v1/search" response = await utils.request(url, headers=headers, payload=opts) try: - await ctx.send(response.get("playlists").get("items")[0].get("external_urls").get("spotify")) + await ctx.send( + response.get("playlists") + .get("items")[0] + .get("external_urls") + .get("spotify") + ) except (KeyError, AttributeError, IndexError): await ctx.send("Couldn't find a song for:\n{}".format(query)) diff --git a/cogs/stats.py b/cogs/stats.py index e6ae15d..93065a9 100644 --- a/cogs/stats.py +++ b/cogs/stats.py @@ -1,4 +1,3 @@ -import re import utils import discord import datetime @@ -14,10 +13,14 @@ class Stats(commands.Cog): async def _get_guild_usage(self, guild): embed = discord.Embed(title="Server Command Usage") - count = await self.bot.db.fetchrow("SELECT COUNT(*), MIN(executed) FROM command_usage WHERE guild=$1", guild.id) + count = await self.bot.db.fetchrow( + "SELECT COUNT(*), MIN(executed) FROM command_usage WHERE guild=$1", guild.id + ) embed.description = f"{count[0]} total commands used" - embed.set_footer(text='Tracking command usage since').timestamp = count[1] or datetime.datetime.utcnow() + embed.set_footer(text="Tracking command usage since").timestamp = ( + count[1] or datetime.datetime.utcnow() + ) query = """ SELECT @@ -34,8 +37,10 @@ LIMIT 5 """ results = await self.bot.db.fetch(query, guild.id) - value = "\n".join(f"{command} ({uses} uses)" for command, uses in results or "No Commands") - embed.add_field(name='Top Commands', value=value) + value = "\n".join( + f"{command} ({uses} uses)" for command, uses in results or "No Commands" + ) + embed.add_field(name="Top Commands", value=value) return embed @@ -43,11 +48,13 @@ LIMIT 5 embed = discord.Embed(title=f"{member.display_name}'s command usage") count = await self.bot.db.fetchrow( "SELECT COUNT(*), MIN(executed) FROM command_usage WHERE author=$1", - member.id + member.id, ) embed.description = f"{count[0]} total commands used" - embed.set_footer(text='Tracking command usage since').timestamp = count[1] or datetime.datetime.utcnow() + embed.set_footer(text="Tracking command usage since").timestamp = ( + count[1] or datetime.datetime.utcnow() + ) query = """ SELECT @@ -64,8 +71,10 @@ LIMIT 5 """ results = await self.bot.db.fetch(query, member.id) - value = "\n".join(f"{command} ({uses} uses)" for command, uses in results or "No Commands") - embed.add_field(name='Top Commands', value=value) + value = "\n".join( + f"{command} ({uses} uses)" for command, uses in results or "No Commands" + ) + embed.add_field(name="Top Commands", value=value) return embed @@ -79,26 +88,35 @@ LIMIT 5 RESULT: Information about your server!""" server = ctx.message.guild # Create our embed that we'll use for the information - embed = discord.Embed(title=server.name, description="Created on: {}".format(server.created_at.date())) + embed = discord.Embed( + title=server.name, + description="Created on: {}".format(server.created_at.date()), + ) # Make sure we only set the icon url if it has been set if server.icon_url: embed.set_thumbnail(url=server.icon_url) # Add our fields, these are self-explanatory - embed.add_field(name='Region', value=str(server.region)) - embed.add_field(name='Total Emojis', value=len(server.emojis)) + embed.add_field(name="Region", value=str(server.region)) + embed.add_field(name="Total Emojis", value=len(server.emojis)) # Get the amount of online members - online_members = [m for m in server.members if str(m.status) == 'online'] - embed.add_field(name='Total members', value='{}/{}'.format(len(online_members), server.member_count)) - embed.add_field(name='Roles', value=len(server.roles)) + online_members = [m for m in server.members if str(m.status) == "online"] + embed.add_field( + name="Total members", + value="{}/{}".format(len(online_members), server.member_count), + ) + embed.add_field(name="Roles", value=len(server.roles)) # Split channels into voice and text channels voice_channels = [c for c in server.channels if type(c) is discord.VoiceChannel] text_channels = [c for c in server.channels if type(c) is discord.TextChannel] - embed.add_field(name='Channels', value='{} text, {} voice'.format(len(text_channels), len(voice_channels))) - embed.add_field(name='Owner', value=server.owner.display_name) + embed.add_field( + name="Channels", + value="{} text, {} voice".format(len(text_channels), len(voice_channels)), + ) + embed.add_field(name="Owner", value=server.owner.display_name) await ctx.send(embed=embed) @@ -117,8 +135,12 @@ LIMIT 5 fmt = "{} ({})".format(str(user), user.id) embed.set_author(name=fmt, icon_url=user.avatar_url) - embed.add_field(name='Joined this server', value=user.joined_at.date(), inline=False) - embed.add_field(name='Joined Discord', value=user.created_at.date(), inline=False) + embed.add_field( + name="Joined this server", value=user.joined_at.date(), inline=False + ) + embed.add_field( + name="Joined Discord", value=user.created_at.date(), inline=False + ) # Sort them based on the hierarchy, but don't include @everyone roles = sorted([x for x in user.roles if not x.is_default()], reverse=True) @@ -126,14 +148,14 @@ LIMIT 5 roles = ", ".join("{}".format(x.name) for x in roles[:5]) # If there are no roles, then just say this roles = roles or "No roles added" - embed.add_field(name='Top 5 roles', value=roles, inline=False) + embed.add_field(name="Top 5 roles", value=roles, inline=False) # Add the activity if there is one act = user.activity if isinstance(act, discord.activity.Spotify): embed.add_field(name="Listening to", value=act.title, inline=False) elif isinstance(act, discord.activity.Game): - embed.add_field(name='Playing', value=act.name, inline=False) + embed.add_field(name="Playing", value=act.name, inline=False) await ctx.send(embed=embed) @commands.group() @@ -180,9 +202,11 @@ LIMIT 1 most = await ctx.bot.db.fetchrow(query, ctx.author.id, members) if most is None or len(most) == 0: - await ctx.send(f"You have not booped anyone in this server {ctx.author.mention}") + await ctx.send( + f"You have not booped anyone in this server {ctx.author.mention}" + ) else: - member = ctx.guild.get_member(most['boopee']) + member = ctx.guild.get_member(most["boopee"]) await ctx.send( f"{ctx.author.mention} you have booped {member.display_name} the most amount of times, " f"coming in at {most['amount']} times" @@ -218,8 +242,8 @@ LIMIT 10 embed = discord.Embed(title="Your booped victims", colour=ctx.author.colour) embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url) for row in most: - member = ctx.guild.get_member(row['boopee']) - embed.add_field(name=member.display_name, value=row['amount']) + member = ctx.guild.get_member(row["boopee"]) + embed.add_field(name=member.display_name, value=row["amount"]) await ctx.send(embed=embed) else: await ctx.send("You haven't booped anyone in this server!") @@ -252,7 +276,7 @@ ORDER BY output = [] for row in results: - member = ctx.guild.get_member(row['id']) + member = ctx.guild.get_member(row["id"]) output.append(f"{member.display_name} (Rating: {row['battle_rating']})") try: @@ -297,7 +321,10 @@ WHERE id = $2 rating = result["battle_rating"] record = f"{result['battle_wins']} - {result['battle_losses']}" - embed = discord.Embed(title="Battling stats for {}".format(ctx.author.display_name), colour=ctx.author.colour) + embed = discord.Embed( + title="Battling stats for {}".format(ctx.author.display_name), + colour=ctx.author.colour, + ) embed.set_author(name=str(member), icon_url=member.avatar_url) embed.add_field(name="Record", value=record, inline=False) embed.add_field(name="Server Rank", value=server_rank, inline=False) diff --git a/cogs/tags.py b/cogs/tags.py index 6e0ddd2..e5f4ce1 100644 --- a/cogs/tags.py +++ b/cogs/tags.py @@ -17,10 +17,12 @@ class Tags(commands.Cog): EXAMPLE: !tags RESULT: All tags setup on this server""" - tags = await ctx.bot.db.fetch("SELECT trigger FROM tags WHERE guild=$1", ctx.guild.id) + tags = await ctx.bot.db.fetch( + "SELECT trigger FROM tags WHERE guild=$1", ctx.guild.id + ) if len(tags) > 0: - entries = [t['trigger'] for t in tags] + entries = [t["trigger"] for t in tags] pages = utils.Pages(ctx, entries=entries) await pages.paginate() else: @@ -37,11 +39,11 @@ class Tags(commands.Cog): tags = await ctx.bot.db.fetch( "SELECT trigger FROM tags WHERE guild=$1 AND creator=$2", ctx.guild.id, - ctx.author.id + ctx.author.id, ) if len(tags) > 0: - entries = [t['trigger'] for t in tags] + entries = [t["trigger"] for t in tags] pages = utils.Pages(ctx, entries=entries) await pages.paginate() else: @@ -59,16 +61,18 @@ class Tags(commands.Cog): tag = await ctx.bot.db.fetchrow( "SELECT id, result FROM tags WHERE guild=$1 AND trigger=$2", ctx.guild.id, - trigger.lower().strip() + trigger.lower().strip(), ) if tag: - await ctx.send("\u200B{}".format(tag['result'])) - await ctx.bot.db.execute("UPDATE tags SET uses = uses + 1 WHERE id = $1", tag['id']) + await ctx.send("\u200B{}".format(tag["result"])) + await ctx.bot.db.execute( + "UPDATE tags SET uses = uses + 1 WHERE id = $1", tag["id"] + ) else: await ctx.send("There is no tag called {}".format(trigger)) - @tag.command(name='add', aliases=['create', 'setup']) + @tag.command(name="add", aliases=["create", "setup"]) @commands.guild_only() @utils.can_run(send_messages=True) async def add_tag(self, ctx): @@ -78,9 +82,15 @@ class Tags(commands.Cog): RESULT: A follow-along in order to create a new tag""" def check(m): - return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0 + return ( + m.channel == ctx.message.channel + and m.author == ctx.message.author + and len(m.content) > 0 + ) - my_msg = await ctx.send("Ready to setup a new tag! What do you want the trigger for the tag to be?") + my_msg = await ctx.send( + "Ready to setup a new tag! What do you want the trigger for the tag to be?" + ) try: msg = await ctx.bot.wait_for("message", check=check, timeout=60) @@ -89,20 +99,32 @@ class Tags(commands.Cog): return trigger = msg.content.lower().strip() - forbidden_tags = ['add', 'create', 'setup', 'edit', 'info', 'delete', 'remove', 'stop'] + forbidden_tags = [ + "add", + "create", + "setup", + "edit", + "info", + "delete", + "remove", + "stop", + ] if len(trigger) > 100: await ctx.send("Please keep tag triggers under 100 characters") return elif trigger.lower() in forbidden_tags: await ctx.send( "Sorry, but your tag trigger was detected to be forbidden. " - "Current forbidden tag triggers are: \n{}".format("\n".join(forbidden_tags))) + "Current forbidden tag triggers are: \n{}".format( + "\n".join(forbidden_tags) + ) + ) return tag = await ctx.bot.db.fetchrow( "SELECT result FROM tags WHERE guild=$1 AND trigger=$2", ctx.guild.id, - trigger.lower().strip() + trigger.lower().strip(), ) if tag: await ctx.send("There is already a tag setup called {}!".format(trigger)) @@ -116,7 +138,9 @@ class Tags(commands.Cog): my_msg = await ctx.send( "Alright, your new tag can be called with {}!\n\nWhat do you want to be displayed with this tag?".format( - trigger)) + trigger + ) + ) try: msg = await ctx.bot.wait_for("message", check=check, timeout=60) @@ -131,34 +155,45 @@ class Tags(commands.Cog): except (discord.Forbidden, discord.HTTPException): pass - await ctx.send("I have just setup a new tag for this server! You can call your tag with {}".format(trigger)) + await ctx.send( + "I have just setup a new tag for this server! You can call your tag with {}".format( + trigger + ) + ) await ctx.bot.db.execute( "INSERT INTO tags(guild, creator, trigger, result) VALUES ($1, $2, $3, $4)", ctx.guild.id, ctx.author.id, trigger, - result + result, ) - @tag.command(name='edit') + @tag.command(name="edit") @commands.guild_only() @utils.can_run(send_messages=True) async def edit_tag(self, ctx, *, trigger: str): """This will allow you to edit a tag that you have created EXAMPLE: !tag edit this tag RESULT: I'll ask what you want the new result to be""" + def check(m): - return m.channel == ctx.message.channel and m.author == ctx.message.author and len(m.content) > 0 + return ( + m.channel == ctx.message.channel + and m.author == ctx.message.author + and len(m.content) > 0 + ) tag = await ctx.bot.db.fetchrow( "SELECT id, trigger FROM tags WHERE guild=$1 AND creator=$2 AND trigger=$3", ctx.guild.id, ctx.author.id, - trigger + trigger, ) if tag: - my_msg = await ctx.send(f"Alright, what do you want the new result for the tag {tag} to be") + my_msg = await ctx.send( + f"Alright, what do you want the new result for the tag {tag} to be" + ) try: msg = await ctx.bot.wait_for("message", check=check, timeout=60) except asyncio.TimeoutError: @@ -174,11 +209,13 @@ class Tags(commands.Cog): pass await ctx.send(f"Alright, the tag {trigger} has been updated") - await ctx.bot.db.execute("UPDATE tags SET result=$1 WHERE id=$2", new_result, tag['id']) + await ctx.bot.db.execute( + "UPDATE tags SET result=$1 WHERE id=$2", new_result, tag["id"] + ) else: await ctx.send(f"You do not have a tag called {trigger} on this server!") - @tag.command(name='delete', aliases=['remove', 'stop']) + @tag.command(name="delete", aliases=["remove", "stop"]) @commands.guild_only() @utils.can_run(send_messages=True) async def del_tag(self, ctx, *, trigger: str): @@ -192,12 +229,12 @@ class Tags(commands.Cog): "SELECT id FROM tags WHERE guild=$1 AND creator=$2 AND trigger=$3", ctx.guild.id, ctx.author.id, - trigger + trigger, ) if tag: await ctx.send(f"I have just deleted the tag {trigger}") - await ctx.bot.db.execute("DELETE FROM tags WHERE id=$1", tag['id']) + await ctx.bot.db.execute("DELETE FROM tags WHERE id=$1", tag["id"]) else: await ctx.send(f"You do not own a tag called {trigger} on this server!") @@ -210,15 +247,15 @@ class Tags(commands.Cog): tag = await ctx.bot.db.fetchrow( "SELECT creator, uses, trigger FROM tags WHERE guild=$1 AND trigger=$2", ctx.guild.id, - trigger + trigger, ) if tag is not None: - embed = discord.Embed(title=tag['trigger']) - creator = ctx.guild.get_member(tag['creator']) + embed = discord.Embed(title=tag["trigger"]) + creator = ctx.guild.get_member(tag["creator"]) if creator: embed.set_author(name=creator.display_name, url=creator.avatar_url) - embed.add_field(name="Uses", value=tag['uses']) + embed.add_field(name="Uses", value=tag["uses"]) embed.add_field(name="Owner", value=creator.mention) await ctx.send(embed=embed) diff --git a/cogs/tictactoe.py b/cogs/tictactoe.py index 683e8d9..bb7add6 100644 --- a/cogs/tictactoe.py +++ b/cogs/tictactoe.py @@ -10,13 +10,13 @@ import random class Board: def __init__(self, player1, player2): # Our board just needs to be a 3x3 grid. To keep formatting nice, each one is going to be a space to start - self.board = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']] + self.board = [[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]] # Randomize who goes first when the board is created if random.SystemRandom().randint(0, 1): - self.challengers = {'x': player1, 'o': player2} + self.challengers = {"x": player1, "o": player2} else: - self.challengers = {'x': player2, 'o': player1} + self.challengers = {"x": player2, "o": player1} # X's always go first self.X_turn = True @@ -24,22 +24,22 @@ class Board: def full(self): # For this check we just need to see if there is a space anywhere, if there is then we're not full for row in self.board: - if ' ' in row: + if " " in row: return False return True def can_play(self, player): # Simple check to see if the player is the one that's up if self.X_turn: - return player == self.challengers['x'] + return player == self.challengers["x"] else: - return player == self.challengers['o'] + return player == self.challengers["o"] def update(self, x, y): # If it's x's turn, we place an x, otherwise place an o - letter = 'x' if self.X_turn else 'o' + letter = "x" if self.X_turn else "o" # Make sure the place we're trying to update is blank, we can't override something - if self.board[x][y] == ' ': + if self.board[x][y] == " ": self.board[x][y] = letter else: return False @@ -52,35 +52,67 @@ class Board: # First base off the top-left corner, see if any possiblities with that match # We need to also make sure that the place is not blank, so that 3 in a row that are blank doesn't cause a 'win' # Top-left, top-middle, top right - if self.board[0][0] == self.board[0][1] and self.board[0][0] == self.board[0][2] and self.board[0][0] != ' ': + if ( + self.board[0][0] == self.board[0][1] + and self.board[0][0] == self.board[0][2] + and self.board[0][0] != " " + ): return self.challengers[self.board[0][0]] # Top-left, middle-left, bottom-left - if self.board[0][0] == self.board[1][0] and self.board[0][0] == self.board[2][0] and self.board[0][0] != ' ': + if ( + self.board[0][0] == self.board[1][0] + and self.board[0][0] == self.board[2][0] + and self.board[0][0] != " " + ): return self.challengers[self.board[0][0]] # Top-left, middle, bottom-right - if self.board[0][0] == self.board[1][1] and self.board[0][0] == self.board[2][2] and self.board[0][0] != ' ': + if ( + self.board[0][0] == self.board[1][1] + and self.board[0][0] == self.board[2][2] + and self.board[0][0] != " " + ): return self.challengers[self.board[0][0]] # Next check the top-right corner, not re-checking the last possiblity that included it # Top-right, middle-right, bottom-right - if self.board[0][2] == self.board[1][2] and self.board[0][2] == self.board[2][2] and self.board[0][2] != ' ': + if ( + self.board[0][2] == self.board[1][2] + and self.board[0][2] == self.board[2][2] + and self.board[0][2] != " " + ): return self.challengers[self.board[0][2]] # Top-right, middle, bottom-left - if self.board[0][2] == self.board[1][1] and self.board[0][2] == self.board[2][0] and self.board[0][2] != ' ': + if ( + self.board[0][2] == self.board[1][1] + and self.board[0][2] == self.board[2][0] + and self.board[0][2] != " " + ): return self.challengers[self.board[0][2]] # Next up, bottom-right corner, only one possiblity to check here, other two have been checked # Bottom-right, bottom-middle, bottom-left - if self.board[2][2] == self.board[2][1] and self.board[2][2] == self.board[2][0] and self.board[2][2] != ' ': + if ( + self.board[2][2] == self.board[2][1] + and self.board[2][2] == self.board[2][0] + and self.board[2][2] != " " + ): return self.challengers[self.board[2][2]] # No need to check the bottom-left, all posiblities have been checked now # Base things off the middle now, as we only need the two 'middle' possiblites that aren't diagonal # Top-middle, middle, bottom-middle - if self.board[1][1] == self.board[0][1] and self.board[1][1] == self.board[2][1] and self.board[1][1] != ' ': + if ( + self.board[1][1] == self.board[0][1] + and self.board[1][1] == self.board[2][1] + and self.board[1][1] != " " + ): return self.challengers[self.board[1][1]] # Left-middle, middle, right-middle - if self.board[1][1] == self.board[1][0] and self.board[1][1] == self.board[1][2] and self.board[1][1] != ' ': + if ( + self.board[1][1] == self.board[1][0] + and self.board[1][1] == self.board[1][2] + and self.board[1][1] != " " + ): return self.challengers[self.board[1][1]] # Otherwise nothing has been found, return None @@ -89,25 +121,32 @@ class Board: def __str__(self): # Simple formatting here when you look at it, enough spaces to even out where everything is # Place whatever is at the grid in place, whether it's x, o, or blank - _board = " {} | {} | {}\n".format(self.board[0][0], self.board[0][1], self.board[0][2]) + _board = " {} | {} | {}\n".format( + self.board[0][0], self.board[0][1], self.board[0][2] + ) _board += "———————————————\n" - _board += " {} | {} | {}\n".format(self.board[1][0], self.board[1][1], self.board[1][2]) + _board += " {} | {} | {}\n".format( + self.board[1][0], self.board[1][1], self.board[1][2] + ) _board += "———————————————\n" - _board += " {} | {} | {}\n".format(self.board[2][0], self.board[2][1], self.board[2][2]) + _board += " {} | {} | {}\n".format( + self.board[2][0], self.board[2][1], self.board[2][2] + ) return "```\n{}```".format(_board) class TicTacToe(commands.Cog): """Pretty self-explanatory""" + boards = {} def create(self, server_id, player1, player2): self.boards[server_id] = Board(player1, player2) # Return whoever is x's so that we know who is going first - return self.boards[server_id].challengers['x'] + return self.boards[server_id].challengers["x"] - @commands.group(aliases=['tic', 'tac', 'toe'], invoke_without_command=True) + @commands.group(aliases=["tic", "tac", "toe"], invoke_without_command=True) @commands.guild_only() @utils.can_run(send_messages=True) async def tictactoe(self, ctx, *, option: str): @@ -131,11 +170,11 @@ class TicTacToe(commands.Cog): return # Search for the positions in the option given, the actual match doesn't matter, just need to check if it exists - top = re.search('top', option) - middle = re.search('middle', option) - bottom = re.search('bottom', option) - left = re.search('left', option) - right = re.search('right', option) + top = re.search("top", option) + middle = re.search("middle", option) + bottom = re.search("bottom", option) + left = re.search("left", option) + right = re.search("right", option) # Just a bit of logic to ensure nothing that doesn't make sense is given if top and bottom: @@ -186,11 +225,18 @@ class TicTacToe(commands.Cog): if winner: # Get the loser based on whether or not the winner is x's # If the winner is x's, the loser is o's...obviously, and vice-versa - loser = board.challengers['x'] if board.challengers['x'] != winner else board.challengers['o'] - await ctx.send("{} has won this game of TicTacToe, better luck next time {}".format(winner.display_name, - loser.display_name)) + loser = ( + board.challengers["x"] + if board.challengers["x"] != winner + else board.challengers["o"] + ) + await ctx.send( + "{} has won this game of TicTacToe, better luck next time {}".format( + winner.display_name, loser.display_name + ) + ) # Handle updating ratings based on the winner and loser - await utils.update_records('tictactoe', ctx.bot.db, winner, loser) + await utils.update_records("tictactoe", ctx.bot.db, winner, loser) # This game has ended, delete it so another one can be made try: del self.boards[ctx.message.guild.id] @@ -206,11 +252,17 @@ class TicTacToe(commands.Cog): pass # If no one has won, and the game has not ended in a tie, print the new updated board else: - player_turn = board.challengers.get('x') if board.X_turn else board.challengers.get('o') - fmt = str(board) + "\n{} It is now your turn to play!".format(player_turn.display_name) + player_turn = ( + board.challengers.get("x") + if board.X_turn + else board.challengers.get("o") + ) + fmt = str(board) + "\n{} It is now your turn to play!".format( + player_turn.display_name + ) await ctx.send(fmt) - @tictactoe.command(name='start', aliases=['challenge', 'create']) + @tictactoe.command(name="start", aliases=["challenge", "create"]) @commands.guild_only() @utils.can_run(send_messages=True) async def start_game(self, ctx, player2: discord.Member): @@ -222,30 +274,41 @@ class TicTacToe(commands.Cog): # For simplicities sake, only allow one game on a server at a time. # Things can easily get confusing (on the server's end) if we allow more than one if self.boards.get(ctx.message.guild.id) is not None: - await ctx.send("Sorry but only one Tic-Tac-Toe game can be running per server!") + await ctx.send( + "Sorry but only one Tic-Tac-Toe game can be running per server!" + ) return # Make sure we're not being challenged, I always win anyway if player2 == ctx.message.guild.me: - await ctx.send("You want to play? Alright lets play.\n\nI win, so quick you didn't even notice it.") + await ctx.send( + "You want to play? Alright lets play.\n\nI win, so quick you didn't even notice it." + ) return if player2 == player1: - await ctx.send("You can't play yourself, I won't allow it. Go find some friends") + await ctx.send( + "You can't play yourself, I won't allow it. Go find some friends" + ) return # Create the board and return who has been decided to go first x_player = self.create(ctx.message.guild.id, player1, player2) - fmt = "A tictactoe game has just started between {} and {}\n".format(player1.display_name, player2.display_name) + fmt = "A tictactoe game has just started between {} and {}\n".format( + player1.display_name, player2.display_name + ) # Print the board too just because fmt += str(self.boards[ctx.message.guild.id]) # We don't need to do anything weird with assigning x_player to something # it is already a member object, just use it - fmt += "I have decided at random, and {} is going to be x's this game. It is your turn first! " \ - "Use the {}tictactoe command, and a position, to choose where you want to play" \ - .format(x_player.display_name, ctx.prefix) + fmt += ( + "I have decided at random, and {} is going to be x's this game. It is your turn first! " + "Use the {}tictactoe command, and a position, to choose where you want to play".format( + x_player.display_name, ctx.prefix + ) + ) await ctx.send(fmt) - @tictactoe.command(name='delete', aliases=['stop', 'remove', 'end']) + @tictactoe.command(name="delete", aliases=["stop", "remove", "end"]) @commands.guild_only() @utils.can_run(kick_members=True) async def stop_game(self, ctx): @@ -260,7 +323,9 @@ class TicTacToe(commands.Cog): return del self.boards[ctx.message.guild.id] - await ctx.send("I have just stopped the game of TicTacToe, a new should be able to be started now!") + await ctx.send( + "I have just stopped the game of TicTacToe, a new should be able to be started now!" + ) def setup(bot): diff --git a/utils/utilities.py b/utils/utilities.py index 09ddccb..9097320 100644 --- a/utils/utilities.py +++ b/utils/utilities.py @@ -2,6 +2,7 @@ import aiohttp from io import BytesIO import inspect import discord +import traceback from discord.ext import commands from . import config @@ -14,7 +15,7 @@ def channel_is_nsfw(channel): async def download_image(url): """Returns a file-like object based on the URL provided""" # Simply read the image, to get the bytes - bts = await request(url, attr='read') + bts = await request(url, attr="read") if bts is None: return None @@ -23,12 +24,20 @@ async def download_image(url): return image -async def request(url, *, headers=None, payload=None, method='GET', attr='json', force_content_type_json=False): +async def request( + url, + *, + headers=None, + payload=None, + method="GET", + attr="json", + force_content_type_json=False, +): # Make sure our User Agent is what's set, and ensure it's sent even if no headers are passed if headers is None: headers = {} - headers['User-Agent'] = config.user_agent + headers["User-Agent"] = config.user_agent # Try 5 times for i in range(5): @@ -50,7 +59,9 @@ async def request(url, *, headers=None, payload=None, method='GET', attr='json', # This causes some places with different mimetypes to fail, even if it's valid json # This check allows us to force the content_type to use whatever content type is given if force_content_type_json: - return_value = return_value(content_type=response.headers['content-type']) + return_value = return_value( + content_type=response.headers["content-type"] + ) else: return_value = return_value() # If this is awaitable, await it @@ -67,6 +78,27 @@ async def request(url, *, headers=None, payload=None, method='GET', attr='json', continue +async def log_error(error, bot, ctx=None): + # First set the actual channel if it's not set yet + if isinstance(bot.error_channel, int): + bot.error_channel = bot.get_channel(bot.error_channel) + # Format the error message + fmt = f"""``` +{''.join(traceback.format_tb(error.__traceback__)).strip()} +{error.__class__.__name__}: {error}```""" + # Add the command if ctx is given + if ctx is not None: + fmt = f"Command = {discord.utils.escape_markdown(ctx.message.clean_content).strip()}\n{fmt}" + # If no channel is set, log to a file + if bot.error_channel is None: + fmt = fmt.strip("`") + with open("error_log", "a") as f: + print(fmt, file=f) + # Otherwise send to the error channel + else: + await bot.error_channel.send(fmt) + + async def convert(ctx, option): """Tries to convert a string to an object of useful representiation""" # Due to id's being ints, it's very possible that an int is passed @@ -130,7 +162,9 @@ async def update_records(key, db, winner, loser): key = f"{key}_rating" winner_found = False loser_found = False - query = f"SELECT id, {key}, {wins}, {losses} FROM users WHERE id = any($1::bigint[])" + query = ( + f"SELECT id, {key}, {wins}, {losses} FROM users WHERE id = any($1::bigint[])" + ) results = await db.fetch(query, [winner.id, loser.id]) # Set our defaults for the stats @@ -138,7 +172,7 @@ async def update_records(key, db, winner, loser): winner_wins = loser_wins = 0 winner_losses = loser_losses = 0 for result in results: - if result['id'] == winner.id: + if result["id"] == winner.id: winner_found = True winner_rating = result[key] winner_wins = result[wins] @@ -174,14 +208,18 @@ async def update_records(key, db, winner, loser): loser_losses += 1 update_query = f"UPDATE users SET {key}=$1, {wins}=$2, {losses}=$3 WHERE id = $4" - create_query = f"INSERT INTO users ({key}, {wins}, {losses}, id) VALUES ($1, $2, $3, $4)" + create_query = ( + f"INSERT INTO users ({key}, {wins}, {losses}, id) VALUES ($1, $2, $3, $4)" + ) if winner_found: - await db.execute(update_query, winner_rating, winner_wins, winner_losses, winner.id) + await db.execute( + update_query, winner_rating, winner_wins, winner_losses, winner.id + ) else: - await db.execute(create_query, winner_rating, winner_wins, winner_losses, winner.id) + await db.execute( + create_query, winner_rating, winner_wins, winner_losses, winner.id + ) if loser_found: await db.execute(update_query, loser_rating, loser_wins, loser_losses, loser.id) else: await db.execute(create_query, loser_rating, loser_wins, loser_losses, loser.id) - -