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

advent: display leaderboards as a colour image #212

Merged
merged 12 commits into from
Dec 2, 2024
98 changes: 95 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ aiohttp = "^3.9"
aio-mc-rcon = "^3.2.0"
PyYAML = "^6.0"
mcstatus = "^11.1.0"
pillow = "^11.0.0"

[tool.poetry.scripts]
botdev = "dev.cli:main"
Expand Down
140 changes: 114 additions & 26 deletions uqcsbot/advent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -20,12 +20,16 @@
InvalidHTTPSCode,
ADVENT_DAYS,
CACHE_TIME,
HL_COLOUR,
parse_leaderboard_column_string,
print_leaderboard,
render_leaderboard_to_image,
render_leaderboard_to_text,
)

# Leaderboard API URL with placeholders for year and code.
LEADERBOARD_URL = "https://adventofcode.com/{year}/leaderboard/private/view/{code}.json"
LEADERBOARD_VIEW_URL = "https://adventofcode.com/{year}/leaderboard/private/view/{code}"
LEADERBOARD_URL = LEADERBOARD_VIEW_URL + ".json"

# UQCS leaderboard ID.
UQCS_LEADERBOARD = 989288
Expand Down Expand Up @@ -134,6 +138,111 @@
}


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 out of {len(self.all_members)}"
)
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
TheOnlyMrCat marked this conversation as resolved.
Show resolved Hide resolved
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).
Expand Down Expand Up @@ -502,13 +611,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:
Expand All @@ -525,25 +628,10 @@ async def leaderboard_command(
)
return

scoreboard_file = io.BytesIO(
bytes(
print_leaderboard(
parse_leaderboard_column_string(leaderboard_style, self.bot),
members,
day,
),
"utf-8",
)
)
await interaction.edit_original_response(
content=message,
attachments=[
discord.File(
scoreboard_file,
filename=f"advent_{code}_{year}_{day}.txt",
)
],
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(
Expand Down
Binary file added uqcsbot/static/NotoSansMono-Regular.ttf
Binary file not shown.
Loading