diff --git a/uqcsbot/advent.py b/uqcsbot/advent.py index a711934..30658f8 100644 --- a/uqcsbot/advent.py +++ b/uqcsbot/advent.py @@ -1,6 +1,6 @@ import io import os -from datetime import datetime +from datetime import datetime, timedelta from random import choices from typing import Any, Callable, Dict, Iterable, List, Optional, Literal import requests @@ -141,7 +141,7 @@ class LeaderboardView(discord.ui.View): TRUNCATED_COUNT = 20 - TIMEOUT = 180 # seconds + TIMEOUT = timedelta(hours=24).total_seconds() def __init__( self, @@ -212,7 +212,7 @@ def make_message_arguments(self) -> Dict[str, Any]: ) leaderboard = self._build_leaderboard(self._visible_members) - scoreboard_image = render_leaderboard_to_image(leaderboard) + scoreboard_image = render_leaderboard_to_image(tuple(leaderboard)) file = discord.File(io.BytesIO(scoreboard_image), self.basename + ".png") embed.set_image(url=f"attachment://{file.filename}") diff --git a/uqcsbot/utils/advent_utils.py b/uqcsbot/utils/advent_utils.py index a7d294b..9b8afa6 100644 --- a/uqcsbot/utils/advent_utils.py +++ b/uqcsbot/utils/advent_utils.py @@ -1,18 +1,21 @@ from typing import ( Any, DefaultDict, + Iterable, List, Literal, Dict, Optional, Callable, - NamedTuple, Tuple, cast, ) +from dataclasses import dataclass from collections import defaultdict +from collections.abc import Hashable from datetime import datetime, timedelta from zoneinfo import ZoneInfo +from functools import lru_cache from io import BytesIO import PIL.Image @@ -33,8 +36,16 @@ Times = Dict[Star, Seconds] Delta = Optional[Seconds] Json = Dict[str, Any] + Colour = str -ColourFragment = NamedTuple("ColourFragment", [("text", str), ("colour", Colour)]) + + +@dataclass(frozen=True) +class ColourFragment(Hashable): + text: str + colour: Colour + + Leaderboard = list[str | ColourFragment] # Puzzles are unlocked at midnight EST. @@ -408,7 +419,7 @@ def render_leaderboard_to_text(leaderboard: Leaderboard) -> str: def _isolate_leaderboard_layers( - leaderboard: Leaderboard, + leaderboard: Iterable[str | ColourFragment], ) -> Tuple[str, Dict[Colour, str]]: """ Given a leaderboard made up of coloured fragments, split the @@ -437,7 +448,8 @@ def _isolate_leaderboard_layers( return spaces_str, cast(Dict[str, Any], layers) -def render_leaderboard_to_image(leaderboard: Leaderboard) -> bytes: +@lru_cache(maxsize=16) +def render_leaderboard_to_image(leaderboard: Tuple[str | ColourFragment, ...]) -> bytes: spaces, layers = _isolate_leaderboard_layers(leaderboard) # NOTE: font choice should support as wide a range of glyphs as possible, @@ -462,7 +474,7 @@ def render_leaderboard_to_image(leaderboard: Leaderboard) -> bytes: buf = BytesIO() img.save(buf, format="PNG", optimize=True) - return buf.getvalue() # XXX: why do we need to getvalue()? + return buf.getvalue() def build_leaderboard(