diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/seasons/christmas/adventofcode.py | 2 | ||||
| -rw-r--r-- | bot/seasons/easter/avatar_easterifier.py | 2 | ||||
| -rw-r--r-- | bot/seasons/easter/bunny_name_generator.py | 10 | ||||
| -rw-r--r-- | bot/seasons/easter/easter_riddle.py | 2 | ||||
| -rw-r--r-- | bot/seasons/easter/egghead_quiz.py | 6 | ||||
| -rw-r--r-- | bot/seasons/easter/traditions.py | 2 | ||||
| -rw-r--r-- | bot/seasons/evergreen/8bitify.py | 6 | ||||
| -rw-r--r-- | bot/seasons/evergreen/minesweeper.py | 18 | ||||
| -rw-r--r-- | bot/seasons/evergreen/showprojects.py | 6 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/utils.py | 76 | ||||
| -rw-r--r-- | bot/seasons/evergreen/speedrun.py | 2 | ||||
| -rw-r--r-- | bot/seasons/halloween/candy_collection.py | 11 | ||||
| -rw-r--r-- | bot/seasons/halloween/hacktoberstats.py | 49 | ||||
| -rw-r--r-- | bot/seasons/halloween/monstersurvey.py | 2 | ||||
| -rw-r--r-- | bot/seasons/halloween/spookyrating.py | 2 | ||||
| -rw-r--r-- | bot/utils/__init__.py | 2 | ||||
| -rw-r--r-- | tox.ini | 5 | 
17 files changed, 103 insertions, 100 deletions
| diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index a9e72805..d2894ec4 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -359,7 +359,7 @@ class AdventOfCode(commands.Cog):              )      async def _check_n_entries(self, ctx: commands.Context, number_of_people_to_display: int) -> int: -        """Check for n > max_entries and n <= 0""" +        """Check for n > max_entries and n <= 0."""          max_entries = AocConfig.leaderboard_max_displayed_members          author = ctx.message.author          if not 0 <= number_of_people_to_display <= max_entries: diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index ad8b5473..98e15982 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -34,7 +34,7 @@ class AvatarEasterifier(commands.Cog):          r1, g1, b1 = x          def distance(point): -            """Finds the difference between a pastel colour and the original pixel colour""" +            """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) diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 76d5c478..3ceaeb9e 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -28,8 +28,8 @@ class BunnyNameGenerator(commands.Cog):          """          Finds vowels in the user's display name. -        If the Discord name contains a vowel and the letter y, -        it will match one or more of these patterns. +        If the Discord name contains a vowel and the letter y, it will match one or more of these patterns. +          Only the most recently matched pattern will apply the changes.          """          expressions = [ @@ -46,7 +46,7 @@ class BunnyNameGenerator(commands.Cog):                  return new_name      def append_name(self, displayname): -        """Adds a suffix to the end of the Discord name""" +        """Adds a suffix to the end of the Discord name."""          extensions = ['foot', 'ear', 'nose', 'tail']          suffix = random.choice(extensions)          appended_name = displayname + suffix @@ -55,12 +55,12 @@ class BunnyNameGenerator(commands.Cog):      @commands.command()      async def bunnyname(self, ctx): -        """Picks a random bunny name from a JSON file""" +        """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""" +        """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 diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 56555586..b612f8b9 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -84,7 +84,7 @@ class EasterRiddle(commands.Cog):      @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""" +        """If a non-bot user enters a correct answer, their username gets added to self.winners."""          if self.current_channel != message.channel:              return diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py index 3e0cc598..b3841993 100644 --- a/bot/seasons/easter/egghead_quiz.py +++ b/bot/seasons/easter/egghead_quiz.py @@ -37,7 +37,7 @@ class EggheadQuiz(commands.Cog):      @commands.command(aliases=["eggheadquiz", "easterquiz"])      async def eggquiz(self, ctx):          """ -        Gives a random quiz question, waits 30 seconds and then outputs the answer +        Gives a random quiz question, waits 30 seconds and then outputs the answer.          Also informs of the percentages and votes of each option          """ @@ -96,13 +96,13 @@ class EggheadQuiz(commands.Cog):      @staticmethod      async def already_reacted(message, user): -        """Returns whether a given user has reacted more than once to a given message""" +        """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""" +        """Listener to listen specifically for reactions of quiz messages."""          if user.bot:              return          if reaction.message.id not in self.quiz_messages: diff --git a/bot/seasons/easter/traditions.py b/bot/seasons/easter/traditions.py index f04b8828..b0bf04d7 100644 --- a/bot/seasons/easter/traditions.py +++ b/bot/seasons/easter/traditions.py @@ -19,7 +19,7 @@ class Traditions(commands.Cog):      @commands.command(aliases=('eastercustoms',))      async def easter_tradition(self, ctx): -        """Responds with a random tradition or custom""" +        """Responds with a random tradition or custom."""          random_country = random.choice(list(traditions))          await ctx.send(f"{random_country}:\n{traditions[random_country]}") diff --git a/bot/seasons/evergreen/8bitify.py b/bot/seasons/evergreen/8bitify.py index 54db71db..60062fc1 100644 --- a/bot/seasons/evergreen/8bitify.py +++ b/bot/seasons/evergreen/8bitify.py @@ -13,17 +13,17 @@ class EightBitify(commands.Cog):      @staticmethod      def pixelate(image: Image) -> Image: -        """Takes an image and pixelates it""" +        """Takes an image and pixelates it."""          return image.resize((32, 32)).resize((1024, 1024))      @staticmethod      def quantize(image: Image) -> Image: -        """Reduces colour palette to 256 colours""" +        """Reduces colour palette to 256 colours."""          return image.quantize(colors=32)      @commands.command(name="8bitify")      async def eightbit_command(self, ctx: commands.Context) -> None: -        """Pixelates your avatar and changes the palette to an 8bit one""" +        """Pixelates your avatar and changes the palette to an 8bit one."""          async with ctx.typing():              image_bytes = await ctx.author.avatar_url.read()              avatar = Image.open(BytesIO(image_bytes)) diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index cb859ea9..3eee92ca 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -33,7 +33,7 @@ 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 x, y""" +        """Take in a coordinate string and turn it into an (x, y) tuple."""          if not 2 <= len(coordinate) <= 3:              raise commands.BadArgument('Invalid co-ordinate provided') @@ -81,7 +81,7 @@ class Minesweeper(commands.Cog):      @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True)      async def minesweeper_group(self, ctx: commands.Context): -        """Commands for Playing Minesweeper""" +        """Commands for Playing Minesweeper."""          await ctx.send_help(ctx.command)      @staticmethod @@ -175,7 +175,7 @@ class Minesweeper(commands.Cog):      @commands.dm_only()      @minesweeper_group.command(name="flag")      async def flag_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: -        """Place multiple flags on the board""" +        """Place multiple flags on the board."""          board: GameBoard = self.games[ctx.author.id].revealed          for x, y in coordinates:              if board[y][x] == "hidden": @@ -185,14 +185,14 @@ class Minesweeper(commands.Cog):      @staticmethod      def reveal_bombs(revealed: GameBoard, board: GameBoard) -> None: -        """Reveals all the bombs""" +        """Reveals all the bombs."""          for y, row in enumerate(board):              for x, cell in enumerate(row):                  if cell == "bomb":                      revealed[y][x] = cell      async def lost(self, ctx: commands.Context) -> None: -        """The player lost the game""" +        """The player lost the game."""          game = self.games[ctx.author.id]          self.reveal_bombs(game.revealed, game.board)          await ctx.author.send(":fire: You lost! :fire:") @@ -200,7 +200,7 @@ class Minesweeper(commands.Cog):              await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:")      async def won(self, ctx: commands.Context) -> None: -        """The player won the game""" +        """The player won the game."""          game = self.games[ctx.author.id]          await ctx.author.send(":tada: You won! :tada:")          if game.activated_on_server: @@ -216,7 +216,7 @@ class Minesweeper(commands.Cog):                  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""" +        """Checks if a player has won."""          if any(              revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb"              for x in range(10) @@ -252,7 +252,7 @@ class Minesweeper(commands.Cog):      @commands.dm_only()      @minesweeper_group.command(name="reveal")      async def reveal_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: -        """Reveal multiple cells""" +        """Reveal multiple cells."""          game = self.games[ctx.author.id]          revealed: GameBoard = game.revealed          board: GameBoard = game.board @@ -268,7 +268,7 @@ class Minesweeper(commands.Cog):      @minesweeper_group.command(name="end")      async def end_command(self, ctx: commands.Context): -        """End your current game""" +        """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 37809b33..5dea78a5 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -8,7 +8,7 @@ log = logging.getLogger(__name__)  class ShowProjects(commands.Cog): -    """Cog that reacts to posts in the #show-your-projects""" +    """Cog that reacts to posts in the #show-your-projects."""      def __init__(self, bot):          self.bot = bot @@ -16,7 +16,7 @@ class ShowProjects(commands.Cog):      @commands.Cog.listener()      async def on_message(self, message): -        """Adds reactions to posts in #show-your-projects""" +        """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 @@ -28,6 +28,6 @@ class ShowProjects(commands.Cog):  def setup(bot): -    """Show Projects Reaction Cog""" +    """Show Projects Reaction Cog."""      bot.add_cog(ShowProjects(bot))      log.info("ShowProjects cog loaded") diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index e8d2ee44..b1d5048a 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -13,6 +13,8 @@ from PIL.ImageDraw import ImageDraw  from discord import File, Member, Reaction  from discord.ext.commands import Context +from bot.constants import Roles +  SNAKE_RESOURCES = Path("bot/resources/snakes").absolute()  h1 = r'''``` @@ -412,7 +414,6 @@ class SnakeAndLaddersGame:              "**Snakes and Ladders**: A new game is about to start!",              file=File(                  str(SNAKE_RESOURCES / "snakes_and_ladders" / "banner.jpg"), -                # os.path.join("bot", "resources", "snakes", "snakes_and_ladders", "banner.jpg"),                  filename='Snakes and Ladders.jpg'              )          ) @@ -435,8 +436,9 @@ class SnakeAndLaddersGame:                  if reaction.emoji == JOIN_EMOJI:                      await self.player_join(user)                  elif reaction.emoji == CANCEL_EMOJI: -                    if self.ctx.author == user: -                        await self.cancel_game(user) +                    if user == self.author or (self._is_moderator(user) and user not in self.players): +                        # Allow game author or non-playing moderation staff to cancel a waiting game +                        await self.cancel_game()                          return                      else:                          await self.player_leave(user) @@ -451,7 +453,7 @@ class SnakeAndLaddersGame:              except asyncio.TimeoutError:                  log.debug("Snakes and Ladders timed out waiting for a reaction") -                self.cancel_game(self.author) +                await self.cancel_game()                  return  # We're done, no reactions for the last 5 minutes      async def _add_player(self, user: Member): @@ -488,20 +490,16 @@ class SnakeAndLaddersGame:              delete_after=10          ) -    async def player_leave(self, user: Member): +    async def player_leave(self, user: Member) -> bool:          """          Handle players leaving the game. -        Leaving is prevented if the user initiated the game or if they weren't part of it in the -        first place. +        Leaving is prevented if the user wasn't part of the game. + +        If the number of players reaches 0, the game is terminated. In this case, a sentinel boolean +        is returned True to prevent a game from continuing after it's destroyed.          """ -        if user == self.author: -            await self.channel.send( -                user.mention + " You are the author, and cannot leave the game. Execute " -                "`sal cancel` to cancel the game.", -                delete_after=10 -            ) -            return +        is_surrendered = False  # Sentinel value to assist with stopping a surrendered game          for p in self.players:              if user == p:                  self.players.remove(p) @@ -512,17 +510,18 @@ class SnakeAndLaddersGame:                      delete_after=10                  ) -                if self.state != 'waiting' and len(self.players) == 1: +                if self.state != 'waiting' and len(self.players) == 0:                      await self.channel.send("**Snakes and Ladders**: The game has been surrendered!") +                    is_surrendered = True                      self._destruct() -                return -        await self.channel.send(user.mention + " You are not in the match.", delete_after=10) -    async def cancel_game(self, user: Member): -        """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 +                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."""          await self.channel.send("**Snakes and Ladders**: Game has been canceled.")          self._destruct() @@ -530,21 +529,16 @@ class SnakeAndLaddersGame:          """          Allow the game author to begin the game. -        The game cannot be started if there aren't enough players joined or if the game is in a -        waiting state. +        The game cannot be started if the game is in a waiting state.          """          if not user == self.author:              await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10)              return -        if len(self.players) < 1: -            await self.channel.send( -                user.mention + " A minimum of 2 players is required to start the game.", -                delete_after=10 -            ) -            return +          if not self.state == 'waiting':              await self.channel.send(user.mention + " The game cannot be started at this time.", delete_after=10)              return +          self.state = 'starting'          player_list = ', '.join(user.mention for user in self.players)          await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) @@ -565,8 +559,6 @@ class SnakeAndLaddersGame:          self.state = 'roll'          for user in self.players:              self.round_has_rolled[user.id] = False -        # board_img = Image.open(os.path.join( -        #     "bot", "resources", "snakes", "snakes_and_ladders", "board.jpg"))          board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg"))          player_row_size = math.ceil(MAX_PLAYERS / 2) @@ -612,6 +604,7 @@ class SnakeAndLaddersGame:          for emoji in GAME_SCREEN_EMOJI:              await self.positions.add_reaction(emoji) +        is_surrendered = False          while True:              try:                  reaction, user = await self.ctx.bot.wait_for( @@ -623,11 +616,12 @@ class SnakeAndLaddersGame:                  if reaction.emoji == ROLL_EMOJI:                      await self.player_roll(user)                  elif reaction.emoji == CANCEL_EMOJI: -                    if self.ctx.author == user: -                        await self.cancel_game(user) +                    if self._is_moderator(user) and user not in self.players: +                        # Only allow non-playing moderation staff to cancel a running game +                        await self.cancel_game()                          return                      else: -                        await self.player_leave(user) +                        is_surrendered = await self.player_leave(user)                  await self.positions.remove_reaction(reaction.emoji, user) @@ -636,11 +630,14 @@ class SnakeAndLaddersGame:              except asyncio.TimeoutError:                  log.debug("Snakes and Ladders timed out waiting for a reaction") -                await self.cancel_game(self.author) +                await self.cancel_game()                  return  # We're done, no reactions for the last 5 minutes          # Round completed -        await self._complete_round() +        # Check to see if the game was surrendered before completing the round, without this +        # sentinel, the game object would be deleted but the next round still posted into purgatory +        if not is_surrendered: +            await self._complete_round()      async def player_roll(self, user: Member):          """Handle the player's roll.""" @@ -708,3 +705,8 @@ class SnakeAndLaddersGame:          if is_reversed:              x_level = 9 - x_level          return x_level, y_level + +    @staticmethod +    def _is_moderator(user: Member) -> bool: +        """Return True if the user is a Moderator.""" +        return any(Roles.moderator == role.id for role in user.roles) diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index f6a43a63..5e3d38a0 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -23,6 +23,6 @@ class Speedrun(commands.Cog):  def setup(bot): -    """Load the Speedrun cog""" +    """Load the Speedrun cog."""      bot.add_cog(Speedrun(bot))      log.info("Speedrun cog loaded") diff --git a/bot/seasons/halloween/candy_collection.py b/bot/seasons/halloween/candy_collection.py index d35cbee5..a7232e0d 100644 --- a/bot/seasons/halloween/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -121,15 +121,16 @@ class CandyCollection(commands.Cog):      async def ten_recent_msg(self):          """Get the last 10 messages sent in the channel."""          ten_recent = [] -        recent_msg = max(message.id for message -                         in self.bot._connection._messages -                         if message.channel.id == Channels.seasonalbot_chat) +        recent_msg_id = max( +            message.id for message in self.bot._connection._messages +            if message.channel.id == Channels.seasonalbot_chat +        )          channel = await self.hacktober_channel() -        ten_recent.append(recent_msg.id) +        ten_recent.append(recent_msg_id)          for i in range(9): -            o = discord.Object(id=recent_msg.id + i) +            o = discord.Object(id=recent_msg_id + i)              msg = await next(channel.history(limit=1, before=o))              ten_recent.append(msg.id) diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index b6b5a900..5687a5c7 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -1,10 +1,10 @@  import json  import logging  import re -import typing  from collections import Counter  from datetime import datetime  from pathlib import Path +from typing import List, Tuple  import aiohttp  import discord @@ -12,6 +12,9 @@ from discord.ext import commands  log = logging.getLogger(__name__) +CURRENT_YEAR = datetime.now().year  # Used to construct GH API query +PRS_FOR_SHIRT = 4  # Minimum number of PRs before a shirt is awarded +  class HacktoberStats(commands.Cog):      """Hacktoberfest statistics Cog.""" @@ -21,12 +24,8 @@ class HacktoberStats(commands.Cog):          self.link_json = Path("bot/resources/github_links.json")          self.linked_accounts = self.load_linked_users() -    @commands.group( -        name='hacktoberstats', -        aliases=('hackstats',), -        invoke_without_command=True -    ) -    async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None): +    @commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True) +    async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None) -> None:          """          Display an embed for a user's Hacktoberfest contributions. @@ -43,8 +42,8 @@ class HacktoberStats(commands.Cog):              else:                  msg = (                      f"{author_mention}, you have not linked a GitHub account\n\n" -                    f"You can link your GitHub account using:\n```{ctx.prefix}stats link github_username```\n" -                    f"Or query GitHub stats directly using:\n```{ctx.prefix}stats github_username```" +                    f"You can link your GitHub account using:\n```{ctx.prefix}hackstats link github_username```\n" +                    f"Or query GitHub stats directly using:\n```{ctx.prefix}hackstats github_username```"                  )                  await ctx.send(msg)                  return @@ -52,7 +51,7 @@ class HacktoberStats(commands.Cog):          await self.get_stats(ctx, github_username)      @hacktoberstats_group.command(name="link") -    async def link_user(self, ctx: commands.Context, github_username: str = None): +    async def link_user(self, ctx: commands.Context, github_username: str = None) -> None:          """          Link the invoking user's Github github_username to their Discord ID. @@ -85,7 +84,7 @@ class HacktoberStats(commands.Cog):              await ctx.send(f"{author_mention}, a GitHub username is required to link your account")      @hacktoberstats_group.command(name="unlink") -    async def unlink_user(self, ctx: commands.Context): +    async def unlink_user(self, ctx: commands.Context) -> None:          """Remove the invoking user's account link from the log."""          author_id, author_mention = HacktoberStats._author_mention_from_context(ctx) @@ -99,7 +98,7 @@ class HacktoberStats(commands.Cog):          self.save_linked_users() -    def load_linked_users(self) -> typing.Dict: +    def load_linked_users(self) -> dict:          """          Load list of linked users from local JSON file. @@ -122,7 +121,7 @@ class HacktoberStats(commands.Cog):              logging.info(f"Linked account log: '{self.link_json}' does not exist")              return {} -    def save_linked_users(self): +    def save_linked_users(self) -> None:          """          Save list of linked users to local JSON file. @@ -139,7 +138,7 @@ class HacktoberStats(commands.Cog):              json.dump(self.linked_accounts, fID, default=str)          logging.info(f"linked_accounts saved to '{self.link_json}'") -    async def get_stats(self, ctx: commands.Context, github_username: str): +    async def get_stats(self, ctx: commands.Context, github_username: str) -> None:          """          Query GitHub's API for PRs created by a GitHub user during the month of October. @@ -158,18 +157,18 @@ class HacktoberStats(commands.Cog):              else:                  await ctx.send(f"No October GitHub contributions found for '{github_username}'") -    def build_embed(self, github_username: str, prs: typing.List[dict]) -> discord.Embed: +    def build_embed(self, github_username: str, prs: List[dict]) -> discord.Embed:          """Return a stats embed built from github_username's PRs."""          logging.info(f"Building Hacktoberfest embed for GitHub user: '{github_username}'")          pr_stats = self._summarize_prs(prs)          n = pr_stats['n_prs'] -        if n >= 5: +        if n >= PRS_FOR_SHIRT:              shirtstr = f"**{github_username} has earned a tshirt!**" -        elif n == 4: +        elif n == PRS_FOR_SHIRT - 1:              shirtstr = f"**{github_username} is 1 PR away from a tshirt!**"          else: -            shirtstr = f"**{github_username} is {5 - n} PRs away from a tshirt!**" +            shirtstr = f"**{github_username} is {PRS_FOR_SHIRT - n} PRs away from a tshirt!**"          stats_embed = discord.Embed(              title=f"{github_username}'s Hacktoberfest", @@ -186,7 +185,7 @@ class HacktoberStats(commands.Cog):          stats_embed.set_author(              name="Hacktoberfest",              url="https://hacktoberfest.digitalocean.com", -            icon_url="https://hacktoberfest.digitalocean.com/assets/logo-hacktoberfest.png" +            icon_url="https://hacktoberfest.digitalocean.com/pretty_logo.png"          )          stats_embed.add_field(              name="Top 5 Repositories:", @@ -197,7 +196,7 @@ class HacktoberStats(commands.Cog):          return stats_embed      @staticmethod -    async def get_october_prs(github_username: str) -> typing.List[dict]: +    async def get_october_prs(github_username: str) -> List[dict]:          """          Query GitHub's API for PRs created during the month of October by github_username. @@ -219,7 +218,7 @@ class HacktoberStats(commands.Cog):          not_label = "invalid"          action_type = "pr"          is_query = f"public+author:{github_username}" -        date_range = "2018-10-01..2018-10-31" +        date_range = f"{CURRENT_YEAR}-10-01..{CURRENT_YEAR}-10-31"          per_page = "300"          query_url = (              f"{base_url}" @@ -274,7 +273,7 @@ class HacktoberStats(commands.Cog):          return re.findall(exp, in_url)[0]      @staticmethod -    def _summarize_prs(prs: typing.List[dict]) -> typing.Dict: +    def _summarize_prs(prs: List[dict]) -> dict:          """          Generate statistics from an input list of PR dictionaries, as output by get_october_prs. @@ -288,7 +287,7 @@ class HacktoberStats(commands.Cog):          return {"n_prs": len(prs), "top5": Counter(contributed_repos).most_common(5)}      @staticmethod -    def _build_top5str(stats: typing.List[tuple]) -> str: +    def _build_top5str(stats: List[tuple]) -> str:          """          Build a string from the Top 5 contributions that is compatible with a discord.Embed field. @@ -316,7 +315,7 @@ class HacktoberStats(commands.Cog):              return "contributions"      @staticmethod -    def _author_mention_from_context(ctx: commands.Context) -> typing.Tuple: +    def _author_mention_from_context(ctx: commands.Context) -> Tuple:          """Return stringified Message author ID and mentionable string from commands.Context."""          author_id = str(ctx.message.author.id)          author_mention = ctx.message.author.mention @@ -324,7 +323,7 @@ class HacktoberStats(commands.Cog):          return author_id, author_mention -def setup(bot): +def setup(bot):  # Noqa      """Hacktoberstats Cog load."""      bot.add_cog(HacktoberStats(bot))      log.info("HacktoberStats cog loaded") diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 173ce8eb..6e71a007 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -60,7 +60,7 @@ class MonsterSurvey(Cog):      @commands.group(          name='monster', -        aliases=('ms',) +        aliases=('mon',)      )      async def monster_group(self, ctx: Context):          """The base voting command. If nothing is called, then it will return an embed.""" diff --git a/bot/seasons/halloween/spookyrating.py b/bot/seasons/halloween/spookyrating.py index 08c17a27..a436e39d 100644 --- a/bot/seasons/halloween/spookyrating.py +++ b/bot/seasons/halloween/spookyrating.py @@ -17,7 +17,7 @@ with Path("bot/resources/halloween/spooky_rating.json").open() as file:  class SpookyRating(commands.Cog): -    """A cog for calculating one's spooky rating""" +    """A cog for calculating one's spooky rating."""      def __init__(self, bot):          self.bot = bot diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 3249a9cf..72a681a3 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -110,7 +110,7 @@ def replace_many(      regex = re.compile(pattern, re.I if ignore_case else 0)      def _repl(match): -        """Returns replacement depending on `ignore_case` and `match_case`""" +        """Returns replacement depending on `ignore_case` and `match_case`."""          word = match.group(0)          replacement = replacements[word.lower() if ignore_case else word] @@ -1,16 +1,17 @@  [flake8]  max-line-length=120  application_import_names=bot +docstring-convention=all  ignore=      P102,B311,W503,E226,S311,      # Missing Docstrings -    D100,D104,D107, +    D100,D104,D105,D107,      # Docstring Whitespace      D203,D212,D214,D215,      # Docstring Quotes      D301,D302,      # Docstring Content -    D400,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414 +    D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417  exclude=      __pycache__,.cache,      venv,.venv, | 
