1
0
Fork 0
mirror of synced 2024-05-14 17:42:23 +12:00

Big update, use ext.tasks, couple updates to handle chunking better, log errors in tasks to error channel

This commit is contained in:
Dan Hess 2020-10-10 22:36:11 -05:00
parent dae89ce4ee
commit d85454f6cf
25 changed files with 1495 additions and 881 deletions

33
bot.py
View file

@ -1,13 +1,8 @@
import discord import discord
import traceback
import logging import logging
import datetime
import pendulum import pendulum
import os
import aiohttp import aiohttp
os.chdir(os.path.dirname(os.path.realpath(__file__)))
from discord.ext import commands from discord.ext import commands
import utils import utils
@ -29,12 +24,17 @@ logging.basicConfig(level=logging.INFO, filename="bonfire.log")
@bot.before_invoke @bot.before_invoke
async def start_typing(ctx): async def before_invocation(ctx):
# Start typing
try: try:
await ctx.trigger_typing() await ctx.trigger_typing()
except (discord.Forbidden, discord.HTTPException): except (discord.Forbidden, discord.HTTPException):
pass pass
# Ensure guild is chunked
if ctx.guild and not ctx.guild.chunked:
await ctx.guild.chunk()
@bot.event @bot.event
async def on_ready(): async def on_ready():
@ -121,26 +121,7 @@ async def on_command_error(ctx, error):
" recheck where your quotes are" " recheck where your quotes are"
) )
else: else:
if isinstance(bot.error_channel, int): await utils.log_error(error, ctx.bot, ctx)
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}```"""
)
except discord.HTTPException: except discord.HTTPException:
pass pass

View file

@ -26,7 +26,7 @@ class Admin(commands.Cog):
await ctx.bot.db.execute( await ctx.bot.db.execute(
"INSERT INTO restrictions (source, destination, from_to, guild) VALUES ($1, 'everyone', 'from', $2)", "INSERT INTO restrictions (source, destination, from_to, guild) VALUES ($1, 'everyone', 'from', $2)",
cmd.qualified_name, cmd.qualified_name,
ctx.guild.id ctx.guild.id,
) )
except UniqueViolationError: except UniqueViolationError:
await ctx.send(f"{cmd.qualified_name} is already disabled") await ctx.send(f"{cmd.qualified_name} is already disabled")
@ -35,7 +35,7 @@ class Admin(commands.Cog):
ctx.bot.cache.add_restriction( ctx.bot.cache.add_restriction(
ctx.guild, ctx.guild,
"from", "from",
{"source": cmd.qualified_name, "destination": "everyone"} {"source": cmd.qualified_name, "destination": "everyone"},
) )
@commands.command() @commands.command()
@ -48,7 +48,7 @@ class Admin(commands.Cog):
await ctx.send("No command called `{}`".format(command)) await ctx.send("No command called `{}`".format(command))
return return
query = f""" query = """
DELETE FROM restrictions WHERE DELETE FROM restrictions WHERE
source=$1 AND source=$1 AND
from_to='from' AND from_to='from' AND
@ -57,9 +57,7 @@ guild=$2
""" """
await ctx.bot.db.execute(query, cmd.qualified_name, ctx.guild.id) await ctx.bot.db.execute(query, cmd.qualified_name, ctx.guild.id)
ctx.bot.cache.remove_restriction( ctx.bot.cache.remove_restriction(
ctx.guild, ctx.guild, "from", {"source": cmd.qualified_name, "destination": "everyone"}
"from",
{"source": cmd.qualified_name, "destination": "everyone"}
) )
await ctx.send(f"{cmd.qualified_name} is no longer disabled") 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 This sets the role to mentionable, mentions the role, then sets it back
""" """
if not ctx.me.guild_permissions.manage_roles: 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 return
try: try:
await role.edit(mentionable=True) await role.edit(mentionable=True)
except discord.Forbidden: except discord.Forbidden:
await ctx.send("I do not have permissions to edit that role. " await ctx.send(
"(I either don't have manage roles permissions, or it is higher on the hierarchy)") "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: else:
fmt = f"{role.mention}\n{message}" fmt = f"{role.mention}\n{message}"
await ctx.send(fmt) await ctx.send(fmt)
@ -95,7 +97,7 @@ guild=$2
RESULT: All the current restrictions""" RESULT: All the current restrictions"""
restrictions = await ctx.bot.db.fetch( restrictions = await ctx.bot.db.fetch(
"SELECT source, destination, from_to FROM restrictions WHERE guild=$1", "SELECT source, destination, from_to FROM restrictions WHERE guild=$1",
ctx.guild.id ctx.guild.id,
) )
entries = [] entries = []
@ -106,7 +108,9 @@ guild=$2
dest = await utils.convert(ctx, restriction["destination"]) dest = await utils.convert(ctx, restriction["destination"])
# If it doesn't exist, don't add it # If it doesn't exist, don't add it
if dest: 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: if entries:
# Then paginate # Then paginate
@ -135,17 +139,23 @@ guild=$2
""" """
# First make sure we're given three options # First make sure we're given three options
if len(options) != 3: 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 return
elif ctx.message.mention_everyone: 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 return
else: else:
# Get the three arguments from this list, then make sure the 2nd is either from or to # Get the three arguments from this list, then make sure the 2nd is either from or to
arg1, arg2, arg3 = options arg1, arg2, arg3 = options
if arg2.lower() not in ['from', 'to']: 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` " await ctx.send(
"or `command to Role`") 'The 2nd option needs to be either "to" or "from". Such as: `command from @user` '
"or `command to Role`"
)
return return
else: else:
# Try to convert the other arguments # Try to convert the other arguments
@ -153,7 +163,11 @@ guild=$2
option1 = await utils.convert(ctx, arg1) option1 = await utils.convert(ctx, arg1)
option2 = await utils.convert(ctx, arg3) option2 = await utils.convert(ctx, arg3)
if option1 is None or option2 is None: 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 return
from_to = arg2 from_to = arg2
@ -173,7 +187,9 @@ guild=$2
# Channels - Command can't be ran in this channel # 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) # Roles - Command can't be ran by anyone in this role (least likely, but still possible uses)
if arg2 == "from": 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 source = option1.qualified_name
destination = str(option2.id) destination = str(option2.id)
# To: # To:
@ -189,16 +205,15 @@ guild=$2
# Command - Command cannot be used by this user # Command - Command cannot be used by this user
if arg2 == "from": if arg2 == "from":
if isinstance(option2, (discord.TextChannel, discord.VoiceChannel)): 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: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=False) ov.update(read_messages=False)
else: else:
ov = discord.PermissionOverwrite(read_messages=False) ov = discord.PermissionOverwrite(read_messages=False)
overwrites = { overwrites = {"channel": option2, option1: ov}
'channel': option2,
option1: ov
}
elif isinstance(option2, (commands.core.Command, commands.core.Group)): elif isinstance(option2, (commands.core.Command, commands.core.Group)):
source = option2.qualified_name source = option2.qualified_name
destination = str(option1.id) destination = str(option1.id)
@ -209,46 +224,51 @@ guild=$2
# Role - Setup an overwrite for this channel so that this Role cannot read it # Role - Setup an overwrite for this channel so that this Role cannot read it
if arg2 == "from": if arg2 == "from":
if isinstance(option2, (discord.Member, discord.Role)): 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: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=False) ov.update(read_messages=False)
else: else:
ov = discord.PermissionOverwrite(read_messages=False) ov = discord.PermissionOverwrite(read_messages=False)
overwrites = { overwrites = {"channel": option1, option2: ov}
'channel': option1, elif isinstance(
option2: ov option2, (commands.core.Command, commands.core.Group)
} ) and isinstance(option1, discord.TextChannel):
elif isinstance(option2, (commands.core.Command, commands.core.Group)) \
and isinstance(option1, discord.TextChannel):
source = option2.qualified_name source = option2.qualified_name
destination = str(option1.id) destination = str(option1.id)
# To: # To:
# Command - Command can only be used in this channel # Command - Command can only be used in this channel
# Role - Setup an overwrite so only this role can read this channel # Role - Setup an overwrite so only this role can read this channel
else: else:
if isinstance(option2, (commands.core.Command, commands.core.Group)) \ if isinstance(
and isinstance(option1, discord.TextChannel): option2, (commands.core.Command, commands.core.Group)
) and isinstance(option1, discord.TextChannel):
source = option2.qualified_name source = option2.qualified_name
destination = str(option1.id) destination = str(option1.id)
elif isinstance(option2, (discord.Member, discord.Role)): 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: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=True) ov.update(read_messages=True)
else: else:
ov = discord.PermissionOverwrite(read_messages=True) ov = discord.PermissionOverwrite(read_messages=True)
ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, ov2 = discord.utils.find(
option1.overwrites) lambda t: t[0] == ctx.message.guild.default_role,
option1.overwrites,
)
if ov2: if ov2:
ov2 = ov2[1] ov2 = ov2[1]
ov2.update(read_messages=False) ov2.update(read_messages=False)
else: else:
ov2 = discord.PermissionOverwrite(read_messages=False) ov2 = discord.PermissionOverwrite(read_messages=False)
overwrites = { overwrites = {
'channel': option1, "channel": option1,
option2: ov, option2: ov,
ctx.message.guild.default_role: ov2 ctx.message.guild.default_role: ov2,
} }
elif isinstance(option1, discord.Role): elif isinstance(option1, discord.Role):
# From: # From:
@ -259,38 +279,41 @@ guild=$2
source = option2.qualified_name source = option2.qualified_name
destination = option1.id destination = option1.id
elif isinstance(option2, (discord.TextChannel, discord.VoiceChannel)): 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: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=False) ov.update(read_messages=False)
else: else:
ov = discord.PermissionOverwrite(read_messages=False) ov = discord.PermissionOverwrite(read_messages=False)
overwrites = { overwrites = {"channel": option2, option1: ov}
'channel': option2,
option1: ov
}
# To: # To:
# Command - You have to have this role to run this command # 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 # Channel - Setup an overwrite so you have to have this role to read this channel
else: else:
if isinstance(option2, (discord.TextChannel, discord.VoiceChannel)): 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: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=True) ov.update(read_messages=True)
else: else:
ov = discord.PermissionOverwrite(read_messages=True) ov = discord.PermissionOverwrite(read_messages=True)
ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, ov2 = discord.utils.find(
option2.overwrites) lambda t: t[0] == ctx.message.guild.default_role,
option2.overwrites,
)
if ov2: if ov2:
ov2 = ov2[1] ov2 = ov2[1]
ov2.update(read_messages=False) ov2.update(read_messages=False)
else: else:
ov2 = discord.PermissionOverwrite(read_messages=False) ov2 = discord.PermissionOverwrite(read_messages=False)
overwrites = { overwrites = {
'channel': option2, "channel": option2,
option1: ov, option1: ov,
ctx.message.guild.default_role: ov2 ctx.message.guild.default_role: ov2,
} }
elif isinstance(option2, (commands.core.Command, commands.core.Group)): elif isinstance(option2, (commands.core.Command, commands.core.Group)):
source = option2.qualified_name source = option2.qualified_name
@ -303,20 +326,26 @@ guild=$2
ctx.guild.id, ctx.guild.id,
source, source,
destination, destination,
from_to from_to,
) )
except UniqueViolationError: except UniqueViolationError:
# If it's already inserted, then nothing needs to be updated # If it's already inserted, then nothing needs to be updated
# It just means this particular restriction is already set # It just means this particular restriction is already set
pass pass
else: 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: elif overwrites:
channel = overwrites.pop('channel') channel = overwrites.pop("channel")
for target, setting in overwrites.items(): for target, setting in overwrites.items():
await channel.set_permissions(target, overwrite=setting) await channel.set_permissions(target, overwrite=setting)
else: 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 return
await ctx.send("I have just restricted {} {} {}".format(arg1, arg2, arg3)) 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 # First make sure we're given three options
if len(options) != 3: 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 return
else: else:
# Get the three arguments from this list, then make sure the 2nd is either from or to # Get the three arguments from this list, then make sure the 2nd is either from or to
arg1, arg2, arg3 = options arg1, arg2, arg3 = options
if arg2.lower() not in ['from', 'to']: 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` " await ctx.send(
"or `command to Role`") 'The 2nd option needs to be either "to" or "from". Such as: `command from @user` '
"or `command to Role`"
)
return return
else: else:
# Try to convert the other arguments # Try to convert the other arguments
@ -351,11 +384,18 @@ guild=$2
option1 = await utils.convert(ctx, arg1) option1 = await utils.convert(ctx, arg1)
option2 = await utils.convert(ctx, arg3) option2 = await utils.convert(ctx, arg3)
if option1 is None or option2 is None: 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 return
# First check if this is a blacklist/whitelist (by checking if we are unrestricting commands) # 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 # The source should always be the command, so just set this based on which order is given (either is
# allowed) # allowed)
if isinstance(option1, (commands.core.Command, commands.core.Group)): if isinstance(option1, (commands.core.Command, commands.core.Group)):
@ -366,15 +406,23 @@ guild=$2
destination = str(option1.id) destination = str(option1.id)
# Now just try to remove it # Now just try to remove it
await ctx.bot.db.execute(""" await ctx.bot.db.execute(
"""
DELETE FROM DELETE FROM
restrictions restrictions
WHERE WHERE
source=$1 AND source=$1 AND
destination=$2 AND destination=$2 AND
from_to=$3 AND from_to=$3 AND
guild=$4""", source, destination, arg2, ctx.guild.id) guild=$4""",
ctx.bot.cache.remove_restriction(ctx.guild, arg2, {"source": source, "destination": destination}) 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 # If this isn't a blacklist/whitelist, then we are attempting to remove an overwrite
else: else:
@ -390,14 +438,21 @@ WHERE
if arg2 == "from": if arg2 == "from":
# Get overwrites if they exist # Get overwrites if they exist
# If it doesn't, there's nothing to do here # 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: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=True) ov.update(read_messages=True)
await destination.set_permissions(source, overwrite=ov) await destination.set_permissions(source, overwrite=ov)
else: else:
ov = discord.utils.find(lambda t: t[0] == source, destination.overwrites) ov = discord.utils.find(
ov2 = discord.utils.find(lambda t: t[0] == ctx.message.guild.default_role, destination.overwrites) lambda t: t[0] == source, destination.overwrites
)
ov2 = discord.utils.find(
lambda t: t[0] == ctx.message.guild.default_role,
destination.overwrites,
)
if ov: if ov:
ov = ov[1] ov = ov[1]
ov.update(read_messages=None) ov.update(read_messages=None)
@ -418,7 +473,7 @@ WHERE
EXAMPLE: !perms help EXAMPLE: !perms help
RESULT: Hopefully a result saying you just need send_messages permissions; otherwise lol 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) cmd = ctx.bot.get_command(command)
if cmd is None: if cmd is None:
@ -444,7 +499,7 @@ WHERE
result = await ctx.bot.db.fetchrow( result = await ctx.bot.db.fetchrow(
"SELECT permission FROM custom_permissions WHERE guild = $1 AND command = $2", "SELECT permission FROM custom_permissions WHERE guild = $1 AND command = $2",
ctx.guild.id, ctx.guild.id,
command command,
) )
perms_value = result["permission"] if result else None 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 # 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 # based on what we set in utils.can_run, if can_run isn't found, we'll get an IndexError
try: 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: except IndexError:
# Loop through and check if there is a check called is_owner # 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 # 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__: if "is_owner" in func.__qualname__:
await ctx.send("You need to own the bot to run this command") await ctx.send("You need to own the bot to run this command")
return return
await ctx.send("You are required to have `manage_guild` permissions to run `{}`".format( await ctx.send(
cmd.qualified_name "You are required to have `manage_guild` permissions to run `{}`".format(
)) cmd.qualified_name
)
)
return return
# Perms will be an attribute if can_run is found no matter what, so no need to check this # 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( 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: else:
# Permissions are saved as bit values, so create an object based on that value # 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 # 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 # There's no need to check for errors here, as we ensure a permission is valid when adding it
permissions = discord.Permissions(perms_value) permissions = discord.Permissions(perms_value)
needed_perm = [perm[0] for perm in permissions if perm[1]][0] needed_perm = [perm[0] for perm in permissions if perm[1]][0]
await ctx.send("You need to have the permission `{}` " await ctx.send(
"to use the command `{}` in this server".format(needed_perm, command)) "You need to have the permission `{}` "
"to use the command `{}` in this server".format(needed_perm, command)
)
@perms.command(name="add", aliases=["setup,create"]) @perms.command(name="add", aliases=["setup,create"])
@commands.guild_only() @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 # 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(" ") command, _, permission = msg.rpartition(" ")
if command == "": if command == "":
await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n" await ctx.send(
"`perms add <command> <permission>`") "Please provide the permissions you want to setup, the format for this must be in:\n"
"`perms add <command> <permission>`"
)
return return
cmd = ctx.bot.get_command(command) cmd = ctx.bot.get_command(command)
if cmd is None: if cmd is None:
await ctx.send( 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 return
# If a user can run a command, they have to have send_messages permissions; so use this as the base # 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: try:
setattr(perm_obj, permission, True) setattr(perm_obj, permission, True)
except AttributeError: except AttributeError:
await ctx.send("{} does not appear to be a valid permission! Valid permissions are: ```\n{}```" await ctx.send(
.format(permission, "\n".join(valid_perms))) "{} does not appear to be a valid permission! Valid permissions are: ```\n{}```".format(
permission, "\n".join(valid_perms)
)
)
return return
perm_value = perm_obj.value perm_value = perm_obj.value
@ -533,19 +605,23 @@ WHERE
"INSERT INTO custom_permissions (guild, command, permission) VALUES ($1, $2, $3)", "INSERT INTO custom_permissions (guild, command, permission) VALUES ($1, $2, $3)",
ctx.guild.id, ctx.guild.id,
cmd.qualified_name, cmd.qualified_name,
perm_value perm_value,
) )
except UniqueViolationError: except UniqueViolationError:
await ctx.bot.db.execute( await ctx.bot.db.execute(
"UPDATE custom_permissions SET permission = $1 WHERE guild = $2 AND command = $3", "UPDATE custom_permissions SET permission = $1 WHERE guild = $2 AND command = $3",
perm_value, perm_value,
ctx.guild.id, ctx.guild.id,
cmd.qualified_name cmd.qualified_name,
) )
ctx.bot.cache.update_custom_permission(ctx.guild, cmd, perm_value) ctx.bot.cache.update_custom_permission(ctx.guild, cmd, perm_value)
await ctx.send("I have just added your custom permissions; " await ctx.send(
"you now need to have `{}` permissions to use the command `{}`".format(permission, command)) "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"]) @perms.command(name="remove", aliases=["delete"])
@commands.guild_only() @commands.guild_only()
@ -560,18 +636,21 @@ WHERE
if cmd is None: if cmd is None:
await ctx.send( 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 return
await ctx.bot.db.execute( 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) ctx.bot.cache.update_custom_permission(ctx.guild, cmd, None)
await ctx.send("I have just removed the custom permissions for {}!".format(cmd)) await ctx.send("I have just removed the custom permissions for {}!".format(cmd))
@commands.command(aliases=['nick']) @commands.command(aliases=["nick"])
@commands.guild_only() @commands.guild_only()
@utils.can_run(kick_members=True) @utils.can_run(kick_members=True)
async def nickname(self, ctx, *, name=None): async def nickname(self, ctx, *, name=None):

View file

@ -1,11 +1,9 @@
import discord import discord
import datetime import datetime
import asyncio
import traceback
import re import re
import calendar import calendar
from discord.ext import commands from discord.ext import commands, tasks
from asyncpg import UniqueViolationError from asyncpg import UniqueViolationError
import utils import utils
@ -40,7 +38,7 @@ def parse_string(date):
"dec": 12, "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()]: for part in [x.lower() for x in date.split()]:
match = num_re.match(part) match = num_re.match(part)
@ -62,17 +60,15 @@ class Birthday(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.task = self.bot.loop.create_task(self.birthday_task())
async def get_birthdays_for_server(self, server, today=False): async def get_birthdays_for_server(self, server, today=False):
members = ", ".join(f"{m.id}" for m in server.members) query = """
query = f"""
SELECT SELECT
id, birthday id, birthday
FROM FROM
users users
WHERE WHERE
id IN ({members}) id=ANY($1::bigint[])
""" """
if today: if today:
query += """ query += """
@ -84,29 +80,16 @@ ORDER BY
birthday birthday
""" """
return await self.bot.db.fetch(query) return await self.bot.db.fetch(query, [m.id for m in server.members])
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)
@tasks.loop(hours=24)
async def notify_birthdays(self): async def notify_birthdays(self):
query = """ query = """
SELECT SELECT
id, COALESCE(birthday_alerts, default_alerts) AS channel id, COALESCE(birthday_alerts, default_alerts) AS channel
FROM FROM
guilds guilds
WHERE WHERE
birthday_notifications=True birthday_notifications=True
AND AND
COALESCE(birthday_alerts, default_alerts) IS NOT NULL COALESCE(birthday_alerts, default_alerts) IS NOT NULL
@ -117,23 +100,33 @@ AND
return return
for s in servers: 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 # 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: if not channel:
continue 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 # A list of the id's that will get updated
for bd in bds: for bd in bds:
try: try:
member = channel.guild.get_member(bd["id"]) member = g.get_member(bd["id"])
await channel.send(f"It is {member.mention}'s birthday today! " await channel.send(
"Wish them a happy birthday! \N{SHORTCAKE}") f"It is {member.mention}'s birthday today! "
"Wish them a happy birthday! \N{SHORTCAKE}"
)
except (discord.Forbidden, discord.HTTPException): except (discord.Forbidden, discord.HTTPException):
pass pass
finally: finally:
update_bds.append(bd['id']) update_bds.append(bd["id"])
if not update_bds: if not update_bds:
return return
@ -148,7 +141,11 @@ WHERE
""" """
await self.bot.db.execute(query) 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() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def birthday(self, ctx, *, member: discord.Member = None): async def birthday(self, ctx, *, member: discord.Member = None):
@ -156,13 +153,20 @@ WHERE
EXAMPLE: !birthdays EXAMPLE: !birthdays
RESULT: A printout of the birthdays from everyone on this server""" RESULT: A printout of the birthdays from everyone on this server"""
if not ctx.guild.chunked:
await ctx.guild.chunk()
if member: 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: if date is None or date["birthday"] is None:
await ctx.send(f"I do not have {member.display_name}'s birthday saved!") await ctx.send(f"I do not have {member.display_name}'s birthday saved!")
else: else:
date = date['birthday'] date = date["birthday"]
await ctx.send(f"{member.display_name}'s birthday is {calendar.month_name[date.month]} {date.day}") await ctx.send(
f"{member.display_name}'s birthday is {calendar.month_name[date.month]} {date.day}"
)
else: else:
# Get this server's birthdays # Get this server's birthdays
bds = await self.get_birthdays_for_server(ctx.guild) bds = await self.get_birthdays_for_server(ctx.guild)
@ -170,7 +174,7 @@ WHERE
entries = [ entries = [
f"{ctx.guild.get_member(bd['id']).display_name} ({bd['birthday'].strftime('%B %-d')})" f"{ctx.guild.get_member(bd['id']).display_name} ({bd['birthday'].strftime('%B %-d')})"
for bd in bds for bd in bds
if bd['birthday'] if bd["birthday"]
] ]
if not entries: if not entries:
await ctx.send("I don't know anyone's birthday in this server!") await ctx.send("I don't know anyone's birthday in this server!")
@ -184,7 +188,7 @@ WHERE
except utils.CannotPaginate as e: except utils.CannotPaginate as e:
await ctx.send(str(e)) await ctx.send(str(e))
@birthday.command(name='add') @birthday.command(name="add")
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def _add_bday(self, ctx, *, date): async def _add_bday(self, ctx, *, date):
"""Used to link your birthday to your account """Used to link your birthday to your account
@ -192,26 +196,36 @@ WHERE
EXAMPLE: !birthday add December 1st EXAMPLE: !birthday add December 1st
RESULT: I now know your birthday is December 1st""" RESULT: I now know your birthday is December 1st"""
if len(date.split()) != 2: 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 return
try: try:
date = parse_string(date) date = parse_string(date)
except ValueError: 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 return
if date is None: 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 return
await ctx.send(f"I have just saved your birthday as {date}") await ctx.send(f"I have just saved your birthday as {date}")
try: 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: 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) @utils.can_run(send_messages=True)
async def _remove_bday(self, ctx): async def _remove_bday(self, ctx):
"""Used to unlink your birthday to your account """Used to unlink your birthday to your account
@ -219,7 +233,9 @@ WHERE
EXAMPLE: !birthday remove EXAMPLE: !birthday remove
RESULT: I have magically forgotten your birthday""" RESULT: I have magically forgotten your birthday"""
await ctx.send("I don't know your birthday anymore :(") 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): def setup(bot):

View file

@ -24,7 +24,7 @@ class Blackjack(commands.Cog):
game = Game(self.bot, message, self) game = Game(self.bot, message, self)
self.games[message.guild.id] = game 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() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def blackjack(self, ctx): async def blackjack(self, ctx):
@ -49,9 +49,11 @@ class Blackjack(commands.Cog):
if game.playing(ctx.message.author): if game.playing(ctx.message.author):
await ctx.send("You are already playing! Wait for your turn!") await ctx.send("You are already playing! Wait for your turn!")
else: 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() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def blackjack_leave(self, ctx): async def blackjack_leave(self, ctx):
@ -69,11 +71,15 @@ class Blackjack(commands.Cog):
status = game.leave(ctx.message.author) status = game.leave(ctx.message.author)
if status: 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: 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() @commands.guild_only()
@utils.can_run(manage_guild=True) @utils.can_run(manage_guild=True)
async def blackjack_stop(self, ctx): async def blackjack_stop(self, ctx):
@ -140,10 +146,10 @@ class Player:
value = card.value.value value = card.value.value
face = card.value.name face = card.value.name
if face in ['queen', 'king', 'jack']: if face in ["queen", "king", "jack"]:
for index, t in enumerate(total): for index, t in enumerate(total):
total[index] += 10 total[index] += 10
elif face == 'ace': elif face == "ace":
total = FOIL(total, [1, 11]) total = FOIL(total, [1, 11])
else: else:
for index, t in enumerate(total): for index, t in enumerate(total):
@ -162,7 +168,7 @@ class Player:
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, Player): 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 True
return False return False
@ -179,7 +185,7 @@ class Game:
player = Player(message.author) player = Player(message.author)
self.bj = bj self.bj = bj
self.bot = bot self.bot = bot
self.players = [{'status': 'playing', 'player': player}] self.players = [{"status": "playing", "player": player}]
# Our buffer for players who want to join # 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 # People cannot join in the middle of a game, so we'll add them at the end
self._added_players = [] self._added_players = []
@ -202,7 +208,7 @@ class Game:
del _deck2 del _deck2
self.deck.shuffle() self.deck.shuffle()
# The dealer # The dealer
self.dealer = Player('Dealer') self.dealer = Player("Dealer")
self.min_bet = 5 self.min_bet = 5
self.max_bet = 500 self.max_bet = 500
@ -269,19 +275,23 @@ class Game:
# Our check to make sure a valid 'command' was provided # Our check to make sure a valid 'command' was provided
def check(m): def check(m):
if m.channel == self.channel and m.author == player.member: 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: else:
return False return False
# First lets handle the blackjacks # First lets handle the blackjacks
for entry in [p for p in self.players if p['status'] == 'blackjack']: for entry in [p for p in self.players if p["status"] == "blackjack"]:
player = entry['player'] player = entry["player"]
fmt = "You got a blackjack {0.member.mention}!\n\n{0}".format(player) fmt = "You got a blackjack {0.member.mention}!\n\n{0}".format(player)
await self.channel.send(fmt) await self.channel.send(fmt)
# Loop through each player (as long as their status is playing) and they have bet chips # 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')]: for entry in [
player = entry['player'] 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 # Let them know it's their turn to play
fmt = "It is your turn to play {0.member.mention}\n\n{0}".format(player) 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 # 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 # 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 # Ask if they want to hit or stand
if first: if first:
@ -300,19 +310,19 @@ class Game:
await self.channel.send(fmt) await self.channel.send(fmt)
try: 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: except asyncio.TimeoutError:
await self.channel.send("Took to long! You're standing!") await self.channel.send("Took to long! You're standing!")
entry['status'] = 'stand' entry["status"] = "stand"
else: else:
# If they want to hit # If they want to hit
if 'hit' in msg.content.lower(): if "hit" in msg.content.lower():
self.hit(player) self.hit(player)
await self.channel.send(player) await self.channel.send(player)
# If they want to stand # If they want to stand
elif 'stand' in msg.content.lower(): elif "stand" in msg.content.lower():
self.stand(player) self.stand(player)
elif 'double' in msg.content.lower() and first: elif "double" in msg.content.lower() and first:
self.double(player) self.double(player)
await self.channel.send(player) await self.channel.send(player)
# TODO: Handle double, split # 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 # this, we'll loop 'infinitely', get a list of players who haven't bet yet, and then use the first person in
# that list # that list
while True: 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 everyone has bet/there is no one playing anymore
if len(players) == 0: if len(players) == 0:
break break
entry = players[0] entry = players[0]
player = entry['player'] player = entry["player"]
def check(_msg): def check(_msg):
"""Makes sure the message provided is within the min and max bets""" """Makes sure the message provided is within the min and max bets"""
@ -343,25 +353,33 @@ class Game:
msg_length = int(_msg.content) msg_length = int(_msg.content)
return self.min_bet <= msg_length <= self.max_bet return self.min_bet <= msg_length <= self.max_bet
except ValueError: except ValueError:
return _msg.content.lower() == 'skip' return _msg.content.lower() == "skip"
else: else:
return False return False
fmt = "Your turn to bet {0.member.mention}, your current chips are: {0.chips}\n" \ fmt = (
"Current min bet is {1}, current max bet is {2}\n" \ "Your turn to bet {0.member.mention}, your current chips are: {0.chips}\n"
"Place your bet now (please provide only the number;" \ "Current min bet is {1}, current max bet is {2}\n"
"'skip' if you would like to leave this game)".format(player, self.min_bet, self.max_bet) "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) await self.channel.send(fmt)
try: 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: except asyncio.TimeoutError:
await self.channel.send("You took too long! You're sitting this round out") await self.channel.send(
entry['status'] = 'stand' "You took too long! You're sitting this round out"
)
entry["status"] = "stand"
else: else:
if msg.content.lower() == 'skip': if msg.content.lower() == "skip":
await self.channel.send("Alright, you've been removed from the game") await self.channel.send(
"Alright, you've been removed from the game"
)
self.leave(player.member) self.leave(player.member)
else: else:
num = int(msg.content) num = int(msg.content)
@ -369,7 +387,7 @@ class Game:
if num <= player.chips: if num <= player.chips:
player.bet = num player.bet = num
player.chips -= num player.chips -= num
entry['status'] = 'bet' entry["status"] = "bet"
else: else:
await self.channel.send("You can't bet more than you have!!") await self.channel.send("You can't bet more than you have!!")
@ -387,10 +405,10 @@ class Game:
blackjack = [] blackjack = []
for entry in self.players: for entry in self.players:
player = entry['player'] player = entry["player"]
# Quick check here to ensure the player isn't someone who got added # Quick check here to ensure the player isn't someone who got added
# Specifically right after the betting phase # Specifically right after the betting phase
if not hasattr(player, 'bet'): if not hasattr(player, "bet"):
continue continue
hand = player.hand hand = player.hand
@ -400,7 +418,7 @@ class Game:
# TODO: Handle blackjacks # TODO: Handle blackjacks
# First if is to check If we can possibly win (a bust is an automatic loss, no matter what) # 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 # Payouts for wins are 2 times the bet
if entry['status'] == 'blackjack': if entry["status"] == "blackjack":
if dealer_count != 21: if dealer_count != 21:
player.chips += math.floor(player.bet * 2.5) player.chips += math.floor(player.bet * 2.5)
blackjack.append(player) blackjack.append(player)
@ -442,7 +460,7 @@ class Game:
if player.chips <= 0: if player.chips <= 0:
self._removed_players.append(player) self._removed_players.append(player)
entry['status'] = 'playing' entry["status"] = "playing"
# Now that we've looped through everyone, send the message regarding the outcome # Now that we've looped through everyone, send the message regarding the outcome
fmt = "Round stats:\n" 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 # What we want to do is remove any players that are in the game and have left the guild
for entry in self.players: for entry in self.players:
m = entry['player'].member m = entry["player"].member
if m not in self.channel.guild.members: 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 # 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() self._removed_players.clear()
def _get_player_index(self, player): def _get_player_index(self, player):
"""Provides the index of a certain player""" """Provides the index of a certain player"""
for i, entry in enumerate(self.players): for i, entry in enumerate(self.players):
if entry['player'] == player: if entry["player"] == player:
return i return i
def get_player(self, member): def get_player(self, member):
"""Returns the player object for the discord member provided""" """Returns the player object for the discord member provided"""
for entry in self.players: for entry in self.players:
if entry['player'].member == member: if entry["player"].member == member:
return entry['player'] return entry["player"]
def playing(self, member): def playing(self, member):
"""Returns true if the member provided is currently in this game""" """Returns true if the member provided is currently in this game"""
for entry in self.players: for entry in self.players:
if member == entry['player'].member: if member == entry["player"].member:
return True return True
return False return False
@ -520,13 +540,13 @@ class Game:
for i in range(2): for i in range(2):
for entry in self.players: for entry in self.players:
card = list(self.deck.draw()) 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 # Make sure we detect blackjack here, as this is when it matters
if 21 in entry['player'].count: if 21 in entry["player"].count:
entry['status'] = 'blackjack' entry["status"] = "blackjack"
else: else:
entry['status'] = 'playing' entry["status"] = "playing"
# Also add a card to the dealer's hand # Also add a card to the dealer's hand
card = list(self.deck.draw()) card = list(self.deck.draw())
self.dealer.hand.insert(card) self.dealer.hand.insert(card)
@ -538,7 +558,7 @@ class Game:
if len(self.players) + len(self._added_players) >= self._max_players: if len(self.players) + len(self._added_players) >= self._max_players:
return False return False
player = Player(member) player = Player(member)
entry = {'status': 'playing', 'player': player} entry = {"status": "playing", "player": player}
self._added_players.append(entry) self._added_players.append(entry)
return True return True
@ -549,10 +569,10 @@ class Game:
if player: if player:
# We need to make sure they haven't already bet # We need to make sure they haven't already bet
index = self._get_player_index(player) index = self._get_player_index(player)
if self.players[index]['status'] == 'bet': if self.players[index]["status"] == "bet":
return False return False
else: else:
self.players[index]['status'] = 'left' self.players[index]["status"] = "left"
self._removed_players.append(player) self._removed_players.append(player)
return True return True
else: else:
@ -571,8 +591,8 @@ class Game:
def stand(self, player): def stand(self, player):
"""Causes a player to stand""" """Causes a player to stand"""
for entry in self.players: for entry in self.players:
if entry['player'] == player: if entry["player"] == player:
entry['status'] = 'stand' entry["status"] = "stand"
return return
def hit(self, player): def hit(self, player):
@ -588,10 +608,10 @@ class Game:
if player.bust: if player.bust:
index = self._get_player_index(player) index = self._get_player_index(player)
self.players[index]['status'] = 'bust' self.players[index]["status"] = "bust"
elif 21 in player.count: elif 21 in player.count:
index = self._get_player_index(player) index = self._get_player_index(player)
self.players[index]['status'] = 'stand' self.players[index]["status"] = "stand"
def setup(bot): def setup(bot):

View file

@ -11,13 +11,11 @@ class ConfigException(Exception):
class WrongSettingType(ConfigException): class WrongSettingType(ConfigException):
def __init__(self, message): def __init__(self, message):
self.message = message self.message = message
class MessageFormatError(ConfigException): class MessageFormatError(ConfigException):
def __init__(self, original, keys): def __init__(self, original, keys):
self.original = original self.original = original
self.keys = keys self.keys = keys
@ -27,9 +25,7 @@ class MessageFormatError(ConfigException):
class GuildConfiguration(commands.Cog): class GuildConfiguration(commands.Cog):
"""Handles configuring the different settings that can be used on the bot""" """Handles configuring the different settings that can be used on the bot"""
keys = { keys = {}
}
def _str_to_bool(self, opt, setting): def _str_to_bool(self, opt, setting):
setting = setting.title() setting = setting.title()
@ -48,27 +44,41 @@ class GuildConfiguration(commands.Cog):
if opt == "prefix": if opt == "prefix":
ctx.bot.cache.update_prefix(ctx.guild, setting) ctx.bot.cache.update_prefix(ctx.guild, setting)
try: 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: 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): 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'}" return f"`{opt}` are currently {'enabled' if result is not None and result[opt] else 'disabled'}"
async def _show_channel_options(self, ctx, opt): async def _show_channel_options(self, ctx, opt):
"""For showing options that rely on a certain channel""" """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: if result is None:
return f"You do not have a channel set for {opt}" return f"You do not have a channel set for {opt}"
channel_id = result[opt] channel_id = result[opt]
if channel_id: if channel_id:
channel = ctx.guild.get_channel(channel_id) channel = ctx.guild.get_channel(channel_id)
if channel: 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: else:
return "It looks like you used to have a channel set for this," \ return (
"however the channel has since been deleted" "It looks like you used to have a channel set for this,"
"however the channel has since been deleted"
)
else: else:
return f"You do not have a channel set for {opt}" 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 _handle_show_raffle_alerts = _show_channel_options
async def _handle_show_welcome_msg(self, ctx, setting): 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: try:
msg = result["welcome_msg"].format(server=ctx.guild.name, member=ctx.author.mention) msg = result["welcome_msg"].format(
return f"Your current welcome message will appear like this:\n\n" server=ctx.guild.name, member=ctx.author.mention
)
return f"Your current welcome message will appear like this:\n\n{msg}"
except (AttributeError, TypeError): except (AttributeError, TypeError):
return "You currently have no welcome message setup" return "You currently have no welcome message setup"
async def _handle_show_goodbye_msg(self, ctx, setting): 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: try:
msg = result["goodbye_msg"].format(server=ctx.guild.name, member=ctx.author.mention) msg = result["goodbye_msg"].format(
return f"Your current goodbye message will appear like this:\n\n" server=ctx.guild.name, member=ctx.author.mention
)
return f"Your current goodbye message will appear like this:\n\n{msg}"
except (AttributeError, TypeError): except (AttributeError, TypeError):
return "You currently have no goodbye message setup" return "You currently have no goodbye message setup"
async def _handle_show_prefix(self, ctx, setting): 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: if result is not None:
prefix = result["prefix"] 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" 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): 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"]: if result and result["followed_picarto_channels"]:
try: try:
@ -127,7 +149,9 @@ class GuildConfiguration(commands.Cog):
return "This server is not following any picarto channels" return "This server is not following any picarto channels"
async def _handle_show_ignored_channels(self, ctx, opt): 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"]: if result and result["ignored_channels"]:
try: try:
@ -144,7 +168,9 @@ class GuildConfiguration(commands.Cog):
return "This server is not ignoring any channels" return "This server is not ignoring any channels"
async def _handle_show_ignored_members(self, ctx, opt): 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"]: if result and result["ignored_members"]:
try: try:
@ -161,7 +187,9 @@ class GuildConfiguration(commands.Cog):
return "This server is not ignoring any members" return "This server is not ignoring any members"
async def _handle_show_rules(self, ctx, opt): 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"]: if result and result["rules"]:
try: try:
@ -173,7 +201,9 @@ class GuildConfiguration(commands.Cog):
return "This server has no rules" return "This server has no rules"
async def _handle_show_assignable_roles(self, ctx, opt): 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"]: if result and result["assignable_roles"]:
try: try:
@ -190,7 +220,9 @@ class GuildConfiguration(commands.Cog):
return "This server has no assignable roles" return "This server has no assignable roles"
async def _handle_show_custom_battles(self, ctx, opt): 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"]: if result and result["custom_battles"]:
try: try:
@ -202,7 +234,9 @@ class GuildConfiguration(commands.Cog):
return "This server has no custom battles" return "This server has no custom battles"
async def _handle_show_custom_hugs(self, ctx, opt): 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"]: if result and result["custom_hugs"]:
try: try:
@ -214,10 +248,12 @@ class GuildConfiguration(commands.Cog):
return "This server has no custom hugs" return "This server has no custom hugs"
async def _handle_show_join_role(self, ctx, opt): 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']: if result and result["join_role"]:
role = ctx.guild.get_role(result['join_role']) role = ctx.guild.get_role(result["join_role"])
if role is None: if role is None:
return "You had a role set, but I can't find it...it's most likely been deleted afterwords!" return "You had a role set, but I can't find it...it's most likely been deleted afterwords!"
else: else:
@ -257,7 +293,7 @@ class GuildConfiguration(commands.Cog):
async def _handle_set_welcome_msg(self, ctx, setting): async def _handle_set_welcome_msg(self, ctx, setting):
try: try:
setting.format(member='test', server='test') setting.format(member="test", server="test")
except KeyError as e: except KeyError as e:
raise MessageFormatError(e, ["member", "server"]) raise MessageFormatError(e, ["member", "server"])
else: else:
@ -265,7 +301,7 @@ class GuildConfiguration(commands.Cog):
async def _handle_set_goodbye_msg(self, ctx, setting): async def _handle_set_goodbye_msg(self, ctx, setting):
try: try:
setting.format(member='test', server='test') setting.format(member="test", server="test")
except KeyError as e: except KeyError as e:
raise MessageFormatError(e, ["member", "server"]) raise MessageFormatError(e, ["member", "server"])
else: else:
@ -306,7 +342,9 @@ class GuildConfiguration(commands.Cog):
async def _handle_set_followed_picarto_channels(self, ctx, setting): async def _handle_set_followed_picarto_channels(self, ctx, setting):
user = await utils.request(f"http://api.picarto.tv/v1/channel/name/{setting}") user = await utils.request(f"http://api.picarto.tv/v1/channel/name/{setting}")
if user is None: 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 = """ query = """
UPDATE UPDATE
@ -401,7 +439,7 @@ WHERE
async def _handle_set_custom_hugs(self, ctx, setting): async def _handle_set_custom_hugs(self, ctx, setting):
try: try:
setting.format(user="user") setting.format(user="user")
except (KeyError, ValueError)as e: except (KeyError, ValueError) as e:
raise MessageFormatError(e, ["user"]) raise MessageFormatError(e, ["user"])
else: else:
query = """ query = """
@ -480,7 +518,9 @@ where
async def _handle_remove_followed_picarto_channels(self, ctx, setting=None): async def _handle_remove_followed_picarto_channels(self, ctx, setting=None):
if setting is 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 = """ query = """
UPDATE UPDATE
@ -494,7 +534,9 @@ WHERE
async def _handle_remove_ignored_channels(self, ctx, setting=None): async def _handle_remove_ignored_channels(self, ctx, setting=None):
if setting is 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) channel = await self._get_channel(ctx, setting)
@ -510,7 +552,9 @@ WHERE
async def _handle_remove_ignored_members(self, ctx, setting=None): async def _handle_remove_ignored_members(self, ctx, setting=None):
if setting is 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 # 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) # So first check if it's a digit (the id)
if not setting.isdigit(): if not setting.isdigit():
@ -532,7 +576,9 @@ WHERE
async def _handle_remove_rules(self, ctx, setting=None): async def _handle_remove_rules(self, ctx, setting=None):
if setting is None or not setting.isdigit(): 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 = """ query = """
UPDATE UPDATE
@ -546,7 +592,9 @@ WHERE
async def _handle_remove_assignable_roles(self, ctx, setting=None): async def _handle_remove_assignable_roles(self, ctx, setting=None):
if setting is 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(): if not setting.isdigit():
converter = commands.converter.RoleConverter() converter = commands.converter.RoleConverter()
role = await converter.convert(ctx, setting) role = await converter.convert(ctx, setting)
@ -566,7 +614,9 @@ WHERE
async def _handle_remove_custom_battles(self, ctx, setting=None): async def _handle_remove_custom_battles(self, ctx, setting=None):
if setting is None or not setting.isdigit(): 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: else:
setting = int(setting) setting = int(setting)
@ -582,7 +632,9 @@ WHERE
async def _handle_remove_custom_hugs(self, ctx, setting=None): async def _handle_remove_custom_hugs(self, ctx, setting=None):
if setting is None or not setting.isdigit(): 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: else:
setting = int(setting) setting = int(setting)
@ -609,7 +661,9 @@ WHERE
try: try:
coro = getattr(self, f"_handle_show_{opt}") coro = getattr(self, f"_handle_show_{opt}")
except AttributeError: 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: else:
try: try:
msg = await coro(ctx, opt) msg = await coro(ctx, opt)
@ -620,16 +674,29 @@ WHERE
else: else:
return await ctx.send(msg) 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 # For convenience, if it's None, just create it and return the default values
if settings is None: if settings is None:
await ctx.bot.db.execute("INSERT INTO guilds (id) VALUES ($1)", ctx.guild.id) await ctx.bot.db.execute(
settings = await ctx.bot.db.fetchrow("SELECT * FROM guilds WHERE id=$1", ctx.guild.id) "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 = {} alerts = {}
# This is dirty I know, but oh well... # 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")) channel = ctx.guild.get_channel(settings.get(f"{alert_type}_alerts"))
name = channel.name if channel else None name = channel.name if channel else None
alerts[alert_type] = name alerts[alert_type] = name
@ -712,7 +779,9 @@ WHERE
**{len(settings.get("custom_hugs"))}** **{len(settings.get("custom_hugs"))}**
""".strip() """.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) embed.set_image(url=ctx.guild.icon_url)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -724,11 +793,15 @@ WHERE
try: try:
coro = getattr(self, f"_handle_set_{option}") coro = getattr(self, f"_handle_set_{option}")
except AttributeError: 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: else:
# First make sure there's an entry for this guild before doing anything # First make sure there's an entry for this guild before doing anything
try: 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: except UniqueViolationError:
pass pass
@ -755,7 +828,9 @@ Extraneous args provided: {', '.join(k for k in exc.original.args)}
try: try:
coro = getattr(self, f"_handle_remove_{option}") coro = getattr(self, f"_handle_remove_{option}")
except AttributeError: 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: else:
try: try:
await coro(ctx, setting=setting) await coro(ctx, setting=setting)

View file

@ -8,9 +8,9 @@ from discord.ext import commands
log = logging.getLogger() 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" 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): class StatsUpdate(commands.Cog):
@ -27,39 +27,42 @@ class StatsUpdate(commands.Cog):
server_count = len(self.bot.guilds) server_count = len(self.bot.guilds)
# Carbonitex request # Carbonitex request
carbon_payload = { carbon_payload = {"key": config.carbon_key, "servercount": server_count}
'key': config.carbon_key,
'servercount': server_count
}
async with self.session.post(carbonitex_url, data=carbon_payload) as resp: 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 # Discord.bots.pw request
payload = json.dumps({ payload = json.dumps({"server_count": server_count})
'server_count': server_count
})
headers = { headers = {
'authorization': config.discord_bots_key, "authorization": config.discord_bots_key,
'content-type': 'application/json' "content-type": "application/json",
} }
url = discord_bots_url.format(self.bot.user.id) url = discord_bots_url.format(self.bot.user.id)
async with self.session.post(url, data=payload, headers=headers) as resp: 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 # discordbots.com request
url = discordbots_url.format(self.bot.user.id) url = discordbots_url.format(self.bot.user.id)
payload = { payload = {"server_count": server_count}
"server_count": server_count
}
headers = { headers = {"Authorization": config.discordbots_key}
"Authorization": config.discordbots_key
}
async with self.session.post(url, data=payload, headers=headers) as resp: 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() @commands.Cog.listener()
async def on_guild_join(self, _): async def on_guild_join(self, _):
@ -88,11 +91,13 @@ WHERE
""" """
settings = await self.bot.db.fetchrow(query, member.guild.id) settings = await self.bot.db.fetchrow(query, member.guild.id)
if settings: 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"]: if settings["notify"]:
try: try:
channel = member.guild.get_channel(settings['channel']) channel = member.guild.get_channel(settings["channel"])
await channel.send(message.format(server=member.guild.name, member=member.mention)) await channel.send(
message.format(server=member.guild.name, member=member.mention)
)
# Forbidden for if the channel has send messages perms off # Forbidden for if the channel has send messages perms off
# HTTP Exception to catch any weird happenings # HTTP Exception to catch any weird happenings
# Attribute Error catches when a channel is set, but that channel doesn't exist any more # Attribute Error catches when a channel is set, but that channel doesn't exist any more
@ -100,7 +105,7 @@ WHERE
pass pass
try: try:
role = member.guild.get_role(settings['role']) role = member.guild.get_role(settings["role"])
await member.add_roles(role) await member.add_roles(role)
except (discord.Forbidden, discord.HTTPException, AttributeError): except (discord.Forbidden, discord.HTTPException, AttributeError):
pass pass
@ -122,10 +127,15 @@ AND
""" """
settings = await self.bot.db.fetchrow(query, member.guild.id) settings = await self.bot.db.fetchrow(query, member.guild.id)
if settings: if settings:
message = settings['msg'] or "{member} has left the server, I hope it wasn't because of something I said :c" message = (
channel = member.guild.get_channel(settings['channel']) 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: 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): except (discord.Forbidden, discord.HTTPException, AttributeError):
pass pass

View file

@ -4,7 +4,6 @@ import utils
class Games(commands.Cog): class Games(commands.Cog):
@commands.guild_only() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
@commands.command(aliases=["word_chain", "しりとり"]) @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 kana cannot be used, as no word in Japanese starts with this
The word used cannot be a previously given word The word used cannot be a previously given word
""" """
def grab_letter(word, last=True): def grab_letter(word, last=True):
iterator = reversed(word) if last else iter(word) iterator = reversed(word) if last else iter(word)
@ -41,6 +41,7 @@ class Games(commands.Cog):
# Setup the info needed for the game # Setup the info needed for the game
message = ctx.message message = ctx.message
message.content = start_word message.content = start_word
last_letter = None
words_used = [] words_used = []
while True: while True:
@ -69,7 +70,7 @@ class Games(commands.Cog):
# If we're here, game over, someone messed up # If we're here, game over, someone messed up
await message.add_reaction("") await message.add_reaction("")
if last_letter in ("", ""): 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: else:
await ctx.send(f"Wrong! {message.author.mention} is a loser!") await ctx.send(f"Wrong! {message.author.mention} is a loser!")

View file

@ -12,7 +12,9 @@ class Game:
def __init__(self, word): def __init__(self, word):
self.word = word self.word = word
# This converts everything but spaces to a blank # 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.failed_letters = []
self.guessed_letters = [] self.guessed_letters = []
self.fails = 0 self.fails = 0
@ -24,8 +26,9 @@ class Game:
# Replace every occurence of the guessed letter, with the correct letter # Replace every occurence of the guessed letter, with the correct letter
# Use the one in the word instead of letter, due to capitalization # Use the one in the word instead of letter, due to capitalization
self.blanks = "".join( self.blanks = "".join(
word_letter if letter.lower() == word_letter.lower() else self.blanks[i] for i, word_letter in word_letter if letter.lower() == word_letter.lower() else self.blanks[i]
enumerate(self.word)) for i, word_letter in enumerate(self.word)
)
return True return True
else: else:
self.fails += 1 self.fails += 1
@ -52,15 +55,22 @@ class Game:
man = " ——\n" man = " ——\n"
man += " | |\n" man += " | |\n"
man += " {} |\n".format("o" if self.fails > 0 else " ") man += " {} |\n".format("o" if self.fails > 0 else " ")
man += " {}{}{} |\n".format("/" if self.fails > 1 else " ", "|" if self.fails > 2 else " ", man += " {}{}{} |\n".format(
"\\" if self.fails > 3 else " ") "/" 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 > 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"
man += " ———————\n" man += " ———————\n"
fmt = "```\n{}```".format(man) fmt = "```\n{}```".format(man)
# Then just add the guesses and the blanks to the string # 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 return fmt
@ -77,7 +87,7 @@ class Hangman(commands.Cog):
game.author = ctx.message.author.id game.author = ctx.message.author.id
return game return game
@commands.group(aliases=['hm'], invoke_without_command=True) @commands.group(aliases=["hm"], invoke_without_command=True)
@commands.guild_only() @commands.guild_only()
@commands.cooldown(1, 7, BucketType.user) @commands.cooldown(1, 7, BucketType.user)
@checks.can_run(send_messages=True) @checks.can_run(send_messages=True)
@ -126,7 +136,7 @@ class Hangman(commands.Cog):
await ctx.send(fmt) await ctx.send(fmt)
@hangman.command(name='create', aliases=['start']) @hangman.command(name="create", aliases=["start"])
@commands.guild_only() @commands.guild_only()
@checks.can_run(send_messages=True) @checks.can_run(send_messages=True)
async def create_hangman(self, ctx): 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!") await ctx.send("Sorry but only one Hangman game can be running per server!")
return return
if ctx.guild.id in self.pending_games: 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 return
try: try:
msg = await ctx.message.author.send( msg = await ctx.message.author.send(
"Please respond with a phrase you would like to use for your hangman game in **{}**\n\nPlease keep " "Please respond with a phrase you would like to use for your hangman game in **{}**\n\nPlease keep "
"phrases less than 31 characters".format( "phrases less than 31 characters".format(ctx.message.guild.name)
ctx.message.guild.name)) )
except discord.Forbidden: except discord.Forbidden:
await ctx.send( await ctx.send(
"I can't message you {}! Please allow DM's so I can message you and ask for the hangman phrase you " "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 return
await ctx.send("I have DM'd you {}, please respond there with the phrase you would like to setup".format( await ctx.send(
ctx.message.author.display_name)) "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): def check(m):
return m.channel == msg.channel and len(m.content) <= 30 return m.channel == msg.channel and len(m.content) <= 30
self.pending_games.append(ctx.guild.id) self.pending_games.append(ctx.guild.id)
try: 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: except asyncio.TimeoutError:
self.pending_games.remove(ctx.guild.id) self.pending_games.remove(ctx.guild.id)
await ctx.send( 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 return
else: else:
self.pending_games.remove(ctx.guild.id) 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: if msg.content in forbidden_phrases:
await ctx.send("Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format( await ctx.send(
"\n".join(forbidden_phrases))) "Detected forbidden hangman phrase; current forbidden phrases are: \n{}".format(
"\n".join(forbidden_phrases)
)
)
return return
game = self.create(msg.content, ctx) game = self.create(msg.content, ctx)
# Let them know the game has started, then print the current game so that the blanks are shown # Let them know the game has started, then print the current game so that the blanks are shown
await ctx.send( 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() @commands.guild_only()
@checks.can_run(kick_members=True) @checks.can_run(kick_members=True)
async def stop_game(self, ctx): async def stop_game(self, ctx):
@ -199,7 +222,9 @@ class Hangman(commands.Cog):
return return
del self.games[ctx.message.guild.id] 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): def setup(bot):

View file

@ -18,7 +18,9 @@ class Images(commands.Cog):
url = data["data"]["file_url_size_large"] url = data["data"]["file_url_size_large"]
filename = data["data"]["file_name"] filename = data["data"]["file_name"]
except (KeyError, TypeError): 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: else:
image = await utils.download_image(url) image = await utils.download_image(url)
f = discord.File(image, filename=filename) 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}" 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) @utils.can_run(send_messages=True)
async def cat(self, ctx): async def cat(self, ctx):
"""Use this to print a random cat image. """Use this to print a random cat image.
@ -38,7 +40,7 @@ class Images(commands.Cog):
RESULT: A beautiful picture of a cat o3o""" RESULT: A beautiful picture of a cat o3o"""
url = "http://thecatapi.com/api/images/get" url = "http://thecatapi.com/api/images/get"
opts = {"format": "src"} opts = {"format": "src"}
result = await utils.request(url, attr='url', payload=opts) result = await utils.request(url, attr="url", payload=opts)
try: try:
image = await utils.download_image(result) 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}" 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) @utils.can_run(send_messages=True)
async def doggo(self, ctx): async def doggo(self, ctx):
"""Use this to print a random doggo image. """Use this to print a random doggo image.
EXAMPLE: !doggo EXAMPLE: !doggo
RESULT: A beautiful picture of a dog o3o""" 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: try:
url = result.get("url") url = result.get("url")
filename = re.match("https://random.dog/(.*)", url).group(1) 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}" 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) @utils.can_run(send_messages=True)
async def snek(self, ctx): async def snek(self, ctx):
"""Use this to print a random snek image. """Use this to print a random snek image.
@ -144,11 +146,11 @@ class Images(commands.Cog):
member = ctx.message.author member = ctx.message.author
url = str(member.avatar_url) url = str(member.avatar_url)
if '.gif' not in url: if ".gif" not in url:
url = str(member.avatar_url_as(format='png')) url = str(member.avatar_url_as(format="png"))
filename = 'avatar.png' filename = "avatar.png"
else: else:
filename = 'avatar.gif' filename = "avatar.gif"
if ctx.message.guild.me.permissions_in(ctx.message.channel).attach_files: if ctx.message.guild.me.permissions_in(ctx.message.channel).attach_files:
filedata = await utils.download_image(url) filedata = await utils.download_image(url)
if filedata is None: if filedata is None:
@ -171,23 +173,25 @@ class Images(commands.Cog):
RESULT: A picture of Rainbow Dash!""" RESULT: A picture of Rainbow Dash!"""
if len(search) > 0: 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) # 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)) query = " ".join(
params = {'q': query} 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) nsfw = utils.channel_is_nsfw(ctx.message.channel)
# If this is a nsfw channel, we just need to tack on 'explicit' to the terms # 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 # 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 the channel is not nsfw, we don't need to do anything, as the default filter blocks explicit
if nsfw: if nsfw:
params['q'] += ", (explicit OR suggestive)" params["q"] += ", (explicit OR suggestive)"
params['filter_id'] = 95938 params["filter_id"] = 95938
else: 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 # 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: try:
# Get the response from derpibooru and parse the 'search' result from it # Get the response from derpibooru and parse the 'search' result from it
@ -196,37 +200,47 @@ class Images(commands.Cog):
if data is None: if data is None:
await ctx.send("Sorry but I failed to connect to Derpibooru!") await ctx.send("Sorry but I failed to connect to Derpibooru!")
return return
results = data['images'] results = data["images"]
except KeyError: 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 return
# The first request we've made ensures there are results # 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 # 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: if len(results) > 0:
# Get the total number of pages # 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 # 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) data = await utils.request(url, payload=params)
if data is None: if data is None:
await ctx.send("Sorry but I failed to connect to Derpibooru!") await ctx.send("Sorry but I failed to connect to Derpibooru!")
return return
# Now get the results again # 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 # Get the image link from the now random page'd and random result from that page
index = random.SystemRandom().randint(0, len(results) - 1) index = random.SystemRandom().randint(0, len(results) - 1)
# image_link = 'https://derpibooru.org/{}'.format(results[index]['id']) # image_link = 'https://derpibooru.org/{}'.format(results[index]['id'])
image_link = results[index]['view_url'] image_link = results[index]["view_url"]
else: 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 return
else: else:
# If no search term was provided, search for a random image # If no search term was provided, search for a random image
# .url will be the URL we end up at, not the one requested. # .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 # 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) await ctx.send(image_link)
@commands.command() @commands.command()
@ -242,29 +256,31 @@ class Images(commands.Cog):
# This changes the formatting for queries, so we don't # This changes the formatting for queries, so we don't
# Have to use e621's stupid formatting when using the command # 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 = { params = {
'login': utils.config.e621_user, "login": utils.config.e621_user,
'api_key': utils.config.e621_key, "api_key": utils.config.e621_key,
'limit': 5, "limit": 5,
'tags': tags "tags": tags,
} }
headers = {'User-Agent': utils.config.user_agent} headers = {"User-Agent": utils.config.user_agent}
nsfw = utils.channel_is_nsfw(ctx.message.channel) nsfw = utils.channel_is_nsfw(ctx.message.channel)
# e621 by default does not filter explicit content, so tack on # e621 by default does not filter explicit content, so tack on
# safe/explicit based on if this channel is nsfw or not # 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 # Tack on a random order
params['tags'] += " order:random" params["tags"] += " order:random"
data = await utils.request(url, payload=params, headers=headers) data = await utils.request(url, payload=params, headers=headers)
if data is None: 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 return
# Try to find an image from the list. If there were no results, we're going to attempt to find # 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"]) await ctx.send(image["file"]["url"])
return return
except (ValueError, KeyError): 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 return
# If we're here then there was nothing in the posts, or nothing found that's not blacklisted # If we're here then there was nothing in the posts, or nothing found that's not blacklisted

View file

@ -8,81 +8,83 @@ import discord
import random import random
import functools import functools
battle_outcomes = \ battle_outcomes = [
["A meteor fell on {loser}, {winner} is left standing and has been declared the victor!", "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", "{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!", "{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!", "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." "{loser} tried to dive at {winner} while fighting, somehow they missed and landed in quicksand."
"Try paying more attention next time {loser}", "Try paying more attention next time {loser}",
"{loser} got a little...heated during the battle and ended up getting set on fire. " "{loser} got a little...heated during the battle and ended up getting set on fire. "
"{winner} wins by remaining cool", "{winner} wins by remaining cool",
"Princess Celestia came in and banished {loser} to the moon. Good luck getting into any battles up there", "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}", "{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", "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} 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. " "{winner} and {loser} had an intense staring contest. "
"Sadly, {loser} forgot to breathe and lost much morethan the 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. " "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?", "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...", "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.", "{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.", "{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.", "{winner} realized it was high noon, {loser} never even saw it coming.",
"{loser} spontaneously combusted...lol rip", "{loser} spontaneously combusted...lol rip",
"after many turns {winner} summons exodia and {loser} is sent to the shadow realm", "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, " "{winner} and {loser} sit down for an intense game of chess, "
"in the heat of the moment {winner} forgot they were playing a " "in the heat of the moment {winner} forgot they were playing a "
"game and summoned a real knight", "game and summoned a real knight",
"{winner} challenges {loser} to rock paper scissors, " "{winner} challenges {loser} to rock paper scissors, "
"unfortunately for {loser}, {winner} chose scissors and stabbed them", "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", "{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}", "{loser} trips down some stairs on their way to the battle with {winner}",
"{winner} books {loser} a one way ticket to Flugendorf prison", "{winner} books {loser} a one way ticket to Flugendorf prison",
"{loser} was already dead", "{loser} was already dead",
"{loser} was crushed under the weight of expectations", "{loser} was crushed under the weight of expectations",
"{loser} was wearing a redshirt and it was their first day", "{loser} was wearing a redshirt and it was their first day",
"{winner} and {loser} were walking along when suddenly {loser} " "{winner} and {loser} were walking along when suddenly {loser} "
"got kidnapped by a flying monkey; hope they had water with them", "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} 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} used multiple simultaneous devestating defensive deep strikes to overwhelm {loser}",
"{winner} and {loser} engage in a dance off; {winner} wiped the floor with {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, " "{loser} tried to hide in the sand to catch {winner} off guard, "
"unfortunately looks like a Giant Antlion had the same " "unfortunately looks like a Giant Antlion had the same "
"idea for him", "idea for him",
"{loser} was busy playing trash videogames the night before the fight and collapsed before {winner}", "{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} 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} 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", "{winner} is the cure and {loser} is the disease....well {loser} was the disease",
"{loser} talked their mouth off at {winner}...literally...", "{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", "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} was too scared by the illuminati and extra-dimensional talking horses to show up",
"{loser} didn't press x enough to not die", "{loser} didn't press x enough to not die",
"{winner} and {loser} go fishing to settle their debate, " "{winner} and {loser} go fishing to settle their debate, "
"{winner} caught a sizeable fish and {loser} caught a boot older than time", "{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"] "{winner} did a hero landing and {loser} was so surprised they gave up immediately",
]
hugs = \ hugs = [
["*hugs {user}.*", "*hugs {user}.*",
"*tackles {user} for a hug.*", "*tackles {user} for a hug.*",
"*drags {user} into her dungeon where hugs ensue*", "*drags {user} into her dungeon where hugs ensue*",
"*pulls {user} to the side for a warm hug*", "*pulls {user} to the side for a warm hug*",
"*goes out to buy a big enough blanket to embrace {user}*", "*goes out to buy a big enough blanket to embrace {user}*",
"*hard codes an electric hug to {user}*", "*hard codes an electric hug to {user}*",
"*hires mercenaries to take {user} out....to a nice dinner*", "*hires mercenaries to take {user} out....to a nice dinner*",
"*pays $10 to not touch {user}*", "*pays $10 to not touch {user}*",
"*clones herself to create a hug pile with {user}*", "*clones herself to create a hug pile with {user}*",
"*orders an airstrike of hugs {user}*", "*orders an airstrike of hugs {user}*",
"*glomps {user}*", "*glomps {user}*",
"*hears a knock at her door, opens it, finds {user} and hugs them excitedly*", "*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}*", "*goes in for a punch but misses and ends up hugging {user}*",
"*hugs {user} from behind*", "*hugs {user} from behind*",
"*denies a hug from {user}*", "*denies a hug from {user}*",
"*does a hug to {user}*", "*does a hug to {user}*",
"*lets {user} cuddle nonchalantly*", "*lets {user} cuddle nonchalantly*",
"*cuddles {user}*", "*cuddles {user}*",
"*burrows underground and pops up underneath {user} she hugs their legs.*", "*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.*"] "*approaches {user} after having gone to the gym for several months and almost crushes them.*",
]
class Interaction(commands.Cog): class Interaction(commands.Cog):
@ -142,7 +144,7 @@ class Interaction(commands.Cog):
settings = await ctx.bot.db.fetchrow( settings = await ctx.bot.db.fetchrow(
"SELECT custom_hugs, include_default_hugs FROM guilds WHERE id = $1", "SELECT custom_hugs, include_default_hugs FROM guilds WHERE id = $1",
ctx.guild.id ctx.guild.id,
) )
msgs = hugs.copy() msgs = hugs.copy()
if settings: if settings:
@ -158,7 +160,7 @@ class Interaction(commands.Cog):
fmt = random.SystemRandom().choice(msgs) fmt = random.SystemRandom().choice(msgs)
await ctx.send(fmt.format(user=user.display_name)) await ctx.send(fmt.format(user=user.display_name))
@commands.command(aliases=['1v1']) @commands.command(aliases=["1v1"])
@commands.guild_only() @commands.guild_only()
@commands.cooldown(1, 20, BucketType.user) @commands.cooldown(1, 20, BucketType.user)
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
@ -169,8 +171,10 @@ class Interaction(commands.Cog):
RESULT: A battle to the death""" RESULT: A battle to the death"""
# First check if everyone was mentioned # First check if everyone was mentioned
if ctx.message.mention_everyone: if ctx.message.mention_everyone:
await ctx.send("You want to battle {} people? Good luck with that...".format( await ctx.send(
len(ctx.channel.members) - 1) "You want to battle {} people? Good luck with that...".format(
len(ctx.channel.members) - 1
)
) )
return return
# Then check if nothing was provided # Then check if nothing was provided
@ -188,7 +192,9 @@ class Interaction(commands.Cog):
# Then check if the person used is the author # Then check if the person used is the author
if ctx.author.id == player2.id: if ctx.author.id == player2.id:
ctx.command.reset_cooldown(ctx) 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 return
# Check if the person battled is me # Check if the person battled is me
if ctx.bot.user.id == player2.id: if ctx.bot.user.id == player2.id:
@ -202,14 +208,18 @@ class Interaction(commands.Cog):
return return
if not self.can_receive_battle(player2): if not self.can_receive_battle(player2):
ctx.command.reset_cooldown(ctx) 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 return
# Add the author and player provided in a new battle # Add the author and player provided in a new battle
battle = self.start_battle(ctx.author, player2) battle = self.start_battle(ctx.author, player2)
fmt = f"{ctx.author.mention} has challenged you to a battle {player2.mention}\n" \ fmt = (
f"{ctx.prefix}accept or {ctx.prefix}decline" 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 # Add a call to turn off battling, if the battle is not accepted/declined in 3 minutes
part = functools.partial(self.battling_off, battle) part = functools.partial(self.battling_off, battle)
ctx.bot.loop.call_later(180, part) ctx.bot.loop.call_later(180, part)
@ -231,14 +241,16 @@ class Interaction(commands.Cog):
return return
if ctx.guild.get_member(battle.initiator.id) is None: 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) self.battling_off(battle)
return return
# Lets get the settings # Lets get the settings
settings = await ctx.bot.db.fetchrow( settings = await ctx.bot.db.fetchrow(
"SELECT custom_battles, include_default_battles FROM guilds WHERE id = $1", "SELECT custom_battles, include_default_battles FROM guilds WHERE id = $1",
ctx.guild.id ctx.guild.id,
) )
msgs = battle_outcomes msgs = battle_outcomes
if settings: if settings:
@ -280,7 +292,7 @@ WHERE id = any($2)
old_winner = old_loser = None old_winner = old_loser = None
for result in results: for result in results:
if result['id'] == loser.id: if result["id"] == loser.id:
old_loser = result old_loser = result
else: else:
old_winner = result old_winner = result
@ -310,9 +322,9 @@ VALUES
await ctx.bot.db.execute( await ctx.bot.db.execute(
update_query, update_query,
loser_rating, loser_rating,
old_loser['battle_wins'], old_loser["battle_wins"],
old_loser['battle_losses'] + 1, old_loser["battle_losses"] + 1,
loser.id loser.id,
) )
else: else:
await ctx.bot.db.execute(insert_query, loser.id, loser_rating, 0, 1) await ctx.bot.db.execute(insert_query, loser.id, loser_rating, 0, 1)
@ -320,9 +332,9 @@ VALUES
await ctx.bot.db.execute( await ctx.bot.db.execute(
update_query, update_query,
winner_rating, winner_rating,
old_winner['battle_wins'] + 1, old_winner["battle_wins"] + 1,
old_winner['battle_losses'], old_winner["battle_losses"],
winner.id winner.id,
) )
else: else:
await ctx.bot.db.execute(insert_query, winner.id, winner_rating, 1, 0) 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 new_winner_rank = new_loser_rank = None
for result in results: for result in results:
if result['id'] == loser.id: if result["id"] == loser.id:
new_loser_rank = result['rank'] new_loser_rank = result["rank"]
else: else:
new_winner_rank = result['rank'] new_winner_rank = result["rank"]
fmt = fmt.format(winner=winner.display_name, loser=loser.display_name) fmt = fmt.format(winner=winner.display_name, loser=loser.display_name)
if old_winner: if old_winner:
fmt += "\n{} - Rank: {} ( +{} )".format( 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: else:
fmt += "\n{} - Rank: {}".format(winner.display_name, new_winner_rank) 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) amount = await ctx.bot.db.fetchrow(query, booper.id, boopee.id)
if amount is None: if amount is None:
amount = 1 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: else:
replacement_query = "UPDATE boops SET amount=$3 WHERE booper=$1 AND boopee=$2" replacement_query = (
amount = amount['amount'] + 1 "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) await ctx.bot.db.execute(replacement_query, booper.id, boopee.id, amount)
class Battle: class Battle:
def __init__(self, initiator, receiver): def __init__(self, initiator, receiver):
self.initiator = initiator self.initiator = initiator
self.receiver = receiver self.receiver = receiver
self.rand = random.SystemRandom() self.rand = random.SystemRandom()
def is_initiator(self, player): 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): 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): def is_battling(self, player):
return self.is_initiator(player) or self.is_receiver(player) return self.is_initiator(player) or self.is_receiver(player)

View file

@ -12,7 +12,7 @@ class Links(commands.Cog):
"""This class contains all the commands that make HTTP requests """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""" 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) @utils.can_run(send_messages=True)
async def google(self, ctx, *, query: str): async def google(self, ctx, *, query: str):
"""Searches google for a provided query """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 # Turn safe filter on or off, based on whether or not this is a nsfw channel
nsfw = utils.channel_is_nsfw(ctx.message.channel) nsfw = utils.channel_is_nsfw(ctx.message.channel)
safe = 'off' if nsfw else 'on' safe = "off" if nsfw else "on"
params = {'q': query, params = {"q": query, "safe": safe, "hl": "en", "cr": "countryUS"}
'safe': safe,
'hl': 'en',
'cr': 'countryUS'}
# Our format we'll end up using to send to the channel # Our format we'll end up using to send to the channel
fmt = "" fmt = ""
# First make the request to google to get the results # 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: if data is None:
await ctx.send("I failed to connect to google! (That can happen??)") await ctx.send("I failed to connect to google! (That can happen??)")
return return
# Convert to a BeautifulSoup element and loop through each result clasified by h3 tags with a class of 'r' # 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 # Get the link's href tag, which looks like q=[url here]&sa
# Use a lookahead and lookbehind to find this url exactly # Use a lookahead and lookbehind to find this url exactly
try: 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: except AttributeError:
await ctx.send("I couldn't find any results for {}!".format(query)) await ctx.send("I couldn't find any results for {}!".format(query))
return return
# Get the next sibling, find the span where the description is, and get the text from this # Get the next sibling, find the span where the description is, and get the text from this
try: try:
description = element.next_sibling.find('span', class_='st').text description = element.next_sibling.find("span", class_="st").text
except Exception: except Exception:
description = "" description = ""
# Add this to our text we'll use to send # 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) fmt = "**Top 3 results for the query** _{}_:{}".format(query, fmt)
await ctx.send(fmt) await ctx.send(fmt)
@commands.command(aliases=['yt']) @commands.command(aliases=["yt"])
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def youtube(self, ctx, *, query: str): async def youtube(self, ctx, *, query: str):
"""Searches youtube for a provided query """Searches youtube for a provided query
@ -73,10 +74,7 @@ class Links(commands.Cog):
RESULT: Cat videos!""" RESULT: Cat videos!"""
key = utils.youtube_key key = utils.youtube_key
url = "https://www.googleapis.com/youtube/v3/search" url = "https://www.googleapis.com/youtube/v3/search"
params = {'key': key, params = {"key": key, "part": "snippet, id", "type": "video", "q": query}
'part': 'snippet, id',
'type': 'video',
'q': query}
data = await utils.request(url, payload=params) data = await utils.request(url, payload=params)
@ -85,16 +83,20 @@ class Links(commands.Cog):
return return
try: try:
result = data['items'][0] result = data["items"][0]
except IndexError: 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 return
result_url = "https://youtube.com/watch?v={}".format(result['id']['videoId']) result_url = "https://youtube.com/watch?v={}".format(result["id"]["videoId"])
title = result['snippet']['title'] title = result["snippet"]["title"]
description = result['snippet']['description'] 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) await ctx.send(fmt)
@commands.command() @commands.command()
@ -106,10 +108,12 @@ class Links(commands.Cog):
RESULT: A link to the wikipedia article for the word test""" 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 # 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" base_url = "https://en.wikipedia.org/w/api.php"
params = {"action": "query", params = {
"list": "search", "action": "query",
"format": "json", "list": "search",
"srsearch": query} "format": "json",
"srsearch": query,
}
data = await utils.request(base_url, payload=params) 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!") await ctx.send("Sorry but I failed to connect to Wikipedia!")
return return
if len(data['query']['search']) == 0: if len(data["query"]["search"]) == 0:
await ctx.send("I could not find any results with that term, I tried my best :c") await ctx.send(
"I could not find any results with that term, I tried my best :c"
)
return return
# Wiki articles' URLs are in the format https://en.wikipedia.org/wiki/[Titlehere] # Wiki articles' URLs are in the format https://en.wikipedia.org/wiki/[Titlehere]
# Replace spaces with %20 # Replace spaces with %20
url = "https://en.wikipedia.org/wiki/{}".format(data['query']['search'][0]['title'].replace(' ', '%20')) url = "https://en.wikipedia.org/wiki/{}".format(
snippet = data['query']['search'][0]['snippet'] 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 # 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 # These are the only ones I've encountered so far through testing, there may be more though
snippet = re.sub('<span class=\\"searchmatch\\">', '', snippet) snippet = re.sub('<span class=\\"searchmatch\\">', "", snippet)
snippet = re.sub('</span>', '', snippet) snippet = re.sub("</span>", "", snippet)
snippet = re.sub('&quot;', '"', snippet) snippet = re.sub("&quot;", '"', snippet)
await ctx.send( await ctx.send(
"Here is the best match I found with the query `{}`:\nURL: <{}>\nSnippet: \n```\n{}```".format(query, url, "Here is the best match I found with the query `{}`:\nURL: <{}>\nSnippet: \n```\n{}```".format(
snippet)) query, url, snippet
)
)
@commands.command() @commands.command()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
@ -151,11 +161,11 @@ class Links(commands.Cog):
return return
# List is the list of definitions found, if it's empty then nothing was found # 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!") await ctx.send("No result with that term!")
# If the list is not empty, use the first result and print it's defintion # If the list is not empty, use the first result and print it's defintion
else: else:
entries = [x['definition'] for x in data['list']] entries = [x["definition"] for x in data["list"]]
try: try:
pages = utils.Pages(ctx, entries=entries[:5], per_page=1) pages = utils.Pages(ctx, entries=entries[:5], per_page=1)
await pages.paginate() await pages.paginate()
@ -163,7 +173,7 @@ class Links(commands.Cog):
await ctx.send(str(e)) await ctx.send(str(e))
# Urban dictionary has some long definitions, some might not be able to be sent # Urban dictionary has some long definitions, some might not be able to be sent
except discord.HTTPException: 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: except KeyError:
await ctx.send("Sorry but I failed to connect to urban dictionary!") await ctx.send("Sorry but I failed to connect to urban dictionary!")
else: else:

View file

@ -15,31 +15,36 @@ def _command_signature(cmd):
result = [cmd.qualified_name] result = [cmd.qualified_name]
if cmd.usage: if cmd.usage:
result.append(cmd.usage) result.append(cmd.usage)
return ' '.join(result) return " ".join(result)
params = cmd.clean_params params = cmd.clean_params
if not params: if not params:
return ' '.join(result) return " ".join(result)
for name, param in params.items(): for name, param in params.items():
if param.default is not param.empty: if param.default is not param.empty:
# We don't want None or '' to trigger the [name=value] case and instead it should # 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. # 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: if should_print:
result.append(f'[{name}={param.default!r}]') result.append(f"[{name}={param.default!r}]")
else: else:
result.append(f'[{name}]') result.append(f"[{name}]")
elif param.kind == param.VAR_POSITIONAL: elif param.kind == param.VAR_POSITIONAL:
result.append(f'[{name}...]') result.append(f"[{name}...]")
else: else:
result.append(f'<{name}>') result.append(f"<{name}>")
return ' '.join(result) return " ".join(result)
class Miscellaneous(commands.Cog): class Miscellaneous(commands.Cog):
"""Core commands, these are the miscallaneous commands that don't fit into other categories'""" """Core commands, these are the miscallaneous commands that don't fit into other categories'"""
process = psutil.Process() process = psutil.Process()
process.cpu_percent() process.cpu_percent()
@ -56,7 +61,7 @@ class Miscellaneous(commands.Cog):
entity = ctx.bot.get_cog(command) or ctx.bot.get_command(command) entity = ctx.bot.get_cog(command) or ctx.bot.get_command(command)
if entity is None: if entity is None:
clean = command.replace('@', '@\u200b') clean = command.replace("@", "@\u200b")
return await ctx.send(f'Command or category "{clean}" not found.') return await ctx.send(f'Command or category "{clean}" not found.')
elif isinstance(entity, commands.Command): elif isinstance(entity, commands.Command):
p = await utils.HelpPaginator.from_command(ctx, entity) p = await utils.HelpPaginator.from_command(ctx, entity)
@ -73,14 +78,18 @@ class Miscellaneous(commands.Cog):
if entity: if entity:
entity = ctx.bot.get_cog(entity) or ctx.bot.get_command(entity) entity = ctx.bot.get_cog(entity) or ctx.bot.get_command(entity)
if entity is None: if entity is None:
fmt = "Hello! Here is a list of the sections of commands that I have " \ fmt = (
"(there are a lot of commands so just start with the sections...I know, I'm pretty great)\n" "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 += "To use a command's paramaters, you need to know the notation for them:\n"
fmt += "\t<argument> This means the argument is __**required**__.\n" fmt += "\t<argument> This means the argument is __**required**__.\n"
fmt += "\t[argument] This means the argument is __**optional**__.\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[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 += "\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" fmt += "**CASE MATTERS** Sections are in `Title Case` and commands are in `lower case`\n\n"
chunks.append(fmt) chunks.append(fmt)
@ -100,14 +109,20 @@ class Miscellaneous(commands.Cog):
chunks.append(tmp) chunks.append(tmp)
else: else:
cmds = sorted(entity.get_commands(), key=lambda c: c.name) 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 = "Here are a list of commands under the section {}\n".format(
fmt += "Type `{}help command` to get more help on a specific command\n\n".format(ctx.prefix) entity.__class__.__name__
)
fmt += "Type `{}help command` to get more help on a specific command\n\n".format(
ctx.prefix
)
chunks.append(fmt) chunks.append(fmt)
for command in cmds: for command in cmds:
for subcommand in command.walk_commands(): 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: if len(chunks[len(chunks) - 1] + tmp) > 2000:
chunks.append(tmp) chunks.append(tmp)
else: else:
@ -115,7 +130,8 @@ class Miscellaneous(commands.Cog):
if utils.dev_server: if utils.dev_server:
tmp = "\n\nIf I'm having issues, then please visit the dev server and ask for help. {}".format( 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: if len(chunks[len(chunks) - 1] + tmp) > 2000:
chunks.append(tmp) chunks.append(tmp)
else: else:
@ -130,7 +146,9 @@ class Miscellaneous(commands.Cog):
for chunk in chunks: for chunk in chunks:
await destination.send(chunk) await destination.send(chunk)
except (discord.Forbidden, discord.HTTPException): 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: else:
if ctx.guild and destination == ctx.author: if ctx.guild and destination == ctx.author:
await ctx.send("I have just DM'd you some information about me!") 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): async def ping(self, ctx):
"""Returns the latency between the server websocket, and between reading messages""" """Returns the latency between the server websocket, and between reading messages"""
msg_latency = datetime.datetime.utcnow() - ctx.message.created_at 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) fmt += "\nWebsocket latency {0:.2f} seconds".format(ctx.bot.latency)
await ctx.send(fmt) await ctx.send(fmt)
@ -191,7 +211,7 @@ class Miscellaneous(commands.Cog):
"september": 9, "september": 9,
"october": 10, "october": 10,
"november": 11, "november": 11,
"december": 12 "december": 12,
} }
# In month was not passed, use the current month # In month was not passed, use the current month
if month is None: if month is None:
@ -208,7 +228,7 @@ class Miscellaneous(commands.Cog):
cal = calendar.TextCalendar().formatmonth(year, month) cal = calendar.TextCalendar().formatmonth(year, month)
await ctx.send("```\n{}```".format(cal)) await ctx.send("```\n{}```".format(cal))
@commands.command(aliases=['about']) @commands.command(aliases=["about"])
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def info(self, ctx): async def info(self, ctx):
"""This command can be used to print out some of my information""" """This command can be used to print out some of my information"""
@ -222,13 +242,15 @@ class Miscellaneous(commands.Cog):
if utils.patreon_link: if utils.patreon_link:
description += "\n[Patreon]({})".format(utils.patreon_link) description += "\n[Patreon]({})".format(utils.patreon_link)
# Now creat the object # Now creat the object
opts = {'title': 'Bonfire', opts = {
'description': description, "title": "Bonfire",
'colour': discord.Colour.green()} "description": description,
"colour": discord.Colour.green(),
}
# Set the owner # Set the owner
embed = discord.Embed(**opts) 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) embed.set_author(name=str(ctx.bot.owner), icon_url=ctx.bot.owner.avatar_url)
# Setup the process statistics # Setup the process statistics
@ -237,10 +259,12 @@ class Miscellaneous(commands.Cog):
memory_usage = self.process.memory_full_info().uss / 1024 ** 2 memory_usage = self.process.memory_full_info().uss / 1024 ** 2
cpu_usage = self.process.cpu_percent() cpu_usage = self.process.cpu_percent()
value += 'Memory: {:.2f} MiB'.format(memory_usage) value += "Memory: {:.2f} MiB".format(memory_usage)
value += '\nCPU: {}%'.format(cpu_usage) value += "\nCPU: {}%".format(cpu_usage)
if hasattr(ctx.bot, 'uptime'): if hasattr(ctx.bot, "uptime"):
value += "\nUptime: {}".format((pendulum.now(tz="UTC") - ctx.bot.uptime).in_words()) value += "\nUptime: {}".format(
(pendulum.now(tz="UTC") - ctx.bot.uptime).in_words()
)
embed.add_field(name=name, value=value, inline=False) embed.add_field(name=name, value=value, inline=False)
# Setup the user and guild statistics # 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 # Lets make this one a list and join it at the end
value = [] value = []
hm = ctx.bot.get_cog('Hangman') hm = ctx.bot.get_cog("Hangman")
ttt = ctx.bot.get_cog('TicTacToe') ttt = ctx.bot.get_cog("TicTacToe")
bj = ctx.bot.get_cog('Blackjack') bj = ctx.bot.get_cog("Blackjack")
interaction = ctx.bot.get_cog('Interaction') interaction = ctx.bot.get_cog("Interaction")
if hm: if hm:
value.append("Hangman games: {}".format(len(hm.games))) value.append("Hangman games: {}".format(len(hm.games)))
@ -271,7 +295,7 @@ class Miscellaneous(commands.Cog):
value.append("Blackjack games: {}".format(len(bj.games))) value.append("Blackjack games: {}".format(len(bj.games)))
if interaction: if interaction:
count_battles = 0 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) count_battles += len(battles)
value.append("Battles running: {}".format(len(bj.games))) value.append("Battles running: {}".format(len(bj.games)))
embed.add_field(name=name, value="\n".join(value), inline=False) embed.add_field(name=name, value="\n".join(value), inline=False)
@ -285,12 +309,18 @@ class Miscellaneous(commands.Cog):
EXAMPLE: !uptime EXAMPLE: !uptime
RESULT: A BAJILLION DAYS""" RESULT: A BAJILLION DAYS"""
if hasattr(ctx.bot, 'uptime'): if hasattr(ctx.bot, "uptime"):
await ctx.send("Uptime: ```\n{}```".format((pendulum.now(tz="UTC") - ctx.bot.uptime).in_words())) await ctx.send(
"Uptime: ```\n{}```".format(
(pendulum.now(tz="UTC") - ctx.bot.uptime).in_words()
)
)
else: 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) @utils.can_run(send_messages=True)
async def addbot(self, ctx): async def addbot(self, ctx):
"""Provides a link that you can use to add me to a server """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.attach_files = True
perms.add_reactions = True perms.add_reactions = True
app_info = await ctx.bot.application_info() app_info = await ctx.bot.application_info()
await ctx.send("Use this URL to add me to a server that you'd like!\n<{}>" await ctx.send(
.format(discord.utils.oauth_url(app_info.id, perms))) "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) @commands.command(enabled=False)
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
@ -337,11 +370,11 @@ class Miscellaneous(commands.Cog):
try: try:
# We do not want to try to convert the dice, because we want d# to # We do not want to try to convert the dice, because we want d# to
# be a valid notation # be a valid notation
dice = re.search("(\d*)d(\d*)", notation).group(1) dice = re.search(r"(\d*)d(\d*)", notation).group(1)
num = int(re.search("(\d*)d(\d*)", notation).group(2)) num = int(re.search(r"(\d*)d(\d*)", notation).group(2))
# Attempt to get addition/subtraction # Attempt to get addition/subtraction
add = re.search("\+ ?(\d+)", notation) add = re.search(r"\+ ?(\d+)", notation)
subtract = re.search("- ?(\d+)", notation) subtract = re.search(r"- ?(\d+)", notation)
# Check if something like ed3 was provided, or something else entirely # Check if something like ed3 was provided, or something else entirely
# was provided # was provided
except (AttributeError, ValueError): except (AttributeError, ValueError):
@ -360,7 +393,9 @@ class Miscellaneous(commands.Cog):
await ctx.send("What die has more than 100 sides? Please, calm down") await ctx.send("What die has more than 100 sides? Please, calm down")
return return
if num <= 1: 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 return
nums = [random.SystemRandom().randint(1, num) for _ in range(0, int(dice))] 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) value_str = ", ".join("{}".format(x) for x in nums)
if dice == 1: 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 ctx, num, value_str
) )
if add or subtract: if add or subtract:
@ -386,7 +421,7 @@ class Miscellaneous(commands.Cog):
fmt += " - {}".format(subtract) fmt += " - {}".format(subtract)
fmt += ")" fmt += ")"
else: 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 ctx, dice, num, value_str
) )
if add or subtract: if add or subtract:

View file

@ -48,7 +48,9 @@ class Moderation(commands.Cog):
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@utils.can_run(ban_members=True) @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 """Used to ban a member
This can be used to ban someone preemptively as well. 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 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 EXAMPLE: !purge 50
RESULT: -50 messages in this channel""" 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...") await ctx.send("I do not have permission to delete messages...")
return return
try: try:
@ -80,10 +84,12 @@ class Moderation(commands.Cog):
await ctx.message.delete() await ctx.message.delete()
except discord.HTTPException: except discord.HTTPException:
try: try:
await ctx.message.channel.send("Detected messages that are too far " await ctx.message.channel.send(
"back for me to delete; I can only bulk delete messages" "Detected messages that are too far "
" that are under 14 days old.") "back for me to delete; I can only bulk delete messages"
except: " that are under 14 days old."
)
except discord.Forbidden:
pass pass
@commands.command() @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 # 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 # 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...") await ctx.send("I do not have permission to delete messages...")
return return
@ -137,7 +145,7 @@ class Moderation(commands.Cog):
try: try:
await msg.delete() await msg.delete()
count += 1 count += 1
except: except discord.HTTPException:
pass pass
if count >= limit: if count >= limit:
break break
@ -147,7 +155,7 @@ class Moderation(commands.Cog):
try: try:
await msg.delete() await msg.delete()
await ctx.message.delete() await ctx.message.delete()
except: except discord.HTTPException:
pass pass

View file

@ -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 # 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 # 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 # 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', check_g_stats = [
'cards', 'damage_done', 'healing_done', 'multikills'] "eliminations",
check_o_stats = ['wins'] "deaths",
"kpd",
"wins",
"losses",
"time_played",
"cards",
"damage_done",
"healing_done",
"multikills",
]
check_o_stats = ["wins"]
log = logging.getLogger() log = logging.getLogger()
@ -36,7 +46,7 @@ class Overwatch(commands.Cog):
EXAMPLE: !ow stats @OtherPerson Junkrat EXAMPLE: !ow stats @OtherPerson Junkrat
RESULT: Whether or not you should unfriend this person because they're a dirty rat""" RESULT: Whether or not you should unfriend this person because they're a dirty rat"""
user = user or ctx.message.author 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: if bt is None:
await ctx.send("I do not have this user's battletag saved!") await ctx.send("I do not have this user's battletag saved!")
@ -52,40 +62,60 @@ class Overwatch(commands.Cog):
log.info(data) log.info(data)
region = [x for x in data.keys() if data[x] is not None and x in ['us', 'any', 'kr', 'eu']][0] region = [
stats = data[region]['stats']['quickplay'] 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 output_data = [
k in check_g_stats] (k.title().replace("_", " "), r)
for k, r in stats["game_stats"].items()
if k in check_g_stats
]
else: else:
# If there was a hero provided, search for a user's data on that hero # 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) url = BASE_URL + "{}/heroes".format(bt)
data = await utils.request(url) data = await utils.request(url)
if data is None: if data is None:
await ctx.send("I couldn't connect to overwatch at the moment!") await ctx.send("I couldn't connect to overwatch at the moment!")
return return
region = [x for x in data.keys() if data[x] is not None and x in ['us', 'any', 'kr', 'eu']][0] region = [
stats = data[region]['heroes']['stats']['quickplay'].get(hero) 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: if stats is None:
fmt = "I couldn't find data with that hero, make sure that is a valid hero, " \ fmt = (
"otherwise {} has never used the hero {} before!".format(user.display_name, hero) "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) await ctx.send(fmt)
return return
# Same list comprehension as before # Same list comprehension as before
output_data = [(k.title().replace("_", " "), r) for k, r in stats['general_stats'].items() if output_data = [
k in check_g_stats] (k.title().replace("_", " "), r)
for k, r in stats['hero_stats'].items(): 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)) output_data.append((k.title().replace("_", " "), r))
try: try:
banner = await utils.create_banner(user, "Overwatch", output_data) 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): except (FileNotFoundError, discord.Forbidden):
fmt = "\n".join("{}: {}".format(k, r) for k, r in output_data) 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") @ow.command(name="add")
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
@ -95,7 +125,6 @@ class Overwatch(commands.Cog):
EXAMPLE: !ow add Username#1234 EXAMPLE: !ow add Username#1234
RESULT: Your battletag is now saved""" RESULT: Your battletag is now saved"""
# Battletags are normally provided like name#id # Battletags are normally provided like name#id
# However the API needs this to be a -, so repliace # with - if it exists # However the API needs this to be a -, so repliace # with - if it exists
bt = bt.replace("#", "-") bt = bt.replace("#", "-")
@ -106,32 +135,34 @@ class Overwatch(commands.Cog):
url = BASE_URL + "{}/stats".format(bt) url = BASE_URL + "{}/stats".format(bt)
data = await utils.request(url) data = await utils.request(url)
if data is None: if data is None:
await ctx.send("Profile does not exist! Battletags are picky, " await ctx.send(
"format needs to be `user#xxxx`. Capitalization matters") "Profile does not exist! Battletags are picky, "
"format needs to be `user#xxxx`. Capitalization matters"
)
return return
# Now just save the battletag # Now just save the battletag
entry = { entry = {"member_id": key, "battletag": bt}
'member_id': key,
'battletag': bt
}
await ctx.bot.db.save('overwatch', entry) await ctx.bot.db.save("overwatch", entry)
await ctx.send("I have just saved your battletag {}".format(ctx.message.author.mention)) 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) @utils.can_run(send_messages=True)
async def delete(self, ctx): async def delete(self, ctx):
"""Removes your battletag from the records """Removes your battletag from the records
EXAMPLE: !ow delete EXAMPLE: !ow delete
RESULT: Your battletag is no longer saved""" RESULT: Your battletag is no longer saved"""
entry = { entry = {"member_id": str(ctx.message.author.id), "battletag": None}
'member_id': str(ctx.message.author.id), await ctx.bot.db.save("overwatch", entry)
'battletag': None await ctx.send(
} "I no longer have your battletag saved {}".format(
await ctx.bot.db.save('overwatch', entry) ctx.message.author.mention
await ctx.send("I no longer have your battletag saved {}".format(ctx.message.author.mention)) )
)
def setup(bot): def setup(bot):

