diff options
author | 2021-09-03 16:28:04 +0100 | |
---|---|---|
committer | 2021-09-03 16:28:04 +0100 | |
commit | cd7060835b5b0d150c6e91d75bc3227ee43db0ba (patch) | |
tree | 4264ddbb25e86184255574cfd0e8fa9bb11d7bcb | |
parent | Handle status not found with 404 picture (diff) | |
parent | Merge pull request #839 from python-discord/android-codeblock-fix (diff) |
Merge branch 'main' into teapot-support
61 files changed, 531 insertions, 502 deletions
@@ -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" |