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:
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user