From 71bbd59d30e6b14339cfd2d451a7e11992e7cbda Mon Sep 17 00:00:00 2001 From: n0Oo0Oo0b Date: Wed, 8 Nov 2023 17:42:06 +0800 Subject: [PATCH 1/8] Refactor time to next AoC calculation into a helper function --- bot/exts/advent_of_code/_cog.py | 13 +++---------- bot/exts/advent_of_code/_helpers.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index b93cb3f..8b292f1 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -1,9 +1,8 @@ import json import logging -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime from pathlib import Path -import arrow import discord from async_rediscache import RedisCache from discord import app_commands @@ -175,14 +174,8 @@ async def aoc_countdown(self, ctx: commands.Context) -> None: await ctx.send(f"Day {tomorrow.day} starts .") return - datetime_now = arrow.now(_helpers.EST) - # Calculate the delta to this & next year's December 1st to see which one is closest and not in the past - this_year = arrow.get(datetime(datetime_now.year, 12, 1, tzinfo=UTC), _helpers.EST) - next_year = arrow.get(datetime(datetime_now.year + 1, 12, 1, tzinfo=UTC), _helpers.EST) - deltas = (dec_first - datetime_now for dec_first in (this_year, next_year)) - delta = min(delta for delta in deltas if delta >= timedelta()) # timedelta() gives 0 duration delta - - next_aoc_timestamp = int((datetime_now + delta).timestamp()) + next_aoc, _ = _helpers.time_left_to_next_aoc() + next_aoc_timestamp = int(next_aoc.timestamp()) await ctx.send( "The Advent of Code event is not currently running. " diff --git a/bot/exts/advent_of_code/_helpers.py b/bot/exts/advent_of_code/_helpers.py index b8fcf22..8854f35 100644 --- a/bot/exts/advent_of_code/_helpers.py +++ b/bot/exts/advent_of_code/_helpers.py @@ -460,6 +460,20 @@ def is_in_advent() -> bool: return arrow.now(EST).day in range(1, 25) and arrow.now(EST).month == 12 +def time_left_to_next_aoc() -> tuple[datetime.datetime, datetime.timedelta]: + """ + Calculate the amount of time left until the next AoC. + + This will be either this year or next year's December 1, whichever one is + closer and not in the past. + """ + datetime_now = arrow.now(EST) + this_year = arrow.get(datetime.datetime(datetime_now.year, 12, 1, tzinfo=datetime.UTC), EST) + next_year = arrow.get(datetime.datetime(datetime_now.year + 1, 12, 1, tzinfo=datetime.UTC), EST) + dec_first = this_year if this_year > datetime_now else next_year + return dec_first, dec_first - datetime_now + + def time_left_to_est_midnight() -> tuple[datetime.datetime, datetime.timedelta]: """Calculate the amount of time left until midnight EST/UTC-5.""" # Change all time properties back to 00:00 From a381bd0f99cd25085ce7b1430df3e55350e69e5f Mon Sep 17 00:00:00 2001 From: n0Oo0Oo0b Date: Wed, 8 Nov 2023 18:04:39 +0800 Subject: [PATCH 2/8] Create slash command for `&aoc countdown` --- bot/exts/advent_of_code/_cog.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index 8b292f1..a9e8524 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -182,6 +182,25 @@ async def aoc_countdown(self, ctx: commands.Context) -> None: f"The next event will start ." ) + @aoc_slash_group.command(name="countdown", description="Return time left until next day") + @whitelist_override(channels=AOC_WHITELIST) + async def aoc_countdown_slash(self, interaction: discord.Interaction) -> None: + """Return time left until next day.""" + if _helpers.is_in_advent(): + tomorrow, _ = _helpers.time_left_to_est_midnight() + next_day_timestamp = int(tomorrow.timestamp()) + + await interaction.response.send_message(f"Day {tomorrow.day} starts .") + return + + next_aoc, _ = _helpers.time_left_to_next_aoc() + next_aoc_timestamp = int(next_aoc.timestamp()) + + await interaction.response.send_message( + "The Advent of Code event is not currently running. " + f"The next event will start ." + ) + @adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code") @whitelist_override(channels=AOC_WHITELIST) async def about_aoc(self, ctx: commands.Context) -> None: From 8d38fdd44c293005d8af6fe6f49d63e4d277cd2c Mon Sep 17 00:00:00 2001 From: n0Oo0Oo0b Date: Thu, 9 Nov 2023 20:31:58 +0800 Subject: [PATCH 3/8] Use a hybrid group instead of duplicating code --- bot/exts/advent_of_code/_cog.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index a9e8524..719e10f 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -127,7 +127,7 @@ async def completionist_task(self) -> None: log.debug(f"Giving completionist role to {member.name} ({member.mention}).") await members.handle_role_change(member, member.add_roles, completionist_role) - @commands.group(name="adventofcode", aliases=("aoc",)) + @commands.hybrid_group(name="adventofcode", aliases=("aoc",)) @whitelist_override(channels=AOC_WHITELIST) async def adventofcode_group(self, ctx: commands.Context) -> None: """All of the Advent of Code commands.""" @@ -182,25 +182,6 @@ async def aoc_countdown(self, ctx: commands.Context) -> None: f"The next event will start ." ) - @aoc_slash_group.command(name="countdown", description="Return time left until next day") - @whitelist_override(channels=AOC_WHITELIST) - async def aoc_countdown_slash(self, interaction: discord.Interaction) -> None: - """Return time left until next day.""" - if _helpers.is_in_advent(): - tomorrow, _ = _helpers.time_left_to_est_midnight() - next_day_timestamp = int(tomorrow.timestamp()) - - await interaction.response.send_message(f"Day {tomorrow.day} starts .") - return - - next_aoc, _ = _helpers.time_left_to_next_aoc() - next_aoc_timestamp = int(next_aoc.timestamp()) - - await interaction.response.send_message( - "The Advent of Code event is not currently running. " - f"The next event will start ." - ) - @adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code") @whitelist_override(channels=AOC_WHITELIST) async def about_aoc(self, ctx: commands.Context) -> None: From d5a0e2dfaa13780fdf3fd3c0592f6c5977d59fc0 Mon Sep 17 00:00:00 2001 From: n0Oo0Oo0b Date: Thu, 9 Nov 2023 20:32:40 +0800 Subject: [PATCH 4/8] Make slash responses for link/unlink ephemeral --- bot/exts/advent_of_code/_cog.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index 719e10f..1e5c460 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -258,7 +258,7 @@ async def aoc_link_account(self, ctx: commands.Context, *, aoc_name: str | None if aoc_name: # Let's check the current values in the cache to make sure it isn't already tied to a different account if aoc_name == await self.account_links.get(ctx.author.id): - await ctx.reply(f"{aoc_name} is already tied to your account.") + await ctx.reply(f"{aoc_name} is already tied to your account.", ephemeral=True) return if aoc_name in cache_aoc_names: log.info( @@ -267,7 +267,8 @@ async def aoc_link_account(self, ctx: commands.Context, *, aoc_name: str | None ) await ctx.reply( f"{aoc_name} is already tied to another account." - " Please contact an admin if you believe this is an error." + " Please contact an admin if you believe this is an error.", + ephemeral=True, ) return @@ -307,10 +308,10 @@ async def aoc_unlink_account(self, ctx: commands.Context) -> None: if aoc_cache_name := await self.account_links.get(ctx.author.id): log.info(f"Unlinking {ctx.author} ({ctx.author.id}) from Advent of Code account {aoc_cache_name}") await self.account_links.delete(ctx.author.id) - await ctx.reply(f"We have removed the link between your Discord ID and {aoc_cache_name}.") + await ctx.reply(f"We have removed the link between your Discord ID and {aoc_cache_name}.", ephemeral=True) else: log.info(f"Attempted to unlink {ctx.author} ({ctx.author.id}), but no link was found.") - await ctx.reply("You don't have an Advent of Code account linked.") + await ctx.reply("You don't have an Advent of Code account linked.", ephemeral=True) @in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY) @adventofcode_group.command( From 57e67f34d9ed718798b80fd7633f00483d719a10 Mon Sep 17 00:00:00 2001 From: Robin5605 Date: Wed, 29 Nov 2023 01:54:50 -0600 Subject: [PATCH 5/8] Import whitelist_override --- bot/exts/advent_of_code/_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index 1603468..74f0b16 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -22,7 +22,7 @@ from bot.exts.advent_of_code import _helpers from bot.exts.advent_of_code.views.dayandstarview import AoCDropdownView from bot.utils import members -from bot.utils.decorators import in_month, in_whitelist, with_role +from bot.utils.decorators import in_month, in_whitelist, whitelist_override, with_role log = logging.getLogger(__name__) From 619a1db4de522892e6b2c6d421f25846c50d601d Mon Sep 17 00:00:00 2001 From: Robin5605 Date: Thu, 30 Nov 2023 22:09:25 -0600 Subject: [PATCH 6/8] Make link/unlink slash-only --- bot/exts/advent_of_code/_cog.py | 77 +++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index 74f0b16..fdfefde 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -226,13 +226,17 @@ async def join_leaderboard(self, interaction: discord.Interaction) -> None: await interaction.response.send_message("\n".join(info_str), ephemeral=True) @in_month(Month.NOVEMBER, Month.DECEMBER, Month.JANUARY, Month.FEBRUARY) - @adventofcode_group.command( + @adventofcode_group.app_command.command( name="link", - aliases=("connect",), - brief="Tie your Discord account with your Advent of Code name." + description="Tie your Discord account with your Advent of Code name.", ) @in_whitelist(channels=AOC_WHITELIST, redirect=AOC_REDIRECT) - async def aoc_link_account(self, ctx: commands.Context, *, aoc_name: str | None = None) -> None: + async def aoc_link_account( + self, + interaction: discord.Interaction, + *, + aoc_name: str | None = None, + ) -> None: """ Link your Discord Account to your Advent of Code name. @@ -243,15 +247,19 @@ async def aoc_link_account(self, ctx: commands.Context, *, aoc_name: str | None if aoc_name: # Let's check the current values in the cache to make sure it isn't already tied to a different account - if aoc_name == await self.account_links.get(ctx.author.id): - await ctx.reply(f"{aoc_name} is already tied to your account.", ephemeral=True) + if aoc_name == await self.account_links.get(interaction.user.id): + await interaction.response.send_message( + f"{aoc_name} is already tied to your account.", + ephemeral=True, + ) return + if aoc_name in cache_aoc_names: log.info( - f"{ctx.author} ({ctx.author.id}) tried to connect their account to {aoc_name}," + f"{interaction.user} ({interaction.user.id}) tried to connect their account to {aoc_name}," " but it's already connected to another user." ) - await ctx.reply( + await interaction.response.send_message( f"{aoc_name} is already tied to another account." " Please contact an admin if you believe this is an error.", ephemeral=True, @@ -259,45 +267,58 @@ async def aoc_link_account(self, ctx: commands.Context, *, aoc_name: str | None return # Update an existing link - if old_aoc_name := await self.account_links.get(ctx.author.id): - log.info(f"Changing link for {ctx.author} ({ctx.author.id}) from {old_aoc_name} to {aoc_name}.") - await self.account_links.set(ctx.author.id, aoc_name) - await ctx.reply(f"Your linked account has been changed to {aoc_name}.") + if old_aoc_name := await self.account_links.get(interaction.user.id): + log.info( + f"Changing link for {interaction.user.id} ({interaction.user.id}) " + f"from {old_aoc_name} to {aoc_name}." + ) + await self.account_links.set(interaction.user.id, aoc_name) + await interaction.response.send_message(f"Your linked account has been changed to {aoc_name}.") else: # Create a new link - log.info(f"Linking {ctx.author} ({ctx.author.id}) to account {aoc_name}.") - await self.account_links.set(ctx.author.id, aoc_name) - await ctx.reply(f"You have linked your Discord ID to {aoc_name}.") + log.info(f"Linking {interaction.user} ({interaction.user.id}) to account {aoc_name}.") + await self.account_links.set(interaction.user.id, aoc_name) + await interaction.response.send_message(f"You have linked your Discord ID to {aoc_name}.") else: # User has not supplied a name, let's check if they're in the cache or not - if cache_name := await self.account_links.get(ctx.author.id): - await ctx.reply(f"You have already linked an Advent of Code account: {cache_name}.") + if cache_name := await self.account_links.get(interaction.user.id): + await interaction.response.send_message( + f"You have already linked an Advent of Code account: {cache_name}." + ) else: - await ctx.reply( + await interaction.response.send_message( "You have not linked an Advent of Code account." " Please re-run the command with one specified." ) @in_month(Month.NOVEMBER, Month.DECEMBER, Month.JANUARY, Month.FEBRUARY) - @adventofcode_group.command( + @adventofcode_group.app_command.command( name="unlink", - aliases=("disconnect",), - brief="Untie your Discord account from your Advent of Code name." + description="Untie your Discord account from your Advent of Code name.", ) @in_whitelist(channels=AOC_WHITELIST, redirect=AOC_REDIRECT) - async def aoc_unlink_account(self, ctx: commands.Context) -> None: + async def aoc_unlink_account(self, interaction: discord.Interaction) -> None: """ Unlink your Discord ID with your Advent of Code leaderboard name. Deletes the entry that was Stored in the Redis cache. """ - if aoc_cache_name := await self.account_links.get(ctx.author.id): - log.info(f"Unlinking {ctx.author} ({ctx.author.id}) from Advent of Code account {aoc_cache_name}") - await self.account_links.delete(ctx.author.id) - await ctx.reply(f"We have removed the link between your Discord ID and {aoc_cache_name}.", ephemeral=True) + if aoc_cache_name := await self.account_links.get(interaction.user.id): + log.info( + f"Unlinking {interaction.user} ({interaction.user.id}) " + f"from Advent of Code account {aoc_cache_name}" + ) + await self.account_links.delete(interaction.user.id) + await interaction.response.send_message( + f"We have removed the link between your Discord ID and {aoc_cache_name}.", + ephemeral=True, + ) else: - log.info(f"Attempted to unlink {ctx.author} ({ctx.author.id}), but no link was found.") - await ctx.reply("You don't have an Advent of Code account linked.", ephemeral=True) + log.info(f"Attempted to unlink {interaction.user} ({interaction.user.id}), but no link was found.") + await interaction.response.send_message( + "You don't have an Advent of Code account linked.", + ephemeral=True, + ) @in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY) @adventofcode_group.command( From 9612a068a6706cb8f47804d2635e4e0ba810707d Mon Sep 17 00:00:00 2001 From: Robin5605 Date: Thu, 30 Nov 2023 22:11:53 -0600 Subject: [PATCH 7/8] Rename /adventofcode to /aoc --- bot/exts/advent_of_code/_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index fdfefde..8efe767 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -128,7 +128,7 @@ async def completionist_task(self) -> None: log.debug(f"Giving completionist role to {member.name} ({member.mention}).") await members.handle_role_change(member, member.add_roles, completionist_role) - @commands.hybrid_group(name="adventofcode", aliases=("aoc",)) + @commands.hybrid_group(name="aoc", aliases=("adventofcode",)) @whitelist_override(channels=AOC_WHITELIST) async def adventofcode_group(self, ctx: commands.Context) -> None: """All of the Advent of Code commands.""" From d568682e2f12a1c75d93e1f5b9fb8a2c9fc3ada3 Mon Sep 17 00:00:00 2001 From: Robin5605 Date: Thu, 30 Nov 2023 22:22:46 -0600 Subject: [PATCH 8/8] Lint --- bot/exts/advent_of_code/_cog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index 8efe767..2110e44 100644 --- a/bot/exts/advent_of_code/_cog.py +++ b/bot/exts/advent_of_code/_cog.py @@ -232,9 +232,9 @@ async def join_leaderboard(self, interaction: discord.Interaction) -> None: ) @in_whitelist(channels=AOC_WHITELIST, redirect=AOC_REDIRECT) async def aoc_link_account( - self, - interaction: discord.Interaction, - *, + self, + interaction: discord.Interaction, + *, aoc_name: str | None = None, ) -> None: """ @@ -249,7 +249,7 @@ async def aoc_link_account( # Let's check the current values in the cache to make sure it isn't already tied to a different account if aoc_name == await self.account_links.get(interaction.user.id): await interaction.response.send_message( - f"{aoc_name} is already tied to your account.", + f"{aoc_name} is already tied to your account.", ephemeral=True, ) return @@ -310,13 +310,13 @@ async def aoc_unlink_account(self, interaction: discord.Interaction) -> None: ) await self.account_links.delete(interaction.user.id) await interaction.response.send_message( - f"We have removed the link between your Discord ID and {aoc_cache_name}.", + f"We have removed the link between your Discord ID and {aoc_cache_name}.", ephemeral=True, ) else: log.info(f"Attempted to unlink {interaction.user} ({interaction.user.id}), but no link was found.") await interaction.response.send_message( - "You don't have an Advent of Code account linked.", + "You don't have an Advent of Code account linked.", ephemeral=True, )