1
0
Fork 0
mirror of synced 2024-04-29 02:02:28 +12:00

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

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

33
bot.py
View file

@ -1,13 +1,8 @@
import discord
import 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

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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)

View file

@ -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

View file

@ -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!")

View file

@ -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):

View file

@ -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

View file

@ -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)

View file

@ -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('&quot;', '"', snippet)
snippet = re.sub('<span class=\\"searchmatch\\">', "", snippet)
snippet = re.sub("</span>", "", snippet)
snippet = re.sub("&quot;", '"', 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:

View file

@ -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:

View file

@ -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

View file

@ -9,9 +9,19 @@ BASE_URL = "https://api.owapi.net/api/v3/u/"
# The API returns something if it exists, and leaves it out of the data returned entirely if it does not
# 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):

View file

@ -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))

View file

@ -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))

View file

@ -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)

View file

@ -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!"

View file

@ -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)

View file

@ -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

View file

@ -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))

View file

@ -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)

View file

@ -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)

View file

@ -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):

View file

@ -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)