diff --git a/bot/exts/advent_of_code/_cog.py b/bot/exts/advent_of_code/_cog.py index 681b5c1..2110e44 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 @@ -23,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__) @@ -129,7 +128,8 @@ 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="aoc", aliases=("adventofcode",)) + @whitelist_override(channels=AOC_WHITELIST) async def adventofcode_group(self, ctx: commands.Context) -> None: """All of the Advent of Code commands.""" if not ctx.invoked_subcommand: @@ -160,14 +160,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. " @@ -232,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. @@ -249,60 +247,78 @@ 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.") + 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." + " Please contact an admin if you believe this is an error.", + ephemeral=True, ) 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}.") + 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.") + 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( diff --git a/bot/exts/advent_of_code/_helpers.py b/bot/exts/advent_of_code/_helpers.py index 5f679a3..569c8b5 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