Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions bot/api_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import asyncio
import json
import logging
from datetime import datetime, timezone
Expand Down Expand Up @@ -293,20 +292,43 @@ async def search(self, search_for: str) -> List[dict]:


class AdventOfCodeAPI(BaseAPIClient):
AOC_REQUEST_HEADER = {"user-agent": "Tortoise Discord Community AoC event bot"}
COOKIES = {"session": os.getenv("AOC_COOKIE")}
AOC_REQUEST_HEADER = {
"User-Agent": "Tortoise Discord Community AoC bot (github: Tortoise-Community)"
}

AOC_API_URL = "https://adventofcode.com/{year}/leaderboard/private/view/{leaderboard_id}"

def __init__(self, leaderboard_id: str, year: int = 2020):
def __init__(
self,
leaderboard_id: str,
year: int,
session_cookie: Optional[str] = None,
) -> None:
session_cookie = session_cookie or os.getenv("AOC_COOKIE")

if not session_cookie:
raise ValueError(
"AdventOfCodeAPI: session cookie is missing. "
"Pass session_cookie=... or set AOC_COOKIE."
)

base_url = self.AOC_API_URL.format(
year=year,
leaderboard_id=leaderboard_id
)

super().__init__(
self.AOC_API_URL.format(year=year, leaderboard_id=leaderboard_id),
base_api_url=base_url,
headers=self.AOC_REQUEST_HEADER,
cookies=self.COOKIES
cookies={"session": session_cookie},
)

async def get_leaderboard(self):
return await self.get(endpoint=".json")

async def close(self):
await self.session.close()


