diff --git a/uqcsbot/advent.py b/uqcsbot/advent.py index 7ff57c4..3136b26 100644 --- a/uqcsbot/advent.py +++ b/uqcsbot/advent.py @@ -2,7 +2,7 @@ import os from datetime import datetime from random import choices -from typing import Callable, Dict, Iterable, List, Optional, Literal +from typing import Any, Callable, Dict, Iterable, List, Optional, Literal import requests from requests.exceptions import RequestException @@ -20,6 +20,7 @@ InvalidHTTPSCode, ADVENT_DAYS, CACHE_TIME, + HL_COLOUR, parse_leaderboard_column_string, print_leaderboard, render_leaderboard_to_image, @@ -137,6 +138,109 @@ } +class LeaderboardView(discord.ui.View): + INITIAL_VISIBLE_ROWS = 20 + + def __init__( + self, + bot: UQCSBot, + code: int, + year: int, + day: Optional[Day], + members: list[Member], + leaderboard_style: str, + sortby: Optional[SortingMethod], + ): + super().__init__(timeout=300) + # constant within one embed + self.bot = bot + self.code = code + self.year = year + self.day = day + self.all_members = members + self.leaderboard_style = leaderboard_style + self.sortby = sortby + self.timestamp = datetime.now() + + # can be changed by interaction + self.visible_members = members[: self.INITIAL_VISIBLE_ROWS] + self.show_text = False + + @property + def is_truncated(self): + return len(self.visible_members) < len(self.all_members) + + def make_message_arguments(self) -> Dict[str, Any]: + view_url = LEADERBOARD_VIEW_URL.format(year=self.year, code=self.code) + + leaderboard = print_leaderboard( + parse_leaderboard_column_string(self.leaderboard_style, self.bot), + self.visible_members, + self.day, + ) + + title = ( + "Advent of Code UQCS Leaderboard" + if self.code == UQCS_LEADERBOARD + else f"Advent of Code Leaderboard `{self.code}`" + ) + title = f":star: {title} :trophy:" + if self.day: + title += f" \u2014 Day {self.day}" + + notes: List[str] = [] + if self.day: + notes.append(f"sorted by {self.sortby}") + if self.is_truncated: + notes.append(f"top {len(self.visible_members)} shown") + body = f"({", ".join(notes)})" if notes else "" + + basename = f"advent_{self.code}_{self.year}_{self.day}" + + embed = discord.Embed( + title=title, + url=view_url, + description=body, + colour=discord.Colour.from_str(HL_COLOUR), + timestamp=self.timestamp, + ) + + if not self.show_text: + scoreboard_image = render_leaderboard_to_image(leaderboard) + file = discord.File(io.BytesIO(scoreboard_image), basename + ".png") + embed.set_image(url=f"attachment://{file.filename}") + else: + scoreboard_text = render_leaderboard_to_text(leaderboard) + file = discord.File( + io.BytesIO(scoreboard_text.encode("utf-8")), basename + ".txt" + ) + + self.show_all_interaction.disabled = not self.is_truncated + self.get_text_interaction.label = ( + "Show as image" if self.show_text else "Show as text" + ) + + return { + "attachments": [file], + "embed": embed, + "view": self, + } + + @discord.ui.button(label="Show all", style=discord.ButtonStyle.gray) + async def show_all_interaction( + self, inter: discord.Interaction, btn: discord.ui.Button["LeaderboardView"] + ): + self.visible_members = self.all_members + await inter.response.edit_message(**self.make_message_arguments()) + + @discord.ui.button(label="Show text", style=discord.ButtonStyle.gray) + async def get_text_interaction( + self, inter: discord.Interaction, btn: discord.ui.Button["LeaderboardView"] + ): + self.show_text = not self.show_text + await inter.response.edit_message(**self.make_message_arguments()) + + class Advent(commands.Cog): """ All of the commands related to Advent of Code (AOC). @@ -505,13 +609,7 @@ async def leaderboard_command( ) return - if code == UQCS_LEADERBOARD: - message = ":star: *Advent of Code UQCS Leaderboard* :trophy:" - else: - message = f":star: *Advent of Code Leaderboard {code}* :trophy:" - if day: - message += f"\n:calendar: *Day {day}* (Sorted By {sortby})" members = [member for member in members if member.attempted_day(day)] members.sort(key=lambda m: sorting_functions_for_day[sortby](m, day)) else: @@ -522,38 +620,16 @@ async def leaderboard_command( ] members.sort(key=sorting_functions_for_month[sortby]) - view_url = LEADERBOARD_VIEW_URL.format(year=year, code=code) - message += f"\n{view_url}" - if not members: await interaction.edit_original_response( content="This leaderboard contains no people." ) return - leaderboard = print_leaderboard( - parse_leaderboard_column_string(leaderboard_style, self.bot), - members, - day, - ) - - scoreboard_text = render_leaderboard_to_text(leaderboard) - scoreboard_image = render_leaderboard_to_image(leaderboard) - basename = f"advent_{code}_{year}_{day}" - - await interaction.edit_original_response( - content=message, - attachments=[ - discord.File( - io.BytesIO(scoreboard_text.encode("utf-8")), - filename=f"{basename}.txt", - ), - discord.File( - io.BytesIO(scoreboard_image), - filename=f"{basename}.png", - ), - ], + view = LeaderboardView( + self.bot, code, year, day, members, leaderboard_style, sortby ) + await interaction.edit_original_response(**view.make_message_arguments()) @advent_command_group.command(name="register") @app_commands.describe(