diff options
| author | 2021-12-03 21:06:10 -0500 | |
|---|---|---|
| committer | 2021-12-03 21:51:56 -0500 | |
| commit | a28abf1b286d841b8f27aba5d31696a9f658f952 (patch) | |
| tree | cddca97a2845221a880fcfbc5c36c91c7e3e98fa | |
| parent | Make aoc_name a keyword arguemnt to accept spaces (diff) | |
| parent | Merge pull request #963 from python-discord/aoc-lb-multiword (diff) | |
Merge branch 'main' into aoc-link
| -rw-r--r-- | bot/constants.py | 10 | ||||
| -rw-r--r-- | bot/exts/core/internal_eval/_internal_eval.py | 6 | ||||
| -rw-r--r-- | bot/exts/events/advent_of_code/_cog.py | 28 | ||||
| -rw-r--r-- | bot/exts/fun/snakes/_utils.py | 25 | ||||
| -rw-r--r-- | bot/exts/fun/trivia_quiz.py | 4 | ||||
| -rw-r--r-- | bot/exts/holidays/hanukkah/hanukkah_embed.py | 84 | ||||
| -rw-r--r-- | bot/utils/pagination.py | 10 | 
7 files changed, 76 insertions, 91 deletions
| diff --git a/bot/constants.py b/bot/constants.py index 33bc8b61..f4b1cab0 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -282,9 +282,9 @@ if Client.month_override is not None:  class Roles(NamedTuple): -    owner = 267627879762755584 -    admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896)) -    moderator = 267629731250176001 +    owners = 267627879762755584 +    admins = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896)) +    moderation_team = 267629731250176001      helpers = int(environ.get("ROLE_HELPERS", 267630620367257601))      core_developers = 587606783669829632      everyone = int(environ.get("BOT_GUILD", 267624335836053506)) @@ -334,8 +334,8 @@ class Reddit:  # Default role combinations -MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner -STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner +MODERATION_ROLES = {Roles.moderation_team, Roles.admins, Roles.owners} +STAFF_ROLES = {Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners}  # Whitelisted channels  WHITELISTED_CHANNELS = ( diff --git a/bot/exts/core/internal_eval/_internal_eval.py b/bot/exts/core/internal_eval/_internal_eval.py index 12a860fa..5b5461f0 100644 --- a/bot/exts/core/internal_eval/_internal_eval.py +++ b/bot/exts/core/internal_eval/_internal_eval.py @@ -147,14 +147,14 @@ class InternalEval(commands.Cog):          await self._send_output(ctx, eval_context.format_output())      @commands.group(name="internal", aliases=("int",)) -    @with_role(Roles.admin) +    @with_role(Roles.admins)      async def internal_group(self, ctx: commands.Context) -> None:          """Internal commands. Top secret!"""          if not ctx.invoked_subcommand:              await invoke_help_command(ctx)      @internal_group.command(name="eval", aliases=("e",)) -    @with_role(Roles.admin) +    @with_role(Roles.admins)      async def eval(self, ctx: commands.Context, *, code: str) -> None:          """Run eval in a REPL-like format."""          if match := list(FORMATTED_CODE_REGEX.finditer(code)): @@ -173,7 +173,7 @@ class InternalEval(commands.Cog):          await self._eval(ctx, code)      @internal_group.command(name="reset", aliases=("clear", "exit", "r", "c")) -    @with_role(Roles.admin) +    @with_role(Roles.admins)      async def reset(self, ctx: commands.Context) -> None:          """Reset the context and locals of the eval session."""          self.locals = {} diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py index 55fd0ac6..16176c69 100644 --- a/bot/exts/events/advent_of_code/_cog.py +++ b/bot/exts/events/advent_of_code/_cog.py @@ -59,6 +59,7 @@ class AdventOfCode(commands.Cog):          if not ctx.invoked_subcommand:              await invoke_help_command(ctx) +    @commands.guild_only()      @adventofcode_group.command(          name="subscribe",          aliases=("sub", "notifications", "notify", "notifs"), @@ -88,6 +89,7 @@ class AdventOfCode(commands.Cog):              )      @in_month(Month.DECEMBER) +    @commands.guild_only()      @adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days")      @whitelist_override(channels=AOC_WHITELIST)      async def aoc_unsubscribe(self, ctx: commands.Context) -> None: @@ -131,6 +133,7 @@ class AdventOfCode(commands.Cog):          """Respond with an explanation of all things Advent of Code."""          await ctx.send(embed=self.cached_about_aoc) +    @commands.guild_only()      @adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join the leaderboard (via DM)")      @whitelist_override(channels=AOC_WHITELIST)      async def join_leaderboard(self, ctx: commands.Context) -> None: @@ -294,20 +297,23 @@ class AdventOfCode(commands.Cog):          brief="Get a snapshot of the PyDis private AoC leaderboard",      )      @whitelist_override(channels=AOC_WHITELIST_RESTRICTED) -    async def aoc_leaderboard( -            self, -            ctx: commands.Context, -            self_placement_name: Optional[str] = None, -    ) -> None: +    async def aoc_leaderboard(self, ctx: commands.Context, *, aoc_name: Optional[str] = None) -> None:          """          Get the current top scorers of the Python Discord Leaderboard. -        Additionally you can specify a `self_placement_name` -        that will append the specified profile's personal stats to the top of the leaderboard +        Additionally you can specify an `aoc_name` that will append the +        specified profile's personal stats to the top of the leaderboard          """ +        # Strip quotes from the AoC username if needed (e.g. "My Name" -> My Name) +        # This is to keep compatibility with those already used to wrapping the AoC name in quotes +        # Note: only strips one layer of quotes to allow names with quotes at the start and end +        #      e.g. ""My Name"" -> "My Name" +        if aoc_name and aoc_name.startswith('"') and aoc_name.endswith('"'): +            aoc_name = aoc_name[1:-1] +          async with ctx.typing():              try: -                leaderboard = await _helpers.fetch_leaderboard(self_placement_name=self_placement_name) +                leaderboard = await _helpers.fetch_leaderboard(self_placement_name=aoc_name)              except _helpers.FetchingLeaderboardFailedError:                  await ctx.send(":x: Unable to fetch leaderboard!")                  return @@ -315,10 +321,10 @@ class AdventOfCode(commands.Cog):          number_of_participants = leaderboard["number_of_participants"]          top_count = min(AocConfig.leaderboard_displayed_members, number_of_participants) -        self_placement_header = "(and your personal stats compared to the top 10)" if self_placement_name else "" +        self_placement_header = "(and your personal stats compared to the top 10)" if aoc_name else ""          header = f"Here's our current top {top_count}{self_placement_header}! {Emojis.christmas_tree * 3}"          table = "```\n" \ -                f"{leaderboard['placement_leaderboard'] if self_placement_name else leaderboard['top_leaderboard']}" \ +                f"{leaderboard['placement_leaderboard'] if aoc_name else leaderboard['top_leaderboard']}" \                  "\n```"          info_embed = _helpers.get_summary_embed(leaderboard) @@ -372,7 +378,7 @@ class AdventOfCode(commands.Cog):              info_embed = _helpers.get_summary_embed(leaderboard)              await ctx.send(f"```\n{table}\n```", embed=info_embed) -    @with_role(Roles.admin) +    @with_role(Roles.admins)      @adventofcode_group.command(          name="refresh",          aliases=("fetch",), diff --git a/bot/exts/fun/snakes/_utils.py b/bot/exts/fun/snakes/_utils.py index de51339d..182fa9d9 100644 --- a/bot/exts/fun/snakes/_utils.py +++ b/bot/exts/fun/snakes/_utils.py @@ -6,13 +6,14 @@ import math  import random  from itertools import product  from pathlib import Path +from typing import Union  from PIL import Image  from PIL.ImageDraw import ImageDraw -from discord import File, Member, Reaction +from discord import File, Member, Reaction, User  from discord.ext.commands import Cog, Context -from bot.constants import Roles +from bot.constants import MODERATION_ROLES  SNAKE_RESOURCES = Path("bot/resources/fun/snakes").absolute() @@ -395,7 +396,7 @@ class SnakeAndLaddersGame:          Listen for reactions until players have joined, and the game has been started.          """ -        def startup_event_check(reaction_: Reaction, user_: Member) -> bool: +        def startup_event_check(reaction_: Reaction, user_: Union[User, Member]) -> bool:              """Make sure that this reaction is what we want to operate on."""              return (                  all(( @@ -460,7 +461,7 @@ class SnakeAndLaddersGame:                  await self.cancel_game()                  return  # We're done, no reactions for the last 5 minutes -    async def _add_player(self, user: Member) -> None: +    async def _add_player(self, user: Union[User, Member]) -> None:          """Add player to game."""          self.players.append(user)          self.player_tiles[user.id] = 1 @@ -469,7 +470,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) -> None: +    async def player_join(self, user: Union[User, Member]) -> None:          """          Handle players joining the game. @@ -495,7 +496,7 @@ class SnakeAndLaddersGame:              delete_after=10          ) -    async def player_leave(self, user: Member) -> bool: +    async def player_leave(self, user: Union[User, Member]) -> bool:          """          Handle players leaving the game. @@ -530,7 +531,7 @@ class SnakeAndLaddersGame:          await self.channel.send("**Snakes and Ladders**: Game has been canceled.")          self._destruct() -    async def start_game(self, user: Member) -> None: +    async def start_game(self, user: Union[User, Member]) -> None:          """          Allow the game author to begin the game. @@ -551,7 +552,7 @@ class SnakeAndLaddersGame:      async def start_round(self) -> None:          """Begin the round.""" -        def game_event_check(reaction_: Reaction, user_: Member) -> bool: +        def game_event_check(reaction_: Reaction, user_: Union[User, Member]) -> bool:              """Make sure that this reaction is what we want to operate on."""              return (                  all(( @@ -644,7 +645,7 @@ class SnakeAndLaddersGame:          if not is_surrendered:              await self._complete_round() -    async def player_roll(self, user: Member) -> None: +    async def player_roll(self, user: Union[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) @@ -691,7 +692,7 @@ class SnakeAndLaddersGame:          await self.channel.send("**Snakes and Ladders**: " + winner.mention + " has won the game! :tada:")          self._destruct() -    def _check_winner(self) -> Member: +    def _check_winner(self) -> Union[User, Member]:          """Return a winning member if we're in the post-round state and there's a winner."""          if self.state != "post_round":              return None @@ -716,6 +717,6 @@ class SnakeAndLaddersGame:          return x_level, y_level      @staticmethod -    def _is_moderator(user: Member) -> bool: +    def _is_moderator(user: Union[User, Member]) -> bool:          """Return True if the user is a Moderator.""" -        return any(Roles.moderator == role.id for role in user.roles) +        return any(role.id in MODERATION_ROLES for role in getattr(user, 'roles', [])) diff --git a/bot/exts/fun/trivia_quiz.py b/bot/exts/fun/trivia_quiz.py index 712c8a12..4a1cec5b 100644 --- a/bot/exts/fun/trivia_quiz.py +++ b/bot/exts/fun/trivia_quiz.py @@ -16,7 +16,7 @@ from discord.ext import commands, tasks  from rapidfuzz import fuzz  from bot.bot import Bot -from bot.constants import Client, Colours, NEGATIVE_REPLIES, Roles +from bot.constants import Client, Colours, MODERATION_ROLES, NEGATIVE_REPLIES  logger = logging.getLogger(__name__) @@ -550,7 +550,7 @@ class TriviaQuiz(commands.Cog):              if self.game_status[ctx.channel.id]:                  # Check if the author is the game starter or a moderator.                  if ctx.author == self.game_owners[ctx.channel.id] or any( -                    Roles.moderator == role.id for role in ctx.author.roles +                    role.id in MODERATION_ROLES for role in getattr(ctx.author, 'roles', [])                  ):                      self.game_status[ctx.channel.id] = False                      del self.game_owners[ctx.channel.id] diff --git a/bot/exts/holidays/hanukkah/hanukkah_embed.py b/bot/exts/holidays/hanukkah/hanukkah_embed.py index ac3eab7b..5767f91e 100644 --- a/bot/exts/holidays/hanukkah/hanukkah_embed.py +++ b/bot/exts/holidays/hanukkah/hanukkah_embed.py @@ -21,45 +21,41 @@ class HanukkahEmbed(commands.Cog):      def __init__(self, bot: Bot):          self.bot = bot -        self.hanukkah_days = [] -        self.hanukkah_months = [] -        self.hanukkah_years = [] +        self.hanukkah_dates: list[datetime.date] = [] -    async def get_hanukkah_dates(self) -> list[str]: +    def _parse_time_to_datetime(self, date: list[str]) -> datetime.datetime: +        """Format the times provided by the api to datetime forms.""" +        try: +            return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S%z") +        except ValueError: +            # there is a possibility of an event not having a time, just a day +            # to catch this, we try again without time information +            return datetime.datetime.strptime(date, "%Y-%m-%d") + +    async def fetch_hanukkah_dates(self) -> list[datetime.date]:          """Gets the dates for hanukkah festival.""" -        hanukkah_dates = [] +        # clear the datetime objects to prevent a memory link +        self.hanukkah_dates = []          async with self.bot.http_session.get(HEBCAL_URL) as response:              json_data = await response.json()          festivals = json_data["items"]          for festival in festivals:              if festival["title"].startswith("Chanukah"):                  date = festival["date"] -                hanukkah_dates.append(date) -        return hanukkah_dates +                self.hanukkah_dates.append(self._parse_time_to_datetime(date).date()) +        return self.hanukkah_dates      @in_month(Month.NOVEMBER, Month.DECEMBER)      @commands.command(name="hanukkah", aliases=("chanukah",))      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) -        hanukkah_start_day = int(self.hanukkah_days[0]) -        hanukkah_start_month = int(self.hanukkah_months[0]) -        hanukkah_start_year = int(self.hanukkah_years[0]) -        hanukkah_end_day = int(self.hanukkah_days[8]) -        hanukkah_end_month = int(self.hanukkah_months[8]) -        hanukkah_end_year = int(self.hanukkah_years[8]) - -        hanukkah_start = datetime.date(hanukkah_start_year, hanukkah_start_month, hanukkah_start_day) -        hanukkah_end = datetime.date(hanukkah_end_year, hanukkah_end_month, hanukkah_end_day) +        hanukkah_dates = await self.fetch_hanukkah_dates() +        start_day = hanukkah_dates[0] +        end_day = hanukkah_dates[-1]          today = datetime.date.today() -        # today = datetime.date(2019, 12, 24) (for testing) -        day = str(today.day) -        month = str(today.month) -        year = str(today.year)          embed = Embed(title="Hanukkah", colour=Colours.blue) -        if day in self.hanukkah_days and month in self.hanukkah_months and year in self.hanukkah_years: -            if int(day) == hanukkah_start_day: +        if start_day <= today <= end_day: +            if start_day == today:                  now = datetime.datetime.utcnow()                  hours = now.hour + 4  # using only hours                  hanukkah_start_hour = 18 @@ -77,35 +73,27 @@ class HanukkahEmbed(commands.Cog):                      )                      await ctx.send(embed=embed)                      return -            festival_day = self.hanukkah_days.index(day) +            festival_day = hanukkah_dates.index(today)              number_suffixes = ["st", "nd", "rd", "th"]              suffix = number_suffixes[festival_day - 1 if festival_day <= 3 else 3]              message = ":menorah:" * festival_day -            embed.description = f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}" -            await ctx.send(embed=embed) +            embed.description = ( +                f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}" +            ) +        elif today < start_day: +            format_start = start_day.strftime("%d of %B") +            embed.description = ( +                "Hanukkah has not started yet. " +                f"Hanukkah will start at sundown on {format_start}." +            )          else: -            if today < hanukkah_start: -                festival_starting_month = hanukkah_start.strftime("%B") -                embed.description = ( -                    f"Hanukkah has not started yet. " -                    f"Hanukkah will start at sundown on {hanukkah_start_day}th " -                    f"of {festival_starting_month}." -                ) -            else: -                festival_end_month = hanukkah_end.strftime("%B") -                embed.description = ( -                    f"Looks like you missed Hanukkah!" -                    f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}." -                ) - -            await ctx.send(embed=embed) +            format_end = end_day.strftime("%d of %B") +            embed.description = ( +                "Looks like you missed Hanukkah! " +                f"Hanukkah ended on {format_end}." +            ) -    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]) -            self.hanukkah_months.append(date[5:7]) -            self.hanukkah_years.append(date[0:4]) +        await ctx.send(embed=embed)  def setup(bot: Bot) -> None: diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index 013ef9e7..188b279f 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -211,8 +211,6 @@ class LinePaginator(Paginator):                  log.debug(f"Got first page reaction - changing to page 1/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text:                      embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") @@ -226,8 +224,6 @@ class LinePaginator(Paginator):                  log.debug(f"Got last page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text:                      embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") @@ -245,8 +241,6 @@ class LinePaginator(Paginator):                  current_page -= 1                  log.debug(f"Got previous page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text: @@ -266,8 +260,6 @@ class LinePaginator(Paginator):                  current_page += 1                  log.debug(f"Got next page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text: @@ -428,8 +420,6 @@ class ImagePaginator(Paginator):                  reaction_type = "next"              # Magic happens here, after page and reaction_type is set -            embed.description = "" -            await message.edit(embed=embed)              embed.description = paginator.pages[current_page]              image = paginator.images[current_page] or EmptyEmbed | 