View file

@ -5,19 +5,21 @@ import discord
import inspect import inspect
import textwrap import textwrap
import traceback import traceback
import subprocess
import io import io
from contextlib import redirect_stdout from contextlib import redirect_stdout
def get_syntax_error(e): def get_syntax_error(e):
if e.text is None: if e.text is None:
return '```py\n{0.__class__.__name__}: {0}\n```'.format(e) 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.text}{1:>{0.offset}}\n{2}: {0}```".format(
e, "^", type(e).__name__
)
class Owner(commands.Cog): class Owner(commands.Cog):
"""Commands that can only be used by the owner of the bot, bot management commands""" """Commands that can only be used by the owner of the bot, bot management commands"""
_last_result = None _last_result = None
sessions = set() sessions = set()
@ -28,62 +30,68 @@ class Owner(commands.Cog):
def cleanup_code(content): def cleanup_code(content):
"""Automatically removes code blocks from the code.""" """Automatically removes code blocks from the code."""
# remove ```py\n``` # remove ```py\n```
if content.startswith('```') and content.endswith('```'): if content.startswith("```") and content.endswith("```"):
return '\n'.join(content.split('\n')[1:-1]) return "\n".join(content.split("\n")[1:-1])
# remove `foo` # remove `foo`
return content.strip('` \n') return content.strip("` \n")
@commands.command(hidden=True) @commands.command(hidden=True)
async def repl(self, ctx): async def repl(self, ctx):
msg = ctx.message msg = ctx.message
variables = { variables = {
'ctx': ctx, "ctx": ctx,
'bot': ctx.bot, "bot": ctx.bot,
'message': msg, "message": msg,
'guild': msg.guild, "guild": msg.guild,
'server': msg.guild, "server": msg.guild,
'channel': msg.channel, "channel": msg.channel,
'author': msg.author, "author": msg.author,
'self': self, "self": self,
'_': None, "_": None,
} }
if msg.channel.id in self.sessions: 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 return
self.sessions.add(msg.channel.id) 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): def check(m):
return m.author.id == msg.author.id and \ return (
m.channel.id == msg.channel.id and \ m.author.id == msg.author.id
m.content.startswith('`') and m.channel.id == msg.channel.id
and m.content.startswith("`")
)
code = None code = None
while True: while True:
try: 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: except asyncio.TimeoutError:
await ctx.send('Exiting REPL session.') await ctx.send("Exiting REPL session.")
self.sessions.remove(msg.channel.id) self.sessions.remove(msg.channel.id)
break break
cleaned = self.cleanup_code(response.content) cleaned = self.cleanup_code(response.content)
if cleaned in ('quit', 'exit', 'exit()'): if cleaned in ("quit", "exit", "exit()"):
await ctx.send('Exiting.') await ctx.send("Exiting.")
self.sessions.remove(msg.channel.id) self.sessions.remove(msg.channel.id)
return return
executor = exec executor = exec
if cleaned.count('\n') == 0: if cleaned.count("\n") == 0:
# single statement, potentially 'eval' # single statement, potentially 'eval'
try: try:
code = compile(cleaned, '<repl session>', 'eval') code = compile(cleaned, "<repl session>", "eval")
except SyntaxError: except SyntaxError:
pass pass
else: else:
@ -91,12 +99,12 @@ class Owner(commands.Cog):
if executor is exec: if executor is exec:
try: try:
code = compile(cleaned, '<repl session>', 'exec') code = compile(cleaned, "<repl session>", "exec")
except SyntaxError as e: except SyntaxError as e:
await ctx.send(get_syntax_error(e)) await ctx.send(get_syntax_error(e))
continue continue
variables['message'] = response variables["message"] = response
fmt = None fmt = None
stdout = io.StringIO() stdout = io.StringIO()
@ -108,25 +116,25 @@ class Owner(commands.Cog):
result = await result result = await result
except Exception: except Exception:
value = stdout.getvalue() value = stdout.getvalue()
fmt = '```py\n{}{}\n```'.format(value, traceback.format_exc()) fmt = "```py\n{}{}\n```".format(value, traceback.format_exc())
else: else:
value = stdout.getvalue() value = stdout.getvalue()
if result is not None: if result is not None:
fmt = '```py\n{}{}\n```'.format(value, result) fmt = "```py\n{}{}\n```".format(value, result)
variables['_'] = result variables["_"] = result
elif value: elif value:
fmt = '```py\n{}\n```'.format(value) fmt = "```py\n{}\n```".format(value)
try: try:
if fmt is not None: if fmt is not None:
if len(fmt) > 2000: if len(fmt) > 2000:
await ctx.send('Content too big to be printed.') await ctx.send("Content too big to be printed.")
else: else:
await ctx.send(fmt) await ctx.send(fmt)
except discord.Forbidden: except discord.Forbidden:
pass pass
except discord.HTTPException as e: except discord.HTTPException as e:
await ctx.send('Unexpected error: `{}`'.format(e)) await ctx.send("Unexpected error: `{}`".format(e))
@commands.command() @commands.command()
async def sendtochannel(self, ctx, cid: int, *, message): async def sendtochannel(self, ctx, cid: int, *, message):
@ -141,15 +149,15 @@ class Owner(commands.Cog):
@commands.command() @commands.command()
async def debug(self, ctx, *, body: str): async def debug(self, ctx, *, body: str):
env = { env = {
'bot': ctx.bot, "bot": ctx.bot,
'ctx': ctx, "ctx": ctx,
'channel': ctx.message.channel, "channel": ctx.message.channel,
'author': ctx.message.author, "author": ctx.message.author,
'server': ctx.message.guild, "server": ctx.message.guild,
'guild': ctx.message.guild, "guild": ctx.message.guild,
'message': ctx.message, "message": ctx.message,
'self': self, "self": self,
'_': self._last_result "_": self._last_result,
} }
env.update(globals()) env.update(globals())
@ -157,14 +165,14 @@ class Owner(commands.Cog):
body = self.cleanup_code(body) body = self.cleanup_code(body)
stdout = io.StringIO() stdout = io.StringIO()
to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ') to_compile = "async def func():\n%s" % textwrap.indent(body, " ")
try: try:
exec(to_compile, env) exec(to_compile, env)
except SyntaxError as e: except SyntaxError as e:
return await ctx.send(get_syntax_error(e)) return await ctx.send(get_syntax_error(e))
func = env['func'] func = env["func"]
try: try:
with redirect_stdout(stdout): with redirect_stdout(stdout):
ret = await func() ret = await func()
@ -174,7 +182,7 @@ class Owner(commands.Cog):
else: else:
value = stdout.getvalue() value = stdout.getvalue()
try: try:
await ctx.message.add_reaction('\u2705') await ctx.message.add_reaction("\u2705")
except Exception: except Exception:
pass pass
@ -189,20 +197,18 @@ class Owner(commands.Cog):
async def bash(self, ctx, *, cmd: str): async def bash(self, ctx, *, cmd: str):
"""Runs a bash command""" """Runs a bash command"""
proc = await asyncio.create_subprocess_shell( proc = await asyncio.create_subprocess_shell(
cmd, cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT
) )
stdout = (await proc.communicate())[0] stdout = (await proc.communicate())[0]
if stdout: if stdout:
await ctx.send(f'[stdout]\n{stdout.decode()}') await ctx.send(f"[stdout]\n{stdout.decode()}")
else: else:
await ctx.send("Process finished, no output") await ctx.send("Process finished, no output")
@commands.command() @commands.command()
async def shutdown(self, ctx): async def shutdown(self, ctx):
"""Shuts the bot down""" """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.send(fmt.format(ctx.message))
await ctx.bot.logout() await ctx.bot.logout()
await ctx.bot.close() await ctx.bot.close()
@ -211,7 +217,7 @@ class Owner(commands.Cog):
async def name(self, ctx, new_nick: str): async def name(self, ctx, new_nick: str):
"""Changes the bot's name""" """Changes the bot's name"""
await ctx.bot.user.edit(username=new_nick) 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() @commands.command()
async def status(self, ctx, *, status: str): async def status(self, ctx, *, status: str):
@ -233,7 +239,7 @@ class Owner(commands.Cog):
ctx.bot.load_extension(module) ctx.bot.load_extension(module)
await ctx.send("I have just loaded the {} module".format(module)) await ctx.send("I have just loaded the {} module".format(module))
except Exception as error: 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)) await ctx.send(fmt.format(type(error).__name__, error))
@commands.command() @commands.command()
@ -263,7 +269,7 @@ class Owner(commands.Cog):
ctx.bot.load_extension(module) ctx.bot.load_extension(module)
await ctx.send("I have just reloaded the {} module".format(module)) await ctx.send("I have just reloaded the {} module".format(module))
except Exception as error: 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)) await ctx.send(fmt.format(type(error).__name__, error))

View file

@ -3,9 +3,9 @@ import discord
import traceback import traceback
import utils 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): def produce_embed(*channels):
@ -19,7 +19,9 @@ def produce_embed(*channels):
**Gaming:** {"Yes" if channel.get("gaming") else "No"} **Gaming:** {"Yes" if channel.get("gaming") else "No"}
**Commissions:** {"Yes" if channel.get("commissions") 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): class Picarto(commands.Cog):
@ -27,17 +29,13 @@ class Picarto(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.task = self.bot.loop.create_task(self.picarto_task())
self.channel_info = {} self.channel_info = {}
# noinspection PyAttributeOutsideInit # noinspection PyAttributeOutsideInit
async def get_online_users(self): async def get_online_users(self):
# This method is in place to just return all online users so we can compare against it # This method is in place to just return all online users so we can compare against it
url = BASE_URL + '/online' url = BASE_URL + "/online"
payload = { payload = {"adult": "true", "gaming": "true"}
'adult': 'true',
'gaming': 'true'
}
channel_info = {} channel_info = {}
channels = await utils.request(url, payload=payload) channels = await utils.request(url, payload=payload)
if channels and isinstance(channels, (list, set, tuple)) and len(channels) > 0: 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 # After loop has finished successfully, we want to override the statuses of the channels
self.channel_info = channel_info self.channel_info = channel_info
async def picarto_task(self): @tasks.loop(seconds=30)
# 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)
async def check_channels(self): async def check_channels(self):
query = """ query = """
SELECT SELECT
@ -99,13 +80,24 @@ WHERE
# If they've gone online, produce the embed for them and send it # If they've gone online, produce the embed for them and send it
if gone_online: if gone_online:
embed = produce_embed(*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: if channel is not None:
try: try:
await channel.send(embed=embed) await channel.send(embed=embed)
except (discord.Forbidden, discord.HTTPException, AttributeError): except (discord.Forbidden, discord.HTTPException, AttributeError):
pass 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): def setup(bot):
bot.add_cog(Picarto(bot)) bot.add_cog(Picarto(bot))

View file

@ -6,7 +6,7 @@ import utils
def to_keycap(c): 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: class Poll:
@ -55,8 +55,7 @@ class Polls(commands.Cog):
else: else:
fmt = "{} asked: {}\n".format(ctx.message.author.display_name, question) fmt = "{} asked: {}\n".format(ctx.message.author.display_name, question)
fmt += "\n".join( fmt += "\n".join(
"{}: {}".format(to_keycap(i + 1), opt) "{}: {}".format(to_keycap(i + 1), opt) for i, opt in enumerate(options)
for i, opt in enumerate(options)
) )
msg = await ctx.send(fmt) msg = await ctx.send(fmt)

View file

@ -11,6 +11,7 @@ import random
class Raffle(commands.Cog): class Raffle(commands.Cog):
"""Used to hold custom raffles""" """Used to hold custom raffles"""
raffles = defaultdict(list) raffles = defaultdict(list)
def create_raffle(self, ctx, title, num): def create_raffle(self, ctx, title, num):
@ -37,9 +38,9 @@ class Raffle(commands.Cog):
embed.add_field( embed.add_field(
name=f"Raffle {num + 1}", name=f"Raffle {num + 1}",
value=f"Title: {raffle.title}\n" value=f"Title: {raffle.title}\n"
f"Total Entrants: {len(raffle.entrants)}\n" f"Total Entrants: {len(raffle.entrants)}\n"
f"Ends in {raffle.remaining}", f"Ends in {raffle.remaining}",
inline=False inline=False,
) )
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -63,7 +64,7 @@ class Raffle(commands.Cog):
else: else:
await ctx.send("You have already entered this raffle!") 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() @commands.guild_only()
@utils.can_run(kick_members=True) @utils.can_run(kick_members=True)
async def raffle_create(self, ctx): async def raffle_create(self, ctx):
@ -76,37 +77,48 @@ class Raffle(commands.Cog):
channel = ctx.channel channel = ctx.channel
await ctx.send( 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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long! >:c") await ctx.send("You took too long! >:c")
return return
title = msg.content title = msg.content
fmt = "Alright, your new raffle will be titled:\n\n{}\n\nHow long would you like this raffle to run for? " \ fmt = (
"The format should be [number] [length] for example, `2 days` or `1 hour` or `30 minutes` etc. " \ "Alright, your new raffle will be titled:\n\n{}\n\nHow long would you like this raffle to run for? "
"The minimum for this is 10 minutes, and the maximum is 3 days" "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)) await ctx.send(fmt.format(title))
# Our check to ensure that a proper length of time was passed # Our check to ensure that a proper length of time was passed
def check(m): def check(m):
if m.author == author and m.channel == channel: 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: else:
return False return False
try: 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: except asyncio.TimeoutError:
await ctx.send("You took too long! >:c") await ctx.send("You took too long! >:c")
return return
# Lets get the length provided, based on the number and type passed # 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 # This should be safe to convert, we already made sure with our check earlier this would match
num = int(num) num = int(num)
@ -120,7 +132,8 @@ class Raffle(commands.Cog):
if not 60 < num < 259200: if not 60 < num < 259200:
await ctx.send( 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 return
self.create_raffle(ctx, title, num) self.create_raffle(ctx, title, num)
@ -132,7 +145,6 @@ def setup(bot):
class GuildRaffle: class GuildRaffle:
def __init__(self, ctx, title, expires): def __init__(self, ctx, title, expires):
self._ctx = ctx self._ctx = ctx
self.title = title self.title = title
@ -178,13 +190,15 @@ AND
result = await self.db.fetch(query, self.guild.id) result = await self.db.fetch(query, self.guild.id)
if result: if result:
channel = self.guild.get_channel(result['channel']) channel = self.guild.get_channel(result["channel"])
if channel is None: if channel is None:
return return
if entrants: if entrants:
winner = random.SystemRandom().choice(self.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: else:
await channel.send( await channel.send(
f"There were no entrants to the raffle `{self.title}`, who are in this server currently!" f"There were no entrants to the raffle `{self.title}`, who are in this server currently!"

View file

@ -10,7 +10,7 @@ import asyncio
class Roles(commands.Cog): class Roles(commands.Cog):
"""Class to handle management of roles on the server""" """Class to handle management of roles on the server"""
@commands.command(aliases=['color']) @commands.command(aliases=["color"])
@commands.guild_only() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def colour(self, ctx, role_colour: discord.Colour): async def colour(self, ctx, role_colour: discord.Colour):
@ -20,10 +20,14 @@ class Roles(commands.Cog):
EXAMPLE: !colour red EXAMPLE: !colour red
RESULT: A role that matches red (#e74c3c) will be given to you""" 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"]: if result and not result["colour_roles"]:
await ctx.send("Colour roles not allowed on this server! " await ctx.send(
"The command `allowcolours` must be ran to enable them!") "Colour roles not allowed on this server! "
"The command `allowcolours` must be ran to enable them!"
)
return return
if not ctx.me.guild_permissions.manage_roles: 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) 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 # 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: if old_roles:
await ctx.author.remove_roles(*old_roles) await ctx.author.remove_roles(*old_roles)
# If the role doesn't exist, we need to create it # If the role doesn't exist, we need to create it
if not role: if not role:
opts = { opts = {"name": name, "colour": role_colour}
"name": name,
"colour": role_colour
}
try: try:
role = await ctx.guild.create_role(**opts) role = await ctx.guild.create_role(**opts)
except discord.HTTPException: except discord.HTTPException:
@ -57,10 +60,10 @@ class Roles(commands.Cog):
await ctx.send("I have just given you your requested colour!") 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() @commands.guild_only()
@utils.can_run(send_messages=True) @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. """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 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 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) embed = discord.Embed(**opts)
# Add details to it # Add details to it
embed.add_field(name="Created", value=role.created_at.date()) 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) total_members = len(role.members)
embed.add_field(name="Total members", value=str(total_members)) embed.add_field(name="Total members", value=str(total_members))
# If there are only a few members in this role, display them # If there are only a few members in this role, display them
if 5 >= total_members > 0: 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) await ctx.send(embed=embed)
else: else:
# Don't include the colour roles # Don't include the colour roles
colour_role = re.compile("Bonfire #.+") colour_role = re.compile("Bonfire #.+")
# Simply get a list of all roles in this server and send them # 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: 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 return
try: try:
@ -101,7 +113,7 @@ class Roles(commands.Cog):
except utils.CannotPaginate as e: except utils.CannotPaginate as e:
await ctx.send(str(e)) await ctx.send(str(e))
@role.command(name='remove') @role.command(name="remove")
@commands.guild_only() @commands.guild_only()
@utils.can_run(manage_roles=True) @utils.can_run(manage_roles=True)
async def remove_role(self, ctx): 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""" 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 # 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: 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 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 # First get the list of all mentioned users
members = ctx.message.mentions 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 no users are mentioned, ask the author for a list of the members they want to remove the role from
if len(members) == 0: 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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
if len(msg.mentions) == 0: 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 return
# Override members if everything has gone alright, and then continue # Override members if everything has gone alright, and then continue
members = msg.mentions members = msg.mentions
# This allows the user to remove multiple roles from the list of users, if they want. # 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. " await ctx.send(
"Make sure the roles, if more than one is provided, are separate by commas. ") "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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
# Split the content based on commas, using regex so we can split if a space was not provided or if it was # 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 = [] roles = []
# This loop is just to get the actual role objects based on the name # This loop is just to get the actual role objects based on the name
for role in role_names: for role in role_names:
@ -158,10 +183,14 @@ class Roles(commands.Cog):
# Otherwise, remove the roles from each member given # Otherwise, remove the roles from each member given
for member in members: for member in members:
await member.remove_roles(*roles) await member.remove_roles(*roles)
await ctx.send("I have just removed the following roles:```\n{}``` from the following members:" await ctx.send(
"```\n{}```".format("\n".join(role_names), "\n".join([m.display_name for m in members]))) "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() @commands.guild_only()
@utils.can_run(manage_roles=True) @utils.can_run(manage_roles=True)
async def add_role(self, ctx): 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""" 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 # 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: 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 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. # 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 members = ctx.message.mentions
if len(members) == 0: 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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
if len(msg.mentions) == 0: 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 return
members = msg.mentions members = msg.mentions
await ctx.send("Alright, please provide the roles you would like to add to this member. " await ctx.send(
"Make sure the roles, if more than one is provided, are separate by commas. ") "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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
role_names = re.split(', ?', msg.content) role_names = re.split(", ?", msg.content)
roles = [] roles = []
for role in role_names: for role in role_names:
_role = discord.utils.get(server_roles, name=role) _role = discord.utils.get(server_roles, name=role)
@ -212,10 +254,14 @@ class Roles(commands.Cog):
for member in members: for member in members:
await member.add_roles(*roles) await member.add_roles(*roles)
await ctx.send("I have just added the following roles:```\n{}``` to the following members:" await ctx.send(
"```\n{}```".format("\n".join(role_names), "\n".join([m.display_name for m in members]))) "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() @commands.guild_only()
@utils.can_run(manage_roles=True) @utils.can_run(manage_roles=True)
async def delete_role(self, ctx, *, role: discord.Role = None): async def delete_role(self, ctx, *, role: discord.Role = None):
@ -225,16 +271,21 @@ class Roles(commands.Cog):
RESULT: No more role called StupidRole""" RESULT: No more role called StupidRole"""
# No use in running through everything if the bot cannot manage roles # 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: 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 return
# If no role was given, get the current roles on the server and ask which ones they'd like to remove # 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: 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( await ctx.send(
"Which role would you like to remove from the server? Here is a list of this server's roles:" "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 # 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 # 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 return False
try: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
@ -255,9 +306,11 @@ class Roles(commands.Cog):
role = discord.utils.get(server_roles, name=msg.content) role = discord.utils.get(server_roles, name=msg.content)
await role.delete() 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() @commands.guild_only()
@utils.can_run(manage_roles=True) @utils.can_run(manage_roles=True)
async def create_role(self, ctx): 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""" RESULT: A follow along in order to create a new role"""
# No use in running through everything if the bot cannot create the 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: 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 return
# Save a couple variables that will be used repeatedly # 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 # A couple checks that will be used in the wait_for_message's
def num_seperated_check(m): def num_seperated_check(m):
if m.author == author and m.channel == channel: 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: else:
return False return False
@ -296,13 +351,16 @@ class Roles(commands.Cog):
else: else:
return False 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 # Start the checks for the role, get the name of the role first
await ctx.send( 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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return 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 # 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()) all_perms = list(discord.Permissions.VALID_FLAGS.keys())
fmt = "\n".join("{}) {}".format(i, perm) for i, perm in enumerate(all_perms)) 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 " await ctx.send(
"the numbers, seperated by commas, of the permissions you want this role to have.\n" "Sounds fancy! Here is a list of all the permissions available. Please respond with just "
"```\n{}```".format(fmt)) "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 # 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 # as it might take a bit to figure out which permissions they want
try: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
# Check if any integer's were provided that are within the length of the list of permissions # 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 # 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: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
@ -337,7 +405,7 @@ class Roles(commands.Cog):
# Check if this role should be able to be mentioned # Check if this role should be able to be mentioned
await ctx.send("Do you want this role to be mentionable? (yes or no)") await ctx.send("Do you want this role to be mentionable? (yes or no)")
try: 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: except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait") await ctx.send("You took too long. I'm impatient, don't make me wait")
return return
@ -350,18 +418,20 @@ class Roles(commands.Cog):
setattr(perms, all_perms[index], True) setattr(perms, all_perms[index], True)
payload = { payload = {
'name': name, "name": name,
'permissions': perms, "permissions": perms,
'hoist': hoist, "hoist": hoist,
'mentionable': mentionable "mentionable": mentionable,
} }
# Create the role, and wait a second, sometimes it goes too quickly and we get a role with 'new role' to print # 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) role = await server.create_role(**payload)
await asyncio.sleep(1) await asyncio.sleep(1)
await ctx.send("We did it! You just created the new role {}\nIf you want to add this role" await ctx.send(
" to some people, mention them now".format(role.name)) "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: 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: except asyncio.TimeoutError:
# There's no need to mention the users, so don't send a failure message if they didn't, just return # There's no need to mention the users, so don't send a failure message if they didn't, just return
return return
@ -386,7 +456,9 @@ class Roles(commands.Cog):
return return
author = ctx.message.author 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: if result is None:
await ctx.send("There are no self-assignable roles on this server") await ctx.send("There are no self-assignable roles on this server")
@ -399,10 +471,14 @@ class Roles(commands.Cog):
fmt = "" fmt = ""
roles = [r for r in role if r.id in self_assignable_roles] roles = [r for r in role if r.id in self_assignable_roles]
fmt += "\n".join(["Successfully added {}".format(r.name) fmt += "\n".join(
if r.id in self_assignable_roles else [
"{} is not available to be self-assigned".format(r.name) "Successfully added {}".format(r.name)
for r in role]) if r.id in self_assignable_roles
else "{} is not available to be self-assigned".format(r.name)
for r in role
]
)
try: try:
await author.add_roles(*roles) await author.add_roles(*roles)
@ -423,7 +499,9 @@ class Roles(commands.Cog):
return return
author = ctx.message.author 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: if result is None:
await ctx.send("There are no self-assignable roles on this server") await ctx.send("There are no self-assignable roles on this server")
@ -436,10 +514,14 @@ class Roles(commands.Cog):
fmt = "" fmt = ""
roles = [r for r in role if str(r.id) in self_assignable_roles] roles = [r for r in role if str(r.id) in self_assignable_roles]
fmt += "\n".join(["Successfully removed {}".format(r.name) fmt += "\n".join(
if str(r.id) in self_assignable_roles else [
"{} is not available to be self-assigned".format(r.name) "Successfully removed {}".format(r.name)
for r in role]) if str(r.id) in self_assignable_roles
else "{} is not available to be self-assigned".format(r.name)
for r in role
]
)
try: try:
await author.remove_roles(*roles) await author.remove_roles(*roles)

View file

@ -10,6 +10,7 @@ import utils
class Roulette(commands.Cog): class Roulette(commands.Cog):
"""A fun game that ends in someone getting kicked!""" """A fun game that ends in someone getting kicked!"""
roulettes = [] roulettes = []
def get_game(self, server): def get_game(self, server):
@ -47,14 +48,17 @@ class Roulette(commands.Cog):
result = r.join(ctx.message.author) result = r.join(ctx.message.author)
time_left = r.time_left time_left = r.time_left
if result: 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: else:
await ctx.send("This roulette will end in " + time_left) 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() @commands.guild_only()
@utils.can_run(kick_members=True) @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 """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) By default, the roulette will end in 5 minutes; provide a number (up to 30)
to change how many minutes until it ends to change how many minutes until it ends
@ -62,15 +66,23 @@ class Roulette(commands.Cog):
EXAMPLE: !roulette start EXAMPLE: !roulette start
RESULT: A new roulette game!""" RESULT: A new roulette game!"""
if time < 1 or time > 30: 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 return
else: else:
game = self.start_game(ctx.message.guild, time) game = self.start_game(ctx.message.guild, time)
if game: if game:
await ctx.send("A new roulette game has just started! A random entrant will be kicked in {} minutes." await ctx.send(
" Type {}roulette to join this roulette...good luck~".format(game.time_left, ctx.prefix)) "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: 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 return
await asyncio.sleep(time * 60) await asyncio.sleep(time * 60)
@ -94,7 +106,6 @@ class Roulette(commands.Cog):
class Game: class Game:
def __init__(self, guild, time): def __init__(self, guild, time):
self.entrants = [] self.entrants = []
self.server = guild self.server = guild

View file

@ -31,9 +31,9 @@ class Spotify(commands.Cog):
try: try:
delay = await self.get_api_token() delay = await self.get_api_token()
except Exception as error: 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) 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: finally:
await asyncio.sleep(delay) await asyncio.sleep(delay)
@ -61,7 +61,12 @@ class Spotify(commands.Cog):
url = "https://api.spotify.com/v1/search" url = "https://api.spotify.com/v1/search"
response = await utils.request(url, headers=headers, payload=opts) response = await utils.request(url, headers=headers, payload=opts)
try: 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): except (KeyError, AttributeError, IndexError):
await ctx.send("Couldn't find a song for:\n{}".format(query)) 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" url = "https://api.spotify.com/v1/search"
response = await utils.request(url, headers=headers, payload=opts) response = await utils.request(url, headers=headers, payload=opts)
try: 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): except (KeyError, AttributeError, IndexError):
await ctx.send("Couldn't find a song for:\n{}".format(query)) await ctx.send("Couldn't find a song for:\n{}".format(query))

