diff options
Diffstat (limited to 'bot/seasons')
39 files changed, 246 insertions, 228 deletions
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 a9e72805..5938ccbe 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 ad8b5473..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,7 +33,7 @@ class AvatarEasterifier(commands.Cog):          """          r1, g1, b1 = x -        def distance(point): +        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 76d5c478..b068ac2c 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,7 +46,7 @@ class BunnyNameGenerator(commands.Cog):              if new_name != displayname:                  return new_name -    def append_name(self, displayname): +    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) @@ -54,12 +55,12 @@ class BunnyNameGenerator(commands.Cog):          return appended_name      @commands.command() -    async def bunnyname(self, ctx): +    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): +    async def bunnifyme(self, ctx: commands.Context) -> None:          """Gets your Discord username and bunnifies it"""          username = ctx.message.author.display_name @@ -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 56555586..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,7 +83,7 @@ class EasterRiddle(commands.Cog):          self.current_channel = None      @commands.Cog.listener() -    async def on_message(self, message): +    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 3e0cc598..f479504c 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,13 +96,13 @@ class EggheadQuiz(commands.Cog):          await ctx.send(content, embed=a_embed)      @staticmethod -    async def already_reacted(message, user): +    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): +    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 @@ -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 f04b8828..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): +    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 cb859ea9..9dadb9f0 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -32,7 +32,7 @@ log = logging.getLogger(__name__)  class CoordinateConverter(commands.Converter):      """Converter for Coordinates.""" -    async def convert(self, ctx, coordinate: str) -> typing.Tuple[int, int]: +    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,7 +80,7 @@ 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): +    async def minesweeper_group(self, ctx: commands.Context) -> None:          """Commands for Playing Minesweeper"""          await ctx.send_help(ctx.command) @@ -215,7 +215,7 @@ 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: +    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" @@ -267,7 +267,7 @@ class Minesweeper(commands.Cog):              await self.update_boards(ctx)      @minesweeper_group.command(name="end") -    async def end_command(self, ctx: commands.Context): +    async def end_command(self, ctx: commands.Context) -> None:          """End your current game"""          game = self.games[ctx.author.id]          game.revealed = game.board diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index 37809b33..2804bdbe 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,12 +11,12 @@ 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): +    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 @@ -27,7 +28,7 @@ class ShowProjects(commands.Cog):              self.lastPoster = message.author.id -def setup(bot): +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 e8d2ee44..24e71227 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  SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() @@ -116,12 +116,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) @@ -138,7 +138,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. @@ -152,7 +152,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 @@ -166,7 +166,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. @@ -186,7 +186,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( @@ -234,7 +234,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. @@ -367,7 +367,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 @@ -382,14 +382,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(( @@ -454,7 +454,7 @@ class SnakeAndLaddersGame:                  self.cancel_game(self.author)                  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 @@ -462,7 +462,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. @@ -488,7 +488,7 @@ class SnakeAndLaddersGame:              delete_after=10          ) -    async def player_leave(self, user: Member): +    async def player_leave(self, user: Member) -> None:          """          Handle players leaving the game. @@ -518,7 +518,7 @@ class SnakeAndLaddersGame:                  return          await self.channel.send(user.mention + " You are not in the match.", delete_after=10) -    async def cancel_game(self, user: Member): +    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) @@ -526,7 +526,7 @@ class SnakeAndLaddersGame:          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. @@ -550,9 +550,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(( @@ -642,7 +642,7 @@ class SnakeAndLaddersGame:          # Round completed          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) @@ -674,7 +674,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 @@ -694,13 +694,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 f6a43a63..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): +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 d35cbee5..65fa9af8 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 = max(message.id for message @@ -135,7 +136,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) @@ -151,11 +152,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(): @@ -164,20 +165,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) @@ -213,7 +214,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 173ce8eb..cfd3edaf 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=('ms',)      ) -    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 08c17a27..02e6f6b9 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/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()  |