diff options
57 files changed, 271 insertions, 272 deletions
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 6201d424..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. 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 fd586613..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. @@ -121,7 +121,7 @@ class AvatarModify(commands.Cog): 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. @@ -158,7 +158,7 @@ class AvatarModify(commands.Cog): 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. 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 98b0f2bf..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 @@ -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 c45f9893..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() 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 3d2bf9cb..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, diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 1014f6b2..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 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/help.py b/bot/exts/evergreen/help.py index 0c0922d6..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__) @@ -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 66597b59..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 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 75212107..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 = [] diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index 225df948..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.""" diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py index a3ff3d12..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 @@ -99,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")) @@ -144,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. @@ -172,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. @@ -282,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. @@ -297,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( @@ -311,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]) @@ -706,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 e7ba2f50..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 @@ -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 8cfe0ebc..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: @@ -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 2f72a302..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 @@ -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 = {} @@ -219,7 +220,7 @@ class Extensions(commands.Cog): 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 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 2a9069b1..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") 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: |