2016-12-10 16:25:08 +13:00
|
|
|
import aiohttp
|
2017-02-13 15:33:24 +13:00
|
|
|
from io import BytesIO
|
2017-01-09 09:05:37 +13:00
|
|
|
import inspect
|
2017-03-23 16:21:34 +13:00
|
|
|
import discord
|
2020-10-11 16:36:11 +13:00
|
|
|
import traceback
|
2017-06-28 12:14:40 +12:00
|
|
|
from discord.ext import commands
|
2016-12-10 16:25:08 +13:00
|
|
|
|
|
|
|
from . import config
|
2017-02-13 15:33:24 +13:00
|
|
|
|
2017-03-08 11:35:30 +13:00
|
|
|
|
2019-01-28 15:58:39 +13:00
|
|
|
def channel_is_nsfw(channel):
|
|
|
|
return isinstance(channel, discord.DMChannel) or channel.is_nsfw()
|
2017-03-08 17:28:30 +13:00
|
|
|
|
2016-12-10 16:25:08 +13:00
|
|
|
|
|
|
|
async def download_image(url):
|
|
|
|
"""Returns a file-like object based on the URL provided"""
|
2017-01-24 11:35:00 +13:00
|
|
|
# Simply read the image, to get the bytes
|
2020-10-11 16:36:11 +13:00
|
|
|
bts = await request(url, attr="read")
|
2017-01-24 11:35:00 +13:00
|
|
|
if bts is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
# Then wrap it in a BytesIO object, to be used like an actual file
|
2017-02-13 15:33:24 +13:00
|
|
|
image = BytesIO(bts)
|
2016-12-10 16:34:17 +13:00
|
|
|
return image
|
2016-12-10 16:25:08 +13:00
|
|
|
|
2017-03-08 11:35:30 +13:00
|
|
|
|
2020-10-11 16:36:11 +13:00
|
|
|
async def request(
|
|
|
|
url,
|
|
|
|
*,
|
|
|
|
headers=None,
|
|
|
|
payload=None,
|
2021-04-01 20:34:34 +13:00
|
|
|
json_data=None,
|
2020-10-11 16:36:11 +13:00
|
|
|
method="GET",
|
|
|
|
attr="json",
|
|
|
|
force_content_type_json=False,
|
|
|
|
):
|
2017-01-21 11:50:02 +13:00
|
|
|
# Make sure our User Agent is what's set, and ensure it's sent even if no headers are passed
|
2017-03-08 11:35:30 +13:00
|
|
|
if headers is None:
|
2017-01-21 11:50:02 +13:00
|
|
|
headers = {}
|
2017-03-08 11:35:30 +13:00
|
|
|
|
2020-10-11 16:36:11 +13:00
|
|
|
headers["User-Agent"] = config.user_agent
|
2017-01-09 09:05:37 +13:00
|
|
|
|
2017-01-21 11:50:02 +13:00
|
|
|
# Try 5 times
|
|
|
|
for i in range(5):
|
2017-01-09 09:05:37 +13:00
|
|
|
try:
|
2017-01-21 11:50:02 +13:00
|
|
|
# Create the session with our headeres
|
2018-07-15 16:05:43 +12:00
|
|
|
async with aiohttp.ClientSession(headers=headers) as session:
|
2017-01-21 11:50:02 +13:00
|
|
|
# Make the request, based on the method, url, and paramaters given
|
2021-04-01 20:34:34 +13:00
|
|
|
async with session.request(
|
|
|
|
method, url, params=payload, json=json_data
|
|
|
|
) as response:
|
2017-01-21 11:50:02 +13:00
|
|
|
# If the request wasn't successful, re-attempt
|
|
|
|
if response.status != 200:
|
2017-01-09 09:05:37 +13:00
|
|
|
continue
|
|
|
|
|
|
|
|
try:
|
2017-01-21 11:50:02 +13:00
|
|
|
# Get the attribute requested
|
|
|
|
return_value = getattr(response, attr)
|
|
|
|
# Next check if this can be called
|
|
|
|
if callable(return_value):
|
2017-07-10 17:20:30 +12:00
|
|
|
# This is use for json; it checks the mimetype instead of checking if the actual data
|
|
|
|
# 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:
|
2020-10-11 16:36:11 +13:00
|
|
|
return_value = return_value(
|
|
|
|
content_type=response.headers["content-type"]
|
|
|
|
)
|
2017-07-10 17:20:30 +12:00
|
|
|
else:
|
|
|
|
return_value = return_value()
|
2017-01-21 11:50:02 +13:00
|
|
|
# If this is awaitable, await it
|
|
|
|
if inspect.isawaitable(return_value):
|
|
|
|
return_value = await return_value
|
|
|
|
|
|
|
|
# Then return it
|
|
|
|
return return_value
|
|
|
|
except AttributeError:
|
|
|
|
# If an invalid attribute was requested, return None
|
|
|
|
return None
|
|
|
|
# If an error was hit other than the one we want to catch, try again
|
2021-03-26 13:03:58 +13:00
|
|
|
except Exception:
|
2017-01-09 09:05:37 +13:00
|
|
|
continue
|
|
|
|
|
2019-01-28 15:58:39 +13:00
|
|
|
|
2020-10-11 16:36:11 +13:00
|
|
|
async def log_error(error, bot, ctx=None):
|
|
|
|
# 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}"
|
2021-03-26 13:20:16 +13:00
|
|
|
# If the channel has been set, use it
|
|
|
|
if isinstance(bot.error_channel, discord.TextChannel):
|
|
|
|
await bot.error_channel.send(fmt)
|
|
|
|
# Otherwise if it hasn't been set yet, try to set it
|
|
|
|
if isinstance(bot.error_channel, int):
|
|
|
|
channel = bot.get_channel(bot.error_channel)
|
|
|
|
if channel is not None:
|
|
|
|
bot.error_channel = channel
|
|
|
|
await bot.error_channel.send(fmt)
|
|
|
|
# If we can't find the channel yet (before ready) just send to file
|
|
|
|
else:
|
|
|
|
fmt = fmt.strip("`")
|
|
|
|
with open("error_log", "a") as f:
|
|
|
|
print(fmt, file=f)
|
|
|
|
# Otherwise just send to file
|
|
|
|
else:
|
2020-10-11 16:36:11 +13:00
|
|
|
fmt = fmt.strip("`")
|
|
|
|
with open("error_log", "a") as f:
|
|
|
|
print(fmt, file=f)
|
|
|
|
|
|
|
|
|
2017-06-28 12:14:40 +12:00
|
|
|
async def convert(ctx, option):
|
|
|
|
"""Tries to convert a string to an object of useful representiation"""
|
2019-01-28 15:58:39 +13:00
|
|
|
# Due to id's being ints, it's very possible that an int is passed
|
|
|
|
option = str(option)
|
2017-06-28 12:14:40 +12:00
|
|
|
cmd = ctx.bot.get_command(option)
|
|
|
|
if cmd:
|
|
|
|
return cmd
|
|
|
|
|
|
|
|
async def do_convert(converter, _ctx, _option):
|
|
|
|
try:
|
|
|
|
return await converter.convert(_ctx, _option)
|
|
|
|
except commands.converter.BadArgument:
|
|
|
|
return None
|
|
|
|
|
|
|
|
member = await do_convert(commands.converter.MemberConverter(), ctx, option)
|
|
|
|
if member:
|
|
|
|
return member
|
|
|
|
|
|
|
|
channel = await do_convert(commands.converter.TextChannelConverter(), ctx, option)
|
|
|
|
if channel:
|
|
|
|
return channel
|
|
|
|
|
|
|
|
channel = await do_convert(commands.converter.VoiceChannelConverter(), ctx, option)
|
|
|
|
if channel:
|
|
|
|
return channel
|
|
|
|
|
|
|
|
role = await do_convert(commands.converter.RoleConverter(), ctx, option)
|
|
|
|
if role:
|
|
|
|
return role
|
|
|
|
|
2017-03-08 11:35:30 +13:00
|
|
|
|
2019-01-28 15:58:39 +13:00
|
|
|
def update_rating(winner_rating, loser_rating):
|
2021-01-03 18:42:21 +13:00
|
|
|
difference = int((loser_rating - winner_rating) / 25 + 16)
|
2019-01-28 15:58:39 +13:00
|
|
|
|
2021-01-03 18:42:21 +13:00
|
|
|
return winner_rating + difference, loser_rating - difference
|
2019-01-28 15:58:39 +13:00
|
|
|
|
|
|
|
|
2017-06-07 20:30:19 +12:00
|
|
|
async def update_records(key, db, winner, loser):
|
2016-12-10 16:25:08 +13:00
|
|
|
# We're using the Harkness scale to rate
|
|
|
|
# http://opnetchessclub.wikidot.com/harkness-rating-system
|
2019-01-28 15:58:39 +13:00
|
|
|
wins = f"{key}_wins"
|
|
|
|
losses = f"{key}_losses"
|
|
|
|
key = f"{key}_rating"
|
2019-02-15 13:36:13 +13:00
|
|
|
winner_found = False
|
|
|
|
loser_found = False
|
2020-10-11 16:36:11 +13:00
|
|
|
query = (
|
|
|
|
f"SELECT id, {key}, {wins}, {losses} FROM users WHERE id = any($1::bigint[])"
|
|
|
|
)
|
2019-02-15 13:36:13 +13:00
|
|
|
results = await db.fetch(query, [winner.id, loser.id])
|
2019-01-28 15:58:39 +13:00
|
|
|
|
2019-02-15 13:36:13 +13:00
|
|
|
# Set our defaults for the stats
|
2019-01-28 15:58:39 +13:00
|
|
|
winner_rating = loser_rating = 1000
|
2019-02-15 13:36:13 +13:00
|
|
|
winner_wins = loser_wins = 0
|
|
|
|
winner_losses = loser_losses = 0
|
2019-01-28 15:58:39 +13:00
|
|
|
for result in results:
|
2020-10-11 16:36:11 +13:00
|
|
|
if result["id"] == winner.id:
|
2019-02-15 13:36:13 +13:00
|
|
|
winner_found = True
|
2019-01-28 15:58:39 +13:00
|
|
|
winner_rating = result[key]
|
2019-02-15 13:36:13 +13:00
|
|
|
winner_wins = result[wins]
|
|
|
|
winner_losses = result[losses]
|
2019-01-28 15:58:39 +13:00
|
|
|
else:
|
2019-02-15 13:36:13 +13:00
|
|
|
loser_found = True
|
2019-01-28 15:58:39 +13:00
|
|
|
loser_rating = result[key]
|
2019-02-15 13:36:13 +13:00
|
|
|
loser_wins = result[wins]
|
|
|
|
loser_losses = result[losses]
|
2016-12-10 16:25:08 +13:00
|
|
|
|
|
|
|
# The scale is based off of increments of 25, increasing the change by 1 for each increment
|
|
|
|
# That is all this loop does, increment the "change" for every increment of 25
|
|
|
|
# The change caps off at 300 however, so break once we are over that limit
|
|
|
|
difference = abs(winner_rating - loser_rating)
|
|
|
|
rating_change = 0
|
|
|
|
count = 25
|
|
|
|
while count <= difference:
|
|
|
|
if count > 300:
|
|
|
|
break
|
|
|
|
rating_change += 1
|
|
|
|
count += 25
|
|
|
|
|
|
|
|
# 16 is the base change, increased or decreased based on whoever has the higher current rating
|
|
|
|
if winner_rating > loser_rating:
|
|
|
|
winner_rating += 16 - rating_change
|
|
|
|
loser_rating -= 16 - rating_change
|
|
|
|
else:
|
|
|
|
winner_rating += 16 + rating_change
|
|
|
|
loser_rating -= 16 + rating_change
|
|
|
|
|
2019-02-15 13:36:13 +13:00
|
|
|
# Just increase wins/losses for each person
|
2016-12-10 16:25:08 +13:00
|
|
|
winner_wins += 1
|
|
|
|
loser_losses += 1
|
|
|
|
|
2019-02-15 13:36:13 +13:00
|
|
|
update_query = f"UPDATE users SET {key}=$1, {wins}=$2, {losses}=$3 WHERE id = $4"
|
2020-10-11 16:36:11 +13:00
|
|
|
create_query = (
|
|
|
|
f"INSERT INTO users ({key}, {wins}, {losses}, id) VALUES ($1, $2, $3, $4)"
|
|
|
|
)
|
2019-02-15 13:36:13 +13:00
|
|
|
if winner_found:
|
2020-10-11 16:36:11 +13:00
|
|
|
await db.execute(
|
|
|
|
update_query, winner_rating, winner_wins, winner_losses, winner.id
|
|
|
|
)
|
2019-02-15 13:36:13 +13:00
|
|
|
else:
|
2020-10-11 16:36:11 +13:00
|
|
|
await db.execute(
|
|
|
|
create_query, winner_rating, winner_wins, winner_losses, winner.id
|
|
|
|
)
|
2019-02-15 13:36:13 +13:00
|
|
|
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)
|