From 60e91fb4e9812e8396b50cc560b05aa2fcb30e1b Mon Sep 17 00:00:00 2001 From: NotSoToothless <67082114+FURRO404@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:04:40 -0700 Subject: [PATCH] meowww (#1336) --- BOT/autologging.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++ web/README.md | 1 + 2 files changed, 97 insertions(+) create mode 100644 web/README.md diff --git a/BOT/autologging.py b/BOT/autologging.py index 10b68e5..e039db8 100644 --- a/BOT/autologging.py +++ b/BOT/autologging.py @@ -33,6 +33,8 @@ _bot: Optional[discord.Client] = None _sent_channels_by_session: dict[str, set[int]] = {} # session_id -> lock guarding the one-time PNG render. _render_locks: dict[str, asyncio.Lock] = {} +# Cap concurrent MP4 renders (CPU/memory heavy) across all "Generate Video" clicks. +_video_render_sem: asyncio.Semaphore = asyncio.Semaphore(2) def set_bot(bot: discord.Client) -> None: @@ -160,6 +162,14 @@ def build_tss_scoreboard_view(session_id: str) -> discord.ui.View: url=f"https://tss.pawjob.us/games/{session_id}", emoji="🌐", )) + video_btn = discord.ui.Button(label="Generate Video", style=discord.ButtonStyle.blurple, emoji="🎬") + + async def _video_cb(interaction: discord.Interaction) -> None: + await handle_view_video(interaction, session_id) + + video_btn.callback = _video_cb + view.add_item(video_btn) + chat_log, battle_log = _load_match_logs(session_id) battle_btn = discord.ui.Button(label="Battle Log", style=discord.ButtonStyle.green) @@ -184,6 +194,92 @@ def build_tss_scoreboard_view(session_id: str) -> discord.ui.View: return view +def _find_replay_data_path(session_id: str) -> Optional[Path]: + """Locate a session's stored replay file, preferring the gzipped form.""" + session_dir = REPLAYS_TSS_DIR / session_id + for name in ("replay_data.json.gz", "replay_data.json"): + candidate = session_dir / name + if candidate.is_file(): + return candidate + return None + + +async def handle_view_video(interaction: discord.Interaction, session_id: str) -> None: + """Callback for 'Generate Video' — render the replay to MP4 (once, cached) and + send it ephemerally, falling back to the website link if it can't be uploaded.""" + web_url = f"https://tss.pawjob.us/games/{session_id}" + try: + try: + await interaction.response.defer(thinking=True, ephemeral=True) + except discord.HTTPException: + return + + replay_path = _find_replay_data_path(session_id) + if replay_path is None: + await interaction.followup.send("No replay data is available for this match.", ephemeral=True) + return + + video_path = REPLAYS_TSS_DIR / session_id / "replay_video.mp4" + + # Render once and cache on disk; serve the cached file on later clicks. + if not video_path.exists() or video_path.stat().st_size == 0: + if _video_render_sem.locked(): + await interaction.followup.send( + "Too many videos are rendering right now — try again in a moment.", + ephemeral=True, + ) + return + + from .render_replay import load_gob_file, render_gob + + def _generate() -> None: + d = load_gob_file(replay_path) + render_gob(d, video_path) + + try: + log.info("[TSS-AUTOLOG] video render start %s", session_id) + async with _video_render_sem: + await asyncio.get_event_loop().run_in_executor(None, _generate) + log.info("[TSS-AUTOLOG] video render done %s", session_id) + except Exception as e: # noqa: BLE001 - report any render failure to the user + log.exception("[TSS-AUTOLOG] video render failed %s", session_id) + if video_path.exists(): + video_path.unlink(missing_ok=True) # don't cache a broken/partial file + await interaction.followup.send(f"Video generation failed: {str(e)[:1800]}", ephemeral=True) + return + + if not video_path.exists() or video_path.stat().st_size == 0: + await interaction.followup.send("Video generation produced no output.", ephemeral=True) + return + + guild = interaction.guild + max_size = guild.filesize_limit if guild else 25 * 1_048_576 + if video_path.stat().st_size > max_size: + await interaction.followup.send( + f"The rendered video is too large to upload here. Watch it on the website instead: {web_url}", + ephemeral=True, + ) + return + + try: + await interaction.followup.send( + content=f"Replay video for `{session_id}`. Interactive replay: {web_url}", + file=discord.File(video_path), + ephemeral=True, + ) + except discord.HTTPException: + await interaction.followup.send( + f"Couldn't upload the video here. Watch it on the website instead: {web_url}", + ephemeral=True, + ) + except Exception: # noqa: BLE001 - never let a button callback raise unhandled + log.exception("[TSS-AUTOLOG] unexpected error in handle_view_video %s", session_id) + try: + await interaction.followup.send("Something went wrong generating the video.", ephemeral=True) + except discord.HTTPException: + pass + + async def _render_scoreboard(game: dict[str, Any], session_id: str, bar_color: str, lang_column: str) -> Optional[Path]: """Render (once, cached on disk) the scoreboard for a session/color/language.""" model = transform.build_scoreboard_model(game, lang_column) diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..21684dd --- /dev/null +++ b/web/README.md @@ -0,0 +1 @@ +FOR TSSBOT WEBSITE, READ ~/GitHub/tssbot.web OR on server ~/tssbot.web, clippi is fucking annoying with this shit \ No newline at end of file