Auto merge dev → main (#1332)

* feat(tssbot): build_match_logs + match_logs persistence

* feat(tssbot): create match_logs table and write logs at ingest

* feat(tssbot): one-time match_logs backfill script

* feat(srebot): persist chat/battle logs to match_logs (parity, no backfill)

* feat(tssbot): Battle/Chat Log buttons on Discord scoreboards
This commit is contained in:
NotSoToothless
2026-06-18 01:02:59 -07:00
committed by GitHub
parent 53e3db9159
commit 32e747212f
6 changed files with 370 additions and 0 deletions
+71
View File
@@ -11,7 +11,9 @@ only receives one scoreboard for a given game.
from __future__ import annotations
import asyncio
import json
import logging
import sqlite3
from pathlib import Path
from typing import Any, Optional
@@ -98,6 +100,53 @@ def _bar_color(game: dict[str, Any], guild_id: int) -> str:
# Scoreboard view + render/send
# ---------------------------------------------------------------------------
def _load_match_logs(session_id: str) -> tuple[list[str], list[str]]:
"""Read (chat_log, battle_log) for a session from match_logs; empty on miss."""
from .storage import TSS_BATTLES_DB_PATH
try:
conn = sqlite3.connect(TSS_BATTLES_DB_PATH)
try:
row = conn.execute(
"SELECT chat_log_json, battle_log_json FROM match_logs WHERE session_id = ?",
(str(session_id),),
).fetchone()
finally:
conn.close()
except Exception:
return [], []
if not row:
return [], []
chat = json.loads(row[0]) if row[0] else []
battle = json.loads(row[1]) if row[1] else []
return chat, battle
async def _send_log(interaction: discord.Interaction, lines: list[str], title: str) -> None:
"""Send a log as ephemeral diff-formatted message(s), chunked under Discord's limit."""
await interaction.response.defer(thinking=True, ephemeral=True)
if not lines:
await interaction.followup.send("No log available for this match.", ephemeral=True)
return
chunks: list[str] = []
chunk: list[str] = []
length = 0
for line in lines:
if length + len(line) + 1 > 1800:
chunks.append("\n".join(chunk))
chunk = [line]
length = len(line) + 1
else:
chunk.append(line)
length += len(line) + 1
if chunk:
chunks.append("\n".join(chunk))
first = True
for c in chunks:
content = (f"**{title}**\n" if first else "") + f"```diff\n{c}\n```"
await interaction.followup.send(content, ephemeral=True)
first = False
def build_tss_scoreboard_view(session_id: str) -> discord.ui.View:
"""Link buttons under a scoreboard: in-game replay + the TSS website."""
view = discord.ui.View(timeout=None)
@@ -110,6 +159,28 @@ def build_tss_scoreboard_view(session_id: str) -> discord.ui.View:
label="View on Website", style=discord.ButtonStyle.link,
url=f"https://tss.pawjob.us/games/{session_id}", emoji="🌐",
))
chat_log, battle_log = _load_match_logs(session_id)
battle_btn = discord.ui.Button(label="Battle Log", style=discord.ButtonStyle.green)
async def _battle_cb(interaction: discord.Interaction) -> None:
_, b = _load_match_logs(session_id)
await _send_log(interaction, b, f"Battle Log · {session_id}")
battle_btn.callback = _battle_cb
view.add_item(battle_btn)
if chat_log:
chat_btn = discord.ui.Button(label="Chat Log", style=discord.ButtonStyle.green)
async def _chat_cb(interaction: discord.Interaction) -> None:
c, _ = _load_match_logs(session_id)
await _send_log(interaction, c, f"Chat Log · {session_id}")
chat_btn.callback = _chat_cb
view.add_item(chat_btn)
return view