View file

@ -1,4 +1,3 @@
import re
import utils import utils
import discord import discord
import datetime import datetime
@ -14,10 +13,14 @@ class Stats(commands.Cog):
async def _get_guild_usage(self, guild): async def _get_guild_usage(self, guild):
embed = discord.Embed(title="Server Command Usage") 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.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 = """ query = """
SELECT SELECT
@ -34,8 +37,10 @@ LIMIT 5
""" """
results = await self.bot.db.fetch(query, guild.id) results = await self.bot.db.fetch(query, guild.id)
value = "\n".join(f"{command} ({uses} uses)" for command, uses in results or "No Commands") value = "\n".join(
embed.add_field(name='Top Commands', value=value) f"{command} ({uses} uses)" for command, uses in results or "No Commands"
)
embed.add_field(name="Top Commands", value=value)
return embed return embed
@ -43,11 +48,13 @@ LIMIT 5
embed = discord.Embed(title=f"{member.display_name}'s command usage") embed = discord.Embed(title=f"{member.display_name}'s command usage")
count = await self.bot.db.fetchrow( count = await self.bot.db.fetchrow(
"SELECT COUNT(*), MIN(executed) FROM command_usage WHERE author=$1", "SELECT COUNT(*), MIN(executed) FROM command_usage WHERE author=$1",
member.id member.id,
) )
embed.description = f"{count[0]} total commands used" 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 = """ query = """
SELECT SELECT
@ -64,8 +71,10 @@ LIMIT 5
""" """
results = await self.bot.db.fetch(query, member.id) results = await self.bot.db.fetch(query, member.id)
value = "\n".join(f"{command} ({uses} uses)" for command, uses in results or "No Commands") value = "\n".join(
embed.add_field(name='Top Commands', value=value) f"{command} ({uses} uses)" for command, uses in results or "No Commands"
)
embed.add_field(name="Top Commands", value=value)
return embed return embed
@ -79,26 +88,35 @@ LIMIT 5
RESULT: Information about your server!""" RESULT: Information about your server!"""
server = ctx.message.guild server = ctx.message.guild
# Create our embed that we'll use for the information # 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 # Make sure we only set the icon url if it has been set
if server.icon_url: if server.icon_url:
embed.set_thumbnail(url=server.icon_url) embed.set_thumbnail(url=server.icon_url)
# Add our fields, these are self-explanatory # Add our fields, these are self-explanatory
embed.add_field(name='Region', value=str(server.region)) embed.add_field(name="Region", value=str(server.region))
embed.add_field(name='Total Emojis', value=len(server.emojis)) embed.add_field(name="Total Emojis", value=len(server.emojis))
# Get the amount of online members # Get the amount of online members
online_members = [m for m in server.members if str(m.status) == 'online'] 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(
embed.add_field(name='Roles', value=len(server.roles)) 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 # Split channels into voice and text channels
voice_channels = [c for c in server.channels if type(c) is discord.VoiceChannel] 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] 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(
embed.add_field(name='Owner', value=server.owner.display_name) 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) await ctx.send(embed=embed)
@ -117,8 +135,12 @@ LIMIT 5
fmt = "{} ({})".format(str(user), user.id) fmt = "{} ({})".format(str(user), user.id)
embed.set_author(name=fmt, icon_url=user.avatar_url) 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(
embed.add_field(name='Joined Discord', value=user.created_at.date(), inline=False) 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 # 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) 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]) roles = ", ".join("{}".format(x.name) for x in roles[:5])
# If there are no roles, then just say this # If there are no roles, then just say this
roles = roles or "No roles added" 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 # Add the activity if there is one
act = user.activity act = user.activity
if isinstance(act, discord.activity.Spotify): if isinstance(act, discord.activity.Spotify):
embed.add_field(name="Listening to", value=act.title, inline=False) embed.add_field(name="Listening to", value=act.title, inline=False)
elif isinstance(act, discord.activity.Game): 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) await ctx.send(embed=embed)
@commands.group() @commands.group()
@ -180,9 +202,11 @@ LIMIT 1
most = await ctx.bot.db.fetchrow(query, ctx.author.id, members) most = await ctx.bot.db.fetchrow(query, ctx.author.id, members)
if most is None or len(most) == 0: 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: else:
member = ctx.guild.get_member(most['boopee']) member = ctx.guild.get_member(most["boopee"])
await ctx.send( await ctx.send(
f"{ctx.author.mention} you have booped {member.display_name} the most amount of times, " f"{ctx.author.mention} you have booped {member.display_name} the most amount of times, "
f"coming in at {most['amount']} 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 = discord.Embed(title="Your booped victims", colour=ctx.author.colour)
embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url) embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url)
for row in most: for row in most:
member = ctx.guild.get_member(row['boopee']) member = ctx.guild.get_member(row["boopee"])
embed.add_field(name=member.display_name, value=row['amount']) embed.add_field(name=member.display_name, value=row["amount"])
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
await ctx.send("You haven't booped anyone in this server!") await ctx.send("You haven't booped anyone in this server!")
@ -252,7 +276,7 @@ ORDER BY
output = [] output = []
for row in results: 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']})") output.append(f"{member.display_name} (Rating: {row['battle_rating']})")
try: try:
@ -297,7 +321,10 @@ WHERE id = $2
rating = result["battle_rating"] rating = result["battle_rating"]
record = f"{result['battle_wins']} - {result['battle_losses']}" 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.set_author(name=str(member), icon_url=member.avatar_url)
embed.add_field(name="Record", value=record, inline=False) embed.add_field(name="Record", value=record, inline=False)
embed.add_field(name="Server Rank", value=server_rank, inline=False) embed.add_field(name="Server Rank", value=server_rank, inline=False)

