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