aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ChrisJL <[email protected]>2021-09-03 16:28:04 +0100
committerGravatar GitHub <[email protected]>2021-09-03 16:28:04 +0100
commitcd7060835b5b0d150c6e91d75bc3227ee43db0ba (patch)
tree4264ddbb25e86184255574cfd0e8fa9bb11d7bcb
parentHandle status not found with 404 picture (diff)
parentMerge pull request #839 from python-discord/android-codeblock-fix (diff)
Merge branch 'main' into teapot-support
-rw-r--r--bot/bot.py23
-rw-r--r--bot/constants.py4
-rw-r--r--bot/exts/__init__.py2
-rw-r--r--bot/exts/christmas/advent_of_code/_cog.py2
-rw-r--r--bot/exts/christmas/advent_of_code/_helpers.py15
-rw-r--r--bot/exts/christmas/hanukkah_embed.py5
-rw-r--r--bot/exts/easter/bunny_name_generator.py6
-rw-r--r--bot/exts/easter/egg_decorating.py8
-rw-r--r--bot/exts/easter/egghead_quiz.py2
-rw-r--r--bot/exts/evergreen/avatar_modification/_effects.py12
-rw-r--r--bot/exts/evergreen/avatar_modification/avatar_modify.py40
-rw-r--r--bot/exts/evergreen/battleship.py30
-rw-r--r--bot/exts/evergreen/bookmark.py8
-rw-r--r--bot/exts/evergreen/cheatsheet.py8
-rw-r--r--bot/exts/evergreen/coinflip.py5
-rw-r--r--bot/exts/evergreen/connect_four.py37
-rw-r--r--bot/exts/evergreen/duck_game.py4
-rw-r--r--bot/exts/evergreen/emoji.py8
-rw-r--r--bot/exts/evergreen/error_handler.py9
-rw-r--r--bot/exts/evergreen/fun.py21
-rw-r--r--bot/exts/evergreen/game.py16
-rw-r--r--bot/exts/evergreen/githubinfo.py2
-rw-r--r--bot/exts/evergreen/help.py10
-rw-r--r--bot/exts/evergreen/issues.py10
-rw-r--r--bot/exts/evergreen/minesweeper.py14
-rw-r--r--bot/exts/evergreen/movie.py10
-rw-r--r--bot/exts/evergreen/realpython.py6
-rw-r--r--bot/exts/evergreen/recommend_game.py4
-rw-r--r--bot/exts/evergreen/reddit.py8
-rw-r--r--bot/exts/evergreen/snakes/_converter.py6
-rw-r--r--bot/exts/evergreen/snakes/_snakes_cog.py24
-rw-r--r--bot/exts/evergreen/snakes/_utils.py108
-rw-r--r--bot/exts/evergreen/source.py4
-rw-r--r--bot/exts/evergreen/space.py6
-rw-r--r--bot/exts/evergreen/stackoverflow.py6
-rw-r--r--bot/exts/evergreen/tic_tac_toe.py24
-rw-r--r--bot/exts/evergreen/trivia_quiz.py6
-rw-r--r--bot/exts/evergreen/wikipedia.py3
-rw-r--r--bot/exts/evergreen/wolfram.py14
-rw-r--r--bot/exts/evergreen/xkcd.py6
-rw-r--r--bot/exts/halloween/hacktober-issue-finder.py6
-rw-r--r--bot/exts/halloween/hacktoberstats.py18
-rw-r--r--bot/exts/halloween/halloween_facts.py3
-rw-r--r--bot/exts/halloween/spookynamerate.py6
-rw-r--r--bot/exts/halloween/spookyrating.py3
-rw-r--r--bot/exts/halloween/timeleft.py3
-rw-r--r--bot/exts/internal_eval/_helpers.py25
-rw-r--r--bot/exts/internal_eval/_internal_eval.py4
-rw-r--r--bot/exts/pride/pride_leader.py8
-rw-r--r--bot/exts/utils/extensions.py17
-rw-r--r--bot/exts/valentines/be_my_valentine.py7
-rw-r--r--bot/exts/valentines/valentine_zodiac.py6
-rw-r--r--bot/utils/__init__.py5
-rw-r--r--bot/utils/checks.py5
-rw-r--r--bot/utils/converters.py4
-rw-r--r--bot/utils/decorators.py37
-rw-r--r--bot/utils/extensions.py3
-rw-r--r--bot/utils/pagination.py5
-rw-r--r--bot/utils/randomization.py7
-rw-r--r--poetry.lock323
-rw-r--r--pyproject.toml2
61 files changed, 531 insertions, 502 deletions
diff --git a/bot/bot.py b/bot/bot.py
index b8de97aa..c7b87a65 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -1,13 +1,15 @@
import asyncio
import logging
import socket
+from contextlib import suppress
from typing import Optional
import discord
from aiohttp import AsyncResolver, ClientSession, TCPConnector
from async_rediscache import RedisSession
-from discord import DiscordException, Embed
+from discord import DiscordException, Embed, Forbidden, Thread
from discord.ext import commands
+from discord.ext.commands import Cog, when_mentioned_or
from bot import constants
@@ -45,6 +47,21 @@ class Bot(commands.Bot):
return None
return guild.me
+ @Cog.listener()
+ async def on_thread_join(self, thread: Thread) -> None:
+ """
+ Try to join newly created threads.
+
+ Despite the event name being misleading, this is dispatched when new threads are created.
+ We want our bots to automatically join threads in order to answer commands using their prefixes.
+ """
+ if thread.me:
+ # Already in this thread, return early
+ return
+
+ with suppress(Forbidden):
+ await thread.join()
+
async def close(self) -> None:
"""Close Redis session when bot is shutting down."""
await super().close()
@@ -120,7 +137,7 @@ class Bot(commands.Bot):
return
if not icon:
- icon = self.user.avatar_url_as(format="png")
+ icon = self.user.display_avatar.url
embed = Embed(description=details)
embed.set_author(name=title, icon_url=icon)
@@ -203,7 +220,7 @@ loop.run_until_complete(redis_session.connect())
bot = Bot(
redis_session=redis_session,
- command_prefix=constants.Client.prefix,
+ command_prefix=when_mentioned_or(constants.Client.prefix),
activity=discord.Game(name=f"Commands: {constants.Client.prefix}help"),
allowed_mentions=discord.AllowedMentions(everyone=False, roles=_allowed_roles),
intents=_intents,
diff --git a/bot/constants.py b/bot/constants.py
index 2730106b..2313bfdb 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -3,7 +3,7 @@ import enum
import logging
from datetime import datetime
from os import environ
-from typing import Dict, NamedTuple
+from typing import NamedTuple
__all__ = (
"AdventOfCode",
@@ -56,7 +56,7 @@ class AdventOfCodeLeaderboard:
return self._session
-def _parse_aoc_leaderboard_env() -> Dict[str, AdventOfCodeLeaderboard]:
+def _parse_aoc_leaderboard_env() -> dict[str, AdventOfCodeLeaderboard]:
"""
Parse the environment variable containing leaderboard information.
diff --git a/bot/exts/__init__.py b/bot/exts/__init__.py
index 13f484ac..cb9c5ae8 100644
--- a/bot/exts/__init__.py
+++ b/bot/exts/__init__.py
@@ -1,6 +1,6 @@
import logging
import pkgutil
-from typing import Iterator
+from collections.abc import Iterator
__all__ = ("get_package_names",)
diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py
index 3d61753b..bc2ccc04 100644
--- a/bot/exts/christmas/advent_of_code/_cog.py
+++ b/bot/exts/christmas/advent_of_code/_cog.py
@@ -29,7 +29,7 @@ AOC_WHITELIST = AOC_WHITELIST_RESTRICTED + (Channels.advent_of_code,)
class AdventOfCode(commands.Cog):
"""Advent of Code festivities! Ho Ho Ho!"""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
self._base_url = f"https://adventofcode.com/{AocConfig.year}"
diff --git a/bot/exts/christmas/advent_of_code/_helpers.py b/bot/exts/christmas/advent_of_code/_helpers.py
index e26a17ca..b64b44a6 100644
--- a/bot/exts/christmas/advent_of_code/_helpers.py
+++ b/bot/exts/christmas/advent_of_code/_helpers.py
@@ -5,8 +5,7 @@ import json
import logging
import math
import operator
-import typing
-from typing import Tuple
+from typing import Any, Optional
import aiohttp
import arrow
@@ -71,7 +70,7 @@ class FetchingLeaderboardFailedError(Exception):
"""Raised when one or more leaderboards could not be fetched at all."""
-def leaderboard_sorting_function(entry: typing.Tuple[str, dict]) -> typing.Tuple[int, int]:
+def leaderboard_sorting_function(entry: tuple[str, dict]) -> tuple[int, int]:
"""
Provide a sorting value for our leaderboard.
@@ -155,7 +154,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict:
return {"daily_stats": daily_stats, "leaderboard": sorted_leaderboard}
-def _format_leaderboard(leaderboard: typing.Dict[str, dict]) -> str:
+def _format_leaderboard(leaderboard: dict[str, dict]) -> str:
"""Format the leaderboard using the AOC_TABLE_TEMPLATE."""
leaderboard_lines = [HEADER]
for rank, data in enumerate(leaderboard.values(), start=1):
@@ -171,7 +170,7 @@ def _format_leaderboard(leaderboard: typing.Dict[str, dict]) -> str:
return "\n".join(leaderboard_lines)
-async def _leaderboard_request(url: str, board: int, cookies: dict) -> typing.Optional[dict]:
+async def _leaderboard_request(url: str, board: str, cookies: dict) -> dict[str, Any]:
"""Make a leaderboard request using the specified session cookie."""
async with aiohttp.request("GET", url, headers=AOC_REQUEST_HEADER, cookies=cookies) as resp:
# The Advent of Code website redirects silently with a 200 response if a
@@ -188,7 +187,7 @@ async def _leaderboard_request(url: str, board: int, cookies: dict) -> typing.Op
return await resp.json()
-async def _fetch_leaderboard_data() -> typing.Dict[str, typing.Any]:
+async def _fetch_leaderboard_data() -> dict[str, Any]:
"""Fetch data for all leaderboards and return a pooled result."""
year = AdventOfCode.year
@@ -333,7 +332,7 @@ def get_summary_embed(leaderboard: dict) -> discord.Embed:
return aoc_embed
-async def get_public_join_code(author: discord.Member) -> typing.Optional[str]:
+async def get_public_join_code(author: discord.Member) -> Optional[str]:
"""
Get the join code for one of the non-staff leaderboards.
@@ -398,7 +397,7 @@ def is_in_advent() -> bool:
return arrow.now(EST).day in range(1, 25) and arrow.now(EST).month == 12
-def time_left_to_est_midnight() -> Tuple[datetime.datetime, datetime.timedelta]:
+def time_left_to_est_midnight() -> tuple[datetime.datetime, datetime.timedelta]:
"""Calculate the amount of time left until midnight EST/UTC-5."""
# Change all time properties back to 00:00
todays_midnight = arrow.now(EST).replace(
diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py
index 119f2446..00125be3 100644
--- a/bot/exts/christmas/hanukkah_embed.py
+++ b/bot/exts/christmas/hanukkah_embed.py
@@ -1,6 +1,5 @@
import datetime
import logging
-from typing import List
from discord import Embed
from discord.ext import commands
@@ -26,7 +25,7 @@ class HanukkahEmbed(commands.Cog):
self.hanukkah_months = []
self.hanukkah_years = []
- async def get_hanukkah_dates(self) -> List[str]:
+ async def get_hanukkah_dates(self) -> list[str]:
"""Gets the dates for hanukkah festival."""
hanukkah_dates = []
async with self.bot.http_session.get(HEBCAL_URL) as response:
@@ -101,7 +100,7 @@ class HanukkahEmbed(commands.Cog):
await ctx.send(embed=embed)
- def hanukkah_dates_split(self, hanukkah_dates: List[str]) -> None:
+ def hanukkah_dates_split(self, hanukkah_dates: list[str]) -> None:
"""We are splitting the dates for hanukkah into days, months and years."""
for date in hanukkah_dates:
self.hanukkah_days.append(date[8:10])
diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py
index 3e97373f..4c3137de 100644
--- a/bot/exts/easter/bunny_name_generator.py
+++ b/bot/exts/easter/bunny_name_generator.py
@@ -3,7 +3,7 @@ import logging
import random
import re
from pathlib import Path
-from typing import List, Union
+from typing import Optional
from discord.ext import commands
@@ -18,7 +18,7 @@ class BunnyNameGenerator(commands.Cog):
"""Generate a random bunny name, or bunnify your Discord username!"""
@staticmethod
- def find_separators(displayname: str) -> Union[List[str], None]:
+ def find_separators(displayname: str) -> Optional[list[str]]:
"""Check if Discord name contains spaces so we can bunnify an individual word in the name."""
new_name = re.split(r"[_.\s]", displayname)
if displayname not in new_name:
@@ -26,7 +26,7 @@ class BunnyNameGenerator(commands.Cog):
return None
@staticmethod
- def find_vowels(displayname: str) -> str:
+ def find_vowels(displayname: str) -> Optional[str]:
"""
Finds vowels in the user's display name.
diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py
index fd7620d4..fb5701c4 100644
--- a/bot/exts/easter/egg_decorating.py
+++ b/bot/exts/easter/egg_decorating.py
@@ -4,7 +4,7 @@ import random
from contextlib import suppress
from io import BytesIO
from pathlib import Path
-from typing import Union
+from typing import Optional, Union
import discord
from PIL import Image
@@ -33,7 +33,7 @@ class EggDecorating(commands.Cog):
"""Decorate some easter eggs!"""
@staticmethod
- def replace_invalid(colour: str) -> Union[int, None]:
+ def replace_invalid(colour: str) -> Optional[int]:
"""Attempts to match with HTML or XKCD colour names, returning the int value."""
with suppress(KeyError):
return int(HTML_COLOURS[colour], 16)
@@ -44,7 +44,7 @@ class EggDecorating(commands.Cog):
@commands.command(aliases=("decorateegg",))
async def eggdecorate(
self, ctx: commands.Context, *colours: Union[discord.Colour, str]
- ) -> Union[Image.Image, None]:
+ ) -> Optional[Image.Image]:
"""
Picks a random egg design and decorates it using the given colours.
@@ -108,7 +108,7 @@ class EggDecorating(commands.Cog):
description="Here is your pretty little egg. Hope you like it!"
)
embed.set_image(url="attachment://egg.png")
- embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.display_avatar.url)
await ctx.send(file=file, embed=embed)
return new_im
diff --git a/bot/exts/easter/egghead_quiz.py b/bot/exts/easter/egghead_quiz.py
index 7c4960cd..ad550567 100644
--- a/bot/exts/easter/egghead_quiz.py
+++ b/bot/exts/easter/egghead_quiz.py
@@ -31,7 +31,7 @@ TIMELIMIT = 30
class EggheadQuiz(commands.Cog):
"""This cog contains the command for the Easter quiz!"""
- def __init__(self) -> None:
+ def __init__(self):
self.quiz_messages = {}
@commands.command(aliases=("eggheadquiz", "easterquiz"))
diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py
index 92244207..df741973 100644
--- a/bot/exts/evergreen/avatar_modification/_effects.py
+++ b/bot/exts/evergreen/avatar_modification/_effects.py
@@ -1,8 +1,8 @@
import math
import random
-import typing as t
from io import BytesIO
from pathlib import Path
+from typing import Callable, Optional
import discord
from PIL import Image, ImageDraw, ImageOps
@@ -18,7 +18,7 @@ class PfpEffects:
"""
@staticmethod
- def apply_effect(image_bytes: bytes, effect: t.Callable, filename: str, *args) -> discord.File:
+ def apply_effect(image_bytes: bytes, effect: Callable, filename: str, *args) -> discord.File:
"""Applies the given effect to the image passed to it."""
im = Image.open(BytesIO(image_bytes))
im = im.convert("RGBA")
@@ -32,7 +32,7 @@ class PfpEffects:
return discord.File(bufferedio, filename=filename)
@staticmethod
- def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]:
+ def closest(x: tuple[int, int, int]) -> tuple[int, int, int]:
"""
Finds the closest "easter" colour to a given pixel.
@@ -40,7 +40,7 @@ class PfpEffects:
"""
r1, g1, b1 = x
- def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]:
+ def distance(point: tuple[int, int, int]) -> int:
"""Finds the difference between a pastel colour and the original pixel colour."""
r2, g2, b2 = point
return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2
@@ -108,7 +108,7 @@ class PfpEffects:
return image
@staticmethod
- def easterify_effect(image: Image.Image, overlay_image: t.Optional[Image.Image] = None) -> Image.Image:
+ def easterify_effect(image: Image.Image, overlay_image: Optional[Image.Image] = None) -> Image.Image:
"""
Applies the easter effect to the given image.
@@ -212,7 +212,7 @@ class PfpEffects:
return new_imgs
@staticmethod
- def join_images(images: t.List[Image.Image]) -> Image.Image:
+ def join_images(images: list[Image.Image]) -> Image.Image:
"""
Stitches all the image squares into a new image.
diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py
index 7b4ae9c7..18202902 100644
--- a/bot/exts/evergreen/avatar_modification/avatar_modify.py
+++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py
@@ -3,10 +3,10 @@ import json
import logging
import math
import string
-import typing as t
import unicodedata
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
+from typing import Callable, Optional, TypeVar, Union
import discord
from discord.ext import commands
@@ -25,12 +25,12 @@ FILENAME_STRING = "{effect}_{author}.png"
MAX_SQUARES = 10_000
-T = t.TypeVar("T")
+T = TypeVar("T")
GENDER_OPTIONS = json.loads(Path("bot/resources/pride/gender_options.json").read_text("utf8"))
-async def in_executor(func: t.Callable[..., T], *args) -> T:
+async def in_executor(func: Callable[..., T], *args) -> T:
"""
Runs the given synchronous function `func` in an executor.
@@ -62,10 +62,10 @@ def file_safe_name(effect: str, display_name: str) -> str:
class AvatarModify(commands.Cog):
"""Various commands for users to apply affects to their own avatars."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
- async def _fetch_user(self, user_id: int) -> t.Optional[discord.User]:
+ async def _fetch_user(self, user_id: int) -> Optional[discord.User]:
"""
Fetches a user and handles errors.
@@ -100,7 +100,7 @@ class AvatarModify(commands.Cog):
await ctx.send(f"{Emojis.cross_mark} Could not get user info.")
return
- image_bytes = await user.avatar_url_as(size=1024).read()
+ image_bytes = await user.display_avatar.replace(size=1024).read()
file_name = file_safe_name("eightbit_avatar", ctx.author.display_name)
file = await in_executor(
@@ -116,12 +116,12 @@ class AvatarModify(commands.Cog):
)
embed.set_image(url=f"attachment://{file_name}")
- embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.avatar_url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.display_avatar.url)
await ctx.send(embed=embed, file=file)
@avatar_modify.command(name="reverse", root_aliases=("reverse",))
- async def reverse(self, ctx: commands.Context, *, text: t.Optional[str]) -> None:
+ async def reverse(self, ctx: commands.Context, *, text: Optional[str]) -> None:
"""
Reverses the sent text.
@@ -137,7 +137,7 @@ class AvatarModify(commands.Cog):
await ctx.send(f"{Emojis.cross_mark} Could not get user info.")
return
- image_bytes = await user.avatar_url_as(size=1024).read()
+ image_bytes = await user.display_avatar.replace(size=1024).read()
filename = file_safe_name("reverse_avatar", ctx.author.display_name)
file = await in_executor(
@@ -153,12 +153,12 @@ class AvatarModify(commands.Cog):
)
embed.set_image(url=f"attachment://{filename}")
- embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.avatar_url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.display_avatar.url)
await ctx.send(embed=embed, file=file)
@avatar_modify.command(aliases=("easterify",), root_aliases=("easterify", "avatareasterify"))
- async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None:
+ async def avatareasterify(self, ctx: commands.Context, *colours: Union[discord.Colour, str]) -> None:
"""
This "Easterifies" the user's avatar.
@@ -193,7 +193,7 @@ class AvatarModify(commands.Cog):
return
ctx.send = send_message # Reassigns ctx.send
- image_bytes = await user.avatar_url_as(size=256).read()
+ image_bytes = await user.display_avatar.replace(size=256).read()
file_name = file_safe_name("easterified_avatar", ctx.author.display_name)
file = await in_executor(
@@ -205,11 +205,11 @@ class AvatarModify(commands.Cog):
)
embed = discord.Embed(
- name="Your Lovely Easterified Avatar!",
+ title="Your Lovely Easterified Avatar!",
description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D"
)
embed.set_image(url=f"attachment://{file_name}")
- embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.avatar_url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.display_avatar.url)
await ctx.send(file=file, embed=embed)
@@ -235,7 +235,7 @@ class AvatarModify(commands.Cog):
)
embed = discord.Embed(
- name="Your Lovely Pride Avatar!",
+ title="Your Lovely Pride Avatar!",
description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D"
)
embed.set_image(url=f"attachment://{file_name}")
@@ -268,7 +268,7 @@ class AvatarModify(commands.Cog):
if not user:
await ctx.send(f"{Emojis.cross_mark} Could not get user info.")
return
- image_bytes = await user.avatar_url_as(size=1024).read()
+ image_bytes = await user.display_avatar.replace(size=1024).read()
await self.send_pride_image(ctx, image_bytes, pixels, flag, option)
@prideavatar.command()
@@ -296,7 +296,7 @@ class AvatarModify(commands.Cog):
return
async with ctx.typing():
- image_bytes = await user.avatar_url_as(size=1024).read()
+ image_bytes = await user.display_avatar.replace(size=1024).read()
file_name = file_safe_name("spooky_avatar", ctx.author.display_name)
@@ -312,7 +312,7 @@ class AvatarModify(commands.Cog):
colour=Colours.soft_red
)
embed.set_image(url=f"attachment://{file_name}")
- embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.display_avatar.url)
await ctx.send(file=file, embed=embed)
@@ -335,7 +335,7 @@ class AvatarModify(commands.Cog):
file_name = file_safe_name("mosaic_avatar", ctx.author.display_name)
- img_bytes = await user.avatar_url_as(size=1024).read()
+ img_bytes = await user.display_avatar.replace(size=1024).read()
file = await in_executor(
PfpEffects.apply_effect,
@@ -362,7 +362,7 @@ class AvatarModify(commands.Cog):
)
embed.set_image(url=f"attachment://{file_name}")
- embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=user.avatar_url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=user.display_avatar.url)
await ctx.send(file=file, embed=embed)
diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py
index c2f2079c..f4351954 100644
--- a/bot/exts/evergreen/battleship.py
+++ b/bot/exts/evergreen/battleship.py
@@ -2,9 +2,9 @@ import asyncio
import logging
import random
import re
-import typing
from dataclasses import dataclass
from functools import partial
+from typing import Optional
import discord
from discord.ext import commands
@@ -19,20 +19,20 @@ log = logging.getLogger(__name__)
class Square:
"""Each square on the battleship grid - if they contain a boat and if they've been aimed at."""
- boat: typing.Optional[str]
+ boat: Optional[str]
aimed: bool
-Grid = typing.List[typing.List[Square]]
-EmojiSet = typing.Dict[typing.Tuple[bool, bool], str]
+Grid = list[list[Square]]
+EmojiSet = dict[tuple[bool, bool], str]
@dataclass
class Player:
"""Each player in the game - their messages for the boards and their current grid."""
- user: typing.Optional[discord.Member]
- board: typing.Optional[discord.Message]
+ user: Optional[discord.Member]
+ board: Optional[discord.Message]
opponent_board: discord.Message
grid: Grid
@@ -100,7 +100,7 @@ class Game:
channel: discord.TextChannel,
player1: discord.Member,
player2: discord.Member
- ) -> None:
+ ):
self.bot = bot
self.public_channel = channel
@@ -110,10 +110,10 @@ class Game:
self.gameover: bool = False
- self.turn: typing.Optional[discord.Member] = None
- self.next: typing.Optional[discord.Member] = None
+ self.turn: Optional[discord.Member] = None
+ self.next: Optional[discord.Member] = None
- self.match: typing.Optional[typing.Match] = None
+ self.match: Optional[re.Match] = None
self.surrender: bool = False
self.setup_grids()
@@ -233,7 +233,7 @@ class Game:
self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI))
return bool(self.match)
- async def take_turn(self) -> typing.Optional[Square]:
+ async def take_turn(self) -> Optional[Square]:
"""Lets the player who's turn it is choose a square."""
square = None
turn_message = await self.turn.user.send(
@@ -268,7 +268,7 @@ class Game:
await turn_message.delete()
return square
- async def hit(self, square: Square, alert_messages: typing.List[discord.Message]) -> None:
+ async def hit(self, square: Square, alert_messages: list[discord.Message]) -> None:
"""Occurs when a player successfully aims for a ship."""
await self.turn.user.send("Hit!", delete_after=3.0)
alert_messages.append(await self.next.user.send("Hit!"))
@@ -322,10 +322,10 @@ class Game:
class Battleship(commands.Cog):
"""Play the classic game Battleship!"""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
- self.games: typing.List[Game] = []
- self.waiting: typing.List[discord.Member] = []
+ self.games: list[Game] = []
+ self.waiting: list[discord.Member] = []
def predicate(
self,
diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py
index f93371a6..a91ef1c0 100644
--- a/bot/exts/evergreen/bookmark.py
+++ b/bot/exts/evergreen/bookmark.py
@@ -1,7 +1,7 @@
import asyncio
import logging
import random
-import typing as t
+from typing import Optional
import discord
from discord.ext import commands
@@ -37,7 +37,7 @@ class Bookmark(commands.Cog):
name="Wanna give it a visit?",
value=f"[Visit original message]({target_message.jump_url})"
)
- embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url)
+ embed.set_author(name=target_message.author, icon_url=target_message.author.display_avatar.url)
embed.set_thumbnail(url=Icons.bookmark)
return embed
@@ -92,7 +92,7 @@ class Bookmark(commands.Cog):
async def bookmark(
self,
ctx: commands.Context,
- target_message: t.Optional[WrappedMessageConverter],
+ target_message: Optional[WrappedMessageConverter],
*,
title: str = "Bookmark"
) -> None:
@@ -103,7 +103,7 @@ class Bookmark(commands.Cog):
target_message = ctx.message.reference.resolved
# Prevent users from bookmarking a message in a channel they don't have access to
- permissions = ctx.author.permissions_in(target_message.channel)
+ permissions = target_message.channel.permissions_for(ctx.author)
if not permissions.read_messages:
log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions.")
embed = discord.Embed(
diff --git a/bot/exts/evergreen/cheatsheet.py b/bot/exts/evergreen/cheatsheet.py
index ae7793c9..33d29f67 100644
--- a/bot/exts/evergreen/cheatsheet.py
+++ b/bot/exts/evergreen/cheatsheet.py
@@ -1,6 +1,6 @@
import random
import re
-import typing as t
+from typing import Union
from urllib.parse import quote_plus
from discord import Embed
@@ -52,7 +52,7 @@ class CheatSheet(commands.Cog):
)
return embed
- def result_fmt(self, url: str, body_text: str) -> t.Tuple[bool, t.Union[str, Embed]]:
+ def result_fmt(self, url: str, body_text: str) -> tuple[bool, Union[str, Embed]]:
"""Format Result."""
if body_text.startswith("# 404 NOT FOUND"):
embed = self.fmt_error_embed()
@@ -64,13 +64,13 @@ class CheatSheet(commands.Cog):
description = (
f"**Result Of cht.sh**\n"
f"```python\n{body_text[:body_space]}\n"
- f"... (truncated - too many lines)```\n"
+ f"... (truncated - too many lines)\n```\n"
f"Full results: {url} "
)
else:
description = (
f"**Result Of cht.sh**\n"
- f"```python\n{body_text}```\n"
+ f"```python\n{body_text}\n```\n"
f"{url}"
)
return False, description
diff --git a/bot/exts/evergreen/coinflip.py b/bot/exts/evergreen/coinflip.py
index d1762463..804306bd 100644
--- a/bot/exts/evergreen/coinflip.py
+++ b/bot/exts/evergreen/coinflip.py
@@ -1,5 +1,4 @@
import random
-from typing import Tuple
from discord.ext import commands
@@ -10,8 +9,8 @@ from bot.constants import Emojis
class CoinSide(commands.Converter):
"""Class used to convert the `side` parameter of coinflip command."""
- HEADS: Tuple[str] = ("h", "head", "heads")
- TAILS: Tuple[str] = ("t", "tail", "tails")
+ HEADS = ("h", "head", "heads")
+ TAILS = ("t", "tail", "tails")
async def convert(self, ctx: commands.Context, side: str) -> str:
"""Converts the provided `side` into the corresponding string."""
diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py
index 5c82ffee..647bb2b7 100644
--- a/bot/exts/evergreen/connect_four.py
+++ b/bot/exts/evergreen/connect_four.py
@@ -1,7 +1,7 @@
import asyncio
import random
-import typing
from functools import partial
+from typing import Optional, Union
import discord
import emojis
@@ -14,8 +14,8 @@ from bot.constants import Emojis
NUMBERS = list(Emojis.number_emojis.values())
CROSS_EMOJI = Emojis.incident_unactioned
-Coordinate = typing.Optional[typing.Tuple[int, int]]
-EMOJI_CHECK = typing.Union[discord.Emoji, str]
+Coordinate = Optional[tuple[int, int]]
+EMOJI_CHECK = Union[discord.Emoji, str]
class Game:
@@ -26,11 +26,10 @@ class Game:
bot: Bot,
channel: discord.TextChannel,
player1: discord.Member,
- player2: typing.Optional[discord.Member],
- tokens: typing.List[str],
+ player2: Optional[discord.Member],
+ tokens: list[str],
size: int = 7
- ) -> None:
-
+ ):
self.bot = bot
self.channel = channel
self.player1 = player1
@@ -48,7 +47,7 @@ class Game:
self.player_inactive = None
@staticmethod
- def generate_board(size: int) -> typing.List[typing.List[int]]:
+ def generate_board(size: int) -> list[list[int]]:
"""Generate the connect 4 board."""
return [[0 for _ in range(size)] for _ in range(size)]
@@ -181,11 +180,11 @@ class Game:
class AI:
"""The Computer Player for Single-Player games."""
- def __init__(self, bot: Bot, game: Game) -> None:
+ def __init__(self, bot: Bot, game: Game):
self.game = game
self.mention = bot.user.mention
- def get_possible_places(self) -> typing.List[Coordinate]:
+ def get_possible_places(self) -> list[Coordinate]:
"""Gets all the coordinates where the AI could possibly place a counter."""
possible_coords = []
for column_num in range(self.game.grid_size):
@@ -196,7 +195,7 @@ class AI:
break
return possible_coords
- def check_ai_win(self, coord_list: typing.List[Coordinate]) -> typing.Optional[Coordinate]:
+ def check_ai_win(self, coord_list: list[Coordinate]) -> Optional[Coordinate]:
"""
Check AI win.
@@ -209,7 +208,7 @@ class AI:
if self.game.check_win(coords, 2):
return coords
- def check_player_win(self, coord_list: typing.List[Coordinate]) -> typing.Optional[Coordinate]:
+ def check_player_win(self, coord_list: list[Coordinate]) -> Optional[Coordinate]:
"""
Check Player win.
@@ -223,11 +222,11 @@ class AI:
return coords
@staticmethod
- def random_coords(coord_list: typing.List[Coordinate]) -> Coordinate:
+ def random_coords(coord_list: list[Coordinate]) -> Coordinate:
"""Picks a random coordinate from the possible ones."""
return random.choice(coord_list)
- def play(self) -> typing.Union[Coordinate, bool]:
+ def play(self) -> Union[Coordinate, bool]:
"""
Plays for the AI.
@@ -256,10 +255,10 @@ class AI:
class ConnectFour(commands.Cog):
"""Connect Four. The Classic Vertical Four-in-a-row Game!"""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
- self.games: typing.List[Game] = []
- self.waiting: typing.List[discord.Member] = []
+ self.games: list[Game] = []
+ self.waiting: list[discord.Member] = []
self.tokens = [":white_circle:", ":blue_circle:", ":red_circle:"]
@@ -330,7 +329,7 @@ class ConnectFour(commands.Cog):
@staticmethod
def check_emojis(
e1: EMOJI_CHECK, e2: EMOJI_CHECK
- ) -> typing.Tuple[bool, typing.Optional[str]]:
+ ) -> tuple[bool, Optional[str]]:
"""Validate the emojis, the user put."""
if isinstance(e1, str) and emojis.count(e1) != 1:
return False, e1
@@ -341,7 +340,7 @@ class ConnectFour(commands.Cog):
async def _play_game(
self,
ctx: commands.Context,
- user: typing.Optional[discord.Member],
+ user: Optional[discord.Member],
board_size: int,
emoji1: str,
emoji2: str
diff --git a/bot/exts/evergreen/duck_game.py b/bot/exts/evergreen/duck_game.py
index 51e7a98a..d592f3df 100644
--- a/bot/exts/evergreen/duck_game.py
+++ b/bot/exts/evergreen/duck_game.py
@@ -110,7 +110,7 @@ class DuckGame:
rows: int = 4,
columns: int = 3,
minimum_solutions: int = 1,
- ) -> None:
+ ):
"""
Take samples from the deck to generate a board.
@@ -172,7 +172,7 @@ class DuckGame:
class DuckGamesDirector(commands.Cog):
"""A cog for running Duck Duck Duck Goose games."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
self.current_games = {}
diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py
index 11615214..55d6b8e9 100644
--- a/bot/exts/evergreen/emoji.py
+++ b/bot/exts/evergreen/emoji.py
@@ -3,7 +3,7 @@ import random
import textwrap
from collections import defaultdict
from datetime import datetime
-from typing import List, Optional, Tuple
+from typing import Optional
from discord import Color, Embed, Emoji
from discord.ext import commands
@@ -21,7 +21,7 @@ class Emojis(commands.Cog):
"""A collection of commands related to emojis in the server."""
@staticmethod
- def embed_builder(emoji: dict) -> Tuple[Embed, List[str]]:
+ def embed_builder(emoji: dict) -> tuple[Embed, list[str]]:
"""Generates an embed with the emoji names and count."""
embed = Embed(
color=Colours.orange,
@@ -52,7 +52,7 @@ class Emojis(commands.Cog):
return embed, msg
@staticmethod
- def generate_invalid_embed(emojis: list) -> Tuple[Embed, List[str]]:
+ def generate_invalid_embed(emojis: list[Emoji]) -> tuple[Embed, list[str]]:
"""Generates error embed for invalid emoji categories."""
embed = Embed(
color=Colours.soft_red,
@@ -65,7 +65,7 @@ class Emojis(commands.Cog):
emoji_dict[emoji.name.split("_")[0]].append(emoji)
error_comp = ", ".join(emoji_dict)
- msg.append(f"These are the valid emoji categories:\n```{error_comp}```")
+ msg.append(f"These are the valid emoji categories:\n```\n{error_comp}\n```")
return embed, msg
@commands.group(name="emoji", invoke_without_command=True)
diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py
index a280c725..fd2123e7 100644
--- a/bot/exts/evergreen/error_handler.py
+++ b/bot/exts/evergreen/error_handler.py
@@ -2,7 +2,8 @@ import difflib
import logging
import math
import random
-from typing import Iterable, Union
+from collections.abc import Iterable
+from typing import Union
from discord import Embed, Message
from discord.ext import commands
@@ -22,7 +23,7 @@ QUESTION_MARK_ICON = "https://cdn.discordapp.com/emojis/512367613339369475.png"
class CommandErrorHandler(commands.Cog):
"""A error handler for the PythonDiscord server."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
@staticmethod
@@ -74,7 +75,7 @@ class CommandErrorHandler(commands.Cog):
if isinstance(error, commands.UserInputError):
self.revert_cooldown_counter(ctx.command, ctx.message)
- usage = f"```{ctx.prefix}{parent_command}{ctx.command} {ctx.command.signature}```"
+ usage = f"```\n{ctx.prefix}{parent_command}{ctx.command} {ctx.command.signature}\n```"
embed = self.error_embed(
f"Your input was invalid: {error}\n\nUsage:{usage}"
)
@@ -107,7 +108,7 @@ class CommandErrorHandler(commands.Cog):
self.revert_cooldown_counter(ctx.command, ctx.message)
embed = self.error_embed(
"The argument you provided was invalid: "
- f"{error}\n\nUsage:\n```{ctx.prefix}{parent_command}{ctx.command} {ctx.command.signature}```"
+ f"{error}\n\nUsage:\n```\n{ctx.prefix}{parent_command}{ctx.command} {ctx.command.signature}\n```"
)
await ctx.send(embed=embed)
return
diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py
index 3b266e1b..4bbfe859 100644
--- a/bot/exts/evergreen/fun.py
+++ b/bot/exts/evergreen/fun.py
@@ -2,8 +2,9 @@ import functools
import json
import logging
import random
+from collections.abc import Iterable
from pathlib import Path
-from typing import Callable, Iterable, Tuple, Union
+from typing import Callable, Optional, Union
from discord import Embed, Message
from discord.ext import commands
@@ -53,7 +54,7 @@ def caesar_cipher(text: str, offset: int) -> Iterable[str]:
class Fun(Cog):
"""A collection of general commands for fun."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
self._caesar_cipher_embed = json.loads(Path("bot/resources/evergreen/caesar_info.json").read_text("UTF-8"))
@@ -182,7 +183,7 @@ class Fun(Cog):
await self._caesar_cipher(ctx, offset, msg, left_shift=True)
@staticmethod
- async def _get_text_and_embed(ctx: Context, text: str) -> Tuple[str, Union[Embed, None]]:
+ async def _get_text_and_embed(ctx: Context, text: str) -> tuple[str, Optional[Embed]]:
"""
Attempts to extract the text and embed from a possible link to a discord Message.
@@ -191,17 +192,19 @@ class Fun(Cog):
Returns a tuple of:
str: If `text` is a valid discord Message, the contents of the message, else `text`.
- Union[Embed, None]: The embed if found in the valid Message, else None
+ Optional[Embed]: The embed if found in the valid Message, else None
"""
embed = None
msg = await Fun._get_discord_message(ctx, text)
# Ensure the user has read permissions for the channel the message is in
- if isinstance(msg, Message) and ctx.author.permissions_in(msg.channel).read_messages:
- text = msg.clean_content
- # Take first embed because we can't send multiple embeds
- if msg.embeds:
- embed = msg.embeds[0]
+ if isinstance(msg, Message):
+ permissions = msg.channel.permissions_for(ctx.author)
+ if permissions.read_messages:
+ text = msg.clean_content
+ # Take first embed because we can't send multiple embeds
+ if msg.embeds:
+ embed = msg.embeds[0]
return (text, embed)
diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py
index 32fe9263..f9c150e6 100644
--- a/bot/exts/evergreen/game.py
+++ b/bot/exts/evergreen/game.py
@@ -5,7 +5,7 @@ import re
from asyncio import sleep
from datetime import datetime as dt, timedelta
from enum import IntEnum
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Optional
from aiohttp import ClientSession
from discord import Embed
@@ -151,7 +151,7 @@ class Games(Cog):
self.bot = bot
self.http_session: ClientSession = bot.http_session
- self.genres: Dict[str, int] = {}
+ self.genres: dict[str, int] = {}
self.headers = BASE_HEADERS
self.bot.loop.create_task(self.renew_access_token())
@@ -342,7 +342,7 @@ class Games(Cog):
sort: Optional[str] = None,
additional_body: str = "",
offset: int = 0
- ) -> List[Dict[str, Any]]:
+ ) -> list[dict[str, Any]]:
"""
Get list of games from IGDB API by parameters that is provided.
@@ -365,7 +365,7 @@ class Games(Cog):
async with self.http_session.post(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp:
return await resp.json()
- async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]:
+ async def create_page(self, data: dict[str, Any]) -> tuple[str, str]:
"""Create content of Game Page."""
# Create cover image URL from template
url = COVER_URL.format(**{"image_id": data["cover"]["image_id"] if "cover" in data else ""})
@@ -399,7 +399,7 @@ class Games(Cog):
return page, url
- async def search_games(self, search_term: str) -> List[str]:
+ async def search_games(self, search_term: str) -> list[str]:
"""Search game from IGDB API by string, return listing of pages."""
lines = []
@@ -422,7 +422,7 @@ class Games(Cog):
return lines
- async def get_companies_list(self, limit: int, offset: int = 0) -> List[Dict[str, Any]]:
+ async def get_companies_list(self, limit: int, offset: int = 0) -> list[dict[str, Any]]:
"""
Get random Game Companies from IGDB API.
@@ -438,7 +438,7 @@ class Games(Cog):
async with self.http_session.post(url=f"{BASE_URL}/companies", data=body, headers=self.headers) as resp:
return await resp.json()
- async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]:
+ async def create_company_page(self, data: dict[str, Any]) -> tuple[str, str]:
"""Create good formatted Game Company page."""
# Generate URL of company logo
url = LOGO_URL.format(**{"image_id": data["logo"]["image_id"] if "logo" in data else ""})
@@ -462,7 +462,7 @@ class Games(Cog):
return page, url
- async def get_best_results(self, query: str) -> List[Tuple[float, str]]:
+ async def get_best_results(self, query: str) -> list[tuple[float, str]]:
"""Get best match result of genre when original genre is invalid."""
results = []
for genre in self.genres:
diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py
index d29f3aa9..bbc9061a 100644
--- a/bot/exts/evergreen/githubinfo.py
+++ b/bot/exts/evergreen/githubinfo.py
@@ -66,7 +66,7 @@ class GithubInfo(commands.Cog):
embed = discord.Embed(
title=f"`{user_data['login']}`'s GitHub profile info",
- description=f"```{user_data['bio']}```\n" if user_data["bio"] else "",
+ description=f"```\n{user_data['bio']}\n```\n" if user_data["bio"] else "",
colour=discord.Colour.blurple(),
url=user_data["html_url"],
timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ")
diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py
index bfb5db17..4b766b50 100644
--- a/bot/exts/evergreen/help.py
+++ b/bot/exts/evergreen/help.py
@@ -3,7 +3,7 @@ import asyncio
import itertools
import logging
from contextlib import suppress
-from typing import List, NamedTuple, Union
+from typing import NamedTuple, Union
from discord import Colour, Embed, HTTPException, Message, Reaction, User
from discord.ext import commands
@@ -34,7 +34,7 @@ class Cog(NamedTuple):
name: str
description: str
- commands: List[Command]
+ commands: list[Command]
log = logging.getLogger(__name__)
@@ -308,7 +308,7 @@ class HelpSession:
signature = self._get_command_params(self.query)
parent = self.query.full_parent_name + " " if self.query.parent else ""
- paginator.add_line(f"**```{prefix}{parent}{signature}```**")
+ paginator.add_line(f"**```\n{prefix}{parent}{signature}\n```**")
aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in self.query.aliases]
aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())]
aliases = ", ".join(sorted(aliases))
@@ -343,7 +343,7 @@ class HelpSession:
for category, cmds in grouped:
await self._format_command_category(paginator, category, list(cmds))
- async def _format_command_category(self, paginator: LinePaginator, category: str, cmds: List[Command]) -> None:
+ async def _format_command_category(self, paginator: LinePaginator, category: str, cmds: list[Command]) -> None:
cmds = sorted(cmds, key=lambda c: c.name)
cat_cmds = []
for command in cmds:
@@ -373,7 +373,7 @@ class HelpSession:
paginator.add_line(details)
- async def _format_command(self, command: Command) -> List[str]:
+ async def _format_command(self, command: Command) -> list[str]:
# skip if hidden and hide if session is set to
if command.hidden and not self._show_hidden:
return []
diff --git a/bot/exts/evergreen/issues.py b/bot/exts/evergreen/issues.py
index 00810de8..8a7ebed0 100644
--- a/bot/exts/evergreen/issues.py
+++ b/bot/exts/evergreen/issues.py
@@ -1,8 +1,8 @@
import logging
import random
import re
-import typing as t
from dataclasses import dataclass
+from typing import Optional, Union
import discord
from discord.ext import commands
@@ -62,7 +62,7 @@ AUTOMATIC_REGEX = re.compile(
class FoundIssue:
"""Dataclass representing an issue found by the regex."""
- organisation: t.Optional[str]
+ organisation: Optional[str]
repository: str
number: str
@@ -106,7 +106,7 @@ class Issues(commands.Cog):
number: int,
repository: str,
user: str
- ) -> t.Union[IssueState, FetchError]:
+ ) -> Union[IssueState, FetchError]:
"""
Retrieve an issue from a GitHub repository.
@@ -162,9 +162,9 @@ class Issues(commands.Cog):
@staticmethod
def format_embed(
- results: t.List[t.Union[IssueState, FetchError]],
+ results: list[Union[IssueState, FetchError]],
user: str,
- repository: t.Optional[str] = None
+ repository: Optional[str] = None
) -> discord.Embed:
"""Take a list of IssueState or FetchError and format a Discord embed for them."""
description_list = []
diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py
index 932358f9..a48b5051 100644
--- a/bot/exts/evergreen/minesweeper.py
+++ b/bot/exts/evergreen/minesweeper.py
@@ -1,7 +1,8 @@
import logging
-import typing
+from collections.abc import Iterator
from dataclasses import dataclass
from random import randint, random
+from typing import Union
import discord
from discord.ext import commands
@@ -33,7 +34,7 @@ MESSAGE_MAPPING = {
log = logging.getLogger(__name__)
-GameBoard = typing.List[typing.List[typing.Union[str, int]]]
+GameBoard = list[list[Union[str, int]]]
@dataclass
@@ -47,14 +48,11 @@ class Game:
activated_on_server: bool
-GamesDict = typing.Dict[int, Game]
-
-
class Minesweeper(commands.Cog):
"""Play a game of Minesweeper."""
- def __init__(self) -> None:
- self.games: GamesDict = {} # Store the currently running games
+ def __init__(self):
+ self.games: dict[int, Game] = {}
@commands.group(name="minesweeper", aliases=("ms",), invoke_without_command=True)
async def minesweeper_group(self, ctx: commands.Context) -> None:
@@ -62,7 +60,7 @@ class Minesweeper(commands.Cog):
await invoke_help_command(ctx)
@staticmethod
- def get_neighbours(x: int, y: int) -> typing.Generator[typing.Tuple[int, int], None, None]:
+ def get_neighbours(x: int, y: int) -> Iterator[tuple[int, int]]:
"""Get all the neighbouring x and y including it self."""
for x_ in [x - 1, x, x + 1]:
for y_ in [y - 1, y, y + 1]:
diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py
index c6af4bcd..a04eeb41 100644
--- a/bot/exts/evergreen/movie.py
+++ b/bot/exts/evergreen/movie.py
@@ -1,7 +1,7 @@
import logging
import random
from enum import Enum
-from typing import Any, Dict, List, Tuple
+from typing import Any
from aiohttp import ClientSession
from discord import Embed
@@ -107,7 +107,7 @@ class Movie(Cog):
"""Show all currently available genres for .movies command."""
await ctx.send(f"Current available genres: {', '.join('`' + genre.name + '`' for genre in MovieGenres)}")
- async def get_movies_data(self, client: ClientSession, genre_id: str, page: int) -> List[Dict[str, Any]]:
+ async def get_movies_data(self, client: ClientSession, genre_id: str, page: int) -> list[dict[str, Any]]:
"""Return JSON of TMDB discover request."""
# Define params of request
params = {
@@ -126,7 +126,7 @@ class Movie(Cog):
async with client.get(url, params=params) as resp:
return await resp.json()
- async def get_pages(self, client: ClientSession, movies: Dict[str, Any], amount: int) -> List[Tuple[str, str]]:
+ async def get_pages(self, client: ClientSession, movies: dict[str, Any], amount: int) -> list[tuple[str, str]]:
"""Fetch all movie pages from movies dictionary. Return list of pages."""
pages = []
@@ -139,7 +139,7 @@ class Movie(Cog):
return pages
- async def get_movie(self, client: ClientSession, movie: int) -> Dict:
+ async def get_movie(self, client: ClientSession, movie: int) -> dict[str, Any]:
"""Get Movie by movie ID from TMDB. Return result dictionary."""
if not isinstance(movie, int):
raise ValueError("Error while fetching movie from TMDB, movie argument must be integer. ")
@@ -148,7 +148,7 @@ class Movie(Cog):
async with client.get(url, params=MOVIE_PARAMS) as resp:
return await resp.json()
- async def create_page(self, movie: Dict[str, Any]) -> Tuple[str, str]:
+ async def create_page(self, movie: dict[str, Any]) -> tuple[str, str]:
"""Create page from TMDB movie request result. Return formatted page + image."""
text = ""
diff --git a/bot/exts/evergreen/realpython.py b/bot/exts/evergreen/realpython.py
index 5d9e5c5c..ef8b2638 100644
--- a/bot/exts/evergreen/realpython.py
+++ b/bot/exts/evergreen/realpython.py
@@ -5,7 +5,7 @@ from urllib.parse import quote_plus
from discord import Embed
from discord.ext import commands
-from bot import bot
+from bot.bot import Bot
from bot.constants import Colours
logger = logging.getLogger(__name__)
@@ -26,7 +26,7 @@ ERROR_EMBED = Embed(
class RealPython(commands.Cog):
"""User initiated command to search for a Real Python article."""
- def __init__(self, bot: bot.Bot):
+ def __init__(self, bot: Bot):
self.bot = bot
@commands.command(aliases=["rp"])
@@ -76,6 +76,6 @@ class RealPython(commands.Cog):
await ctx.send(embed=article_embed)
-def setup(bot: bot.Bot) -> None:
+def setup(bot: Bot) -> None:
"""Load the Real Python Cog."""
bot.add_cog(RealPython(bot))
diff --git a/bot/exts/evergreen/recommend_game.py b/bot/exts/evergreen/recommend_game.py
index 35d60128..bdd3acb1 100644
--- a/bot/exts/evergreen/recommend_game.py
+++ b/bot/exts/evergreen/recommend_game.py
@@ -21,7 +21,7 @@ shuffle(game_recs)
class RecommendGame(commands.Cog):
"""Commands related to recommending games."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
self.index = 0
@@ -39,7 +39,7 @@ class RecommendGame(commands.Cog):
# Creating and formatting Embed
embed = discord.Embed(color=discord.Colour.blue())
if author is not None:
- embed.set_author(name=author.name, icon_url=author.avatar_url)
+ embed.set_author(name=author.name, icon_url=author.display_avatar.url)
embed.set_image(url=game["image"])
embed.add_field(name=f"Recommendation: {game['title']}\n{game['link']}", value=game["description"])
diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py
index 4df170c6..e6cb5337 100644
--- a/bot/exts/evergreen/reddit.py
+++ b/bot/exts/evergreen/reddit.py
@@ -4,7 +4,7 @@ import random
import textwrap
from collections import namedtuple
from datetime import datetime, timedelta
-from typing import List, Union
+from typing import Union
from aiohttp import BasicAuth, ClientError
from discord import Colour, Embed, TextChannel
@@ -59,7 +59,7 @@ class Reddit(Cog):
"""Get the #reddit channel object from the bot's cache."""
return self.bot.get_channel(Channels.reddit)
- def build_pagination_pages(self, posts: List[dict], paginate: bool) -> Union[List[tuple], str]:
+ def build_pagination_pages(self, posts: list[dict], paginate: bool) -> Union[list[tuple], str]:
"""Build embed pages required for Paginator."""
pages = []
first_page = ""
@@ -180,7 +180,7 @@ class Reddit(Cog):
else:
log.warning(f"Unable to revoke access token: status {response.status}.")
- async def fetch_posts(self, route: str, *, amount: int = 25, params: dict = None) -> List[dict]:
+ async def fetch_posts(self, route: str, *, amount: int = 25, params: dict = None) -> list[dict]:
"""A helper method to fetch a certain amount of Reddit posts at a given route."""
# Reddit's JSON responses only provide 25 posts at most.
if not 25 >= amount > 0:
@@ -213,7 +213,7 @@ class Reddit(Cog):
async def get_top_posts(
self, subreddit: Subreddit, time: str = "all", amount: int = 5, paginate: bool = False
- ) -> Union[Embed, List[tuple]]:
+ ) -> Union[Embed, list[tuple]]:
"""
Get the top amount of posts for a given subreddit within a specified timeframe.
diff --git a/bot/exts/evergreen/snakes/_converter.py b/bot/exts/evergreen/snakes/_converter.py
index c8d1909b..765b983d 100644
--- a/bot/exts/evergreen/snakes/_converter.py
+++ b/bot/exts/evergreen/snakes/_converter.py
@@ -1,7 +1,7 @@
import json
import logging
import random
-from typing import Iterable, List
+from collections.abc import Iterable
import discord
from discord.ext.commands import Context, Converter
@@ -27,7 +27,7 @@ class Snake(Converter):
if name == "python":
return "Python (programming language)"
- def get_potential(iterable: Iterable, *, threshold: int = 80) -> List[str]:
+ def get_potential(iterable: Iterable, *, threshold: int = 80) -> list[str]:
nonlocal name
potential = []
@@ -53,7 +53,7 @@ class Snake(Converter):
embed = discord.Embed(
title="Found multiple choices. Please choose the correct one.", colour=0x59982F)
- embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url)
+ embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url)
name = await disambiguate(ctx, get_potential(all_names), timeout=timeout, embed=embed)
return names.get(name, name)
diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py
index 07d3c363..04804222 100644
--- a/bot/exts/evergreen/snakes/_snakes_cog.py
+++ b/bot/exts/evergreen/snakes/_snakes_cog.py
@@ -9,7 +9,7 @@ import textwrap
import urllib
from functools import partial
from io import BytesIO
-from typing import Any, Dict, List, Optional
+from typing import Any, Optional
import async_timeout
from PIL import Image, ImageDraw, ImageFont
@@ -284,7 +284,7 @@ class Snakes(Cog):
async with self.bot.http_session.get(url, params=params) as response:
return await response.json()
- def _get_random_long_message(self, messages: List[str], retries: int = 10) -> str:
+ def _get_random_long_message(self, messages: list[str], retries: int = 10) -> str:
"""
Fetch a message that's at least 3 words long, if possible to do so in retries attempts.
@@ -299,7 +299,7 @@ class Snakes(Cog):
return long_message
- async def _get_snek(self, name: str) -> Dict[str, Any]:
+ async def _get_snek(self, name: str) -> dict[str, Any]:
"""
Fetches all the data from a wikipedia article about a snake.
@@ -401,11 +401,11 @@ class Snakes(Cog):
return snake_info
- async def _get_snake_name(self) -> Dict[str, str]:
+ async def _get_snake_name(self) -> dict[str, str]:
"""Gets a random snake name."""
return random.choice(self.snake_names)
- async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list) -> None:
+ async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: dict[str, str]) -> None:
"""Validate the answer using a reaction event loop."""
def predicate(reaction: Reaction, user: Member) -> bool:
"""Test if the the answer is valid and can be evaluated."""
@@ -486,7 +486,7 @@ class Snakes(Cog):
win = False
antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote")
- antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
+ antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.display_avatar.url)
# Generate answer
antidote_answer = list(ANTIDOTE_EMOJI) # Duplicate list, not reference it
@@ -569,7 +569,7 @@ class Snakes(Cog):
# Winning / Ending Screen
if win is True:
antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote")
- antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
+ antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.display_avatar.url)
antidote_embed.set_image(url="https://i.makeagif.com/media/7-12-2015/Cj1pts.gif")
antidote_embed.add_field(name="You have created the snake antidote!",
value=f"The solution was: {' '.join(antidote_answer)}\n"
@@ -577,7 +577,7 @@ class Snakes(Cog):
await board_id.edit(embed=antidote_embed)
else:
antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote")
- antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
+ antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.display_avatar.url)
antidote_embed.set_image(url="https://media.giphy.com/media/ceeN6U57leAhi/giphy.gif")
antidote_embed.add_field(
name=EMPTY_UNICODE,
@@ -1063,16 +1063,10 @@ class Snakes(Cog):
message = self._get_random_long_message(messages)
- # Set the avatar
- if user.avatar is not None:
- avatar = f"https://cdn.discordapp.com/avatars/{user.id}/{user.avatar}"
- else:
- avatar = ctx.author.default_avatar_url
-
# Build and send the embed
embed.set_author(
name=f"{user.name}#{user.discriminator}",
- icon_url=avatar,
+ icon_url=user.display_avatar.url,
)
embed.description = f"*{self._snakify(message)}*"
diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py
index f996d7f8..b5f13c53 100644
--- a/bot/exts/evergreen/snakes/_utils.py
+++ b/bot/exts/evergreen/snakes/_utils.py
@@ -6,7 +6,6 @@ import math
import random
from itertools import product
from pathlib import Path
-from typing import List, Tuple
from PIL import Image
from PIL.ImageDraw import ImageDraw
@@ -18,37 +17,41 @@ from bot.constants import Roles
SNAKE_RESOURCES = Path("bot/resources/snakes").absolute()
h1 = r"""```
- ----
- ------
- /--------\
- |--------|
- |--------|
- \------/
- ----```"""
+ ----
+ ------
+/--------\
+|--------|
+|--------|
+ \------/
+ ----
+```"""
h2 = r"""```
- ----
- ------
- /---\-/--\
- |-----\--|
- |--------|
- \------/
- ----```"""
+ ----
+ ------
+/---\-/--\
+|-----\--|
+|--------|
+ \------/
+ ----
+```"""
h3 = r"""```
- ----
- ------
- /---\-/--\
- |-----\--|
- |-----/--|
- \----\-/
- ----```"""
+ ----
+ ------
+/---\-/--\
+|-----\--|
+|-----/--|
+ \----\-/
+ ----
+```"""
h4 = r"""```
- -----
- ----- \
- /--| /---\
- |--\ -\---|
- |--\--/-- /
- \------- /
- ------```"""
+ -----
+ ----- \
+/--| /---\
+|--\ -\---|
+|--\--/-- /
+ \------- /
+ ------
+```"""
stages = [h1, h2, h3, h4]
snakes = {
"Baby Python": "https://i.imgur.com/SYOcmSa.png",
@@ -95,25 +98,25 @@ BOARD = {
16: 6
}
-DEFAULT_SNAKE_COLOR: int = 0x15c7ea
-DEFAULT_BACKGROUND_COLOR: int = 0
-DEFAULT_IMAGE_DIMENSIONS: Tuple[int] = (200, 200)
-DEFAULT_SNAKE_LENGTH: int = 22
-DEFAULT_SNAKE_WIDTH: int = 8
-DEFAULT_SEGMENT_LENGTH_RANGE: Tuple[int] = (7, 10)
-DEFAULT_IMAGE_MARGINS: Tuple[int] = (50, 50)
-DEFAULT_TEXT: str = "snek\nit\nup"
-DEFAULT_TEXT_POSITION: Tuple[int] = (
+DEFAULT_SNAKE_COLOR = 0x15c7ea
+DEFAULT_BACKGROUND_COLOR = 0
+DEFAULT_IMAGE_DIMENSIONS = (200, 200)
+DEFAULT_SNAKE_LENGTH = 22
+DEFAULT_SNAKE_WIDTH = 8
+DEFAULT_SEGMENT_LENGTH_RANGE = (7, 10)
+DEFAULT_IMAGE_MARGINS = (50, 50)
+DEFAULT_TEXT = "snek\nit\nup"
+DEFAULT_TEXT_POSITION = (
10,
10
)
-DEFAULT_TEXT_COLOR: int = 0xf2ea15
+DEFAULT_TEXT_COLOR = 0xf2ea15
X = 0
Y = 1
ANGLE_RANGE = math.pi * 2
-def get_resource(file: str) -> List[dict]:
+def get_resource(file: str) -> list[dict]:
"""Load Snake resources JSON."""
return json.loads((SNAKE_RESOURCES / f"{file}.json").read_text("utf-8"))
@@ -140,7 +143,7 @@ class PerlinNoiseFactory(object):
Licensed under ISC
"""
- def __init__(self, dimension: int, octaves: int = 1, tile: Tuple[int] = (), unbias: bool = False):
+ def __init__(self, dimension: int, octaves: int = 1, tile: tuple[int, ...] = (), unbias: bool = False):
"""
Create a new Perlin noise factory in the given number of dimensions.
@@ -168,7 +171,7 @@ class PerlinNoiseFactory(object):
self.gradient = {}
- def _generate_gradient(self) -> Tuple[float, ...]:
+ def _generate_gradient(self) -> tuple[float, ...]:
"""
Generate a random unit vector at each grid point.
@@ -278,13 +281,14 @@ class PerlinNoiseFactory(object):
def create_snek_frame(
perlin_factory: PerlinNoiseFactory, perlin_lookup_vertical_shift: float = 0,
- image_dimensions: Tuple[int] = DEFAULT_IMAGE_DIMENSIONS, image_margins: Tuple[int] = DEFAULT_IMAGE_MARGINS,
+ image_dimensions: tuple[int, int] = DEFAULT_IMAGE_DIMENSIONS,
+ image_margins: tuple[int, int] = DEFAULT_IMAGE_MARGINS,
snake_length: int = DEFAULT_SNAKE_LENGTH,
snake_color: int = DEFAULT_SNAKE_COLOR, bg_color: int = DEFAULT_BACKGROUND_COLOR,
- segment_length_range: Tuple[int] = DEFAULT_SEGMENT_LENGTH_RANGE, snake_width: int = DEFAULT_SNAKE_WIDTH,
- text: str = DEFAULT_TEXT, text_position: Tuple[int] = DEFAULT_TEXT_POSITION,
- text_color: Tuple[int] = DEFAULT_TEXT_COLOR
-) -> Image:
+ segment_length_range: tuple[int, int] = DEFAULT_SEGMENT_LENGTH_RANGE, snake_width: int = DEFAULT_SNAKE_WIDTH,
+ text: str = DEFAULT_TEXT, text_position: tuple[float, float] = DEFAULT_TEXT_POSITION,
+ text_color: int = DEFAULT_TEXT_COLOR
+) -> Image.Image:
"""
Creates a single random snek frame using Perlin noise.
@@ -293,7 +297,7 @@ def create_snek_frame(
"""
start_x = random.randint(image_margins[X], image_dimensions[X] - image_margins[X])
start_y = random.randint(image_margins[Y], image_dimensions[Y] - image_margins[Y])
- points = [(start_x, start_y)]
+ points: list[tuple[float, float]] = [(start_x, start_y)]
for index in range(0, snake_length):
angle = perlin_factory.get_plain_noise(
@@ -307,8 +311,8 @@ def create_snek_frame(
))
# normalize bounds
- min_dimensions = [start_x, start_y]
- max_dimensions = [start_x, start_y]
+ min_dimensions: list[float] = [start_x, start_y]
+ max_dimensions: list[float] = [start_x, start_y]
for point in points:
min_dimensions[X] = min(point[X], min_dimensions[X])
min_dimensions[Y] = min(point[Y], min_dimensions[Y])
@@ -461,7 +465,7 @@ class SnakeAndLaddersGame:
self.players.append(user)
self.player_tiles[user.id] = 1
- avatar_bytes = await user.avatar_url_as(format="jpeg", size=PLAYER_ICON_IMAGE_SIZE).read()
+ avatar_bytes = await user.display_avatar.replace(size=PLAYER_ICON_IMAGE_SIZE).read()
im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE))
self.avatar_images[user.id] = im
@@ -702,7 +706,7 @@ class SnakeAndLaddersGame:
"""Clean up the finished game object."""
del self.snakes.active_sal[self.channel]
- def _board_coordinate_from_index(self, index: int) -> Tuple[int, int]:
+ def _board_coordinate_from_index(self, index: int) -> tuple[int, int]:
"""Convert the tile number to the x/y coordinates for graphical purposes."""
y_level = 9 - math.floor((index - 1) / 10)
is_reversed = math.floor((index - 1) / 10) % 2 != 0
diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py
index fc209bc3..7572ce51 100644
--- a/bot/exts/evergreen/source.py
+++ b/bot/exts/evergreen/source.py
@@ -1,6 +1,6 @@
import inspect
from pathlib import Path
-from typing import Optional, Tuple
+from typing import Optional
from discord import Embed
from discord.ext import commands
@@ -26,7 +26,7 @@ class BotSource(commands.Cog):
embed = await self.build_embed(source_item)
await ctx.send(embed=embed)
- def get_source_link(self, source_item: SourceType) -> Tuple[str, str, Optional[int]]:
+ def get_source_link(self, source_item: SourceType) -> tuple[str, str, Optional[int]]:
"""
Build GitHub link of source item, return this link, file location and first line number.
diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py
index 5e87c6d5..48ad0f96 100644
--- a/bot/exts/evergreen/space.py
+++ b/bot/exts/evergreen/space.py
@@ -1,7 +1,7 @@
import logging
import random
from datetime import date, datetime
-from typing import Any, Dict, Optional
+from typing import Any, Optional
from urllib.parse import urlencode
from discord import Embed
@@ -203,10 +203,10 @@ class Space(Cog):
async def fetch_from_nasa(
self,
endpoint: str,
- additional_params: Optional[Dict[str, Any]] = None,
+ additional_params: Optional[dict[str, Any]] = None,
base: Optional[str] = NASA_BASE_URL,
use_api_key: bool = True
- ) -> Dict[str, Any]:
+ ) -> dict[str, Any]:
"""Fetch information from NASA API, return result."""
params = {}
if use_api_key:
diff --git a/bot/exts/evergreen/stackoverflow.py b/bot/exts/evergreen/stackoverflow.py
index 40f149c9..64455e33 100644
--- a/bot/exts/evergreen/stackoverflow.py
+++ b/bot/exts/evergreen/stackoverflow.py
@@ -5,7 +5,7 @@ from urllib.parse import quote_plus
from discord import Embed, HTTPException
from discord.ext import commands
-from bot import bot
+from bot.bot import Bot
from bot.constants import Colours, Emojis
logger = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ ERR_EMBED = Embed(
class Stackoverflow(commands.Cog):
"""Contains command to interact with stackoverflow from discord."""
- def __init__(self, bot: bot.Bot):
+ def __init__(self, bot: Bot):
self.bot = bot
@commands.command(aliases=["so"])
@@ -83,6 +83,6 @@ class Stackoverflow(commands.Cog):
await ctx.send(embed=search_query_too_long)
-def setup(bot: bot.Bot) -> None:
+def setup(bot: Bot) -> None:
"""Load the Stackoverflow Cog."""
bot.add_cog(Stackoverflow(bot))
diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py
index 164e056d..5c4f8051 100644
--- a/bot/exts/evergreen/tic_tac_toe.py
+++ b/bot/exts/evergreen/tic_tac_toe.py
@@ -1,6 +1,6 @@
import asyncio
import random
-import typing as t
+from typing import Callable, Optional, Union
import discord
from discord.ext.commands import Cog, Context, check, group, guild_only
@@ -15,7 +15,7 @@ CONFIRMATION_MESSAGE = (
)
-def check_win(board: t.Dict[int, str]) -> bool:
+def check_win(board: dict[int, str]) -> bool:
"""Check from board, is any player won game."""
return any(
(
@@ -42,7 +42,7 @@ class Player:
self.ctx = ctx
self.symbol = symbol
- async def get_move(self, board: t.Dict[int, str], msg: discord.Message) -> t.Tuple[bool, t.Optional[int]]:
+ async def get_move(self, board: dict[int, str], msg: discord.Message) -> tuple[bool, Optional[int]]:
"""
Get move from user.
@@ -75,7 +75,7 @@ class AI:
def __init__(self, symbol: str):
self.symbol = symbol
- async def get_move(self, board: t.Dict[int, str], _: discord.Message) -> t.Tuple[bool, int]:
+ async def get_move(self, board: dict[int, str], _: discord.Message) -> tuple[bool, int]:
"""Get move from AI. AI use Minimax strategy."""
possible_moves = [i for i, emoji in board.items() if emoji in list(Emojis.number_emojis.values())]
@@ -104,7 +104,7 @@ class AI:
class Game:
"""Class that contains information and functions about Tic Tac Toe game."""
- def __init__(self, players: t.List[t.Union[Player, AI]], ctx: Context):
+ def __init__(self, players: list[Union[Player, AI]], ctx: Context):
self.players = players
self.ctx = ctx
self.board = {
@@ -122,13 +122,13 @@ class Game:
self.current = self.players[0]
self.next = self.players[1]
- self.winner: t.Optional[t.Union[Player, AI]] = None
- self.loser: t.Optional[t.Union[Player, AI]] = None
+ self.winner: Optional[Union[Player, AI]] = None
+ self.loser: Optional[Union[Player, AI]] = None
self.over = False
self.canceled = False
self.draw = False
- async def get_confirmation(self) -> t.Tuple[bool, t.Optional[str]]:
+ async def get_confirmation(self) -> tuple[bool, Optional[str]]:
"""
Ask does user want to play TicTacToe against requester. First player is always requester.
@@ -227,14 +227,14 @@ class Game:
self.over = True
-def is_channel_free() -> t.Callable:
+def is_channel_free() -> Callable:
"""Check is channel where command will be invoked free."""
async def predicate(ctx: Context) -> bool:
return all(game.channel != ctx.channel for game in ctx.cog.games if not game.over)
return check(predicate)
-def is_requester_free() -> t.Callable:
+def is_requester_free() -> Callable:
"""Check is requester not already in any game."""
async def predicate(ctx: Context) -> bool:
return all(
@@ -247,13 +247,13 @@ class TicTacToe(Cog):
"""TicTacToe cog contains tic-tac-toe game commands."""
def __init__(self):
- self.games: t.List[Game] = []
+ self.games: list[Game] = []
@guild_only()
@is_channel_free()
@is_requester_free()
@group(name="tictactoe", aliases=("ttt", "tic"), invoke_without_command=True)
- async def tic_tac_toe(self, ctx: Context, opponent: t.Optional[discord.User]) -> None:
+ async def tic_tac_toe(self, ctx: Context, opponent: Optional[discord.User]) -> None:
"""Tic Tac Toe game. Play against friends or AI. Use reactions to add your mark to field."""
if opponent == ctx.author:
await ctx.send("You can't play against yourself.")
diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py
index bc25cbf7..aa4020d6 100644
--- a/bot/exts/evergreen/trivia_quiz.py
+++ b/bot/exts/evergreen/trivia_quiz.py
@@ -5,7 +5,7 @@ import operator
import random
from dataclasses import dataclass
from pathlib import Path
-from typing import Callable, List, Optional
+from typing import Callable, Optional
import discord
from discord.ext import commands
@@ -193,7 +193,7 @@ DYNAMIC_QUESTIONS_FORMAT_FUNCS = {
class TriviaQuiz(commands.Cog):
"""A cog for all quiz commands."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
self.game_status = {} # A variable to store the game status: either running or not running.
@@ -559,7 +559,7 @@ class TriviaQuiz(commands.Cog):
@staticmethod
async def send_answer(
channel: discord.TextChannel,
- answers: List[str],
+ answers: list[str],
answer_is_correct: bool,
question_dict: dict,
q_left: int,
diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py
index 27e68397..eccc1f8c 100644
--- a/bot/exts/evergreen/wikipedia.py
+++ b/bot/exts/evergreen/wikipedia.py
@@ -2,7 +2,6 @@ import logging
import re
from datetime import datetime
from html import unescape
-from typing import List
from discord import Color, Embed, TextChannel
from discord.ext import commands
@@ -43,7 +42,7 @@ class WikipediaSearch(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
- async def wiki_request(self, channel: TextChannel, search: str) -> List[str]:
+ async def wiki_request(self, channel: TextChannel, search: str) -> list[str]:
"""Search wikipedia search string and return formatted first 10 pages found."""
params = WIKI_PARAMS | {"srlimit": 10, "srsearch": search}
async with self.bot.http_session.get(url=SEARCH_API, params=params) as resp:
diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py
index 26674d37..9a26e545 100644
--- a/bot/exts/evergreen/wolfram.py
+++ b/bot/exts/evergreen/wolfram.py
@@ -1,6 +1,6 @@
import logging
from io import BytesIO
-from typing import Callable, List, Optional, Tuple
+from typing import Callable, Optional
from urllib.parse import urlencode
import arrow
@@ -54,7 +54,7 @@ async def send_embed(
await ctx.send(embed=embed, file=f)
-def custom_cooldown(*ignore: List[int]) -> Callable:
+def custom_cooldown(*ignore: int) -> Callable:
"""
Implement per-user and per-guild cooldowns for requests to the Wolfram API.
@@ -105,7 +105,7 @@ def custom_cooldown(*ignore: List[int]) -> Callable:
return check(predicate)
-async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tuple]]:
+async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[list[tuple[str, str]]]:
"""Get the Wolfram API pod pages for the provided query."""
async with ctx.typing():
params = {
@@ -133,22 +133,22 @@ async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tup
f"processing a wolfram request: {log_full_url}, Response: {json}"
)
await send_embed(ctx, message)
- return
+ return None
message = "Something went wrong internally with your request, please notify staff!"
log.warning(f"Something went wrong getting a response from wolfram: {log_full_url}, Response: {json}")
await send_embed(ctx, message)
- return
+ return None
if not result["success"]:
message = f"I couldn't find anything for {query}."
await send_embed(ctx, message)
- return
+ return None
if not result["numpods"]:
message = "Could not find any results."
await send_embed(ctx, message)
- return
+ return None
pods = result["pods"]
pages = []
diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py
index c98830bc..b56c53d9 100644
--- a/bot/exts/evergreen/xkcd.py
+++ b/bot/exts/evergreen/xkcd.py
@@ -1,7 +1,7 @@
import logging
import re
from random import randint
-from typing import Dict, Optional, Union
+from typing import Optional, Union
from discord import Embed
from discord.ext import tasks
@@ -19,9 +19,9 @@ BASE_URL = "https://xkcd.com"
class XKCD(Cog):
"""Retrieving XKCD comics."""
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
- self.latest_comic_info: Dict[str, Union[str, int]] = {}
+ self.latest_comic_info: dict[str, Union[str, int]] = {}
self.get_latest_comic_info.start()
def cog_unload(self) -> None:
diff --git a/bot/exts/halloween/hacktober-issue-finder.py b/bot/exts/halloween/hacktober-issue-finder.py
index 20a06770..e3053851 100644
--- a/bot/exts/halloween/hacktober-issue-finder.py
+++ b/bot/exts/halloween/hacktober-issue-finder.py
@@ -1,7 +1,7 @@
import datetime
import logging
import random
-from typing import Dict, Optional
+from typing import Optional
import discord
from discord.ext import commands
@@ -49,7 +49,7 @@ class HacktoberIssues(commands.Cog):
embed = self.format_embed(issue)
await ctx.send(embed=embed)
- async def get_issues(self, ctx: commands.Context, option: str) -> Optional[Dict]:
+ async def get_issues(self, ctx: commands.Context, option: str) -> Optional[dict]:
"""Get a list of the python issues with the label 'hacktoberfest' from the Github api."""
if option == "beginner":
if (ctx.message.created_at - self.cache_timer_beginner).seconds <= 60:
@@ -96,7 +96,7 @@ class HacktoberIssues(commands.Cog):
return data
@staticmethod
- def format_embed(issue: Dict) -> discord.Embed:
+ def format_embed(issue: dict) -> discord.Embed:
"""Format the issue data into a embed."""
title = issue["title"]
issue_url = issue["url"].replace("api.", "").replace("/repos/", "/")
diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py
index 24106a5e..72067dbe 100644
--- a/bot/exts/halloween/hacktoberstats.py
+++ b/bot/exts/halloween/hacktoberstats.py
@@ -3,7 +3,7 @@ import random
import re
from collections import Counter
from datetime import datetime, timedelta
-from typing import List, Optional, Tuple, Union
+from typing import Optional, Union
from urllib.parse import quote_plus
import discord
@@ -61,8 +61,8 @@ class HacktoberStats(commands.Cog):
else:
msg = (
f"{author_mention}, you have not linked a GitHub account\n\n"
- f"You can link your GitHub account using:\n```{ctx.prefix}hackstats link github_username```\n"
- f"Or query GitHub stats directly using:\n```{ctx.prefix}hackstats github_username```"
+ f"You can link your GitHub account using:\n```\n{ctx.prefix}hackstats link github_username\n```\n"
+ f"Or query GitHub stats directly using:\n```\n{ctx.prefix}hackstats github_username\n```"
)
await ctx.send(msg)
return
@@ -139,7 +139,7 @@ class HacktoberStats(commands.Cog):
else:
await ctx.send(f"No valid Hacktoberfest PRs found for '{github_username}'")
- async def build_embed(self, github_username: str, prs: List[dict]) -> discord.Embed:
+ async def build_embed(self, github_username: str, prs: list[dict]) -> discord.Embed:
"""Return a stats embed built from github_username's PRs."""
logging.info(f"Building Hacktoberfest embed for GitHub user: '{github_username}'")
in_review, accepted = await self._categorize_prs(prs)
@@ -185,7 +185,7 @@ class HacktoberStats(commands.Cog):
logging.info(f"Hacktoberfest PR built for GitHub user '{github_username}'")
return stats_embed
- async def get_october_prs(self, github_username: str) -> Optional[List[dict]]:
+ async def get_october_prs(self, github_username: str) -> Optional[list[dict]]:
"""
Query GitHub's API for PRs created during the month of October by github_username.
@@ -302,7 +302,7 @@ class HacktoberStats(commands.Cog):
return await resp.json()
@staticmethod
- def _has_label(pr: dict, labels: Union[List[str], str]) -> bool:
+ def _has_label(pr: dict, labels: Union[list[str], str]) -> bool:
"""
Check if a PR has label 'labels'.
@@ -368,7 +368,7 @@ class HacktoberStats(commands.Cog):
exp = r"https?:\/\/api.github.com\/repos\/([/\-\_\.\w]+)"
return re.findall(exp, in_url)[0]
- async def _categorize_prs(self, prs: List[dict]) -> tuple:
+ async def _categorize_prs(self, prs: list[dict]) -> tuple:
"""
Categorize PRs into 'in_review' and 'accepted' and returns as a tuple.
@@ -391,7 +391,7 @@ class HacktoberStats(commands.Cog):
return in_review, accepted
@staticmethod
- def _build_prs_string(prs: List[tuple], user: str) -> str:
+ def _build_prs_string(prs: list[tuple], user: str) -> str:
"""
Builds a discord embed compatible string for a list of PRs.
@@ -424,7 +424,7 @@ class HacktoberStats(commands.Cog):
return "contributions"
@staticmethod
- def _author_mention_from_context(ctx: commands.Context) -> Tuple[str, str]:
+ def _author_mention_from_context(ctx: commands.Context) -> tuple[str, str]:
"""Return stringified Message author ID and mentionable string from commands.Context."""
author_id = str(ctx.author.id)
author_mention = ctx.author.mention
diff --git a/bot/exts/halloween/halloween_facts.py b/bot/exts/halloween/halloween_facts.py
index 5ad8cc57..ba3b5d17 100644
--- a/bot/exts/halloween/halloween_facts.py
+++ b/bot/exts/halloween/halloween_facts.py
@@ -3,7 +3,6 @@ import logging
import random
from datetime import timedelta
from pathlib import Path
-from typing import Tuple
import discord
from discord.ext import commands
@@ -32,7 +31,7 @@ FACTS = list(enumerate(FACTS))
class HalloweenFacts(commands.Cog):
"""A Cog for displaying interesting facts about Halloween."""
- def random_fact(self) -> Tuple[int, str]:
+ def random_fact(self) -> tuple[int, str]:
"""Return a random fact from the loaded facts."""
return random.choice(FACTS)
diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py
index 3d6d95fa..5c21ead7 100644
--- a/bot/exts/halloween/spookynamerate.py
+++ b/bot/exts/halloween/spookynamerate.py
@@ -6,7 +6,7 @@ from datetime import datetime, timedelta
from logging import getLogger
from os import getenv
from pathlib import Path
-from typing import Union
+from typing import Optional
from async_rediscache import RedisCache
from discord import Embed, Reaction, TextChannel, User
@@ -91,7 +91,7 @@ class SpookyNameRate(Cog):
# will automatically start the scoring and announcing the result (without waiting for 12, so do not expect it to.).
# Also, it won't wait for the two hours (when the poll closes).
- def __init__(self, bot: Bot) -> None:
+ def __init__(self, bot: Bot):
self.bot = bot
self.name = None
@@ -355,7 +355,7 @@ class SpookyNameRate(Cog):
return embed
- async def get_channel(self) -> Union[TextChannel, None]:
+ async def get_channel(self) -> Optional[TextChannel]:
"""Gets the sir-lancebot-channel after waiting until ready."""
await self.bot.wait_until_ready()
channel = self.bot.get_channel(
diff --git a/bot/exts/halloween/spookyrating.py b/bot/exts/halloween/spookyrating.py
index 105d2164..f566fac2 100644
--- a/bot/exts/halloween/spookyrating.py
+++ b/bot/exts/halloween/spookyrating.py
@@ -3,7 +3,6 @@ import json
import logging
import random
from pathlib import Path
-from typing import Dict
import discord
from discord.ext import commands
@@ -13,7 +12,7 @@ from bot.constants import Colours
log = logging.getLogger(__name__)
-data: Dict[str, Dict[str, str]] = json.loads(Path("bot/resources/halloween/spooky_rating.json").read_text("utf8"))
+data: dict[str, dict[str, str]] = json.loads(Path("bot/resources/halloween/spooky_rating.json").read_text("utf8"))
SPOOKY_DATA = sorted((int(key), value) for key, value in data.items())
diff --git a/bot/exts/halloween/timeleft.py b/bot/exts/halloween/timeleft.py
index e80025dc..55109599 100644
--- a/bot/exts/halloween/timeleft.py
+++ b/bot/exts/halloween/timeleft.py
@@ -1,6 +1,5 @@
import logging
from datetime import datetime
-from typing import Tuple
from discord.ext import commands
@@ -21,7 +20,7 @@ class TimeLeft(commands.Cog):
return start <= now <= end
@staticmethod
- def load_date() -> Tuple[datetime, datetime, datetime]:
+ def load_date() -> tuple[datetime, datetime, datetime]:
"""Return of a tuple of the current time and the end and start times of the next October."""
now = datetime.utcnow()
year = now.year
diff --git a/bot/exts/internal_eval/_helpers.py b/bot/exts/internal_eval/_helpers.py
index 3a50b9f3..5b2f8f5d 100644
--- a/bot/exts/internal_eval/_helpers.py
+++ b/bot/exts/internal_eval/_helpers.py
@@ -8,14 +8,13 @@ import logging
import sys
import traceback
import types
-import typing
-
+from typing import Any, Optional, Union
log = logging.getLogger(__name__)
# A type alias to annotate the tuples returned from `sys.exc_info()`
-ExcInfo = typing.Tuple[typing.Type[Exception], Exception, types.TracebackType]
-Namespace = typing.Dict[str, typing.Any]
+ExcInfo = tuple[type[Exception], Exception, types.TracebackType]
+Namespace = dict[str, Any]
# This will be used as an coroutine function wrapper for the code
# to be evaluated. The wrapper contains one `pass` statement which
@@ -81,7 +80,7 @@ class EvalContext:
clear the context, use the `.internal clear` command.
"""
- def __init__(self, context_vars: Namespace, local_vars: Namespace) -> None:
+ def __init__(self, context_vars: Namespace, local_vars: Namespace):
self._locals = dict(local_vars)
self.context_vars = dict(context_vars)
@@ -93,7 +92,7 @@ class EvalContext:
self.eval_tree = None
@property
- def dependencies(self) -> typing.Dict[str, typing.Any]:
+ def dependencies(self) -> dict[str, Any]:
"""
Return a mapping of the dependencies for the wrapper function.
@@ -111,17 +110,17 @@ class EvalContext:
}
@property
- def locals(self) -> typing.Dict[str, typing.Any]:
+ def locals(self) -> dict[str, Any]:
"""Return a mapping of names->values needed for evaluation."""
return {**collections.ChainMap(self.dependencies, self.context_vars, self._locals)}
@locals.setter
- def locals(self, locals_: typing.Dict[str, typing.Any]) -> None:
+ def locals(self, locals_: dict[str, Any]) -> None:
"""Update the contextual mapping of names to values."""
log.trace(f"Updating {self._locals} with {locals_}")
self._locals.update(locals_)
- def prepare_eval(self, code: str) -> typing.Optional[str]:
+ def prepare_eval(self, code: str) -> Optional[str]:
"""Prepare an evaluation by processing the code and setting up the context."""
self.code = code
@@ -183,7 +182,7 @@ class EvalContext:
class WrapEvalCodeTree(ast.NodeTransformer):
"""Wraps the AST of eval code with the wrapper function."""
- def __init__(self, eval_code_tree: ast.AST, *args, **kwargs) -> None:
+ def __init__(self, eval_code_tree: ast.AST, *args, **kwargs):
super().__init__(*args, **kwargs)
self.eval_code_tree = eval_code_tree
@@ -195,7 +194,7 @@ class WrapEvalCodeTree(ast.NodeTransformer):
new_tree = self.visit(self.wrapper)
return ast.fix_missing_locations(new_tree)
- def visit_Pass(self, node: ast.Pass) -> typing.List[ast.AST]: # noqa: N802
+ def visit_Pass(self, node: ast.Pass) -> list[ast.AST]: # noqa: N802
"""
Replace the `_ast.Pass` node in the wrapper function by the eval AST.
@@ -208,12 +207,12 @@ class WrapEvalCodeTree(ast.NodeTransformer):
class CaptureLastExpression(ast.NodeTransformer):
"""Captures the return value from a loose expression."""
- def __init__(self, tree: ast.AST, *args, **kwargs) -> None:
+ def __init__(self, tree: ast.AST, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tree = tree
self.last_node = list(ast.iter_child_nodes(tree))[-1]
- def visit_Expr(self, node: ast.Expr) -> typing.Union[ast.Expr, ast.Assign]: # noqa: N802
+ def visit_Expr(self, node: ast.Expr) -> Union[ast.Expr, ast.Assign]: # noqa: N802
"""
Replace the Expr node that is last child node of Module with an assignment.
diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py
index b7749144..4f6b4321 100644
--- a/bot/exts/internal_eval/_internal_eval.py
+++ b/bot/exts/internal_eval/_internal_eval.py
@@ -1,7 +1,7 @@
import logging
import re
import textwrap
-import typing
+from typing import Optional
import discord
from discord.ext import commands
@@ -82,7 +82,7 @@ class InternalEval(commands.Cog):
return shortened_output
- async def _upload_output(self, output: str) -> typing.Optional[str]:
+ async def _upload_output(self, output: str) -> Optional[str]:
"""Upload `internal eval` output to our pastebin and return the url."""
try:
async with self.bot.http_session.post(
diff --git a/bot/exts/pride/pride_leader.py b/bot/exts/pride/pride_leader.py
index 8e88183b..5684ff37 100644
--- a/bot/exts/pride/pride_leader.py
+++ b/bot/exts/pride/pride_leader.py
@@ -8,8 +8,8 @@ import discord
from discord.ext import commands
from rapidfuzz import fuzz
-from bot import bot
from bot import constants
+from bot.bot import Bot
log = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ MINIMUM_FUZZ_RATIO = 40
class PrideLeader(commands.Cog):
"""Gives information about Pride Leaders."""
- def __init__(self, bot: bot.Bot):
+ def __init__(self, bot: Bot):
self.bot = bot
def invalid_embed_generate(self, pride_leader: str) -> discord.Embed:
@@ -51,7 +51,7 @@ class PrideLeader(commands.Cog):
valid_names = "\n".join(valid_names)
error_msg = "Did you mean?"
- embed.description = f"{error_msg}\n```{valid_names}```"
+ embed.description = f"{error_msg}\n```\n{valid_names}\n```"
embed.set_footer(text="To add more pride leaders, feel free to open a pull request!")
return embed
@@ -112,6 +112,6 @@ class PrideLeader(commands.Cog):
await ctx.send(embed=embed)
-def setup(bot: bot.Bot) -> None:
+def setup(bot: Bot) -> None:
"""Load the Pride Leader Cog."""
bot.add_cog(PrideLeader(bot))
diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py
index 64e404d2..424bacac 100644
--- a/bot/exts/utils/extensions.py
+++ b/bot/exts/utils/extensions.py
@@ -1,7 +1,8 @@
import functools
import logging
-import typing as t
+from collections.abc import Mapping
from enum import Enum
+from typing import Optional
from discord import Colour, Embed
from discord.ext import commands
@@ -60,7 +61,7 @@ class Extension(commands.Converter):
names = "\n".join(matches)
raise commands.BadArgument(
f":x: `{argument}` is an ambiguous extension name. "
- f"Please use one of the following fully-qualified names.```\n{names}```"
+ f"Please use one of the following fully-qualified names.```\n{names}\n```"
)
elif matches:
return matches[0]
@@ -110,7 +111,7 @@ class Extensions(commands.Cog):
blacklisted = "\n".join(UNLOAD_BLACKLIST & set(extensions))
if blacklisted:
- msg = f":x: The following extension(s) may not be unloaded:```{blacklisted}```"
+ msg = f":x: The following extension(s) may not be unloaded:```\n{blacklisted}\n```"
else:
if "*" in extensions or "**" in extensions:
extensions = set(self.bot.extensions.keys()) - UNLOAD_BLACKLIST
@@ -155,7 +156,7 @@ class Extensions(commands.Cog):
embed.set_author(
name="Extensions List",
url=Client.github_bot_repo,
- icon_url=str(self.bot.user.avatar_url)
+ icon_url=str(self.bot.user.display_avatar.url)
)
lines = []
@@ -170,7 +171,7 @@ class Extensions(commands.Cog):
log.debug(f"{ctx.author} requested a list of all cogs. Returning a paginated list.")
await LinePaginator.paginate(lines, ctx, embed, max_size=1200, empty=False)
- def group_extension_statuses(self) -> t.Mapping[str, str]:
+ def group_extension_statuses(self) -> Mapping[str, str]:
"""Return a mapping of extension names and statuses to their categories."""
categories = {}
@@ -213,13 +214,13 @@ class Extensions(commands.Cog):
if failures:
failures = "\n".join(f"{ext}\n {err}" for ext, err in failures.items())
- msg += f"\nFailures:```{failures}```"
+ msg += f"\nFailures:```\n{failures}\n```"
log.debug(f"Batch {verb}ed extensions.")
return msg
- def manage(self, action: Action, ext: str) -> t.Tuple[str, t.Optional[str]]:
+ def manage(self, action: Action, ext: str) -> tuple[str, Optional[str]]:
"""Apply an action to an extension and return the status message and any error message."""
verb = action.name.lower()
error_msg = None
@@ -240,7 +241,7 @@ class Extensions(commands.Cog):
log.exception(f"Extension '{ext}' failed to {verb}.")
error_msg = f"{e.__class__.__name__}: {e}"
- msg = f":x: Failed to {verb} extension `{ext}`:\n```{error_msg}```"
+ msg = f":x: Failed to {verb} extension `{ext}`:\n```\n{error_msg}\n```"
else:
msg = f":ok_hand: Extension successfully {verb}ed: `{ext}`."
log.debug(msg[10:])
diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py
index 8b522a72..c238027a 100644
--- a/bot/exts/valentines/be_my_valentine.py
+++ b/bot/exts/valentines/be_my_valentine.py
@@ -2,7 +2,6 @@ import logging
import random
from json import loads
from pathlib import Path
-from typing import Tuple
import discord
from discord.ext import commands
@@ -146,7 +145,7 @@ class BeMyValentine(commands.Cog):
else:
await ctx.author.send(f"Your message has been sent to {user}")
- def valentine_check(self, valentine_type: str) -> Tuple[str, str]:
+ def valentine_check(self, valentine_type: str) -> tuple[str, str]:
"""Return the appropriate Valentine type & title based on the invoking user's input."""
if valentine_type is None:
return self.random_valentine()
@@ -162,13 +161,13 @@ class BeMyValentine(commands.Cog):
return valentine_type, "A message for"
@staticmethod
- def random_emoji() -> Tuple[str, str]:
+ def random_emoji() -> tuple[str, str]:
"""Return two random emoji from the module-defined constants."""
emoji_1 = random.choice(HEART_EMOJIS)
emoji_2 = random.choice(HEART_EMOJIS)
return emoji_1, emoji_2
- def random_valentine(self) -> Tuple[str, str]:
+ def random_valentine(self) -> tuple[str, str]:
"""Grabs a random poem or a compliment (any message)."""
valentine_poem = random.choice(self.valentines["valentine_poems"])
valentine_compliment = random.choice(self.valentines["valentine_compliments"])
diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py
index d862ee63..243f156e 100644
--- a/bot/exts/valentines/valentine_zodiac.py
+++ b/bot/exts/valentines/valentine_zodiac.py
@@ -4,7 +4,7 @@ import logging
import random
from datetime import datetime
from pathlib import Path
-from typing import Tuple, Union
+from typing import Union
import discord
from discord.ext import commands
@@ -25,7 +25,7 @@ class ValentineZodiac(commands.Cog):
self.zodiacs, self.zodiac_fact = self.load_comp_json()
@staticmethod
- def load_comp_json() -> Tuple[dict, dict]:
+ def load_comp_json() -> tuple[dict, dict]:
"""Load zodiac compatibility from static JSON resource."""
explanation_file = Path("bot/resources/valentines/zodiac_explanation.json")
compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json")
@@ -108,7 +108,7 @@ class ValentineZodiac(commands.Cog):
except ValueError as e:
final_embed = discord.Embed()
final_embed.color = Colours.soft_red
- final_embed.description = f"Zodiac sign could not be found because.\n```{e}```"
+ final_embed.description = f"Zodiac sign could not be found because.\n```\n{e}\n```"
log.info(f"Error in 'zodiac date' command:\n{e}.")
else:
final_embed = self.zodiac_build_embed(zodiac_sign_based_on_date)
diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py
index bef12d25..91682dbc 100644
--- a/bot/utils/__init__.py
+++ b/bot/utils/__init__.py
@@ -2,8 +2,9 @@ import asyncio
import contextlib
import re
import string
+from collections.abc import Iterable
from datetime import datetime
-from typing import Iterable, List, Optional
+from typing import Optional
import discord
from discord.ext.commands import BadArgument, Context
@@ -32,7 +33,7 @@ def resolve_current_month() -> Month:
async def disambiguate(
ctx: Context,
- entries: List[str],
+ entries: list[str],
*,
timeout: float = 30,
entries_per_page: int = 20,
diff --git a/bot/utils/checks.py b/bot/utils/checks.py
index 438ec750..612d1ed6 100644
--- a/bot/utils/checks.py
+++ b/bot/utils/checks.py
@@ -1,6 +1,7 @@
import datetime
import logging
-from typing import Callable, Container, Iterable, Optional
+from collections.abc import Container, Iterable
+from typing import Callable, Optional
from discord.ext.commands import (
BucketType,
@@ -21,7 +22,7 @@ log = logging.getLogger(__name__)
class InWhitelistCheckFailure(CheckFailure):
"""Raised when the `in_whitelist` check fails."""
- def __init__(self, redirect_channel: Optional[int]) -> None:
+ def __init__(self, redirect_channel: Optional[int]):
self.redirect_channel = redirect_channel
if redirect_channel:
diff --git a/bot/utils/converters.py b/bot/utils/converters.py
index fe2c980c..7227a406 100644
--- a/bot/utils/converters.py
+++ b/bot/utils/converters.py
@@ -1,5 +1,5 @@
from datetime import datetime
-from typing import Tuple, Union
+from typing import Union
import discord
from discord.ext import commands
@@ -23,7 +23,7 @@ class CoordinateConverter(commands.Converter):
"""Converter for Coordinates."""
@staticmethod
- async def convert(ctx: commands.Context, coordinate: str) -> Tuple[int, int]:
+ async def convert(ctx: commands.Context, coordinate: str) -> tuple[int, int]:
"""Take in a coordinate string and turn it into an (x, y) tuple."""
if len(coordinate) not in (2, 3):
raise commands.BadArgument("Invalid co-ordinate provided.")
diff --git a/bot/utils/decorators.py b/bot/utils/decorators.py
index c0783144..132aaa87 100644
--- a/bot/utils/decorators.py
+++ b/bot/utils/decorators.py
@@ -2,9 +2,10 @@ import asyncio
import functools
import logging
import random
-import typing as t
from asyncio import Lock
+from collections.abc import Container
from functools import wraps
+from typing import Callable, Optional, Union
from weakref import WeakValueDictionary
from discord import Colour, Embed
@@ -32,7 +33,7 @@ class InMonthCheckFailure(CheckFailure):
pass
-def seasonal_task(*allowed_months: Month, sleep_time: t.Union[float, int] = ONE_DAY) -> t.Callable:
+def seasonal_task(*allowed_months: Month, sleep_time: Union[float, int] = ONE_DAY) -> Callable:
"""
Perform the decorated method periodically in `allowed_months`.
@@ -44,7 +45,7 @@ def seasonal_task(*allowed_months: Month, sleep_time: t.Union[float, int] = ONE_
The wrapped task is responsible for waiting for the bot to be ready, if necessary.
"""
- def decorator(task_body: t.Callable) -> t.Callable:
+ def decorator(task_body: Callable) -> Callable:
@functools.wraps(task_body)
async def decorated_task(*args, **kwargs) -> None:
"""Call `task_body` once every `sleep_time` seconds in `allowed_months`."""
@@ -63,13 +64,13 @@ def seasonal_task(*allowed_months: Month, sleep_time: t.Union[float, int] = ONE_
return decorator
-def in_month_listener(*allowed_months: Month) -> t.Callable:
+def in_month_listener(*allowed_months: Month) -> Callable:
"""
Shield a listener from being invoked outside of `allowed_months`.
The check is performed against current UTC month.
"""
- def decorator(listener: t.Callable) -> t.Callable:
+ def decorator(listener: Callable) -> Callable:
@functools.wraps(listener)
async def guarded_listener(*args, **kwargs) -> None:
"""Wrapped listener will abort if not in allowed month."""
@@ -84,7 +85,7 @@ def in_month_listener(*allowed_months: Month) -> t.Callable:
return decorator
-def in_month_command(*allowed_months: Month) -> t.Callable:
+def in_month_command(*allowed_months: Month) -> Callable:
"""
Check whether the command was invoked in one of `enabled_months`.
@@ -106,7 +107,7 @@ def in_month_command(*allowed_months: Month) -> t.Callable:
return commands.check(predicate)
-def in_month(*allowed_months: Month) -> t.Callable:
+def in_month(*allowed_months: Month) -> Callable:
"""
Universal decorator for season-locking commands and listeners alike.
@@ -124,7 +125,7 @@ def in_month(*allowed_months: Month) -> t.Callable:
manually set to True - this causes a circumvention of the group's callback
and the seasonal check applied to it.
"""
- def decorator(callable_: t.Callable) -> t.Callable:
+ def decorator(callable_: Callable) -> Callable:
# Functions decorated as commands are turned into instances of `Command`
if isinstance(callable_, Command):
logging.debug(f"Command {callable_.qualified_name} will be locked to {human_months(allowed_months)}")
@@ -144,7 +145,7 @@ def in_month(*allowed_months: Month) -> t.Callable:
return decorator
-def with_role(*role_ids: int) -> t.Callable:
+def with_role(*role_ids: int) -> Callable:
"""Check to see whether the invoking user has any of the roles specified in role_ids."""
async def predicate(ctx: Context) -> bool:
if not ctx.guild: # Return False in a DM
@@ -167,7 +168,7 @@ def with_role(*role_ids: int) -> t.Callable:
return commands.check(predicate)
-def without_role(*role_ids: int) -> t.Callable:
+def without_role(*role_ids: int) -> Callable:
"""Check whether the invoking user does not have all of the roles specified in role_ids."""
async def predicate(ctx: Context) -> bool:
if not ctx.guild: # Return False in a DM
@@ -187,7 +188,7 @@ def without_role(*role_ids: int) -> t.Callable:
return commands.check(predicate)
-def whitelist_check(**default_kwargs: t.Container[int]) -> t.Callable[[Context], bool]:
+def whitelist_check(**default_kwargs: Container[int]) -> Callable[[Context], bool]:
"""
Checks if a message is sent in a whitelisted context.
@@ -222,8 +223,8 @@ def whitelist_check(**default_kwargs: t.Container[int]) -> t.Callable[[Context],
kwargs[arg] = new_value
# Merge containers
- elif isinstance(default_value, t.Container):
- if isinstance(new_value, t.Container):
+ elif isinstance(default_value, Container):
+ if isinstance(new_value, Container):
kwargs[arg] = (*default_value, *new_value)
else:
kwargs[arg] = new_value
@@ -279,7 +280,7 @@ def whitelist_check(**default_kwargs: t.Container[int]) -> t.Callable[[Context],
return predicate
-def whitelist_override(bypass_defaults: bool = False, **kwargs: t.Container[int]) -> t.Callable:
+def whitelist_override(bypass_defaults: bool = False, **kwargs: Container[int]) -> Callable:
"""
Override global whitelist context, with the kwargs specified.
@@ -288,7 +289,7 @@ def whitelist_override(bypass_defaults: bool = False, **kwargs: t.Container[int]
This decorator has to go before (below) below the `command` decorator.
"""
- def inner(func: t.Callable) -> t.Callable:
+ def inner(func: Callable) -> Callable:
func.override = kwargs
func.override_reset = bypass_defaults
return func
@@ -296,7 +297,7 @@ def whitelist_override(bypass_defaults: bool = False, **kwargs: t.Container[int]
return inner
-def locked() -> t.Union[t.Callable, None]:
+def locked() -> Optional[Callable]:
"""
Allows the user to only run one instance of the decorated command at a time.
@@ -304,11 +305,11 @@ def locked() -> t.Union[t.Callable, None]:
This decorator has to go before (below) the `command` decorator.
"""
- def wrap(func: t.Callable) -> t.Union[t.Callable, None]:
+ def wrap(func: Callable) -> Optional[Callable]:
func.__locks = WeakValueDictionary()
@wraps(func)
- async def inner(self: t.Callable, ctx: Context, *args, **kwargs) -> t.Union[t.Callable, None]:
+ async def inner(self: Callable, ctx: Context, *args, **kwargs) -> Optional[Callable]:
lock = func.__locks.setdefault(ctx.author.id, Lock())
if lock.locked():
embed = Embed()
diff --git a/bot/utils/extensions.py b/bot/utils/extensions.py
index cd491c4b..cbb8f15e 100644
--- a/bot/utils/extensions.py
+++ b/bot/utils/extensions.py
@@ -1,7 +1,8 @@
import importlib
import inspect
import pkgutil
-from typing import Iterator, NoReturn
+from collections.abc import Iterator
+from typing import NoReturn
from discord.ext.commands import Context
diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py
index b1062c09..013ef9e7 100644
--- a/bot/utils/pagination.py
+++ b/bot/utils/pagination.py
@@ -1,6 +1,7 @@
import asyncio
import logging
-from typing import Iterable, List, Optional, Tuple
+from collections.abc import Iterable
+from typing import Optional
from discord import Embed, Member, Reaction
from discord.abc import User
@@ -313,7 +314,7 @@ class ImagePaginator(Paginator):
self.images.append(image)
@classmethod
- async def paginate(cls, pages: List[Tuple[str, str]], ctx: Context, embed: Embed,
+ async def paginate(cls, pages: list[tuple[str, str]], ctx: Context, embed: Embed,
prefix: str = "", suffix: str = "", timeout: int = 300,
exception_on_empty_embed: bool = False) -> None:
"""
diff --git a/bot/utils/randomization.py b/bot/utils/randomization.py
index 8f47679a..c9eabbd2 100644
--- a/bot/utils/randomization.py
+++ b/bot/utils/randomization.py
@@ -1,6 +1,7 @@
import itertools
import random
-import typing as t
+from collections.abc import Iterable
+from typing import Any
class RandomCycle:
@@ -10,11 +11,11 @@ class RandomCycle:
The iterable is reshuffled after each full cycle.
"""
- def __init__(self, iterable: t.Iterable) -> None:
+ def __init__(self, iterable: Iterable):
self.iterable = list(iterable)
self.index = itertools.cycle(range(len(iterable)))
- def __next__(self) -> t.Any:
+ def __next__(self) -> Any:
idx = next(self.index)
if idx == 0:
diff --git a/poetry.lock b/poetry.lock
index 64709d7a..8042fd92 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -121,7 +121,7 @@ pycparser = "*"
[[package]]
name = "cfgv"
-version = "3.3.0"
+version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
@@ -156,19 +156,23 @@ six = "*"
[[package]]
name = "discord.py"
-version = "1.7.3"
+version = "2.0.0a0"
description = "A Python wrapper for the Discord API"
category = "main"
optional = false
-python-versions = ">=3.5.3"
+python-versions = ">=3.8.0"
[package.dependencies]
aiohttp = ">=3.6.0,<3.8.0"
[package.extras]
-docs = ["sphinx (==3.0.3)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"]
+docs = ["sphinx (==4.0.2)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"]
+speed = ["orjson (>=3.5.4)"]
voice = ["PyNaCl (>=1.3.0,<1.5)"]
+[package.source]
+type = "url"
+url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip"
[[package]]
name = "distlib"
version = "0.3.2"
@@ -187,7 +191,7 @@ python-versions = "*"
[[package]]
name = "fakeredis"
-version = "1.5.2"
+version = "1.6.0"
description = "Fake implementation of redis API for testing purposes."
category = "main"
optional = false
@@ -199,7 +203,7 @@ six = ">=1.12"
sortedcontainers = "*"
[package.extras]
-aioredis = ["aioredis (<2)"]
+aioredis = ["aioredis"]
lua = ["lupa"]
[[package]]
@@ -296,14 +300,14 @@ flake8 = "*"
[[package]]
name = "flake8-tidy-imports"
-version = "4.3.0"
+version = "4.4.1"
description = "A flake8 plugin that helps you write tidier imports."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-flake8 = ">=3.0,<3.2.0 || >3.2.0,<4"
+flake8 = ">=3.8.0,<4"
[[package]]
name = "flake8-todo"
@@ -326,7 +330,7 @@ python-versions = ">=3.6"
[[package]]
name = "identify"
-version = "2.2.12"
+version = "2.2.13"
description = "File identification library for Python"
category = "dev"
optional = false
@@ -345,15 +349,15 @@ python-versions = ">=3.5"
[[package]]
name = "kiwisolver"
-version = "1.3.1"
+version = "1.3.2"
description = "A fast implementation of the Cassowary constraint solver"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[[package]]
name = "matplotlib"
-version = "3.4.2"
+version = "3.4.3"
description = "Python plotting package"
category = "main"
optional = false
@@ -409,7 +413,7 @@ python-versions = ">=3.7"
[[package]]
name = "pep8-naming"
-version = "0.12.0"
+version = "0.12.1"
description = "Check PEP-8 naming conventions, plugin for flake8"
category = "dev"
optional = false
@@ -429,7 +433,7 @@ python-versions = ">=3.6"
[[package]]
name = "platformdirs"
-version = "2.2.0"
+version = "2.3.0"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
@@ -441,7 +445,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
[[package]]
name = "pre-commit"
-version = "2.13.0"
+version = "2.14.1"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
@@ -558,7 +562,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
name = "rapidfuzz"
-version = "1.4.1"
+version = "1.5.0"
description = "rapid fuzzy string matching"
category = "main"
optional = false
@@ -651,7 +655,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typing-extensions"
-version = "3.10.0.0"
+version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "main"
optional = false
@@ -672,7 +676,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
-version = "20.7.0"
+version = "20.7.2"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
@@ -704,7 +708,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "a62da963535ba0b679739b026c6d86f6b2c1993b50e81c06d7d89f63507b9aa1"
+content-hash = "5e24cca35cc0083a2f94edfa2de7aaaac8e3275f354c2faac9e09a41c2a76744"
[metadata.files]
aiodns = [
@@ -826,8 +830,8 @@ cffi = [
{file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
]
cfgv = [
- {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"},
- {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"},
+ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
+ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
@@ -841,10 +845,7 @@ cycler = [
{file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
{file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"},
]
-"discord.py" = [
- {file = "discord.py-1.7.3-py3-none-any.whl", hash = "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c"},
- {file = "discord.py-1.7.3.tar.gz", hash = "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408"},
-]
+"discord.py" = []
distlib = [
{file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
{file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
@@ -854,8 +855,8 @@ emojis = [
{file = "emojis-0.6.0.tar.gz", hash = "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec"},
]
fakeredis = [
- {file = "fakeredis-1.5.2-py3-none-any.whl", hash = "sha256:f1ffdb134538e6d7c909ddfb4fc5edeb4a73d0ea07245bc69b8135fbc4144b04"},
- {file = "fakeredis-1.5.2.tar.gz", hash = "sha256:18fc1808d2ce72169d3f11acdb524a00ef96bd29970c6d34cfeb2edb3fc0c020"},
+ {file = "fakeredis-1.6.0-py3-none-any.whl", hash = "sha256:3449b306f3a85102b28f8180c24722ef966fcb1e3c744758b6f635ec80321a5c"},
+ {file = "fakeredis-1.6.0.tar.gz", hash = "sha256:11ccfc9769d718d37e45b382e64a6ba02586b622afa0371a6bd85766d72255f3"},
]
filelock = [
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
@@ -890,8 +891,8 @@ flake8-string-format = [
{file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"},
]
flake8-tidy-imports = [
- {file = "flake8-tidy-imports-4.3.0.tar.gz", hash = "sha256:e66d46f58ed108f36da920e7781a728dc2d8e4f9269e7e764274105700c0a90c"},
- {file = "flake8_tidy_imports-4.3.0-py3-none-any.whl", hash = "sha256:d6e64cb565ca9474d13d5cb3f838b8deafb5fed15906998d4a674daf55bd6d89"},
+ {file = "flake8-tidy-imports-4.4.1.tar.gz", hash = "sha256:c18b3351b998787db071e766e318da1f0bd9d5cecc69c4022a69e7aa2efb2c51"},
+ {file = "flake8_tidy_imports-4.4.1-py3-none-any.whl", hash = "sha256:631a1ba9daaedbe8bb53f6086c5a92b390e98371205259e0e311a378df8c3dc8"},
]
flake8-todo = [
{file = "flake8-todo-0.7.tar.gz", hash = "sha256:6e4c5491ff838c06fe5a771b0e95ee15fc005ca57196011011280fc834a85915"},
@@ -940,67 +941,81 @@ hiredis = [
{file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"},
]
identify = [
- {file = "identify-2.2.12-py2.py3-none-any.whl", hash = "sha256:a510cbe155f39665625c8a4c4b4f9360cbce539f51f23f47836ab7dd852db541"},
- {file = "identify-2.2.12.tar.gz", hash = "sha256:242332b3bdd45a8af1752d5d5a3afb12bee26f8e67c4be06e394f82d05ef1a4d"},
+ {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"},
+ {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"},
]
idna = [
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
]
kiwisolver = [
- {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"},
- {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"},
- {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21"},
- {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05"},
- {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b"},
- {file = "kiwisolver-1.3.1-cp36-cp36m-win32.whl", hash = "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9"},
- {file = "kiwisolver-1.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc"},
- {file = "kiwisolver-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454"},
- {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72"},
- {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3"},
- {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131"},
- {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de"},
- {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18"},
- {file = "kiwisolver-1.3.1-cp38-cp38-win32.whl", hash = "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81"},
- {file = "kiwisolver-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e"},
- {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000"},
- {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598"},
- {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882"},
- {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621"},
- {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54"},
- {file = "kiwisolver-1.3.1-cp39-cp39-win32.whl", hash = "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030"},
- {file = "kiwisolver-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6"},
- {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d"},
- {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3"},
- {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6"},
- {file = "kiwisolver-1.3.1.tar.gz", hash = "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1d819553730d3c2724582124aee8a03c846ec4362ded1034c16fb3ef309264e6"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d93a1095f83e908fc253f2fb569c2711414c0bfd451cab580466465b235b470"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4550a359c5157aaf8507e6820d98682872b9100ce7607f8aa070b4b8af6c298"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2210f28778c7d2ee13f3c2a20a3a22db889e75f4ec13a21072eabb5693801e84"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82f49c5a79d3839bc8f38cb5f4bfc87e15f04cbafa5fbd12fb32c941cb529cfb"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9661a04ca3c950a8ac8c47f53cbc0b530bce1b52f516a1e87b7736fec24bfff0"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ddb500a2808c100e72c075cbb00bf32e62763c82b6a882d403f01a119e3f402"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72be6ebb4e92520b9726d7146bc9c9b277513a57a38efcf66db0620aec0097e0"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-win32.whl", hash = "sha256:83d2c9db5dfc537d0171e32de160461230eb14663299b7e6d18ca6dca21e4977"},
+ {file = "kiwisolver-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:cba430db673c29376135e695c6e2501c44c256a81495da849e85d1793ee975ad"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4116ba9a58109ed5e4cb315bdcbff9838f3159d099ba5259c7c7fb77f8537492"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19554bd8d54cf41139f376753af1a644b63c9ca93f8f72009d50a2080f870f77"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a4cf5bbdc861987a7745aed7a536c6405256853c94abc9f3287c3fa401b174"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0007840186bacfaa0aba4466d5890334ea5938e0bb7e28078a0eb0e63b5b59d5"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec2eba188c1906b05b9b49ae55aae4efd8150c61ba450e6721f64620c50b59eb"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3dbb3cea20b4af4f49f84cffaf45dd5f88e8594d18568e0225e6ad9dec0e7967"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:5326ddfacbe51abf9469fe668944bc2e399181a2158cb5d45e1d40856b2a0589"},
+ {file = "kiwisolver-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c6572c2dab23c86a14e82c245473d45b4c515314f1f859e92608dcafbd2f19b8"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b5074fb09429f2b7bc82b6fb4be8645dcbac14e592128beeff5461dcde0af09f"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22521219ca739654a296eea6d4367703558fba16f98688bd8ce65abff36eaa84"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c358721aebd40c243894298f685a19eb0491a5c3e0b923b9f887ef1193ddf829"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba5a1041480c6e0a8b11a9544d53562abc2d19220bfa14133e0cdd9967e97af"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44e6adf67577dbdfa2d9f06db9fbc5639afefdb5bf2b4dfec25c3a7fbc619536"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d45d1c74f88b9f41062716c727f78f2a59a5476ecbe74956fafb423c5c87a76"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70adc3658138bc77a36ce769f5f183169bc0a2906a4f61f09673f7181255ac9b"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6a5431940f28b6de123de42f0eb47b84a073ee3c3345dc109ad550a3307dd28"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-win32.whl", hash = "sha256:ee040a7de8d295dbd261ef2d6d3192f13e2b08ec4a954de34a6fb8ff6422e24c"},
+ {file = "kiwisolver-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8dc3d842fa41a33fe83d9f5c66c0cc1f28756530cd89944b63b072281e852031"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a498bcd005e8a3fedd0022bb30ee0ad92728154a8798b703f394484452550507"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80efd202108c3a4150e042b269f7c78643420cc232a0a771743bb96b742f838f"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f8eb7b6716f5b50e9c06207a14172cf2de201e41912ebe732846c02c830455b9"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f441422bb313ab25de7b3dbfd388e790eceb76ce01a18199ec4944b369017009"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:30fa008c172355c7768159983a7270cb23838c4d7db73d6c0f6b60dde0d432c6"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f8f6c8f4f1cff93ca5058d6ec5f0efda922ecb3f4c5fb76181f327decff98b8"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba677bcaff9429fd1bf01648ad0901cea56c0d068df383d5f5856d88221fe75b"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7843b1624d6ccca403a610d1277f7c28ad184c5aa88a1750c1a999754e65b439"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-win32.whl", hash = "sha256:e6f5eb2f53fac7d408a45fbcdeda7224b1cfff64919d0f95473420a931347ae9"},
+ {file = "kiwisolver-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:eedd3b59190885d1ebdf6c5e0ca56828beb1949b4dfe6e5d0256a461429ac386"},
+ {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dedc71c8eb9c5096037766390172c34fb86ef048b8e8958b4e484b9e505d66bc"},
+ {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bf7eb45d14fc036514c09554bf983f2a72323254912ed0c3c8e697b62c4c158f"},
+ {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b65bd35f3e06a47b5c30ea99e0c2b88f72c6476eedaf8cfbc8e66adb5479dcf"},
+ {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25405f88a37c5f5bcba01c6e350086d65e7465fd1caaf986333d2a045045a223"},
+ {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bcadb05c3d4794eb9eee1dddf1c24215c92fb7b55a80beae7a60530a91060560"},
+ {file = "kiwisolver-1.3.2.tar.gz", hash = "sha256:fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c"},
]
matplotlib = [
- {file = "matplotlib-3.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c541ee5a3287efe066bbe358320853cf4916bc14c00c38f8f3d8d75275a405a9"},
- {file = "matplotlib-3.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3a5c18dbd2c7c366da26a4ad1462fe3e03a577b39e3b503bbcf482b9cdac093c"},
- {file = "matplotlib-3.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a9d8cb5329df13e0cdaa14b3b43f47b5e593ec637f13f14db75bb16e46178b05"},
- {file = "matplotlib-3.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:7ad19f3fb6145b9eb41c08e7cbb9f8e10b91291396bee21e9ce761bb78df63ec"},
- {file = "matplotlib-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:7a58f3d8fe8fac3be522c79d921c9b86e090a59637cb88e3bc51298d7a2c862a"},
- {file = "matplotlib-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6382bc6e2d7e481bcd977eb131c31dee96e0fb4f9177d15ec6fb976d3b9ace1a"},
- {file = "matplotlib-3.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a6a44f27aabe720ec4fd485061e8a35784c2b9ffa6363ad546316dfc9cea04e"},
- {file = "matplotlib-3.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1c1779f7ab7d8bdb7d4c605e6ffaa0614b3e80f1e3c8ccf7b9269a22dbc5986b"},
- {file = "matplotlib-3.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5826f56055b9b1c80fef82e326097e34dc4af8c7249226b7dd63095a686177d1"},
- {file = "matplotlib-3.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0bea5ec5c28d49020e5d7923c2725b837e60bc8be99d3164af410eb4b4c827da"},
- {file = "matplotlib-3.4.2-cp38-cp38-win32.whl", hash = "sha256:6475d0209024a77f869163ec3657c47fed35d9b6ed8bccba8aa0f0099fbbdaa8"},
- {file = "matplotlib-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:21b31057bbc5e75b08e70a43cefc4c0b2c2f1b1a850f4a0f7af044eb4163086c"},
- {file = "matplotlib-3.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b26535b9de85326e6958cdef720ecd10bcf74a3f4371bf9a7e5b2e659c17e153"},
- {file = "matplotlib-3.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:32fa638cc10886885d1ca3d409d4473d6a22f7ceecd11322150961a70fab66dd"},
- {file = "matplotlib-3.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:956c8849b134b4a343598305a3ca1bdd3094f01f5efc8afccdebeffe6b315247"},
- {file = "matplotlib-3.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:85f191bb03cb1a7b04b5c2cca4792bef94df06ef473bc49e2818105671766fee"},
- {file = "matplotlib-3.4.2-cp39-cp39-win32.whl", hash = "sha256:b1d5a2cedf5de05567c441b3a8c2651fbde56df08b82640e7f06c8cd91e201f6"},
- {file = "matplotlib-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:df815378a754a7edd4559f8c51fc7064f779a74013644a7f5ac7a0c31f875866"},
- {file = "matplotlib-3.4.2.tar.gz", hash = "sha256:d8d994cefdff9aaba45166eb3de4f5211adb4accac85cbf97137e98f26ea0219"},
+ {file = "matplotlib-3.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913"},
+ {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa"},
+ {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eeb1859efe7754b1460e1d4991bbd4a60a56f366bc422ef3a9c5ae05f0bc70b5"},
+ {file = "matplotlib-3.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:844a7b0233e4ff7fba57e90b8799edaa40b9e31e300b8d5efc350937fa8b1bea"},
+ {file = "matplotlib-3.4.3-cp37-cp37m-win32.whl", hash = "sha256:85f0c9cf724715e75243a7b3087cf4a3de056b55e05d4d76cc58d610d62894f3"},
+ {file = "matplotlib-3.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c70b6311dda3e27672f1bf48851a0de816d1ca6aaf3d49365fbdd8e959b33d2b"},
+ {file = "matplotlib-3.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b884715a59fec9ad3b6048ecf3860f3b2ce965e676ef52593d6fa29abcf7d330"},
+ {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a78a3b51f29448c7f4d4575e561f6b0dbb8d01c13c2046ab6c5220eb25c06506"},
+ {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a724e3a48a54b8b6e7c4ae38cd3d07084508fa47c410c8757e9db9791421838"},
+ {file = "matplotlib-3.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48e1e0859b54d5f2e29bb78ca179fd59b971c6ceb29977fb52735bfd280eb0f5"},
+ {file = "matplotlib-3.4.3-cp38-cp38-win32.whl", hash = "sha256:01c9de93a2ca0d128c9064f23709362e7fefb34910c7c9e0b8ab0de8258d5eda"},
+ {file = "matplotlib-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebfb01a65c3f5d53a8c2a8133fec2b5221281c053d944ae81ff5822a68266617"},
+ {file = "matplotlib-3.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b53f336a4688cfce615887505d7e41fd79b3594bf21dd300531a4f5b4f746a"},
+ {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:fcd6f1954943c0c192bfbebbac263f839d7055409f1173f80d8b11a224d236da"},
+ {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6be8df61b1626e1a142c57e065405e869e9429b4a6dab4a324757d0dc4d42235"},
+ {file = "matplotlib-3.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:41b6e307458988891fcdea2d8ecf84a8c92d53f84190aa32da65f9505546e684"},
+ {file = "matplotlib-3.4.3-cp39-cp39-win32.whl", hash = "sha256:f72657f1596199dc1e4e7a10f52a4784ead8a711f4e5b59bea95bdb97cf0e4fd"},
+ {file = "matplotlib-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:f15edcb0629a0801738925fe27070480f446fcaa15de65946ff946ad99a59a40"},
+ {file = "matplotlib-3.4.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:556965514b259204637c360d213de28d43a1f4aed1eca15596ce83f768c5a56f"},
+ {file = "matplotlib-3.4.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:54a026055d5f8614f184e588f6e29064019a0aa8448450214c0b60926d62d919"},
+ {file = "matplotlib-3.4.3.tar.gz", hash = "sha256:fc4f526dfdb31c9bd6b8ca06bf9fab663ca12f3ec9cdf4496fb44bc680140318"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
@@ -1084,8 +1099,8 @@ numpy = [
{file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"},
]
pep8-naming = [
- {file = "pep8-naming-0.12.0.tar.gz", hash = "sha256:1f9a3ecb2f3fd83240fd40afdd70acc89695c49c333413e49788f93b61827e12"},
- {file = "pep8_naming-0.12.0-py2.py3-none-any.whl", hash = "sha256:2321ac2b7bf55383dd19a6a9c8ae2ebf05679699927a3af33e60dd7d337099d3"},
+ {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"},
+ {file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"},
]
pillow = [
{file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"},
@@ -1129,12 +1144,12 @@ pillow = [
{file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"},
]
platformdirs = [
- {file = "platformdirs-2.2.0-py3-none-any.whl", hash = "sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c"},
- {file = "platformdirs-2.2.0.tar.gz", hash = "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"},
+ {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"},
+ {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"},
]
pre-commit = [
- {file = "pre_commit-2.13.0-py2.py3-none-any.whl", hash = "sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4"},
- {file = "pre_commit-2.13.0.tar.gz", hash = "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378"},
+ {file = "pre_commit-2.14.1-py2.py3-none-any.whl", hash = "sha256:a22d12a02da4d8df314187dfe7a61bda6291d57992060522feed30c8cd658b68"},
+ {file = "pre_commit-2.14.1.tar.gz", hash = "sha256:7977a3103927932d4823178cbe4719ab55bb336f42a9f3bb2776cff99007a117"},
]
psutil = [
{file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"},
@@ -1261,67 +1276,67 @@ pyyaml = [
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
rapidfuzz = [
- {file = "rapidfuzz-1.4.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:72878878d6744883605b5453c382361716887e9e552f677922f76d93d622d8cb"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:56a67a5b3f783e9af73940f6945366408b3a2060fc6ab18466e5a2894fd85617"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f5d396b64f8ae3a793633911a1fb5d634ac25bf8f13d440139fa729131be42d8"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4990698233e7eda7face7c09f5874a09760c7524686045cbb10317e3a7f3225f"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a87e212855b18a951e79ec71d71dbd856d98cd2019d0c2bd46ec30688a8aa68a"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1897d2ef03f5b51bc19bdb2d0398ae968766750fa319843733f0a8f12ddde986"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:e1fc4fd219057f5f1fa40bb9bc5e880f8ef45bf19350d4f5f15ca2ce7f61c99b"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:21300c4d048798985c271a8bf1ed1611902ebd4479fcacda1a3eaaebbad2f744"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:d2659967c6ac74211a87a1109e79253e4bc179641057c64800ef4e2dc0534fdb"},
- {file = "rapidfuzz-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:26ac4bfe564c516e053fc055f1543d2b2433338806738c7582e1f75ed0485f7e"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3b485c98ad1ce3c04556f65aaab5d6d6d72121cde656d43505169c71ae956476"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:59db06356eaf22c83f44b0dded964736cbb137291cdf2cf7b4974c0983b94932"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fef95249af9a535854b617a68788c38cd96308d97ee14d44bc598cc73e986167"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:7d8c186e8270e103d339b26ef498581cf3178470ccf238dfd5fd0e47d80e4c7d"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9246b9c5c8992a83a08ac7813c8bbff2e674ad0b681f9b3fb1ec7641eff6c21f"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58c17f7a82b1bcc2ce304942cae14287223e6b6eead7071241273da7d9b9770"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:ed708620b23a09ac52eaaec0761943c1bbc9a62d19ecd2feb4da8c3f79ef9d37"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:bdec9ae5fd8a8d4d8813b4aac3505c027b922b4033a32a7aab66a9b2f03a7b47"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:fc668fd706ad1162ce14f26ca2957b4690d47770d23609756536c918a855ced0"},
- {file = "rapidfuzz-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f9f35df5dd9b02669ff6b1d4a386607ff56982c86a7e57d95eb08c6afbab4ddd"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8427310ea29ce2968e1c6f6779ae5a458b3a4984f9150fc4d16f92b96456f848"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1430dc745476e3798742ad835f61f6e6bf5d3e9a22cf9cd0288b28b7440a9872"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1d20311da611c8f4638a09e2bc5e04b327bae010cb265ef9628d9c13c6d5da7b"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7881965e428cf6fe248d6e702e6d5857da02278ab9b21313bee717c080e443e"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f76c965f15861ec4d39e904bd65b84a39121334439ac17bfb8b900d1e6779a93"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:61167f989415e701ac379de247e6b0a21ea62afc86c54d8a79f485b4f0173c02"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:645cfb9456229f0bd5752b3eda69f221d825fbb8cbb8855433516bc185111506"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:c28be57c9bc47b3d7f484340fab1bec8ed4393dee1090892c2774a4584435eb8"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:3c94b6d3513c693f253ff762112cc4580d3bd377e4abacb96af31a3d606fbe14"},
- {file = "rapidfuzz-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:506d50a066451502ee2f8bf016bc3ba3e3b04eede7a4059d7956248e2dd96179"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:80b375098658bb3db14215a975d354f6573d3943ac2ae0c4627c7760d57ce075"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ba8f7cbd8fdbd3ae115f4484888f3cb94bc2ac7cbd4eb1ca95a3d4f874261ff8"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5fa8570720b0fdfc52f24f5663d66c52ea88ba19cb8b1ff6a39a8bc0b925b33b"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f35c8a4c690447fd335bfd77df4da42dfea37cfa06a8ecbf22543d86dc720e12"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:27f9eef48e212d73e78f0f5ceedc62180b68f6a25fa0752d2ccfaedc3a840bec"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:31e99216e2a04aec4f281d472b28a683921f1f669a429cf605d11526623eaeed"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:f22bf7ba6eddd59764457f74c637ab5c3ed976c5fcfaf827e1d320cc0478e12b"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:c43ddb354abd00e56f024ce80affb3023fa23206239bb81916d5877cba7f2d1e"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-win32.whl", hash = "sha256:62c1f4ac20c8019ce8d481fb27235306ef3912a8d0b9a60b17905699f43ff072"},
- {file = "rapidfuzz-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:2963f356c70b710dc6337b012ec976ce2fc2b81c2a9918a686838fead6eb4e1d"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c07f301fd549b266410654850c6918318d7dcde8201350e9ac0819f0542cf147"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa4c8b6fc7e93e3a3fb9be9566f1fe7ef920735eadcee248a0d70f3ca8941341"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c200bd813bbd3b146ba0fd284a9ad314bbad9d95ed542813273bdb9d0ee4e796"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2cccc84e1f0c6217747c09cafe93164e57d3644e18a334845a2dfbdd2073cd2c"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f2033e3d61d1e498f618123b54dc7436d50510b0d18fd678d867720e8d7b2f23"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:26b7f48b3ddd9d97cf8482a88f0f6cba47ac13ff16e63386ea7ce06178174770"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bf18614f87fe3bfff783f0a3d0fad0eb59c92391e52555976e55570a651d2330"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8cb5c2502ff06028a1468bdf61323b53cc3a37f54b5d62d62c5371795b81086a"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f37f80c1541d6e0a30547261900086b8c0bac519ebc12c9cd6b61a9a43a7e195"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:c13cd1e840aa93639ac1d131fbfa740a609fd20dfc2a462d5cd7bce747a2398d"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-win32.whl", hash = "sha256:0ec346f271e96c485716c091c8b0b78ba52da33f7c6ebb52a349d64094566c2d"},
- {file = "rapidfuzz-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:5208ce1b1989a10e6fc5b5ef5d0bb7d1ffe5408838f3106abde241aff4dab08c"},
- {file = "rapidfuzz-1.4.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4fa195ea9ca35bacfa2a4319c6d4ab03aa6a283ad2089b70d2dfa0f6a7d9c1bc"},
- {file = "rapidfuzz-1.4.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:6e336cfd8103b0b38e107e01502e9d6bf7c7f04e49b970fb11a4bf6c7a932b94"},
- {file = "rapidfuzz-1.4.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c798c5b87efe8a7e63f408e07ff3bc03ba8b94f4498a89b48eaab3a9f439d52c"},
- {file = "rapidfuzz-1.4.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:bb16a10b40f5bd3c645f7748fbd36f49699a03f550c010a2c665905cc8937de8"},
- {file = "rapidfuzz-1.4.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2278001924031d9d75f821bff2c5fef565c8376f252562e04d8eec8857475c36"},
- {file = "rapidfuzz-1.4.1-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:a89d11f3b5da35fdf3e839186203b9367d56e2be792e8dccb098f47634ec6eb9"},
- {file = "rapidfuzz-1.4.1-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:f8c79cd11b4778d387366a59aa747f5268433f9d68be37b00d16f4fb08fdf850"},
- {file = "rapidfuzz-1.4.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:4364db793ed4b439f9dd28a335bee14e2a828283d3b93c2d2686cc645eeafdd5"},
- {file = "rapidfuzz-1.4.1.tar.gz", hash = "sha256:de20550178376d21bfe1b34a7dc42ab107bb282ef82069cf6dfe2805a0029e26"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:670a330e90e962de5823e01e8ae1b8903af788325fbce1ef3fd5ece4d22e0ba4"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:079afafa6e6b00ee799e16d9fc6c6522132cbd7742a7a9e78bd301321e1b5ad6"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:26cb066e79c9867d313450514bb70124d392ac457640c4ec090d29eb68b75541"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:542fbe8fb4403af36bfffd53e42cb1ff3f8d969a046208373d004804072b744c"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:407a5c4d2af813e803b828b004f8686300baf298e9bf90b3388a568b1637a8dc"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:662b4021951ac9edb9a0d026820529e891cea69c11f280188c5b80fefe6ee257"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:03c97beb1c7ce5cb1d12bbb8eb87777e9a5fad23216dab78d6850cafdd3ecaf1"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:eaafa0349d47850ed2c3ae121b62e078a63daf1d533b1cd43fca0c675a85a025"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-win32.whl", hash = "sha256:f0b7e15209208ee74bc264b97e111a3c73e19336eda7255c406e56cc6fbbd384"},
+ {file = "rapidfuzz-1.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:0679af3d85082dcb27e75ea30c5047dbcc99340f38490c7d4769ae16909c246a"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a3ef319fd1162e7e38bf11259d86fc6ea3885d2abae6359e5b4dafad62592db"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:60ea1cee33a5a847aeac91a35865c6f7f35a87613df282bda2e7f984e91526f5"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2ba6ffe8ac66dbeae91a0b2cb50f4836ec16920f58746eaf46ff3e9c4f9c0ad8"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:7c101bafb27436affcaa14c631e2bf99d6a7a7860a201ce17ee98447c9c0e7f4"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a8f3f374b4e8e80516b955a1da6364c526d480311a5c6be48264cf7dc06d2fba"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f2fe161526cce52eae224c2af9ae1b9c475ae3e1001fe76024603b290bc8f719"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:8b086b2f70571c9bf16ead5f65976414f8e75a1c680220a839b8ddf005743060"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:814cd474c31db0383c69eed5b457571f63521f38829955c842b141b4835f067f"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-win32.whl", hash = "sha256:0a901aa223a4b051846cb828c33967a6f9c66b8fe0ba7e2a4dc70f6612006988"},
+ {file = "rapidfuzz-1.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f03a5fa9fe38d7f8d566bff0b66600f488d56700469bf1e5e36078f4b58290b6"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:122b7c25792eb27ca59ab23623a922a7290d881d296556d0c23da63ed1691cd5"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:73509dbfcf556233d62683aed0e5f23282ec7138eeedc3ecda2938ad8e8c969d"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6e8c4fd87361699e0cf5cf7ff075e4cd70a2698e9f914368f0c3e198c77c755c"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d627ec73d324d804af4c95909e2fa30b0e59f7efaf69264e553a0e498034404b"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c57f3b74942ae0d0869336e613cbd0760de61a462ff441095eb5fca6575cf964"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:075b8bf76dd4bbc9ccb5177806c9867424d365898415433bf88e7b8e88dc4dfe"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:8049a500b431724d283ddf97d67fe48aa67b4523d617a203c22fd9da3a496223"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:a2d84fde07c32514758d283dd1227453db3ed5372a3e9eae85d0c29b2953f252"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:0e35b9b92a955018ebd09d4d9d70f8e81a0106fe1ed04bc82e3a05166cd04ea5"},
+ {file = "rapidfuzz-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8ae7bf62f0382d13e9b36babc897742bac5e7ee04b4e5e94cd67085bfccfd2fd"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:466d9c644fa235278ef376eefb1fc4382107b07764fbc3c7280533ad9ce49bb4"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d04a8465738363d0b9ee39abb3b289e1198d1f3cbc98bc43b8e21ec8e0b21774"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2c1ce8e8419ac8462289a6e021b8802701ea0f111ebde7607ba3c9588c3d6f30"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f44564a29e96af0925e68733859d8247a692968034e1b37407d9cfa746d3a853"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d2d1bea50f54387bc1e82b93f6e3a433084e0fa538a7ada8e4d4d7200bae4b83"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b409f0f86a316b6132253258185c7b011e779ed2170d1ad83c79515fea7d78c8"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:bf5a6f4f2eb44f32271e9c2d1e46b657764dbd1b933dd84d7c0433eab48741f8"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bbdee2e3c2cee9c59e1d1a3f351760a1b510e96379d14ba2fa2484a79f56d0ea"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-win32.whl", hash = "sha256:575a0eceaf84632f2014fd55a42a0621e448115adf6fcbc2b0e5c7ae1c18b501"},
+ {file = "rapidfuzz-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:cd6603b94e2a3d56d143a5100f8f3c1d29ad8f5416bdc2a25b079f96eee3c306"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fa261479e3828eff1f3d0265def8d0d893f2e2f90692d5dae96b3f4ae44d69e"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7a386fe0aad7e89b5017768492ea085d241c32f6dc5a6774b0a309d28f61e720"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68156a67d541bb4584cb31e366fb7de9326f5b77ed07f9882e9b9aaa40b2e5b8"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b62b2a2d2532d357d1b970107a90e85305bdd8e302995dd251f67a19495033f5"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:190b48ba8e3fbcb1cfc522300dbd6a007f50c13cd71002c95bd3946a63b749f6"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:51f9ac3316e713b4a10554a4d6b75fe6f802dd9b4073082cc98968ace6377cac"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:e00198aa7ca8408616d9821501ff90157c429c952d55a2a53987a9b064f73d49"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5784c24e2de539064d8d5ce3f68756630b54fc33af31e054373a65bbed68823a"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:712a4d510c466d6ca75138dad53a1cbd8db0da4bbfa5fc431fcebb0a426e5323"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:2647e00e2211ed741aecb4e676461b7202ce46d536c3439ede911b088432b7a4"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-win32.whl", hash = "sha256:0b77ca0dacb129e878c2583295b76e12da890bd091115417d23b4049b02c2566"},
+ {file = "rapidfuzz-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:dec0d429d117ffd7df1661e5f6ca56bfb6806e117be0b75b5d414df43aa4b6d5"},
+ {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a533d17d177d11b7c177c849adb728035621462f6ce2baaeb9cf1f42ba3e326c"},
+ {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:ac9a2d5a47a4a4eab060882a162d3626889abdec69f899a59fe7b9e01ce122c9"},
+ {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:0e6e2f02bb67a35d75a5613509bb49f0050c0ec4471a9af14da3ad5488d6d5ff"},
+ {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:8c61ced6729146e695ecad403165bf3a07e60b8e8a18df91962b3abf72aae6d5"},
+ {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:360415125e967d8682291f00bcea311c738101e0aee4cb90e5572d7e54483f0d"},
+ {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:2fb9d47fc16a2e8f5e900c8334d823a7307148ea764321f861b876f85a880d57"},
+ {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:2134ac91e8951d42c9a7de131d767580b8ac50820475221024e5bd63577a376f"},
+ {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:04c4fd372e858f25e0898ba27b5bb7ed8dc528b0915b7aa02d20237e9cdd4feb"},
+ {file = "rapidfuzz-1.5.0.tar.gz", hash = "sha256:141ee381c16f7e58640ef1f1dbf76beb953d248297a7165f7ba25d81ac1161c7"},
]
redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
@@ -1352,17 +1367,17 @@ toml = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
typing-extensions = [
- {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
- {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
- {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
+ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
+ {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
+ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
urllib3 = [
{file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
{file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
]
virtualenv = [
- {file = "virtualenv-20.7.0-py2.py3-none-any.whl", hash = "sha256:fdfdaaf0979ac03ae7f76d5224a05b58165f3c804f8aa633f3dd6f22fbd435d5"},
- {file = "virtualenv-20.7.0.tar.gz", hash = "sha256:97066a978431ec096d163e72771df5357c5c898ffdd587048f45e0aecc228094"},
+ {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"},
+ {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"},
]
yarl = [
{file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
diff --git a/pyproject.toml b/pyproject.toml
index 293d4e12..0e3d9a51 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,6 +7,7 @@ license = "MIT"
[tool.poetry.dependencies]
python = "^3.9"
+"discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip"}
aiodns = "~=2.0"
aioredis = "~1.3"
rapidfuzz = "~=1.4"
@@ -14,7 +15,6 @@ arrow = "~=1.1.0"
pillow = "~=8.1"
sentry-sdk = "~=0.19"
PyYAML = "~=5.4"
-"discord.py" = "~=1.7.2"
async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"}
emojis = "~=0.6.0"
matplotlib = "~=3.4.1"