View file

@ -17,10 +17,12 @@ class Tags(commands.Cog):
EXAMPLE: !tags EXAMPLE: !tags
RESULT: All tags setup on this server""" 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: if len(tags) > 0:
entries = [t['trigger'] for t in tags] entries = [t["trigger"] for t in tags]
pages = utils.Pages(ctx, entries=entries) pages = utils.Pages(ctx, entries=entries)
await pages.paginate() await pages.paginate()
else: else:
@ -37,11 +39,11 @@ class Tags(commands.Cog):
tags = await ctx.bot.db.fetch( tags = await ctx.bot.db.fetch(
"SELECT trigger FROM tags WHERE guild=$1 AND creator=$2", "SELECT trigger FROM tags WHERE guild=$1 AND creator=$2",
ctx.guild.id, ctx.guild.id,
ctx.author.id ctx.author.id,
) )
if len(tags) > 0: if len(tags) > 0:
entries = [t['trigger'] for t in tags] entries = [t["trigger"] for t in tags]
pages = utils.Pages(ctx, entries=entries) pages = utils.Pages(ctx, entries=entries)
await pages.paginate() await pages.paginate()
else: else:
@ -59,16 +61,18 @@ class Tags(commands.Cog):
tag = await ctx.bot.db.fetchrow( tag = await ctx.bot.db.fetchrow(
"SELECT id, result FROM tags WHERE guild=$1 AND trigger=$2", "SELECT id, result FROM tags WHERE guild=$1 AND trigger=$2",
ctx.guild.id, ctx.guild.id,
trigger.lower().strip() trigger.lower().strip(),
) )
if tag: if tag:
await ctx.send("\u200B{}".format(tag['result'])) 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.bot.db.execute(
"UPDATE tags SET uses = uses + 1 WHERE id = $1", tag["id"]
)
else: else:
await ctx.send("There is no tag called {}".format(trigger)) 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() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def add_tag(self, ctx): 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""" RESULT: A follow-along in order to create a new tag"""
def check(m): 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: try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60) msg = await ctx.bot.wait_for("message", check=check, timeout=60)
@ -89,20 +99,32 @@ class Tags(commands.Cog):
return return
trigger = msg.content.lower().strip() 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: if len(trigger) > 100:
await ctx.send("Please keep tag triggers under 100 characters") await ctx.send("Please keep tag triggers under 100 characters")
return return
elif trigger.lower() in forbidden_tags: elif trigger.lower() in forbidden_tags:
await ctx.send( await ctx.send(
"Sorry, but your tag trigger was detected to be forbidden. " "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 return
tag = await ctx.bot.db.fetchrow( tag = await ctx.bot.db.fetchrow(
"SELECT result FROM tags WHERE guild=$1 AND trigger=$2", "SELECT result FROM tags WHERE guild=$1 AND trigger=$2",
ctx.guild.id, ctx.guild.id,
trigger.lower().strip() trigger.lower().strip(),
) )
if tag: if tag:
await ctx.send("There is already a tag setup called {}!".format(trigger)) 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( 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( "Alright, your new tag can be called with {}!\n\nWhat do you want to be displayed with this tag?".format(
trigger)) trigger
)
)
try: try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60) msg = await ctx.bot.wait_for("message", check=check, timeout=60)
@ -131,34 +155,45 @@ class Tags(commands.Cog):
except (discord.Forbidden, discord.HTTPException): except (discord.Forbidden, discord.HTTPException):
pass 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( await ctx.bot.db.execute(
"INSERT INTO tags(guild, creator, trigger, result) VALUES ($1, $2, $3, $4)", "INSERT INTO tags(guild, creator, trigger, result) VALUES ($1, $2, $3, $4)",
ctx.guild.id, ctx.guild.id,
ctx.author.id, ctx.author.id,
trigger, trigger,
result result,
) )
@tag.command(name='edit') @tag.command(name="edit")
@commands.guild_only() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def edit_tag(self, ctx, *, trigger: str): async def edit_tag(self, ctx, *, trigger: str):
"""This will allow you to edit a tag that you have created """This will allow you to edit a tag that you have created
EXAMPLE: !tag edit this tag EXAMPLE: !tag edit this tag
RESULT: I'll ask what you want the new result to be""" RESULT: I'll ask what you want the new result to be"""
def check(m): 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( tag = await ctx.bot.db.fetchrow(
"SELECT id, trigger FROM tags WHERE guild=$1 AND creator=$2 AND trigger=$3", "SELECT id, trigger FROM tags WHERE guild=$1 AND creator=$2 AND trigger=$3",
ctx.guild.id, ctx.guild.id,
ctx.author.id, ctx.author.id,
trigger trigger,
) )
if tag: 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: 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: except asyncio.TimeoutError:
@ -174,11 +209,13 @@ class Tags(commands.Cog):
pass pass
await ctx.send(f"Alright, the tag {trigger} has been updated") 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: else:
await ctx.send(f"You do not have a tag called {trigger} on this server!") 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() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def del_tag(self, ctx, *, trigger: str): 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", "SELECT id FROM tags WHERE guild=$1 AND creator=$2 AND trigger=$3",
ctx.guild.id, ctx.guild.id,
ctx.author.id, ctx.author.id,
trigger trigger,
) )
if tag: if tag:
await ctx.send(f"I have just deleted the tag {trigger}") 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: else:
await ctx.send(f"You do not own a tag called {trigger} on this server!") 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( tag = await ctx.bot.db.fetchrow(
"SELECT creator, uses, trigger FROM tags WHERE guild=$1 AND trigger=$2", "SELECT creator, uses, trigger FROM tags WHERE guild=$1 AND trigger=$2",
ctx.guild.id, ctx.guild.id,
trigger trigger,
) )
if tag is not None: if tag is not None:
embed = discord.Embed(title=tag['trigger']) embed = discord.Embed(title=tag["trigger"])
creator = ctx.guild.get_member(tag['creator']) creator = ctx.guild.get_member(tag["creator"])
if creator: if creator:
embed.set_author(name=creator.display_name, url=creator.avatar_url) 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) embed.add_field(name="Owner", value=creator.mention)
await ctx.send(embed=embed) await ctx.send(embed=embed)

