From 13f13dc55c6adea595d565b3bf34553aeaed7869 Mon Sep 17 00:00:00 2001 From: FirePlank <44502537+FirePlank@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:19:04 +0200 Subject: [PATCH] Did the requested changes --- bot/config.py | 2 + bot/extensions/adventofcode/commands.py | 56 ++++++++++++++----------- bot/extensions/adventofcode/tasks.py | 33 ++++++++++++++- cli.py | 8 ++-- example.env | 2 + poetry.lock | 13 +++++- pyproject.toml | 1 + 7 files changed, 83 insertions(+), 32 deletions(-) diff --git a/bot/config.py b/bot/config.py index f06a98e8..494feeef 100644 --- a/bot/config.py +++ b/bot/config.py @@ -10,6 +10,8 @@ class AoC(BaseModel): channel_id: int role_id: int + leaderboard_id: int + leaderboard_code: str session_cookie: str diff --git a/bot/extensions/adventofcode/commands.py b/bot/extensions/adventofcode/commands.py index 932bb117..4879ab13 100644 --- a/bot/extensions/adventofcode/commands.py +++ b/bot/extensions/adventofcode/commands.py @@ -1,6 +1,6 @@ -import logging import re from datetime import datetime +from typing import Optional import aiohttp import discord @@ -8,16 +8,17 @@ from bs4 import BeautifulSoup from discord import app_commands from discord.ext import commands +from pydantic import BaseModel, validator from bot import core from bot.config import settings -log = logging.getLogger(__name__) - YEAR = datetime.now(tz=pytz.timezone("EST")).year -API_URL = f"https://adventofcode.com/{YEAR}/leaderboard/private/view/975452.json" -INTERVAL = 120 -AOC_REQUEST_HEADER = {"user-agent": "TWT AoC Event Bot"} + +LEADERBOARD_ID = settings.aoc.leaderboard_id +LEADERBOARD_CODE = settings.aoc.leaderboard_code +API_URL = f"https://adventofcode.com/{YEAR}/leaderboard/private/view/{LEADERBOARD_ID}.json" +AOC_REQUEST_HEADER = {"user-agent": "Tech With Tim Discord Bot https://github.com/SylteA/Discord-Bot"} AOC_SESSION_COOKIE = {"session": settings.aoc.session_cookie} @@ -29,15 +30,20 @@ def ordinal(n: int): return str(n) + suffix -class Member: - def __init__(self, results): - self.global_score = results["global_score"] - self.name = results["name"] - self.stars = results["stars"] - self.last_star_ts = results["last_star_ts"] - self.completion_day_level = results["completion_day_level"] - self.id = results["id"] - self.local_score = results["local_score"] +class Member(BaseModel): + global_score: int + name: str + stars: int + last_star_ts: int + completion_day_level: dict + id: int + local_score: int + + @validator("name", pre=True) + def set_name_to_anonymous(cls, val: Optional[str]) -> str: + if val is None: + return "Anonymous User" + return val class AdventOfCode(commands.GroupCog, group_name="aoc"): @@ -49,7 +55,7 @@ def role(self) -> discord.Role: return self.bot.get_role(settings.aoc.role_id) @app_commands.command() - async def subscribe(self, interaction: core.InteractionType) -> None: + async def subscribe(self, interaction: core.InteractionType): """Subscribe to receive notifications for new puzzles""" if self.role not in interaction.user.roles: @@ -67,7 +73,7 @@ async def subscribe(self, interaction: core.InteractionType) -> None: ) @app_commands.command() - async def unsubscribe(self, interaction: core.InteractionType) -> None: + async def unsubscribe(self, interaction: core.InteractionType): """Unsubscribe from receiving notifications for new puzzles""" if self.role in interaction.user.roles: @@ -83,7 +89,7 @@ async def unsubscribe(self, interaction: core.InteractionType) -> None: ) @app_commands.command() - async def countdown(self, interaction: core.InteractionType) -> None: + async def countdown(self, interaction: core.InteractionType): """Get the time left until the next puzzle is released""" if ( @@ -105,17 +111,17 @@ async def countdown(self, interaction: core.InteractionType) -> None: await interaction.response.send_message("Advent of Code is not currently running.", ephemeral=True) @app_commands.command(name="join") - async def join_leaderboard(self, interaction: core.InteractionType) -> None: + async def join_leaderboard(self, interaction: core.InteractionType): """Learn how to join the leaderboard""" await interaction.response.send_message( "Head over to https://adventofcode.com/leaderboard/private" - "with the code `975452-d90a48b0` to join the TWT private leaderboard!", + f"with the code `{LEADERBOARD_CODE}` to join the TWT private leaderboard!", ephemeral=True, ) @app_commands.command() - async def leaderboard(self, interaction: core.InteractionType) -> None: + async def leaderboard(self, interaction: core.InteractionType): """Get a snapshot of the TWT private AoC leaderboard""" if interaction.channel_id != settings.aoc.channel_id: @@ -130,12 +136,12 @@ async def leaderboard(self, interaction: core.InteractionType) -> None: else: resp.raise_for_status() - members = [Member(leaderboard["members"][id]) for id in leaderboard["members"]] + members = [Member(**member_data) for member_data in leaderboard["members"].values()] embed = discord.Embed( title=f"{interaction.guild.name} Advent of Code Leaderboard", - colour=discord.Colour(0x68C290), - url=f"https://adventofcode.com/{YEAR}/leaderboard/private/view/975452", + colour=0x68C290, + url=f"https://adventofcode.com/{YEAR}/leaderboard/private/view/{LEADERBOARD_ID}", ) leaderboard = { @@ -205,7 +211,7 @@ async def global_leaderboard(self, interaction: core.InteractionType, number_of_ embed = discord.Embed( title="Advent of Code Global Leaderboard", - colour=discord.Colour(0x68C290), + colour=0x68C290, url="https://adventofcode.com", description=s_desc, ) diff --git a/bot/extensions/adventofcode/tasks.py b/bot/extensions/adventofcode/tasks.py index ab0e394b..bf648918 100644 --- a/bot/extensions/adventofcode/tasks.py +++ b/bot/extensions/adventofcode/tasks.py @@ -1,5 +1,7 @@ import asyncio import datetime as dt +import logging +import traceback from datetime import datetime import aiohttp @@ -9,6 +11,9 @@ from bot import core from bot.config import settings +from bot.services import http, paste + +log = logging.getLogger(__name__) aoc_time = dt.time(hour=0, minute=0, second=1, tzinfo=pytz.timezone("EST")) YEAR = datetime.now(tz=pytz.timezone("EST")).year @@ -21,7 +26,7 @@ def __init__(self, bot: core.DiscordBot): self.bot = bot self.daily_puzzle.start() - def cog_unload(self) -> None: + def cog_unload(self): self.daily_puzzle.cancel() @property @@ -29,7 +34,7 @@ def channel(self) -> discord.TextChannel: return self.bot.get_channel(settings.aoc.channel_id) @tasks.loop(time=aoc_time) - async def daily_puzzle(self) -> None: + async def daily_puzzle(self): """Post the daily Advent of Code puzzle""" day = datetime.now(tz=pytz.timezone("EST")).day @@ -51,3 +56,27 @@ async def daily_puzzle(self) -> None: f"View it online now at {puzzle_url}. Good luck!", allowed_mentions=discord.AllowedMentions(roles=True), ) + + @daily_puzzle.error + async def daily_puzzle_error(self, error: Exception): + """Log any errors raised by the daily puzzle task""" + + content = "\n".join(traceback.format_exception(type(error), error, error.__traceback__)) + header = "Ignored exception in daily puzzle task" + + def wrap(code: str) -> str: + code = code.replace("`", "\u200b`") + return f"```py\n{code}\n```" + + if len(content) > 1024: + document = await paste.create(content) + content = wrap(content[:1024]) + f"\n\n [Full traceback]({document.url})" + else: + content = wrap(content) + + embed = discord.Embed( + title=header, description=content, color=discord.Color.red(), timestamp=discord.utils.utcnow() + ) + await discord.Webhook.from_url(url=settings.errors.webhook_url, session=http.session).send(embed=embed) + + log.error("Daily puzzle task failed", exc_info=error) diff --git a/cli.py b/cli.py index 0677de46..ab56b2fc 100644 --- a/cli.py +++ b/cli.py @@ -130,15 +130,15 @@ async def main(ctx): prefixes = ("t.",) extensions = ( "jishaku", + "bot.extensions.adventofcode", "bot.extensions.challenges", - "bot.extensions.readthedocs", - "bot.extensions.suggestions", "bot.extensions.github", - "bot.extensions.tags", "bot.extensions.levelling", "bot.extensions.persistent_roles", "bot.extensions.polls", - "bot.extensions.adventofcode", + "bot.extensions.readthedocs", + "bot.extensions.suggestions", + "bot.extensions.tags", "bot.cogs._help", "bot.cogs.clashofcode", "bot.cogs.roles", diff --git a/example.env b/example.env index 1429bf2e..296d2baa 100644 --- a/example.env +++ b/example.env @@ -2,6 +2,8 @@ # --- AoC AOC__CHANNEL_ID=0 AOC__ROLE_ID=0 +AOC__LEADERBOARD_ID = 975452 +AOC__LEADERBOARD_CODE = 975452-d90a48b0 AOC__SESSION_COOKIE=... # --- Bots diff --git a/poetry.lock b/poetry.lock index de4938f5..e673dfe8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1073,6 +1073,17 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + [[package]] name = "pyyaml" version = "6.0" @@ -1320,4 +1331,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "7ebebbfc6febbd084171f90fe33a92e8a460fc1dfcc3bf5080bc7ca389469eba" +content-hash = "db36dee59de44a2fbc6f503360b58f91c44e53b34bd36a840f7f5a81d2cc7a77" diff --git a/pyproject.toml b/pyproject.toml index 6a5af0bf..1a54eb3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ jishaku = {extras = ["procinfo", "profiling"], version = "^2.5.1"} beautifulsoup4 = "^4.12.2" tabulate = "^0.9.0" pillow = "^10.1.0" +pytz = "^2023.3.post1" [tool.poetry.group.dev.dependencies] isort = "^5.12.0"