class StackAPI(BaseAPIClient):
STACK_API_URL = "https://api.stackexchange.com"
Expand Down
1 change: 0 additions & 1 deletion bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class Bot(commands.Bot):
banned_extensions = (
"advent_of_code",
"documentation",
"games",
"help",
"piston",
"reddit",
Expand Down
123 changes: 95 additions & 28 deletions bot/cogs/advent_of_code.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,90 @@
import datetime

import discord
from discord.ext import commands, tasks

from discord import app_commands
from bot.api_client import AdventOfCodeAPI
from bot.utils.misc import format_timedelta
from bot.utils.embed_handler import info, failure


class AdventOfCode(commands.Cog):
TORTOISE_LEADERBOARD_ID = "432225"
TORTOISE_LEADERBOARD_INVITE = "432225-ab5dcfc1"
TORTOISE_LEADERBOARD_ID = "4922988"
TORTOISE_LEADERBOARD_INVITE = "4922988-d5f6845a"

def __init__(self, bot):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.aoc_api = AdventOfCodeAPI(self.TORTOISE_LEADERBOARD_ID)
self.aoc_api = AdventOfCodeAPI(
leaderboard_id=self.TORTOISE_LEADERBOARD_ID,
year=2025,
)
self._leaderboard_cache = None
self.update_leaderboard_cache.start()

@tasks.loop(minutes=30)
async def update_leaderboard_cache(self):
self._leaderboard_cache = await self.aoc_api.get_leaderboard()

@commands.command()
async def invite(self, ctx):
@update_leaderboard_cache.before_loop
async def before_update_leaderboard_cache(self):
# Ensure bot is ready before the loop starts
await self.bot.wait_until_ready()


@app_commands.command(
name="aoc_invite",
description="Shows invite code to the Tortoise Advent of Code leaderboard."
)
async def invite(self, interaction: discord.Interaction):
"""Shows invite to our Tortoise Advent of Code leaderboard."""
guild = interaction.guild
member = guild.me if guild is not None else interaction.client.user

invite_embed = info(
f"Use this code to join Tortoise AoC leaderboard: **{self.TORTOISE_LEADERBOARD_INVITE}**\n\n"
"To join you can go to the AoC website: https://adventofcode.com/2020/leaderboard",
(
f"Use this code to join Tortoise AoC leaderboard: "
f"**{self.TORTOISE_LEADERBOARD_INVITE}**\n\n"
"To join you can go to the AoC website: "
"https://adventofcode.com/2025/leaderboard/private"
),
title="Tortoise AoC",
member=ctx.guild.me
member=member
)
await ctx.send(embed=invite_embed)
await interaction.response.send_message(embed=invite_embed)

@commands.command(aliases=["lb"])
async def leaderboard(self, ctx):
@app_commands.command(
name="aoc_leaderboard",
description="Show the Tortoise Advent of Code leaderboard (cached)."
)
async def leaderboard(self, interaction: discord.Interaction):
"""
Shows Tortoise leaderboard.

Leaderboard is updated each 30 minutes.
"""
guild = interaction.guild
if guild is None:
await interaction.response.send_message(
embed=failure("This command can only be used in a server."),
ephemeral=True,
)
return
if self._leaderboard_cache is None:
return await ctx.send(embed=failure("Please try again in few seconds as cache is not yet loaded."))
await interaction.response.send_message(
embed=failure("Please try again in a few seconds, cache is not yet loaded."),
ephemeral=True,
)
return

sorted_members = {
k: v for k, v in sorted(
self._leaderboard_cache["members"].items(), key=lambda item: item[1]["local_score"], reverse=True
self._leaderboard_cache["members"].items(),
key=lambda item: item[1]["local_score"],
reverse=True
)
}

leaderboard = ["```py"]
leaderboard_lines = ["```py"]
num_of_members = 10
position_counter = 0

Expand All @@ -58,33 +94,64 @@ async def leaderboard(self, ctx):
break

stars_pretty = f"{'★' + str(member_data['stars']):4}"
leaderboard.append(
f"{position_counter}. {member_data['local_score']:4}p {stars_pretty} {member_data['name']}"
leaderboard_lines.append(
f"{position_counter}. {member_data['local_score']:4}p "
f"{stars_pretty} {member_data['name']}"
)

leaderboard.append("```")
leaderboard_text = "\n".join(leaderboard)
leaderboard_lines.append("```")
leaderboard_text = "\n".join(leaderboard_lines)

embed = info(
f"{leaderboard_text}\n\nThe leaderboard is refreshed each 30 minutes.",
member=ctx.guild.me,
f"{leaderboard_text}\n\nThe leaderboard is refreshed every 30 minutes.",
member=guild.me,
title="Tortoise AoC leaderboard"
)
await ctx.send(embed=embed)
await interaction.response.send_message(embed=embed)

@commands.command()
async def aoc_countdown(self, ctx):
@app_commands.command(
name="aoc_countdown",
description="Time until the next Advent of Code challenge starts."
)
async def aoc_countdown(self, interaction: discord.Interaction):
"""Time until next challenge starts."""
guild = interaction.guild
if guild is None:
await interaction.response.send_message(
embed=failure("This command can only be used in a server."),
ephemeral=True,
)
return

utc_minus_5 = datetime.timezone(offset=datetime.timedelta(hours=-5))
now = datetime.datetime.now(tz=utc_minus_5)

if now.month != 12:
return await ctx.send(embed=failure("AoC is over!"))
await interaction.response.send_message(
embed=failure("AoC is over!"),
ephemeral=True,
)
return

current_day = now.day
end_date = datetime.datetime(year=2020, month=12, day=current_day+1, tzinfo=utc_minus_5)
end_date = datetime.datetime(
year=2025,
month=12,
day=current_day + 1,
tzinfo=utc_minus_5
)

difference = end_date - now
ends_in = format_timedelta(difference)
await ctx.send(embed=info(f"Day {current_day} ends in {ends_in}", title="Countdown", member=ctx.guild.me))

embed = info(
f"Day {current_day} ends in {ends_in}",
title="Countdown",
member=guild.me,
)
await interaction.response.send_message(embed=embed)




async def setup(bot):
Expand Down
148 changes: 0 additions & 148 deletions bot/cogs/games.py

This file was deleted.

Loading
Loading