View file

@ -10,13 +10,13 @@ import random
class Board: class Board:
def __init__(self, player1, player2): 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 # 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 # Randomize who goes first when the board is created
if random.SystemRandom().randint(0, 1): if random.SystemRandom().randint(0, 1):
self.challengers = {'x': player1, 'o': player2} self.challengers = {"x": player1, "o": player2}
else: else:
self.challengers = {'x': player2, 'o': player1} self.challengers = {"x": player2, "o": player1}
# X's always go first # X's always go first
self.X_turn = True self.X_turn = True
@ -24,22 +24,22 @@ class Board:
def full(self): 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 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: for row in self.board:
if ' ' in row: if " " in row:
return False return False
return True return True
def can_play(self, player): def can_play(self, player):
# Simple check to see if the player is the one that's up # Simple check to see if the player is the one that's up
if self.X_turn: if self.X_turn:
return player == self.challengers['x'] return player == self.challengers["x"]
else: else:
return player == self.challengers['o'] return player == self.challengers["o"]
def update(self, x, y): def update(self, x, y):
# If it's x's turn, we place an x, otherwise place an o # 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 # 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 self.board[x][y] = letter
else: else:
return False return False
@ -52,35 +52,67 @@ class Board:
# First base off the top-left corner, see if any possiblities with that match # 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' # 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 # 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]] return self.challengers[self.board[0][0]]
# Top-left, middle-left, bottom-left # 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]] return self.challengers[self.board[0][0]]
# Top-left, middle, bottom-right # 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]] return self.challengers[self.board[0][0]]
# Next check the top-right corner, not re-checking the last possiblity that included it # Next check the top-right corner, not re-checking the last possiblity that included it
# Top-right, middle-right, bottom-right # 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]] return self.challengers[self.board[0][2]]
# Top-right, middle, bottom-left # 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]] return self.challengers[self.board[0][2]]
# Next up, bottom-right corner, only one possiblity to check here, other two have been checked # Next up, bottom-right corner, only one possiblity to check here, other two have been checked
# Bottom-right, bottom-middle, bottom-left # 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]] return self.challengers[self.board[2][2]]
# No need to check the bottom-left, all posiblities have been checked now # 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 # Base things off the middle now, as we only need the two 'middle' possiblites that aren't diagonal
# Top-middle, middle, bottom-middle # 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]] return self.challengers[self.board[1][1]]
# Left-middle, middle, right-middle # 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]] return self.challengers[self.board[1][1]]
# Otherwise nothing has been found, return None # Otherwise nothing has been found, return None
@ -89,25 +121,32 @@ class Board:
def __str__(self): def __str__(self):
# Simple formatting here when you look at it, enough spaces to even out where everything is # 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 # 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"
_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"
_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) return "```\n{}```".format(_board)
class TicTacToe(commands.Cog): class TicTacToe(commands.Cog):
"""Pretty self-explanatory""" """Pretty self-explanatory"""
boards = {} boards = {}
def create(self, server_id, player1, player2): def create(self, server_id, player1, player2):
self.boards[server_id] = Board(player1, player2) self.boards[server_id] = Board(player1, player2)
# Return whoever is x's so that we know who is going first # 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() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def tictactoe(self, ctx, *, option: str): async def tictactoe(self, ctx, *, option: str):
@ -131,11 +170,11 @@ class TicTacToe(commands.Cog):
return return
# Search for the positions in the option given, the actual match doesn't matter, just need to check if it exists # 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) top = re.search("top", option)
middle = re.search('middle', option) middle = re.search("middle", option)
bottom = re.search('bottom', option) bottom = re.search("bottom", option)
left = re.search('left', option) left = re.search("left", option)
right = re.search('right', option) right = re.search("right", option)
# Just a bit of logic to ensure nothing that doesn't make sense is given # Just a bit of logic to ensure nothing that doesn't make sense is given
if top and bottom: if top and bottom:
@ -186,11 +225,18 @@ class TicTacToe(commands.Cog):
if winner: if winner:
# Get the loser based on whether or not the winner is x's # 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 # 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'] loser = (
await ctx.send("{} has won this game of TicTacToe, better luck next time {}".format(winner.display_name, board.challengers["x"]
loser.display_name)) 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 # 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 # This game has ended, delete it so another one can be made
try: try:
del self.boards[ctx.message.guild.id] del self.boards[ctx.message.guild.id]
@ -206,11 +252,17 @@ class TicTacToe(commands.Cog):
pass pass
# If no one has won, and the game has not ended in a tie, print the new updated board # If no one has won, and the game has not ended in a tie, print the new updated board
else: else:
player_turn = board.challengers.get('x') if board.X_turn else board.challengers.get('o') player_turn = (
fmt = str(board) + "\n{} It is now your turn to play!".format(player_turn.display_name) 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) await ctx.send(fmt)
@tictactoe.command(name='start', aliases=['challenge', 'create']) @tictactoe.command(name="start", aliases=["challenge", "create"])
@commands.guild_only() @commands.guild_only()
@utils.can_run(send_messages=True) @utils.can_run(send_messages=True)
async def start_game(self, ctx, player2: discord.Member): 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. # 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 # 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: 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 return
# Make sure we're not being challenged, I always win anyway # Make sure we're not being challenged, I always win anyway
if player2 == ctx.message.guild.me: 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 return
if player2 == player1: 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 return
# Create the board and return who has been decided to go first # Create the board and return who has been decided to go first
x_player = self.create(ctx.message.guild.id, player1, player2) 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 # Print the board too just because
fmt += str(self.boards[ctx.message.guild.id]) fmt += str(self.boards[ctx.message.guild.id])
# We don't need to do anything weird with assigning x_player to something # We don't need to do anything weird with assigning x_player to something
# it is already a member object, just use it # 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! " \ fmt += (
"Use the {}tictactoe command, and a position, to choose where you want to play" \ "I have decided at random, and {} is going to be x's this game. It is your turn first! "
.format(x_player.display_name, ctx.prefix) "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) await ctx.send(fmt)
@tictactoe.command(name='delete', aliases=['stop', 'remove', 'end']) @tictactoe.command(name="delete", aliases=["stop", "remove", "end"])
@commands.guild_only() @commands.guild_only()
@utils.can_run(kick_members=True) @utils.can_run(kick_members=True)
async def stop_game(self, ctx): async def stop_game(self, ctx):
@ -260,7 +323,9 @@ class TicTacToe(commands.Cog):
return return
del self.boards[ctx.message.guild.id] 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): def setup(bot):

