diff options
58 files changed, 359 insertions, 327 deletions
@@ -14,6 +14,7 @@ pytz = "~=2019.2" [dev-packages] flake8 = "~=3.7" +flake8-annotations = "~=1.0" flake8-bugbear = "~=19.8" flake8-docstrings = "~=1.4" flake8-import-order = "~=0.18" diff --git a/Pipfile.lock b/Pipfile.lock index 8ec13528..abf39d9f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0d4a825399a5c48cd15c096608f8e935c70c101bdfa0711264a2f4270512b865" + "sha256": "8861de068d14f2c48bebdc2691ab62753c502ffb20735aa7b037b154f9f84a9c" }, "pipfile-spec": 6, "requires": { @@ -53,11 +53,11 @@ }, "arrow": { "hashes": [ - "sha256:8c53fad4e723a56c02d8df75fd7a879800148d6e51df4713f91502e70a65e1be", - "sha256:9f1503d359011a74cd41169652f0eb0232822f58c67c414229f4174a15ae71f9" + "sha256:704f5403299fe092c69479e0a2516a434003e82d37439a9e47c31285faf3947b", + "sha256:9b92a8e151e168b742a36b622deadf860d1686af8c5bbe46eca8da04b10fe92f" ], "index": "pypi", - "version": "==0.14.6" + "version": "==0.15.0" }, "async-timeout": { "hashes": [ @@ -230,6 +230,7 @@ }, "pycparser": { "hashes": [ + "sha256:83870b0dff6e9c1b1f721ee062f2e198c1ce7f107220020bf4de788dca009944", "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" @@ -343,6 +344,14 @@ "index": "pypi", "version": "==3.7.8" }, + "flake8-annotations": { + "hashes": [ + "sha256:1309f2bc9853a2d77d578b089d331b0b832b40c97932641e136e1b49d3650c82", + "sha256:3ecdd27054c3eed6484139025698465e3c9f4e68dbd5043d0204fcb2550ee27b" + ], + "index": "pypi", + "version": "==1.0.0" + }, "flake8-bugbear": { "hashes": [ "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571", @@ -480,9 +489,9 @@ }, "snowballstemmer": { "hashes": [ - "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" + "sha256:713e53b79cbcf97bc5245a06080a33d54a77e7cce2f789c835a143bcdb5c033e" ], - "version": "==1.9.0" + "version": "==1.9.1" }, "toml": { "hashes": [ diff --git a/bot/__init__.py b/bot/__init__.py index 9e0290a7..4729e50c 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -13,7 +13,7 @@ logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") -def monkeypatch_trace(self, msg, *args, **kwargs): +def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: """ Log 'msg % args' with severity 'TRACE'. @@ -4,7 +4,7 @@ from traceback import format_exc from typing import List from aiohttp import AsyncResolver, ClientSession, TCPConnector -from discord import Embed +from discord import DiscordException, Embed from discord.ext import commands from bot.constants import Channels, Client @@ -23,7 +23,7 @@ class SeasonalBot(commands.Bot): connector=TCPConnector(resolver=AsyncResolver(), family=socket.AF_INET) ) - def load_extensions(self, exts: List[str]): + def load_extensions(self, exts: List[str]) -> None: """Unload all current extensions, then load the given extensions.""" # Unload all cogs extensions = list(self.extensions.keys()) @@ -40,7 +40,7 @@ class SeasonalBot(commands.Bot): except Exception as e: log.error(f'Failed to load extension {cog}: {repr(e)} {format_exc()}') - async def send_log(self, title: str, details: str = None, *, icon: str = None): + async def send_log(self, title: str, details: str = None, *, icon: str = None) -> None: """Send an embed message to the devlog channel.""" devlog = self.get_channel(Channels.devlog) @@ -56,7 +56,7 @@ class SeasonalBot(commands.Bot): await devlog.send(embed=embed) - async def on_command_error(self, context, exception): + async def on_command_error(self, context: commands.Context, exception: DiscordException) -> None: """Check command errors for UserInputError and reset the cooldown if thrown.""" if isinstance(exception, commands.UserInputError): context.command.reset_cooldown(context) diff --git a/bot/decorators.py b/bot/decorators.py index 02cf4b8a..dbaad4a2 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -20,9 +20,9 @@ class InChannelCheckFailure(CheckFailure): pass -def with_role(*role_ids: int): +def with_role(*role_ids: int) -> bool: """Check to see whether the invoking user has any of the roles specified in role_ids.""" - async def predicate(ctx: Context): + async def predicate(ctx: Context) -> bool: if not ctx.guild: # Return False in a DM log.debug( f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " @@ -43,9 +43,9 @@ def with_role(*role_ids: int): return commands.check(predicate) -def without_role(*role_ids: int): +def without_role(*role_ids: int) -> bool: """Check whether the invoking user does not have all of the roles specified in role_ids.""" - async def predicate(ctx: Context): + async def predicate(ctx: Context) -> bool: if not ctx.guild: # Return False in a DM log.debug( f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " @@ -117,7 +117,7 @@ def override_in_channel(func: typing.Callable) -> typing.Callable: return func -def locked(): +def locked() -> typing.Union[typing.Callable, None]: """ Allows the user to only run one instance of the decorated command at a time. @@ -125,11 +125,11 @@ def locked(): This decorator has to go before (below) the `command` decorator. """ - def wrap(func): + def wrap(func: typing.Callable) -> typing.Union[typing.Callable, None]: func.__locks = WeakValueDictionary() @wraps(func) - async def inner(self, ctx, *args, **kwargs): + async def inner(self: typing.Callable, ctx: Context, *args, **kwargs) -> typing.Union[typing.Callable, None]: lock = func.__locks.setdefault(ctx.author.id, Lock()) if lock.locked(): embed = Embed() diff --git a/bot/pagination.py b/bot/pagination.py index c12b6233..f1233482 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -24,7 +24,7 @@ class EmptyPaginatorEmbed(Exception): class LinePaginator(Paginator): """A class that aids in paginating code blocks for Discord messages.""" - def __init__(self, prefix='```', suffix='```', max_size=2000, max_lines=None): + def __init__(self, prefix: str = '```', suffix: str = '```', max_size: int = 2000, max_lines: int = None): """ Overrides the Paginator.__init__ from inside discord.ext.commands. @@ -42,7 +42,7 @@ class LinePaginator(Paginator): self._count = len(prefix) + 1 # prefix + newline self._pages = [] - def add_line(self, line='', *, empty=False): + def add_line(self, line: str = '', *, empty: bool = False) -> None: """ Adds a line to the current page. @@ -98,7 +98,7 @@ class LinePaginator(Paginator): ... ctx, embed ... ) """ - def event_check(reaction_: Reaction, user_: Member): + def event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" no_restrictions = ( # Pagination is not restricted @@ -274,7 +274,7 @@ class ImagePaginator(Paginator): Refer to ImagePaginator.paginate for documentation on how to use. """ - def __init__(self, prefix="", suffix=""): + def __init__(self, prefix: str = "", suffix: str = ""): super().__init__(prefix, suffix) self._current_page = [prefix] self.images = [] diff --git a/bot/seasons/__init__.py b/bot/seasons/__init__.py index 1512fae2..7faf9164 100644 --- a/bot/seasons/__init__.py +++ b/bot/seasons/__init__.py @@ -1,5 +1,7 @@ import logging +from discord.ext import commands + from bot.seasons.season import SeasonBase, SeasonManager, get_season __all__ = ("SeasonBase", "get_season") @@ -7,6 +9,6 @@ __all__ = ("SeasonBase", "get_season") log = logging.getLogger(__name__) -def setup(bot): +def setup(bot: commands.Bot) -> None: bot.add_cog(SeasonManager(bot)) log.info("SeasonManager cog loaded") diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index d2894ec4..6609387e 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -46,7 +46,7 @@ def time_left_to_aoc_midnight() -> Tuple[datetime, timedelta]: return tomorrow, tomorrow - datetime.now(EST) -async def countdown_status(bot: commands.Bot): +async def countdown_status(bot: commands.Bot) -> None: """Set the playing status of the bot to the minutes & hours left until the next day's challenge.""" while is_in_advent(): _, time_left = time_left_to_aoc_midnight() @@ -73,7 +73,7 @@ async def countdown_status(bot: commands.Bot): await asyncio.sleep(delay) -async def day_countdown(bot: commands.Bot): +async def day_countdown(bot: commands.Bot) -> None: """ Calculate the number of seconds left until the next day of Advent. @@ -127,7 +127,7 @@ class AdventOfCode(commands.Cog): @commands.group(name="adventofcode", aliases=("aoc",), invoke_without_command=True) @override_in_channel - async def adventofcode_group(self, ctx: commands.Context): + async def adventofcode_group(self, ctx: commands.Context) -> None: """All of the Advent of Code commands.""" await ctx.send_help(ctx.command) @@ -136,7 +136,7 @@ class AdventOfCode(commands.Cog): aliases=("sub", "notifications", "notify", "notifs"), brief="Notifications for new days" ) - async def aoc_subscribe(self, ctx: commands.Context): + async def aoc_subscribe(self, ctx: commands.Context) -> None: """Assign the role for notifications about new days being ready.""" role = ctx.guild.get_role(AocConfig.role_id) unsubscribe_command = f"{ctx.prefix}{ctx.command.root_parent} unsubscribe" @@ -150,7 +150,7 @@ class AdventOfCode(commands.Cog): f"If you don't want them any more, run `{unsubscribe_command}` instead.") @adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days") - async def aoc_unsubscribe(self, ctx: commands.Context): + async def aoc_unsubscribe(self, ctx: commands.Context) -> None: """Remove the role for notifications about new days being ready.""" role = ctx.guild.get_role(AocConfig.role_id) @@ -161,7 +161,7 @@ class AdventOfCode(commands.Cog): await ctx.send("Hey, you don't even get any notifications about new Advent of Code tasks currently anyway.") @adventofcode_group.command(name="countdown", aliases=("count", "c"), brief="Return time left until next day") - async def aoc_countdown(self, ctx: commands.Context): + async def aoc_countdown(self, ctx: commands.Context) -> None: """Return time left until next day.""" if not is_in_advent(): datetime_now = datetime.now(EST) @@ -178,12 +178,12 @@ class AdventOfCode(commands.Cog): await ctx.send(f"There are {hours} hours and {minutes} minutes left until day {tomorrow.day}.") @adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code") - async def about_aoc(self, ctx: commands.Context): + async def about_aoc(self, ctx: commands.Context) -> None: """Respond with an explanation of all things Advent of Code.""" await ctx.send("", embed=self.cached_about_aoc) @adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join PyDis' private AoC leaderboard") - async def join_leaderboard(self, ctx: commands.Context): + async def join_leaderboard(self, ctx: commands.Context) -> None: """DM the user the information for joining the PyDis AoC private leaderboard.""" author = ctx.message.author log.info(f"{author.name} ({author.id}) has requested the PyDis AoC leaderboard code") @@ -203,7 +203,7 @@ class AdventOfCode(commands.Cog): aliases=("board", "lb"), brief="Get a snapshot of the PyDis private AoC leaderboard", ) - async def aoc_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10): + async def aoc_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10) -> None: """ Pull the top number_of_people_to_display members from the PyDis leaderboard and post an embed. @@ -244,7 +244,7 @@ class AdventOfCode(commands.Cog): aliases=("dailystats", "ds"), brief="Get daily statistics for the PyDis private leaderboard" ) - async def private_leaderboard_daily_stats(self, ctx: commands.Context): + async def private_leaderboard_daily_stats(self, ctx: commands.Context) -> None: """ Respond with a table of the daily completion statistics for the PyDis private leaderboard. @@ -287,7 +287,7 @@ class AdventOfCode(commands.Cog): aliases=("globalboard", "gb"), brief="Get a snapshot of the global AoC leaderboard", ) - async def global_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10): + async def global_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10) -> None: """ Pull the top number_of_people_to_display members from the global AoC leaderboard and post an embed. @@ -319,7 +319,7 @@ class AdventOfCode(commands.Cog): embed=aoc_embed, ) - async def _check_leaderboard_cache(self, ctx, global_board: bool = False): + async def _check_leaderboard_cache(self, ctx: commands.Context, global_board: bool = False) -> None: """ Check age of current leaderboard & pull a new one if the board is too old. @@ -390,7 +390,7 @@ class AdventOfCode(commands.Cog): return about_embed - async def _boardgetter(self, global_board: bool): + async def _boardgetter(self, global_board: bool) -> None: """Invoke the proper leaderboard getter based on the global_board boolean.""" if global_board: self.cached_global_leaderboard = await AocGlobalLeaderboard.from_url() diff --git a/bot/seasons/christmas/hanukkah_embed.py b/bot/seasons/christmas/hanukkah_embed.py index 652a1f35..aaa02b27 100644 --- a/bot/seasons/christmas/hanukkah_embed.py +++ b/bot/seasons/christmas/hanukkah_embed.py @@ -1,5 +1,6 @@ import datetime import logging +from typing import List from discord import Embed from discord.ext import commands @@ -13,7 +14,7 @@ log = logging.getLogger(__name__) class HanukkahEmbed(commands.Cog): """A cog that returns information about Hanukkah festival.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.url = ("https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on") @@ -21,7 +22,7 @@ class HanukkahEmbed(commands.Cog): self.hanukkah_months = [] self.hanukkah_years = [] - async def get_hanukkah_dates(self): + async def get_hanukkah_dates(self) -> List[str]: """Gets the dates for hanukkah festival.""" hanukkah_dates = [] async with self.bot.http_session.get(self.url) as response: @@ -34,7 +35,7 @@ class HanukkahEmbed(commands.Cog): return hanukkah_dates @commands.command(name='hanukkah', aliases=['chanukah']) - async def hanukkah_festival(self, ctx): + async def hanukkah_festival(self, ctx: commands.Context) -> None: """Tells you about the Hanukkah Festivaltime of festival, festival day, etc).""" hanukkah_dates = await self.get_hanukkah_dates() self.hanukkah_dates_split(hanukkah_dates) @@ -98,7 +99,7 @@ class HanukkahEmbed(commands.Cog): await ctx.send(embed=embed) - def hanukkah_dates_split(self, hanukkah_dates): + 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]) @@ -106,7 +107,7 @@ class HanukkahEmbed(commands.Cog): self.hanukkah_years.append(date[0:4]) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(HanukkahEmbed(bot)) log.info("Hanukkah embed cog loaded") diff --git a/bot/seasons/easter/april_fools_vids.py b/bot/seasons/easter/april_fools_vids.py index d921d07c..4869f510 100644 --- a/bot/seasons/easter/april_fools_vids.py +++ b/bot/seasons/easter/april_fools_vids.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) class AprilFoolVideos(commands.Cog): """A cog for April Fools' that gets a random April Fools' video from Youtube.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.yt_vids = self.load_json() self.youtubers = ['google'] # will add more in future @staticmethod - def load_json(): + def load_json() -> dict: """A function to load JSON data.""" p = Path('bot/resources/easter/april_fools_vids.json') with p.open() as json_file: @@ -25,7 +25,7 @@ class AprilFoolVideos(commands.Cog): return all_vids @commands.command(name='fool') - async def aprial_fools(self, ctx): + async def april_fools(self, ctx: commands.Context) -> None: """Get a random April Fools' video from Youtube.""" random_youtuber = random.choice(self.youtubers) category = self.yt_vids[random_youtuber] @@ -33,7 +33,7 @@ class AprilFoolVideos(commands.Cog): await ctx.send(f"Check out this April Fools' video by {random_youtuber}.\n\n{random_vid['link']}") -def setup(bot): +def setup(bot: commands.Bot) -> None: """April Fools' Cog load.""" bot.add_cog(AprilFoolVideos(bot)) log.info('April Fools videos cog loaded!') diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index 98e15982..f056068e 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -2,7 +2,7 @@ import asyncio import logging from io import BytesIO from pathlib import Path -from typing import Union +from typing import Tuple, Union import discord from PIL import Image @@ -21,11 +21,11 @@ COLOURS = [ class AvatarEasterifier(commands.Cog): """Put an Easter spin on your avatar or image!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def closest(x): + def closest(x: Tuple[int, int, int]) -> Tuple[int, int, int]: """ Finds the closest easter colour to a given pixel. @@ -33,8 +33,8 @@ class AvatarEasterifier(commands.Cog): """ r1, g1, b1 = x - def distance(point): - """Finds the difference between a pastel colour and the original pixel colour.""" + def distance(point: Tuple[int, int, int]) -> Tuple[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) @@ -47,7 +47,7 @@ class AvatarEasterifier(commands.Cog): return (r, g, b) @commands.command(pass_context=True, aliases=["easterify"]) - async def avatareasterify(self, ctx, *colours: Union[discord.Colour, str]): + async def avatareasterify(self, ctx: commands.Context, *colours: Union[discord.Colour, str]) -> None: """ This "Easterifies" the user's avatar. @@ -123,7 +123,7 @@ class AvatarEasterifier(commands.Cog): await ctx.send(file=file, embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Avatar Easterifier Cog load.""" bot.add_cog(AvatarEasterifier(bot)) log.info("AvatarEasterifier cog loaded") diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 3ceaeb9e..22957b7f 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -3,6 +3,7 @@ import logging import random import re from pathlib import Path +from typing import List, Union from discord.ext import commands @@ -15,16 +16,16 @@ with Path("bot/resources/easter/bunny_names.json").open("r", encoding="utf8") as class BunnyNameGenerator(commands.Cog): """Generate a random bunny name, or bunnify your Discord username!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - def find_separators(self, displayname): + def find_separators(self, displayname: str) -> Union[List[str], None]: """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: return new_name - def find_vowels(self, displayname): + def find_vowels(self, displayname: str) -> str: """ Finds vowels in the user's display name. @@ -45,8 +46,8 @@ class BunnyNameGenerator(commands.Cog): if new_name != displayname: return new_name - def append_name(self, displayname): - """Adds a suffix to the end of the Discord name.""" + def append_name(self, displayname: str) -> str: + """Adds a suffix to the end of the Discord name""" extensions = ['foot', 'ear', 'nose', 'tail'] suffix = random.choice(extensions) appended_name = displayname + suffix @@ -54,13 +55,13 @@ class BunnyNameGenerator(commands.Cog): return appended_name @commands.command() - async def bunnyname(self, ctx): - """Picks a random bunny name from a JSON file.""" + async def bunnyname(self, ctx: commands.Context) -> None: + """Picks a random bunny name from a JSON file""" await ctx.send(random.choice(BUNNY_NAMES["names"])) @commands.command() - async def bunnifyme(self, ctx): - """Gets your Discord username and bunnifies it.""" + async def bunnifyme(self, ctx: commands.Context) -> None: + """Gets your Discord username and bunnifies it""" username = ctx.message.author.display_name # If name contains spaces or other separators, get the individual words to randomly bunnify @@ -86,7 +87,7 @@ class BunnyNameGenerator(commands.Cog): await ctx.send(bunnified_name) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Bunny Name Generator Cog load.""" bot.add_cog(BunnyNameGenerator(bot)) log.info("BunnyNameGenerator cog loaded.") diff --git a/bot/seasons/easter/conversationstarters.py b/bot/seasons/easter/conversationstarters.py index c2cdf26c..3f38ae82 100644 --- a/bot/seasons/easter/conversationstarters.py +++ b/bot/seasons/easter/conversationstarters.py @@ -14,16 +14,16 @@ with open(Path("bot/resources/easter/starter.json"), "r", encoding="utf8") as f: class ConvoStarters(commands.Cog): """Easter conversation topics.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def topic(self, ctx): + async def topic(self, ctx: commands.Context) -> None: """Responds with a random topic to start a conversation.""" await ctx.send(random.choice(starters['starters'])) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Conversation starters Cog load.""" bot.add_cog(ConvoStarters(bot)) log.info("ConvoStarters cog loaded") diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index b612f8b9..c3f19055 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -20,14 +20,14 @@ TIMELIMIT = 10 class EasterRiddle(commands.Cog): """This cog contains the command for the Easter quiz!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.winners = [] self.correct = "" self.current_channel = None @commands.command(aliases=["riddlemethis", "riddleme"]) - async def riddle(self, ctx): + async def riddle(self, ctx: commands.Context) -> None: """ Gives a random riddle, then provides 2 hints at certain intervals before revealing the answer. @@ -83,8 +83,8 @@ class EasterRiddle(commands.Cog): self.current_channel = None @commands.Cog.listener() - async def on_message(self, message): - """If a non-bot user enters a correct answer, their username gets added to self.winners.""" + async def on_message(self, message: discord.Messaged) -> None: + """If a non-bot user enters a correct answer, their username gets added to self.winners""" if self.current_channel != message.channel: return @@ -95,7 +95,7 @@ class EasterRiddle(commands.Cog): self.winners.append(message.author.mention) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Easter Riddle Cog load.""" bot.add_cog(EasterRiddle(bot)) log.info("Easter Riddle bot loaded") diff --git a/bot/seasons/easter/egg_decorating.py b/bot/seasons/easter/egg_decorating.py index ee8a80e5..51f52264 100644 --- a/bot/seasons/easter/egg_decorating.py +++ b/bot/seasons/easter/egg_decorating.py @@ -31,11 +31,11 @@ IRREPLACEABLE = [ class EggDecorating(commands.Cog): """Decorate some easter eggs!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot @staticmethod - def replace_invalid(colour: str): + def replace_invalid(colour: str) -> Union[int, None]: """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,9 @@ class EggDecorating(commands.Cog): return None @commands.command(aliases=["decorateegg"]) - async def eggdecorate(self, ctx, *colours: Union[discord.Colour, str]): + async def eggdecorate( + self, ctx: commands.Context, *colours: Union[discord.Colour, str] + ) -> Union[Image, discord.Message]: """ Picks a random egg design and decorates it using the given colours. @@ -111,7 +113,7 @@ class EggDecorating(commands.Cog): return new_im -def setup(bot): +def setup(bot: commands.bot) -> None: """Egg decorating Cog load.""" bot.add_cog(EggDecorating(bot)) log.info("EggDecorating cog loaded.") diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index ae08ccd4..9e6fb1cb 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -21,18 +21,18 @@ class EasterFacts(commands.Cog): It also contains a background task which sends an easter egg fact in the event channel everyday. """ - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.facts = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load a list of easter egg facts from the resource JSON file.""" p = Path("bot/resources/easter/easter_egg_facts.json") with p.open(encoding="utf8") as f: return load(f) - async def send_egg_fact_daily(self): + async def send_egg_fact_daily(self) -> None: """A background task that sends an easter egg fact in the event channel everyday.""" channel = self.bot.get_channel(Channels.seasonalbot_chat) while True: @@ -41,12 +41,12 @@ class EasterFacts(commands.Cog): await asyncio.sleep(24 * 60 * 60) @commands.command(name='eggfact', aliases=['fact']) - async def easter_facts(self, ctx): + async def easter_facts(self, ctx: commands.Context) -> None: """Get easter egg facts.""" embed = self.make_embed() await ctx.send(embed=embed) - def make_embed(self): + def make_embed(self) -> discord.Embed: """Makes a nice embed for the message to be sent.""" return discord.Embed( colour=Colours.soft_red, @@ -55,7 +55,7 @@ class EasterFacts(commands.Cog): ) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Easter Egg facts cog load.""" bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) diff --git a/bot/seasons/easter/egg_hunt/__init__.py b/bot/seasons/easter/egg_hunt/__init__.py index 0e4b9e16..e7e71ccb 100644 --- a/bot/seasons/easter/egg_hunt/__init__.py +++ b/bot/seasons/easter/egg_hunt/__init__.py @@ -1,11 +1,13 @@ import logging +from discord.ext import commands + from .cog import EggHunt log = logging.getLogger(__name__) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Easter Egg Hunt Cog load.""" bot.add_cog(EggHunt()) log.info("EggHunt cog loaded") diff --git a/bot/seasons/easter/egg_hunt/cog.py b/bot/seasons/easter/egg_hunt/cog.py index a4ad27df..8178b4ef 100644 --- a/bot/seasons/easter/egg_hunt/cog.py +++ b/bot/seasons/easter/egg_hunt/cog.py @@ -91,7 +91,7 @@ class EggMessage: """Builds the SQL for adding a score to a team in the database.""" return f"UPDATE team_scores SET team_score=team_score+{score} WHERE team_id='{team_name}'" - def finalise_score(self): + def finalise_score(self) -> None: """Sums and actions scoring for this egg drop session.""" db = sqlite3.connect(DB_PATH) c = db.cursor() @@ -133,7 +133,7 @@ class EggMessage: f"FIRST({self.first}) REST({self.users})." ) - async def start_timeout(self, seconds: int = 5): + async def start_timeout(self, seconds: int = 5) -> None: """Begins a task that will sleep until the given seconds before finalizing the session.""" if self.timeout_task: self.timeout_task.cancel() @@ -164,7 +164,7 @@ class EggMessage: return True - async def collect_reacts(self, reaction: discord.Reaction, user: discord.Member): + async def collect_reacts(self, reaction: discord.Reaction, user: discord.Member) -> None: """Handles emitted reaction_add events via listener.""" if not self.is_valid_react(reaction, user): return @@ -182,7 +182,7 @@ class EggMessage: if user != self.first: self.users.add(user) - async def start(self): + async def start(self) -> None: """Starts the egg drop session.""" log.debug(f"EggHunt session started for message {self.message.id}.") bot.add_listener(self.collect_reacts, name="on_reaction_add") @@ -207,7 +207,7 @@ class SuperEggMessage(EggMessage): super().__init__(message, egg) self.window = window - async def finalise_score(self): + async def finalise_score(self) -> None: """Sums and actions scoring for this super egg session.""" try: message = await self.message.channel.fetch_message(self.message.id) @@ -280,7 +280,7 @@ class SuperEggMessage(EggMessage): with contextlib.suppress(discord.HTTPException): await self.message.edit(embed=embed) - async def start_timeout(self, seconds=None): + async def start_timeout(self, seconds: int = None) -> None: """Starts the super egg session.""" if not seconds: return @@ -337,7 +337,7 @@ class EggHunt(commands.Cog): self.task = asyncio.create_task(self.super_egg()) self.task.add_done_callback(self.task_cleanup) - def prepare_db(self): + def prepare_db(self) -> None: """Ensures database tables all exist and if not, creates them.""" db = sqlite3.connect(DB_PATH) c = db.cursor() @@ -358,7 +358,7 @@ class EggHunt(commands.Cog): db.commit() db.close() - def task_cleanup(self, task): + def task_cleanup(self, task: asyncio.Task) -> None: """Returns task result and restarts. Used as a done callback to show raised exceptions.""" task.result() self.task = asyncio.create_task(self.super_egg()) @@ -368,7 +368,7 @@ class EggHunt(commands.Cog): """Returns a timestamp of the current UTC time.""" return datetime.utcnow().replace(tzinfo=timezone.utc).timestamp() - async def super_egg(self): + async def super_egg(self) -> None: """Manages the timing of super egg drops.""" while True: now = int(self.current_timestamp()) @@ -455,7 +455,7 @@ class EggHunt(commands.Cog): await asyncio.sleep(next_loop) @commands.Cog.listener() - async def on_raw_reaction_add(self, payload): + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None: """Reaction event listener for reaction logging for later anti-cheat analysis.""" if payload.channel_id not in EggHuntSettings.allowed_channels: return @@ -471,7 +471,7 @@ class EggHunt(commands.Cog): db.close() @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Message) -> None: """Message event listener for random egg drops.""" if self.current_timestamp() < EggHuntSettings.start_time: return @@ -487,7 +487,7 @@ class EggHunt(commands.Cog): await EggMessage(message, random.choice([Emoji.egg_white, Emoji.egg_blurple])).start() @commands.group(invoke_without_command=True) - async def hunt(self, ctx): + async def hunt(self, ctx: commands.Context) -> None: """ For 48 hours, hunt down as many eggs randomly appearing as possible. @@ -514,7 +514,7 @@ class EggHunt(commands.Cog): await ctx.invoke(bot.get_command("help"), command="hunt") @hunt.command() - async def countdown(self, ctx): + async def countdown(self, ctx: commands.Context) -> None: """Show the time status of the Egg Hunt event.""" now = self.current_timestamp() if now > EggHuntSettings.end_time: @@ -532,7 +532,7 @@ class EggHunt(commands.Cog): await ctx.send(f"{msg} {hours:.0f}hrs, {minutes:.0f}mins & {r:.0f}secs") @hunt.command() - async def leaderboard(self, ctx): + async def leaderboard(self, ctx: commands.Context) -> None: """Show the Egg Hunt Leaderboards.""" db = sqlite3.connect(DB_PATH) c = db.cursor() @@ -573,7 +573,7 @@ class EggHunt(commands.Cog): await ctx.send(embed=embed) @hunt.command() - async def rank(self, ctx, *, member: discord.Member = None): + async def rank(self, ctx: commands.Context, *, member: discord.Member = None) -> None: """Get your ranking in the Egg Hunt Leaderboard.""" member = member or ctx.author db = sqlite3.connect(DB_PATH) @@ -593,7 +593,7 @@ class EggHunt(commands.Cog): @with_role(MainRoles.admin) @hunt.command() - async def clear_db(self, ctx): + async def clear_db(self, ctx: commands.Context) -> None: """Resets the database to it's initial state.""" def check(msg): if msg.author != ctx.author: diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py index b3841993..0b175bf1 100644 --- a/bot/seasons/easter/egghead_quiz.py +++ b/bot/seasons/easter/egghead_quiz.py @@ -3,6 +3,7 @@ import logging import random from json import load from pathlib import Path +from typing import Union import discord from discord.ext import commands @@ -30,12 +31,12 @@ TIMELIMIT = 30 class EggheadQuiz(commands.Cog): """This cog contains the command for the Easter quiz!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot self.quiz_messages = {} @commands.command(aliases=["eggheadquiz", "easterquiz"]) - async def eggquiz(self, ctx): + async def eggquiz(self, ctx: commands.Context) -> None: """ Gives a random quiz question, waits 30 seconds and then outputs the answer. @@ -95,14 +96,14 @@ class EggheadQuiz(commands.Cog): await ctx.send(content, embed=a_embed) @staticmethod - async def already_reacted(message, user): - """Returns whether a given user has reacted more than once to a given message.""" + async def already_reacted(message: discord.Message, user: Union[discord.Member, discord.User]) -> bool: + """Returns whether a given user has reacted more than once to a given message""" users = [u.id for reaction in [await r.users().flatten() for r in message.reactions] for u in reaction] return users.count(user.id) > 1 # Old reaction plus new reaction @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): - """Listener to listen specifically for reactions of quiz messages.""" + async def on_reaction_add(self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]) -> None: + """Listener to listen specifically for reactions of quiz messages""" if user.bot: return if reaction.message.id not in self.quiz_messages: @@ -113,7 +114,7 @@ class EggheadQuiz(commands.Cog): return await reaction.message.remove_reaction(reaction, user) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Egghead Quiz Cog load.""" bot.add_cog(EggheadQuiz(bot)) log.info("EggheadQuiz bot loaded") diff --git a/bot/seasons/easter/traditions.py b/bot/seasons/easter/traditions.py index b0bf04d7..4fb4694f 100644 --- a/bot/seasons/easter/traditions.py +++ b/bot/seasons/easter/traditions.py @@ -14,18 +14,18 @@ with open(Path("bot/resources/easter/traditions.json"), "r", encoding="utf8") as class Traditions(commands.Cog): """A cog which allows users to get a random easter tradition or custom from a random country.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('eastercustoms',)) - async def easter_tradition(self, ctx): - """Responds with a random tradition or custom.""" + async def easter_tradition(self, ctx: commands.Context) -> None: + """Responds with a random tradition or custom""" random_country = random.choice(list(traditions)) await ctx.send(f"{random_country}:\n{traditions[random_country]}") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Traditions Cog load.""" bot.add_cog(Traditions(bot)) log.info("Traditions cog loaded") diff --git a/bot/seasons/evergreen/error_handler.py b/bot/seasons/evergreen/error_handler.py index 6690cf89..120462ee 100644 --- a/bot/seasons/evergreen/error_handler.py +++ b/bot/seasons/evergreen/error_handler.py @@ -4,7 +4,7 @@ import random import sys
import traceback
-from discord import Colour, Embed
+from discord import Colour, Embed, Message
from discord.ext import commands
from bot.constants import NEGATIVE_REPLIES
@@ -16,11 +16,11 @@ log = logging.getLogger(__name__) class CommandErrorHandler(commands.Cog):
"""A error handler for the PythonDiscord server."""
- def __init__(self, bot):
+ def __init__(self, bot: commands.Bot):
self.bot = bot
@staticmethod
- def revert_cooldown_counter(command, message):
+ def revert_cooldown_counter(command: commands.Command, message: Message) -> None:
"""Undoes the last cooldown counter for user-error cases."""
if command._buckets.valid:
bucket = command._buckets.get_bucket(message)
@@ -30,7 +30,7 @@ class CommandErrorHandler(commands.Cog): )
@commands.Cog.listener()
- async def on_command_error(self, ctx, error):
+ async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None:
"""Activates when a command opens an error."""
if hasattr(ctx.command, 'on_error'):
return logging.debug(
@@ -113,7 +113,7 @@ class CommandErrorHandler(commands.Cog): traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
-def setup(bot):
+def setup(bot: commands.Bot) -> None:
"""Error handler Cog load."""
bot.add_cog(CommandErrorHandler(bot))
log.info("CommandErrorHandler cog loaded")
diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 87077f36..7b3363fc 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -77,7 +77,7 @@ class Fun(Cog): return text -def setup(bot) -> None: +def setup(bot: commands.Bot) -> None: """Fun Cog load.""" bot.add_cog(Fun(bot)) log.info("Fun cog loaded") diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index f19a1129..0ba74d9c 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -12,12 +12,14 @@ log = logging.getLogger(__name__) class Issues(commands.Cog): """Cog that allows users to retrieve issues from GitHub.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=("issues",)) @override_in_channel - async def issue(self, ctx, number: int, repository: str = "seasonalbot", user: str = "python-discord"): + async def issue( + self, ctx: commands.Context, number: int, repository: str = "seasonalbot", user: str = "python-discord" + ) -> None: """Command to retrieve issues from a GitHub repository.""" api_url = f"https://api.github.com/repos/{user}/{repository}/issues/{number}" failed_status = { @@ -49,7 +51,7 @@ class Issues(commands.Cog): await ctx.send(embed=issue_embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Github Issues Cog Load.""" bot.add_cog(Issues(bot)) log.info("Issues cog loaded") diff --git a/bot/seasons/evergreen/magic_8ball.py b/bot/seasons/evergreen/magic_8ball.py index 55652af7..e47ef454 100644 --- a/bot/seasons/evergreen/magic_8ball.py +++ b/bot/seasons/evergreen/magic_8ball.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot with open(Path("bot/resources/evergreen/magic8ball.json"), "r") as file: self.answers = json.load(file) @commands.command(name="8ball") - async def output_answer(self, ctx, *, question): + async def output_answer(self, ctx: commands.Context, *, question: str) -> None: """Return a Magic 8ball answer from answers list.""" if len(question.split()) >= 3: answer = random.choice(self.answers) @@ -26,7 +26,7 @@ class Magic8ball(commands.Cog): await ctx.send("Usage: .8ball <question> (minimum length of 3 eg: `will I win?`)") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Magic 8ball Cog load.""" bot.add_cog(Magic8ball(bot)) log.info("Magic8ball cog loaded") diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 3eee92ca..015b09df 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -32,8 +32,8 @@ log = logging.getLogger(__name__) class CoordinateConverter(commands.Converter): """Converter for Coordinates.""" - async def convert(self, ctx, coordinate: str) -> typing.Tuple[int, int]: - """Take in a coordinate string and turn it into an (x, y) tuple.""" + async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: + """Take in a coordinate string and turn it into x, y""" if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') @@ -80,8 +80,8 @@ class Minesweeper(commands.Cog): self.games: GamesDict = {} # Store the currently running games @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) - async def minesweeper_group(self, ctx: commands.Context): - """Commands for Playing Minesweeper.""" + async def minesweeper_group(self, ctx: commands.Context) -> None: + """Commands for Playing Minesweeper""" await ctx.send_help(ctx.command) @staticmethod @@ -215,8 +215,8 @@ class Minesweeper(commands.Cog): if board[y_][x_] == 0: self.reveal_zeros(revealed, board, x_, y_) - async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: - """Checks if a player has won.""" + async def check_if_won(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard) -> bool: + """Checks if a player has won""" if any( revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb" for x in range(10) @@ -267,8 +267,8 @@ class Minesweeper(commands.Cog): await self.update_boards(ctx) @minesweeper_group.command(name="end") - async def end_command(self, ctx: commands.Context): - """End your current game.""" + async def end_command(self, ctx: commands.Context) -> None: + """End your current game""" game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index 5dea78a5..d41132aa 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -1,5 +1,6 @@ import logging +from discord import Message from discord.ext import commands from bot.constants import Channels @@ -10,13 +11,13 @@ log = logging.getLogger(__name__) class ShowProjects(commands.Cog): """Cog that reacts to posts in the #show-your-projects.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.lastPoster = 0 # Given 0 as the default last poster ID as no user can actually have 0 assigned to them @commands.Cog.listener() - async def on_message(self, message): - """Adds reactions to posts in #show-your-projects.""" + async def on_message(self, message: Message) -> None: + """Adds reactions to posts in #show-your-projects""" reactions = ["\U0001f44d", "\U00002764", "\U0001f440", "\U0001f389", "\U0001f680", "\U00002b50", "\U0001f6a9"] if (message.channel.id == Channels.show_your_projects and message.author.bot is False @@ -27,7 +28,7 @@ class ShowProjects(commands.Cog): self.lastPoster = message.author.id -def setup(bot): - """Show Projects Reaction Cog.""" +def setup(bot: commands.Bot) -> None: + """Show Projects Reaction Cog""" bot.add_cog(ShowProjects(bot)) log.info("ShowProjects cog loaded") diff --git a/bot/seasons/evergreen/snakes/__init__.py b/bot/seasons/evergreen/snakes/__init__.py index d0e57dae..d7f9f20c 100644 --- a/bot/seasons/evergreen/snakes/__init__.py +++ b/bot/seasons/evergreen/snakes/__init__.py @@ -1,11 +1,13 @@ import logging +from discord.ext import commands + from bot.seasons.evergreen.snakes.snakes_cog import Snakes log = logging.getLogger(__name__) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Snakes Cog load.""" bot.add_cog(Snakes(bot)) log.info("Snakes cog loaded") diff --git a/bot/seasons/evergreen/snakes/converter.py b/bot/seasons/evergreen/snakes/converter.py index f2637530..57103b57 100644 --- a/bot/seasons/evergreen/snakes/converter.py +++ b/bot/seasons/evergreen/snakes/converter.py @@ -1,9 +1,10 @@ import json import logging import random +from typing import Iterable, List import discord -from discord.ext.commands import Converter +from discord.ext.commands import Context, Converter from fuzzywuzzy import fuzz from bot.seasons.evergreen.snakes.utils import SNAKE_RESOURCES @@ -18,7 +19,7 @@ class Snake(Converter): snakes = None special_cases = None - async def convert(self, ctx, name): + async def convert(self, ctx: Context, name: str) -> str: """Convert the input snake name to the closest matching Snake object.""" await self.build_list() name = name.lower() @@ -26,7 +27,7 @@ class Snake(Converter): if name == 'python': return 'Python (programming language)' - def get_potential(iterable, *, threshold=80): + def get_potential(iterable: Iterable, *, threshold: int = 80) -> List[str]: nonlocal name potential = [] @@ -58,7 +59,7 @@ class Snake(Converter): return names.get(name, name) @classmethod - async def build_list(cls): + async def build_list(cls) -> None: """Build list of snakes from the static snake resources.""" # Get all the snakes if cls.snakes is None: @@ -72,7 +73,7 @@ class Snake(Converter): cls.special_cases = {snake['name'].lower(): snake for snake in special_cases} @classmethod - async def random(cls): + async def random(cls) -> str: """ Get a random Snake from the loaded resources. diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 38878706..1ed38f86 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -9,13 +9,13 @@ import textwrap import urllib from functools import partial from io import BytesIO -from typing import Any, Dict +from typing import Any, Dict, List import aiohttp import async_timeout from PIL import Image, ImageDraw, ImageFont from discord import Colour, Embed, File, Member, Message, Reaction -from discord.ext.commands import BadArgument, Bot, Cog, Context, bot_has_permissions, group +from discord.ext.commands import BadArgument, Bot, Cog, CommandError, Context, bot_has_permissions, group from bot.constants import ERROR_REPLIES, Tokens from bot.decorators import locked @@ -154,7 +154,7 @@ class Snakes(Cog): # region: Helper methods @staticmethod - def _beautiful_pastel(hue): + def _beautiful_pastel(hue: float) -> int: """Returns random bright pastels.""" light = random.uniform(0.7, 0.85) saturation = 1 @@ -250,7 +250,7 @@ class Snakes(Cog): return buffer @staticmethod - def _snakify(message): + def _snakify(message: str) -> str: """Sssnakifffiesss a sstring.""" # Replace fricatives with exaggerated snake fricatives. simple_fricatives = [ @@ -272,7 +272,7 @@ class Snakes(Cog): return message - async def _fetch(self, session, url, params=None): + async def _fetch(self, session: aiohttp.ClientSession, url: str, params: dict = None) -> dict: """Asynchronous web request helper method.""" if params is None: params = {} @@ -281,7 +281,7 @@ class Snakes(Cog): async with session.get(url, params=params) as response: return await response.json() - def _get_random_long_message(self, messages, retries=10): + 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. @@ -403,9 +403,9 @@ class Snakes(Cog): """Gets a random snake name.""" return random.choice(self.snake_names) - async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list): + async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list) -> None: """Validate the answer using a reaction event loop.""" - def predicate(reaction, user): + def predicate(reaction: Reaction, user: Member) -> bool: """Test if the the answer is valid and can be evaluated.""" return ( reaction.message.id == message.id # The reaction is attached to the question we asked. @@ -436,14 +436,14 @@ class Snakes(Cog): # region: Commands @group(name='snakes', aliases=('snake',), invoke_without_command=True) - async def snakes_group(self, ctx: Context): + async def snakes_group(self, ctx: Context) -> None: """Commands from our first code jam.""" await ctx.send_help(ctx.command) @bot_has_permissions(manage_messages=True) @snakes_group.command(name='antidote') @locked() - async def antidote_command(self, ctx: Context): + async def antidote_command(self, ctx: Context) -> None: """ Antidote! Can you create the antivenom before the patient dies? @@ -458,7 +458,7 @@ class Snakes(Cog): This game was created by Lord Bisk and Runew0lf. """ - def predicate(reaction_: Reaction, user_: Member): + def predicate(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( all(( @@ -584,7 +584,7 @@ class Snakes(Cog): await board_id.clear_reactions() @snakes_group.command(name='draw') - async def draw_command(self, ctx: Context): + async def draw_command(self, ctx: Context) -> None: """ Draws a random snek using Perlin noise. @@ -672,7 +672,7 @@ class Snakes(Cog): @snakes_group.command(name='guess', aliases=('identify',)) @locked() - async def guess_command(self, ctx): + async def guess_command(self, ctx: Context) -> None: """ Snake identifying game. @@ -706,7 +706,7 @@ class Snakes(Cog): await self._validate_answer(ctx, guess, answer, options) @snakes_group.command(name='hatch') - async def hatch_command(self, ctx: Context): + async def hatch_command(self, ctx: Context) -> None: """ Hatches your personal snake. @@ -737,7 +737,7 @@ class Snakes(Cog): await ctx.channel.send(embed=my_snake_embed) @snakes_group.command(name='movie') - async def movie_command(self, ctx: Context): + async def movie_command(self, ctx: Context) -> None: """ Gets a random snake-related movie from OMDB. @@ -807,7 +807,7 @@ class Snakes(Cog): @snakes_group.command(name='quiz') @locked() - async def quiz_command(self, ctx: Context): + async def quiz_command(self, ctx: Context) -> None: """ Asks a snake-related question in the chat and validates the user's guess. @@ -832,7 +832,7 @@ class Snakes(Cog): await self._validate_answer(ctx, quiz, answer, options) @snakes_group.command(name='name', aliases=('name_gen',)) - async def name_command(self, ctx: Context, *, name: str = None): + async def name_command(self, ctx: Context, *, name: str = None) -> None: """ Snakifies a username. @@ -904,7 +904,7 @@ class Snakes(Cog): @snakes_group.command(name='sal') @locked() - async def sal_command(self, ctx: Context): + async def sal_command(self, ctx: Context) -> None: """ Play a game of Snakes and Ladders. @@ -922,7 +922,7 @@ class Snakes(Cog): await game.open_game() @snakes_group.command(name='about') - async def about_command(self, ctx: Context): + async def about_command(self, ctx: Context) -> None: """Show an embed with information about the event, its participants, and its winners.""" contributors = [ "<@!245270749919576066>", @@ -965,7 +965,7 @@ class Snakes(Cog): await ctx.channel.send(embed=embed) @snakes_group.command(name='card') - async def card_command(self, ctx: Context, *, name: Snake = None): + async def card_command(self, ctx: Context, *, name: Snake = None) -> None: """ Create an interesting little card from a snake. @@ -1003,7 +1003,7 @@ class Snakes(Cog): ) @snakes_group.command(name='fact') - async def fact_command(self, ctx: Context): + async def fact_command(self, ctx: Context) -> None: """ Gets a snake-related fact. @@ -1019,7 +1019,7 @@ class Snakes(Cog): await ctx.channel.send(embed=embed) @snakes_group.command(name='snakify') - async def snakify_command(self, ctx: Context, *, message: str = None): + async def snakify_command(self, ctx: Context, *, message: str = None) -> None: """ How would I talk if I were a snake? @@ -1060,7 +1060,7 @@ class Snakes(Cog): await ctx.channel.send(embed=embed) @snakes_group.command(name='video', aliases=('get_video',)) - async def video_command(self, ctx: Context, *, search: str = None): + async def video_command(self, ctx: Context, *, search: str = None) -> None: """ Gets a YouTube video about snakes. @@ -1100,7 +1100,7 @@ class Snakes(Cog): log.warning(f"YouTube API error. Full response looks like {response}") @snakes_group.command(name='zen') - async def zen_command(self, ctx: Context): + async def zen_command(self, ctx: Context) -> None: """ Gets a random quote from the Zen of Python, except as if spoken by a snake. @@ -1127,7 +1127,7 @@ class Snakes(Cog): @get_command.error @card_command.error @video_command.error - async def command_error(self, ctx, error): + async def command_error(self, ctx: Context, error: CommandError) -> None: """Local error handler for the Snake Cog.""" embed = Embed() embed.colour = Colour.red() diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index b1d5048a..76809bd4 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -11,7 +11,7 @@ from typing import List, Tuple from PIL import Image from PIL.ImageDraw import ImageDraw from discord import File, Member, Reaction -from discord.ext.commands import Context +from discord.ext.commands import Cog, Context from bot.constants import Roles @@ -118,12 +118,12 @@ def get_resource(file: str) -> List[dict]: return json.load(snakefile) -def smoothstep(t): +def smoothstep(t: float) -> float: """Smooth curve with a zero derivative at 0 and 1, making it useful for interpolating.""" return t * t * (3. - 2. * t) -def lerp(t, a, b): +def lerp(t: float, a: float, b: float) -> float: """Linear interpolation between a and b, given a fraction t.""" return a + t * (b - a) @@ -140,7 +140,7 @@ class PerlinNoiseFactory(object): Licensed under ISC """ - def __init__(self, dimension, octaves=1, tile=(), unbias=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. @@ -154,7 +154,7 @@ class PerlinNoiseFactory(object): This will produce noise that tiles every 3 units vertically, but never tiles horizontally. - If ``unbias`` is true, the smoothstep function will be applied to the output before returning + If ``unbias`` is True, the smoothstep function will be applied to the output before returning it, to counteract some of Perlin noise's significant bias towards the center of its output range. """ self.dimension = dimension @@ -168,7 +168,7 @@ class PerlinNoiseFactory(object): self.gradient = {} - def _generate_gradient(self): + def _generate_gradient(self) -> Tuple[float, ...]: """ Generate a random unit vector at each grid point. @@ -188,7 +188,7 @@ class PerlinNoiseFactory(object): scale = sum(n * n for n in random_point) ** -0.5 return tuple(coord * scale for coord in random_point) - def get_plain_noise(self, *point): + def get_plain_noise(self, *point) -> float: """Get plain noise for a single point, without taking into account either octaves or tiling.""" if len(point) != self.dimension: raise ValueError("Expected {0} values, got {1}".format( @@ -236,7 +236,7 @@ class PerlinNoiseFactory(object): return dots[0] * self.scale_factor - def __call__(self, *point): + def __call__(self, *point) -> float: """ Get the value of this Perlin noise function at the given point. @@ -369,7 +369,7 @@ GAME_SCREEN_EMOJI = [ class SnakeAndLaddersGame: """Snakes and Ladders game Cog.""" - def __init__(self, snakes, context: Context): + def __init__(self, snakes: Cog, context: Context): self.snakes = snakes self.ctx = context self.channel = self.ctx.channel @@ -384,14 +384,14 @@ class SnakeAndLaddersGame: self.positions = None self.rolls = [] - async def open_game(self): + async def open_game(self) -> None: """ Create a new Snakes and Ladders game. Listen for reactions until players have joined, and the game has been started. """ - def startup_event_check(reaction_: Reaction, user_: Member): + def startup_event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( all(( @@ -456,7 +456,7 @@ class SnakeAndLaddersGame: await self.cancel_game() return # We're done, no reactions for the last 5 minutes - async def _add_player(self, user: Member): + async def _add_player(self, user: Member) -> None: self.players.append(user) self.player_tiles[user.id] = 1 @@ -464,7 +464,7 @@ class SnakeAndLaddersGame: im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE)) self.avatar_images[user.id] = im - async def player_join(self, user: Member): + async def player_join(self, user: Member) -> None: """ Handle players joining the game. @@ -490,7 +490,7 @@ class SnakeAndLaddersGame: delete_after=10 ) - async def player_leave(self, user: Member) -> bool: + async def player_leave(self, user: Member) -> None: """ Handle players leaving the game. @@ -515,17 +515,15 @@ class SnakeAndLaddersGame: is_surrendered = True self._destruct() - return is_surrendered - else: - await self.channel.send(user.mention + " You are not in the match.", delete_after=10) - return is_surrendered - - async def cancel_game(self): - """Cancel the running game.""" + async def cancel_game(self, user: Member) -> None: + """Allow the game author to cancel the running game.""" + if not user == self.author: + await self.channel.send(user.mention + " Only the author of the game can cancel it.", delete_after=10) + return await self.channel.send("**Snakes and Ladders**: Game has been canceled.") self._destruct() - async def start_game(self, user: Member): + async def start_game(self, user: Member) -> None: """ Allow the game author to begin the game. @@ -544,9 +542,9 @@ class SnakeAndLaddersGame: await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) await self.start_round() - async def start_round(self): + async def start_round(self) -> None: """Begin the round.""" - def game_event_check(reaction_: Reaction, user_: Member): + def game_event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( all(( @@ -639,7 +637,7 @@ class SnakeAndLaddersGame: if not is_surrendered: await self._complete_round() - async def player_roll(self, user: Member): + async def player_roll(self, user: Member) -> None: """Handle the player's roll.""" if user.id not in self.player_tiles: await self.channel.send(user.mention + " You are not in the match.", delete_after=10) @@ -671,7 +669,7 @@ class SnakeAndLaddersGame: self.player_tiles[user.id] = min(100, next_tile) self.round_has_rolled[user.id] = True - async def _complete_round(self): + async def _complete_round(self) -> None: self.state = 'post_round' # check for winner @@ -691,13 +689,13 @@ class SnakeAndLaddersGame: return next((player for player in self.players if self.player_tiles[player.id] == 100), None) - def _check_all_rolled(self): + def _check_all_rolled(self) -> bool: return all(rolled for rolled in self.round_has_rolled.values()) - def _destruct(self): + def _destruct(self) -> None: del self.snakes.active_sal[self.channel] - def _board_coordinate_from_index(self, index: int): + def _board_coordinate_from_index(self, index: int) -> Tuple[float, float]: # converts 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/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index 5e3d38a0..2f59c886 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -13,16 +13,16 @@ with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf-8") class Speedrun(commands.Cog): """Commands about the video game speedrunning community.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="speedrun") - async def get_speedrun(self, ctx): + async def get_speedrun(self, ctx: commands.Context) -> None: """Sends a link to a video of a random speedrun.""" await ctx.send(choice(LINKS)) -def setup(bot): - """Load the Speedrun cog.""" +def setup(bot: commands.Bot) -> None: + """Load the Speedrun cog""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") diff --git a/bot/seasons/evergreen/uptime.py b/bot/seasons/evergreen/uptime.py index 92066e0a..6f24f545 100644 --- a/bot/seasons/evergreen/uptime.py +++ b/bot/seasons/evergreen/uptime.py @@ -12,11 +12,11 @@ log = logging.getLogger(__name__) class Uptime(commands.Cog): """A cog for posting the bot's uptime.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="uptime") - async def uptime(self, ctx): + async def uptime(self, ctx: commands.Context) -> None: """Responds with the uptime of the bot.""" difference = relativedelta(start_time - arrow.utcnow()) uptime_string = start_time.shift( @@ -28,7 +28,7 @@ class Uptime(commands.Cog): await ctx.send(f"I started up {uptime_string}.") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Uptime Cog load.""" bot.add_cog(Uptime(bot)) log.info("Uptime cog loaded") diff --git a/bot/seasons/halloween/8ball.py b/bot/seasons/halloween/8ball.py index faf59ca9..2e1c2804 100644 --- a/bot/seasons/halloween/8ball.py +++ b/bot/seasons/halloween/8ball.py @@ -15,11 +15,11 @@ with open(Path("bot/resources/halloween/responses.json"), "r", encoding="utf8") class SpookyEightBall(commands.Cog): """Spooky Eightball answers.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('spooky8ball',)) - async def spookyeightball(self, ctx, *, question: str): + async def spookyeightball(self, ctx: commands.Context, *, question: str) -> None: """Responds with a random response to a question.""" choice = random.choice(responses['responses']) msg = await ctx.send(choice[0]) @@ -28,7 +28,7 @@ class SpookyEightBall(commands.Cog): await msg.edit(content=f"{choice[0]} \n{choice[1]}") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky Eight Ball Cog Load.""" bot.add_cog(SpookyEightBall(bot)) log.info("SpookyEightBall cog loaded") diff --git a/bot/seasons/halloween/candy_collection.py b/bot/seasons/halloween/candy_collection.py index a7232e0d..64da7ced 100644 --- a/bot/seasons/halloween/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -3,6 +3,7 @@ import json import logging import os import random +from typing import List, Union import discord from discord.ext import commands @@ -23,7 +24,7 @@ ADD_SKULL_EXISTING_REACTION_CHANCE = 20 # 5% class CandyCollection(commands.Cog): """Candy collection game Cog.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot with open(json_location) as candy: self.candy_json = json.load(candy) @@ -34,7 +35,7 @@ class CandyCollection(commands.Cog): self.get_candyinfo[userid] = userinfo @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Message) -> None: """Randomly adds candy or skull reaction to non-bot messages in the Event channel.""" # make sure its a human message if message.author.bot: @@ -55,7 +56,7 @@ class CandyCollection(commands.Cog): return await message.add_reaction('\N{CANDY}') @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): + async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Member) -> None: """Add/remove candies from a person if the reaction satisfies criteria.""" message = reaction.message # check to ensure the reactor is human @@ -101,7 +102,7 @@ class CandyCollection(commands.Cog): self.candy_json['records'].append(d) await self.remove_reactions(reaction) - async def reacted_msg_chance(self, message): + async def reacted_msg_chance(self, message: discord.Message) -> None: """ Randomly add a skull or candy reaction to a message if there is a reaction there already. @@ -118,7 +119,7 @@ class CandyCollection(commands.Cog): self.msg_reacted.append(d) return await message.add_reaction('\N{CANDY}') - async def ten_recent_msg(self): + async def ten_recent_msg(self) -> List[int]: """Get the last 10 messages sent in the channel.""" ten_recent = [] recent_msg_id = max( @@ -136,7 +137,7 @@ class CandyCollection(commands.Cog): return ten_recent - async def get_message(self, msg_id): + async def get_message(self, msg_id: int) -> Union[discord.Message, None]: """Get the message from its ID.""" try: o = discord.Object(id=msg_id + 1) @@ -152,11 +153,11 @@ class CandyCollection(commands.Cog): except Exception: return None - async def hacktober_channel(self): + async def hacktober_channel(self) -> discord.TextChannel: """Get #hacktoberbot channel from its ID.""" return self.bot.get_channel(id=Channels.seasonalbot_chat) - async def remove_reactions(self, reaction): + async def remove_reactions(self, reaction: discord.Reaction) -> None: """Remove all candy/skull reactions.""" try: async for user in reaction.users(): @@ -165,20 +166,20 @@ class CandyCollection(commands.Cog): except discord.HTTPException: pass - async def send_spook_msg(self, author, channel, candies): + async def send_spook_msg(self, author: discord.Member, channel: discord.TextChannel, candies: int) -> None: """Send a spooky message.""" e = discord.Embed(colour=author.colour) e.set_author(name="Ghosts and Ghouls and Jack o' lanterns at night; " f"I took {candies} candies and quickly took flight.") await channel.send(embed=e) - def save_to_json(self): + def save_to_json(self) -> None: """Save JSON to a local file.""" with open(json_location, 'w') as outfile: json.dump(self.candy_json, outfile) @commands.command() - async def candy(self, ctx): + async def candy(self, ctx: commands.Context) -> None: """Get the candy leaderboard and save to JSON.""" # Use run_in_executor to prevent blocking thing = functools.partial(self.save_to_json) @@ -214,7 +215,7 @@ class CandyCollection(commands.Cog): await ctx.send(embed=e) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Candy Collection game Cog load.""" bot.add_cog(CandyCollection(bot)) log.info("CandyCollection cog loaded") diff --git a/bot/seasons/halloween/halloween_facts.py b/bot/seasons/halloween/halloween_facts.py index f09aa4ad..f8610bd3 100644 --- a/bot/seasons/halloween/halloween_facts.py +++ b/bot/seasons/halloween/halloween_facts.py @@ -3,6 +3,7 @@ import logging import random from datetime import timedelta from pathlib import Path +from typing import Tuple import discord from discord.ext import commands @@ -28,7 +29,7 @@ INTERVAL = timedelta(hours=6).total_seconds() class HalloweenFacts(commands.Cog): """A Cog for displaying interesting facts about Halloween.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot with open(Path("bot/resources/halloween/halloween_facts.json"), "r") as file: self.halloween_facts = json.load(file) @@ -37,31 +38,31 @@ class HalloweenFacts(commands.Cog): random.shuffle(self.facts) @commands.Cog.listener() - async def on_ready(self): + async def on_ready(self) -> None: """Get event Channel object and initialize fact task loop.""" self.channel = self.bot.get_channel(Channels.seasonalbot_chat) self.bot.loop.create_task(self._fact_publisher_task()) - def random_fact(self): + def random_fact(self) -> Tuple[int, str]: """Return a random fact from the loaded facts.""" return random.choice(self.facts) @commands.command(name="spookyfact", aliases=("halloweenfact",), brief="Get the most recent Halloween fact") - async def get_random_fact(self, ctx): + async def get_random_fact(self, ctx: commands.Context) -> None: """Reply with the most recent Halloween fact.""" index, fact = self.random_fact() embed = self._build_embed(index, fact) await ctx.send(embed=embed) @staticmethod - def _build_embed(index, fact): + def _build_embed(index: int, fact: str) -> discord.Embed: """Builds a Discord embed from the given fact and its index.""" emoji = random.choice(SPOOKY_EMOJIS) title = f"{emoji} Halloween Fact #{index + 1}" return discord.Embed(title=title, description=fact, color=PUMPKIN_ORANGE) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Halloween facts Cog load.""" bot.add_cog(HalloweenFacts(bot)) log.info("HalloweenFacts cog loaded") diff --git a/bot/seasons/halloween/halloweenify.py b/bot/seasons/halloween/halloweenify.py index 334781ab..dfcc2b1e 100644 --- a/bot/seasons/halloween/halloweenify.py +++ b/bot/seasons/halloween/halloweenify.py @@ -13,12 +13,12 @@ log = logging.getLogger(__name__) class Halloweenify(commands.Cog): """A cog to change a invokers nickname to a spooky one!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.cooldown(1, 300, BucketType.user) @commands.command() - async def halloweenify(self, ctx): + async def halloweenify(self, ctx: commands.Context) -> None: """Change your nickname into a much spookier one!""" async with ctx.typing(): with open(Path("bot/resources/halloween/halloweenify.json"), "r") as f: @@ -46,7 +46,7 @@ class Halloweenify(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Halloweenify Cog load.""" bot.add_cog(Halloweenify(bot)) log.info("Halloweenify cog loaded") diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 6e71a007..12e1d022 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -30,7 +30,7 @@ class MonsterSurvey(Cog): with open(self.registry_location, 'r') as jason: self.voter_registry = json.load(jason) - def json_write(self): + def json_write(self) -> None: """Write voting results to a local JSON file.""" log.info("Saved Monster Survey Results") with open(self.registry_location, 'w') as jason: @@ -50,7 +50,7 @@ class MonsterSurvey(Cog): if id in vr[m]['votes'] and m != monster: vr[m]['votes'].remove(id) - def get_name_by_leaderboard_index(self, n): + def get_name_by_leaderboard_index(self, n: int) -> str: """Return the monster at the specified leaderboard index.""" n = n - 1 vr = self.voter_registry @@ -62,7 +62,7 @@ class MonsterSurvey(Cog): name='monster', aliases=('mon',) ) - async def monster_group(self, ctx: Context): + async def monster_group(self, ctx: Context) -> None: """The base voting command. If nothing is called, then it will return an embed.""" if ctx.invoked_subcommand is None: async with ctx.typing(): @@ -92,7 +92,7 @@ class MonsterSurvey(Cog): @monster_group.command( name='vote' ) - async def monster_vote(self, ctx: Context, name=None): + async def monster_vote(self, ctx: Context, name: str = None) -> None: """ Cast a vote for a particular monster. @@ -143,7 +143,7 @@ class MonsterSurvey(Cog): @monster_group.command( name='show' ) - async def monster_show(self, ctx: Context, name=None) -> None: + async def monster_show(self, ctx: Context, name: str = None) -> None: """Shows the named monster. If one is not named, it sends the default voting embed instead.""" if name is None: await ctx.invoke(self.monster_leaderboard) @@ -200,7 +200,7 @@ class MonsterSurvey(Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: Bot) -> None: """Monster survey Cog load.""" bot.add_cog(MonsterSurvey(bot)) log.info("MonsterSurvey cog loaded") diff --git a/bot/seasons/halloween/scarymovie.py b/bot/seasons/halloween/scarymovie.py index cd95a3a2..3823a3e4 100644 --- a/bot/seasons/halloween/scarymovie.py +++ b/bot/seasons/halloween/scarymovie.py @@ -16,11 +16,11 @@ TMDB_TOKEN = environ.get('TMDB_TOKEN') class ScaryMovie(commands.Cog): """Selects a random scary movie and embeds info into Discord chat.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name='scarymovie', alias=['smovie']) - async def random_movie(self, ctx): + async def random_movie(self, ctx: commands.Context) -> None: """Randomly select a scary movie and display information about it.""" async with ctx.typing(): selection = await self.select_movie() @@ -29,7 +29,7 @@ class ScaryMovie(commands.Cog): await ctx.send(embed=movie_details) @staticmethod - async def select_movie(): + async def select_movie() -> dict: """Selects a random movie and returns a JSON of movie details from TMDb.""" url = 'https://api.themoviedb.org/4/discover/movie' params = { @@ -62,7 +62,7 @@ class ScaryMovie(commands.Cog): return await selection.json() @staticmethod - async def format_metadata(movie): + async def format_metadata(movie: dict) -> Embed: """Formats raw TMDb data to be embedded in Discord chat.""" # Build the relevant URLs. movie_id = movie.get("id") @@ -126,7 +126,7 @@ class ScaryMovie(commands.Cog): return embed -def setup(bot): +def setup(bot: commands.Bot) -> None: """Scary movie Cog load.""" bot.add_cog(ScaryMovie(bot)) log.info("ScaryMovie cog loaded") diff --git a/bot/seasons/halloween/spookyavatar.py b/bot/seasons/halloween/spookyavatar.py index 9bdef1a8..268de3fb 100644 --- a/bot/seasons/halloween/spookyavatar.py +++ b/bot/seasons/halloween/spookyavatar.py @@ -15,10 +15,10 @@ log = logging.getLogger(__name__) class SpookyAvatar(commands.Cog): """A cog that spookifies an avatar.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - async def get(self, url): + async def get(self, url: str) -> bytes: """Returns the contents of the supplied URL.""" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: @@ -26,7 +26,7 @@ class SpookyAvatar(commands.Cog): @commands.command(name='savatar', aliases=('spookyavatar', 'spookify'), brief='Spookify an user\'s avatar.') - async def spooky_avatar(self, ctx, user: discord.Member = None): + async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: """A command to print the user's spookified avatar.""" if user is None: user = ctx.message.author @@ -47,7 +47,7 @@ class SpookyAvatar(commands.Cog): os.remove(str(ctx.message.id)+'.png') -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky avatar Cog load.""" bot.add_cog(SpookyAvatar(bot)) log.info("SpookyAvatar cog loaded") diff --git a/bot/seasons/halloween/spookygif.py b/bot/seasons/halloween/spookygif.py index ba2ad6e5..818de8cd 100644 --- a/bot/seasons/halloween/spookygif.py +++ b/bot/seasons/halloween/spookygif.py @@ -12,11 +12,11 @@ log = logging.getLogger(__name__) class SpookyGif(commands.Cog): """A cog to fetch a random spooky gif from the web!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="spookygif", aliases=("sgif", "scarygif")) - async def spookygif(self, ctx): + async def spookygif(self, ctx: commands.Context) -> None: """Fetches a random gif from the GIPHY API and responds with it.""" async with ctx.typing(): async with aiohttp.ClientSession() as session: @@ -33,7 +33,7 @@ class SpookyGif(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky GIF Cog load.""" bot.add_cog(SpookyGif(bot)) log.info("SpookyGif cog loaded") diff --git a/bot/seasons/halloween/spookyrating.py b/bot/seasons/halloween/spookyrating.py index a436e39d..7f78f536 100644 --- a/bot/seasons/halloween/spookyrating.py +++ b/bot/seasons/halloween/spookyrating.py @@ -19,13 +19,13 @@ with Path("bot/resources/halloween/spooky_rating.json").open() as file: class SpookyRating(commands.Cog): """A cog for calculating one's spooky rating.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.local_random = random.Random() @commands.command() @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) - async def spookyrating(self, ctx, who: discord.Member = None): + async def spookyrating(self, ctx: commands.Context, who: discord.Member = None) -> None: """ Calculates the spooky rating of someone. @@ -61,7 +61,7 @@ class SpookyRating(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky Rating Cog load.""" bot.add_cog(SpookyRating(bot)) log.info("SpookyRating cog loaded") diff --git a/bot/seasons/halloween/spookyreact.py b/bot/seasons/halloween/spookyreact.py index 5a086072..90b1254d 100644 --- a/bot/seasons/halloween/spookyreact.py +++ b/bot/seasons/halloween/spookyreact.py @@ -2,7 +2,7 @@ import logging import re import discord -from discord.ext.commands import Cog +from discord.ext.commands import Bot, Cog log = logging.getLogger(__name__) @@ -20,11 +20,11 @@ SPOOKY_TRIGGERS = { class SpookyReact(Cog): """A cog that makes the bot react to message triggers.""" - def __init__(self, bot): + def __init__(self, bot: Bot): self.bot = bot @Cog.listener() - async def on_message(self, ctx: discord.Message): + async def on_message(self, ctx: discord.Message) -> None: """ A command to send the seasonalbot github project. @@ -66,7 +66,7 @@ class SpookyReact(Cog): return False -def setup(bot): +def setup(bot: Bot) -> None: """Spooky reaction Cog load.""" bot.add_cog(SpookyReact(bot)) log.info("SpookyReact cog loaded") diff --git a/bot/seasons/halloween/spookysound.py b/bot/seasons/halloween/spookysound.py index 44fdd9d6..e0676d0a 100644 --- a/bot/seasons/halloween/spookysound.py +++ b/bot/seasons/halloween/spookysound.py @@ -13,14 +13,14 @@ log = logging.getLogger(__name__) class SpookySound(commands.Cog): """A cog that plays a spooky sound in a voice channel on command.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.sound_files = list(Path("bot/resources/halloween/spookysounds").glob("*.mp3")) self.channel = None @commands.cooldown(rate=1, per=1) @commands.command(brief="Play a spooky sound, restricted to once per 2 mins") - async def spookysound(self, ctx): + async def spookysound(self, ctx: commands.Context) -> None: """ Connect to the Hacktoberbot voice channel, play a random spooky sound, then disconnect. @@ -37,12 +37,12 @@ class SpookySound(commands.Cog): voice.play(src, after=lambda e: self.bot.loop.create_task(self.disconnect(voice))) @staticmethod - async def disconnect(voice): + async def disconnect(voice: discord.VoiceClient) -> None: """Helper method to disconnect a given voice client.""" await voice.disconnect() -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky sound Cog load.""" bot.add_cog(SpookySound(bot)) log.info("SpookySound cog loaded") diff --git a/bot/seasons/halloween/timeleft.py b/bot/seasons/halloween/timeleft.py index a2b16a6c..77767baa 100644 --- a/bot/seasons/halloween/timeleft.py +++ b/bot/seasons/halloween/timeleft.py @@ -1,5 +1,6 @@ import logging from datetime import datetime +from typing import Tuple from discord.ext import commands @@ -9,16 +10,16 @@ log = logging.getLogger(__name__) class TimeLeft(commands.Cog): """A Cog that tells you how long left until Hacktober is over!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def in_october(): + def in_october() -> bool: """Return True if the current month is October.""" return datetime.utcnow().month == 10 @staticmethod - def load_date(): + def load_date() -> Tuple[int, 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 @@ -29,7 +30,7 @@ class TimeLeft(commands.Cog): return now, end, start @commands.command() - async def timeleft(self, ctx): + async def timeleft(self, ctx: commands.Context) -> None: """ Calculates the time left until the end of Hacktober. @@ -53,7 +54,7 @@ class TimeLeft(commands.Cog): ) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(TimeLeft(bot)) log.info("TimeLeft cog loaded") diff --git a/bot/seasons/pride/pride_anthem.py b/bot/seasons/pride/pride_anthem.py index f226f4bb..b0c6d34e 100644 --- a/bot/seasons/pride/pride_anthem.py +++ b/bot/seasons/pride/pride_anthem.py @@ -11,7 +11,7 @@ log = logging.getLogger(__name__) class PrideAnthem(commands.Cog): """Embed a random youtube video for a gay anthem!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.anthems = self.load_vids() @@ -39,7 +39,7 @@ class PrideAnthem(commands.Cog): return anthems @commands.command(name="prideanthem", aliases=["anthem", "pridesong"]) - async def prideanthem(self, ctx, genre: str = None): + async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None: """ Sends a message with a video of a random pride anthem. @@ -52,7 +52,7 @@ class PrideAnthem(commands.Cog): await ctx.send("I couldn't find a video, sorry!") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog loader for pride anthem.""" bot.add_cog(PrideAnthem(bot)) log.info("Pride anthems cog loaded!") diff --git a/bot/seasons/pride/pride_avatar.py b/bot/seasons/pride/pride_avatar.py index a5b38d20..85e49d5c 100644 --- a/bot/seasons/pride/pride_avatar.py +++ b/bot/seasons/pride/pride_avatar.py @@ -56,11 +56,11 @@ OPTIONS = { class PrideAvatar(commands.Cog): """Put an LGBT spin on your avatar!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def crop_avatar(avatar): + def crop_avatar(avatar: Image) -> Image: """This crops the avatar into a circle.""" mask = Image.new("L", avatar.size, 0) draw = ImageDraw.Draw(mask) @@ -69,7 +69,7 @@ class PrideAvatar(commands.Cog): return avatar @staticmethod - def crop_ring(ring, px): + def crop_ring(ring: Image, px: int) -> Image: """This crops the ring into a circle.""" mask = Image.new("L", ring.size, 0) draw = ImageDraw.Draw(mask) @@ -79,7 +79,7 @@ class PrideAvatar(commands.Cog): return ring @commands.group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True) - async def prideavatar(self, ctx, option="lgbt", pixels: int = 64): + async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: """ This surrounds an avatar with a border of a specified LGBT flag. @@ -126,7 +126,7 @@ class PrideAvatar(commands.Cog): await ctx.send(file=file, embed=embed) @prideavatar.command() - async def flags(self, ctx): + async def flags(self, ctx: commands.Context) -> None: """This lists the flags that can be used with the prideavatar command.""" choices = sorted(set(OPTIONS.values())) options = "• " + "\n• ".join(choices) @@ -139,7 +139,7 @@ class PrideAvatar(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(PrideAvatar(bot)) log.info("PrideAvatar cog loaded") diff --git a/bot/seasons/season.py b/bot/seasons/season.py index c88ef2a7..4e2141c7 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -264,7 +264,7 @@ class SeasonBase: return await self.apply_server_icon() - async def announce_season(self): + async def announce_season(self) -> None: """ Announces a change in season in the announcement channel. @@ -320,7 +320,7 @@ class SeasonBase: await channel.send(mention, embed=embed) - async def load(self): + async def load(self) -> None: """ Loads extensions, bot name and avatar, server icon and announces new season. @@ -361,7 +361,7 @@ class SeasonBase: class SeasonManager(commands.Cog): """A cog for managing seasons.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.season = get_season(date=datetime.datetime.utcnow()) self.season_task = bot.loop.create_task(self.load_seasons()) @@ -378,7 +378,7 @@ class SeasonManager(commands.Cog): ) self.sleep_time = (midnight - datetime.datetime.now()).seconds + 60 - async def load_seasons(self): + async def load_seasons(self) -> None: """Asynchronous timer loop to check for a new season every midnight.""" await self.bot.wait_until_ready() await self.season.load() @@ -397,7 +397,7 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command(name="season") - async def change_season(self, ctx, new_season: str): + async def change_season(self, ctx: commands.Context, new_season: str) -> None: """Changes the currently active season on the bot.""" self.season = get_season(season_name=new_season) await self.season.load() @@ -405,10 +405,10 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command(name="seasons") - async def show_seasons(self, ctx): + async def show_seasons(self, ctx: commands.Context) -> None: """Shows the available seasons and their dates.""" # Sort by start order, followed by lower duration - def season_key(season_class: Type[SeasonBase]): + def season_key(season_class: Type[SeasonBase]) -> Tuple[datetime.datetime, datetime.timedelta]: return season_class.start(), season_class.end() - datetime.datetime.max current_season = self.season.name @@ -448,13 +448,13 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.group() - async def refresh(self, ctx): + async def refresh(self, ctx: commands.Context) -> None: """Refreshes certain seasonal elements without reloading seasons.""" if not ctx.invoked_subcommand: await ctx.send_help(ctx.command) @refresh.command(name="avatar") - async def refresh_avatar(self, ctx): + async def refresh_avatar(self, ctx: commands.Context) -> None: """Re-applies the bot avatar for the currently loaded season.""" # Attempt the change is_changed = await self.season.apply_avatar() @@ -477,7 +477,7 @@ class SeasonManager(commands.Cog): await ctx.send(embed=embed) @refresh.command(name="icon") - async def refresh_server_icon(self, ctx): + async def refresh_server_icon(self, ctx: commands.Context) -> None: """Re-applies the server icon for the currently loaded season.""" # Attempt the change is_changed = await self.season.apply_server_icon() @@ -500,7 +500,7 @@ class SeasonManager(commands.Cog): await ctx.send(embed=embed) @refresh.command(name="username", aliases=("name",)) - async def refresh_username(self, ctx): + async def refresh_username(self, ctx: commands.Context) -> None: """Re-applies the bot username for the currently loaded season.""" old_username = str(bot.user) old_display_name = ctx.guild.me.display_name @@ -539,10 +539,10 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command() - async def announce(self, ctx): + async def announce(self, ctx: commands.Context) -> None: """Announces the currently loaded season.""" await self.season.announce_season() - def cog_unload(self): + def cog_unload(self) -> None: """Cancel season-related tasks on cog unload.""" self.season_task.cancel() diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index c4acf17a..a073e1bd 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -1,8 +1,8 @@ import logging import random -import typing from json import load from pathlib import Path +from typing import Optional, Tuple import discord from discord.ext import commands @@ -18,12 +18,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class BeMyValentine(commands.Cog): """A cog that sends Valentines to other users!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.valentines = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load Valentines messages from the static resources.""" p = Path("bot/resources/valentines/bemyvalentine_valentines.json") with p.open() as json_data: @@ -31,7 +31,7 @@ class BeMyValentine(commands.Cog): return valentines @commands.group(name="lovefest", invoke_without_command=True) - async def lovefest_role(self, ctx): + async def lovefest_role(self, ctx: commands.Context) -> None: """ Subscribe or unsubscribe from the lovefest role. @@ -43,7 +43,7 @@ class BeMyValentine(commands.Cog): await ctx.send_help(ctx.command) @lovefest_role.command(name="sub") - async def add_role(self, ctx): + async def add_role(self, ctx: commands.Context) -> None: """Adds the lovefest role.""" user = ctx.author role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) @@ -54,7 +54,7 @@ class BeMyValentine(commands.Cog): await ctx.send("You already have the role !") @lovefest_role.command(name="unsub") - async def remove_role(self, ctx): + async def remove_role(self, ctx: commands.Context) -> None: """Removes the lovefest role.""" user = ctx.author role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) @@ -66,7 +66,9 @@ class BeMyValentine(commands.Cog): @commands.cooldown(1, 1800, BucketType.user) @commands.group(name='bemyvalentine', invoke_without_command=True) - async def send_valentine(self, ctx, user: typing.Optional[discord.Member] = None, *, valentine_type=None): + async def send_valentine( + self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None + ) -> None: """ Send a valentine to user, if specified, or to a random user with the lovefest role. @@ -112,7 +114,9 @@ class BeMyValentine(commands.Cog): @commands.cooldown(1, 1800, BucketType.user) @send_valentine.command(name='secret') - async def anonymous(self, ctx, user: typing.Optional[discord.Member] = None, *, valentine_type=None): + async def anonymous( + self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None + ) -> None: """ Send an anonymous Valentine via DM to to a user, if specified, or to a random with the lovefest role. @@ -164,7 +168,7 @@ class BeMyValentine(commands.Cog): else: await ctx.author.send(f"Your message has been sent to {user}") - def valentine_check(self, valentine_type): + 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: valentine, title = self.random_valentine() @@ -184,7 +188,7 @@ class BeMyValentine(commands.Cog): return valentine, title @staticmethod - def random_user(author: discord.Member, members: discord.Member): + def random_user(author: discord.Member, members: discord.Member) -> None: """ Picks a random member from the list provided in `members`. @@ -196,13 +200,13 @@ class BeMyValentine(commands.Cog): return random.choice(members) if members else None @staticmethod - def random_emoji(): + 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): + 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']) @@ -213,18 +217,18 @@ class BeMyValentine(commands.Cog): title = 'A compliment for ' return random_valentine, title - def valentine_poem(self): + def valentine_poem(self) -> str: """Grabs a random poem.""" valentine_poem = random.choice(self.valentines['valentine_poems']) return valentine_poem - def valentine_compliment(self): + def valentine_compliment(self) -> str: """Grabs a random compliment.""" valentine_compliment = random.choice(self.valentines['valentine_compliments']) return valentine_compliment -def setup(bot): +def setup(bot: commands.Bot) -> None: """Be my Valentine Cog load.""" bot.add_cog(BeMyValentine(bot)) log.info("BeMyValentine cog loaded") diff --git a/bot/seasons/valentines/lovecalculator.py b/bot/seasons/valentines/lovecalculator.py index 1d5a028d..207ef557 100644 --- a/bot/seasons/valentines/lovecalculator.py +++ b/bot/seasons/valentines/lovecalculator.py @@ -23,12 +23,12 @@ with Path("bot/resources/valentines/love_matches.json").open() as file: class LoveCalculator(Cog): """A cog for calculating the love between two people.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('love_calculator', 'love_calc')) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) - async def love(self, ctx, who: Union[Member, str], whom: Union[Member, str] = None): + async def love(self, ctx: commands.Context, who: Union[Member, str], whom: Union[Member, str] = None) -> None: """ Tells you how much the two love each other. @@ -98,7 +98,7 @@ class LoveCalculator(Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Love calculator Cog load.""" bot.add_cog(LoveCalculator(bot)) log.info("LoveCalculator cog loaded") diff --git a/bot/seasons/valentines/movie_generator.py b/bot/seasons/valentines/movie_generator.py index fa5f236a..ce1d7d5b 100644 --- a/bot/seasons/valentines/movie_generator.py +++ b/bot/seasons/valentines/movie_generator.py @@ -14,11 +14,11 @@ log = logging.getLogger(__name__) class RomanceMovieFinder(commands.Cog): """A Cog that returns a random romance movie suggestion to a user.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="romancemovie") - async def romance_movie(self, ctx): + async def romance_movie(self, ctx: commands.Context) -> None: """Randomly selects a romance movie and displays information about it.""" # Selecting a random int to parse it to the page parameter random_page = random.randint(0, 20) @@ -57,7 +57,7 @@ class RomanceMovieFinder(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Romance movie Cog load.""" bot.add_cog(RomanceMovieFinder(bot)) log.info("RomanceMovieFinder cog loaded") diff --git a/bot/seasons/valentines/myvalenstate.py b/bot/seasons/valentines/myvalenstate.py index fad202e3..0256c39a 100644 --- a/bot/seasons/valentines/myvalenstate.py +++ b/bot/seasons/valentines/myvalenstate.py @@ -18,10 +18,10 @@ with open(Path("bot/resources/valentines/valenstates.json"), "r") as file: class MyValenstate(commands.Cog): """A Cog to find your most likely Valentine's vacation destination.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - def levenshtein(self, source, goal): + def levenshtein(self, source: str, goal: str) -> int: """Calculates the Levenshtein Distance between source and goal.""" if len(source) < len(goal): return self.levenshtein(goal, source) @@ -42,7 +42,7 @@ class MyValenstate(commands.Cog): return pre_row[-1] @commands.command() - async def myvalenstate(self, ctx, *, name=None): + async def myvalenstate(self, ctx: commands.Context, *, name: str = None) -> None: """Find the vacation spot(s) with the most matching characters to the invoking user.""" eq_chars = collections.defaultdict(int) if name is None: @@ -81,7 +81,7 @@ class MyValenstate(commands.Cog): await ctx.channel.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Valenstate Cog load.""" bot.add_cog(MyValenstate(bot)) log.info("MyValenstate cog loaded") diff --git a/bot/seasons/valentines/pickuplines.py b/bot/seasons/valentines/pickuplines.py index 46772197..8b2c9822 100644 --- a/bot/seasons/valentines/pickuplines.py +++ b/bot/seasons/valentines/pickuplines.py @@ -17,11 +17,11 @@ with open(Path("bot/resources/valentines/pickup_lines.json"), "r", encoding="utf class PickupLine(commands.Cog): """A cog that gives random cheesy pickup lines.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def pickupline(self, ctx): + async def pickupline(self, ctx: commands.Context) -> None: """ Gives you a random pickup line. @@ -39,7 +39,7 @@ class PickupLine(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Pickup lines Cog load.""" bot.add_cog(PickupLine(bot)) log.info('PickupLine cog loaded') diff --git a/bot/seasons/valentines/savethedate.py b/bot/seasons/valentines/savethedate.py index 34264183..e0bc3904 100644 --- a/bot/seasons/valentines/savethedate.py +++ b/bot/seasons/valentines/savethedate.py @@ -19,11 +19,11 @@ with open(Path("bot/resources/valentines/date_ideas.json"), "r", encoding="utf8" class SaveTheDate(commands.Cog): """A cog that gives random suggestion for a Valentine's date.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def savethedate(self, ctx): + async def savethedate(self, ctx: commands.Context) -> None: """Gives you ideas for what to do on a date with your valentine.""" random_date = random.choice(VALENTINES_DATES['ideas']) emoji_1 = random.choice(HEART_EMOJIS) @@ -36,7 +36,7 @@ class SaveTheDate(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Save the date Cog Load.""" bot.add_cog(SaveTheDate(bot)) log.info("SaveTheDate cog loaded") diff --git a/bot/seasons/valentines/valentine_zodiac.py b/bot/seasons/valentines/valentine_zodiac.py index fa849cb2..c8d77e75 100644 --- a/bot/seasons/valentines/valentine_zodiac.py +++ b/bot/seasons/valentines/valentine_zodiac.py @@ -17,12 +17,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.zodiacs = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load zodiac compatibility from static JSON resource.""" p = Path("bot/resources/valentines/zodiac_compatibility.json") with p.open() as json_data: @@ -30,7 +30,7 @@ class ValentineZodiac(commands.Cog): return zodiacs @commands.command(name="partnerzodiac") - async def counter_zodiac(self, ctx, zodiac_sign): + async def counter_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) @@ -52,7 +52,7 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Valentine zodiac Cog load.""" bot.add_cog(ValentineZodiac(bot)) log.info("ValentineZodiac cog loaded") diff --git a/bot/seasons/valentines/whoisvalentine.py b/bot/seasons/valentines/whoisvalentine.py index d73ccd9b..b8586dca 100644 --- a/bot/seasons/valentines/whoisvalentine.py +++ b/bot/seasons/valentines/whoisvalentine.py @@ -17,11 +17,11 @@ with open(Path("bot/resources/valentines/valentine_facts.json"), "r") as file: class ValentineFacts(commands.Cog): """A Cog for displaying facts about Saint Valentine.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('whoisvalentine', 'saint_valentine')) - async def who_is_valentine(self, ctx): + async def who_is_valentine(self, ctx: commands.Context) -> None: """Displays info about Saint Valentine.""" embed = discord.Embed( title="Who is Saint Valentine?", @@ -36,7 +36,7 @@ class ValentineFacts(commands.Cog): await ctx.channel.send(embed=embed) @commands.command() - async def valentine_fact(self, ctx): + async def valentine_fact(self, ctx: commands.Context) -> None: """Shows a random fact about Valentine's Day.""" embed = discord.Embed( title=choice(FACTS['titles']), @@ -47,7 +47,7 @@ class ValentineFacts(commands.Cog): await ctx.channel.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Who is Valentine Cog load.""" bot.add_cog(ValentineFacts(bot)) log.info("ValentineFacts cog loaded") diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 72a681a3..8732eb22 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -29,7 +29,7 @@ async def disambiguate( choices = (f'{index}: {entry}' for index, entry in enumerate(entries, start=1)) - def check(message): + def check(message: discord.Message) -> bool: return (message.content.isdigit() and message.author == ctx.author and message.channel == ctx.channel) @@ -109,8 +109,8 @@ def replace_many( pattern = "|".join(re.escape(word) for word in words_to_replace) regex = re.compile(pattern, re.I if ignore_case else 0) - def _repl(match): - """Returns replacement depending on `ignore_case` and `match_case`.""" + def _repl(match: re.Match) -> str: + """Returns replacement depending on `ignore_case` and `match_case`""" word = match.group(0) replacement = replacements[word.lower() if ignore_case else word] diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py index 69b49919..11f69850 100644 --- a/bot/utils/halloween/spookifications.py +++ b/bot/utils/halloween/spookifications.py @@ -7,7 +7,7 @@ from PIL import ImageOps log = logging.getLogger() -def inversion(im): +def inversion(im: Image) -> Image: """ Inverts the image. @@ -18,7 +18,7 @@ def inversion(im): return inv -def pentagram(im): +def pentagram(im: Image) -> Image: """Adds pentagram to the image.""" im = im.convert('RGB') wt, ht = im.size @@ -28,7 +28,7 @@ def pentagram(im): return im -def bat(im): +def bat(im: Image) -> Image: """ Adds a bat silhoutte to the image. @@ -50,7 +50,7 @@ def bat(im): return im -def get_random_effect(im): +def get_random_effect(im: Image) -> Image: """Randomly selects and applies an effect.""" effects = [inversion, pentagram, bat] effect = choice(effects) @@ -11,7 +11,9 @@ ignore= # Docstring Quotes D301,D302, # Docstring Content - D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417 + D400,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414 + # Type Annotations + TYP002,TYP003,TYP101,TYP102,TYP204,TYP206 exclude= __pycache__,.cache, venv,.venv, |