Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn AoC commands into hybrid commands #101

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
98 changes: 57 additions & 41 deletions bot/exts/advent_of_code/_cog.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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__)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -160,14 +160,8 @@ async def aoc_countdown(self, ctx: commands.Context) -> None:
await ctx.send(f"Day {tomorrow.day} starts <t:{next_day_timestamp}:R>.")
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. "
Expand Down Expand Up @@ -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.

Expand All @@ -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(
Expand Down
14 changes: 14 additions & 0 deletions bot/exts/advent_of_code/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down