View file

@ -2,6 +2,7 @@ import aiohttp
from io import BytesIO from io import BytesIO
import inspect import inspect
import discord import discord
import traceback
from discord.ext import commands from discord.ext import commands
from . import config from . import config
@ -14,7 +15,7 @@ def channel_is_nsfw(channel):
async def download_image(url): async def download_image(url):
"""Returns a file-like object based on the URL provided""" """Returns a file-like object based on the URL provided"""
# Simply read the image, to get the bytes # Simply read the image, to get the bytes
bts = await request(url, attr='read') bts = await request(url, attr="read")
if bts is None: if bts is None:
return None return None
@ -23,12 +24,20 @@ async def download_image(url):
return image 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 # Make sure our User Agent is what's set, and ensure it's sent even if no headers are passed
if headers is None: if headers is None:
headers = {} headers = {}
headers['User-Agent'] = config.user_agent headers["User-Agent"] = config.user_agent
# Try 5 times # Try 5 times
for i in range(5): 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 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 # This check allows us to force the content_type to use whatever content type is given
if force_content_type_json: 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: else:
return_value = return_value() return_value = return_value()
# If this is awaitable, await it # If this is awaitable, await it
@ -67,6 +78,27 @@ async def request(url, *, headers=None, payload=None, method='GET', attr='json',
continue 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): async def convert(ctx, option):
"""Tries to convert a string to an object of useful representiation""" """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 # 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" key = f"{key}_rating"
winner_found = False winner_found = False
loser_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]) results = await db.fetch(query, [winner.id, loser.id])
# Set our defaults for the stats # Set our defaults for the stats
@ -138,7 +172,7 @@ async def update_records(key, db, winner, loser):
winner_wins = loser_wins = 0 winner_wins = loser_wins = 0
winner_losses = loser_losses = 0 winner_losses = loser_losses = 0
for result in results: for result in results:
if result['id'] == winner.id: if result["id"] == winner.id:
winner_found = True winner_found = True
winner_rating = result[key] winner_rating = result[key]
winner_wins = result[wins] winner_wins = result[wins]
@ -174,14 +208,18 @@ async def update_records(key, db, winner, loser):
loser_losses += 1 loser_losses += 1
update_query = f"UPDATE users SET {key}=$1, {wins}=$2, {losses}=$3 WHERE id = $4" 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: 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: 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: if loser_found:
await db.execute(update_query, loser_rating, loser_wins, loser_losses, loser.id) await db.execute(update_query, loser_rating, loser_wins, loser_losses, loser.id)
else: else:
await db.execute(create_query, loser_rating, loser_wins, loser_losses, loser.id) await db.execute(create_query, loser_rating, loser_wins, loser_losses, loser.id)