Files
2026-04-22 00:49:43 -04:00

224 lines
6.9 KiB
Python

import discord
from dataclasses import dataclass
# ── Data class ─────────────────────────────────────────────────────────────────
@dataclass
class MatchSummary:
match_id: str
champion: str
won: bool
kills: int
deaths: int
assists: int
cs: int
vision: int
damage: int
gold: int
duration: int # seconds
queue_id: int
items: list[int]
spell1_id: int
spell2_id: int
game_name: str
tag_line: str
patch: str
# ── Parser ─────────────────────────────────────────────────────────────────────
def parse_match(
match: dict, puuid: str, game_name: str, tag_line: str, patch: str
) -> MatchSummary | None:
"""Extract a clean MatchSummary from raw Riot API match data."""
participants = match["info"]["participants"]
player = next((p for p in participants if p["puuid"] == puuid), None)
if not player:
return None
return MatchSummary(
match_id=match["metadata"]["matchId"],
champion=player["championName"],
won=player["win"],
kills=player["kills"],
deaths=player["deaths"],
assists=player["assists"],
cs=player["totalMinionsKilled"] + player["neutralMinionsKilled"],
vision=player["visionScore"],
damage=player["totalDamageDealtToChampions"],
gold=player["goldEarned"],
duration=match["info"]["gameDuration"],
queue_id=match["info"]["queueId"],
items=[player[f"item{i}"] for i in range(7) if player.get(f"item{i}", 0) != 0],
spell1_id=player["summoner1Id"],
spell2_id=player["summoner2Id"],
game_name=game_name,
tag_line=tag_line,
patch=patch,
)
# ── Formatters ─────────────────────────────────────────────────────────────────
def fmt_kda(kills: int, deaths: int, assists: int) -> str:
ratio = (kills + assists) / max(deaths, 1)
return f"{kills}/{deaths}/{assists} ({ratio:.2f} KDA)"
def fmt_duration(seconds: int) -> str:
m, s = divmod(seconds, 60)
return f"{m}m {s}s"
def fmt_cs(cs: int, duration: int) -> str:
per_min = round(cs / max(duration / 60, 1), 1)
return f"{cs} ({per_min}/min)"
def fmt_damage(damage: int) -> str:
return f"{damage:,}"
def fmt_gold(gold: int) -> str:
return f"{gold:,} g"
def fmt_rank_entry(entry: dict | None) -> str:
if not entry:
return "Unranked"
tier = entry["tier"].capitalize()
division = entry["rank"]
lp = entry["leaguePoints"]
wins = entry["wins"]
losses = entry["losses"]
total = wins + losses
wr = f"{round(wins / total * 100)}%" if total > 0 else "N/A"
return f"{tier} {division} — {lp} LP ({wr} WR)"
QUEUE_NAMES = {
420: "Ranked Solo/Duo",
440: "Ranked Flex",
450: "ARAM",
400: "Normal Draft",
430: "Normal Blind",
900: "URF",
1020: "One for All",
1300: "Nexus Blitz",
}
SUMMONER_SPELL_NAMES = {
1: "Cleanse",
3: "Exhaust",
4: "Flash",
6: "Ghost",
7: "Heal",
11: "Smite",
12: "Teleport",
13: "Clarity",
14: "Ignite",
21: "Barrier",
32: "Mark",
}
def fmt_queue(queue_id: int) -> str:
return QUEUE_NAMES.get(queue_id, "Other")
def fmt_spells(spell1_id: int, spell2_id: int) -> str:
s1 = SUMMONER_SPELL_NAMES.get(spell1_id, f"#{spell1_id}")
s2 = SUMMONER_SPELL_NAMES.get(spell2_id, f"#{spell2_id}")
return f"{s1} / {s2}"
# ── Embed builders ─────────────────────────────────────────────────────────────
def build_match_embed(s: MatchSummary) -> discord.Embed:
"""Full match embed with all stats."""
result = "✅ Victory" if s.won else "❌ Defeat"
color = 0x2ECC71 if s.won else 0xE74C3C
embed = discord.Embed(
title=f"{result} — {s.champion}",
description=f"**{fmt_queue(s.queue_id)}** · {fmt_duration(s.duration)}",
color=color,
)
embed.add_field(
name="⚔️ KDA", value=fmt_kda(s.kills, s.deaths, s.assists), inline=True
)
embed.add_field(name="🌾 CS", value=fmt_cs(s.cs, s.duration), inline=True)
embed.add_field(name="👁️ Vision", value=str(s.vision), inline=True)
embed.add_field(name="💥 Damage", value=fmt_damage(s.damage), inline=True)
embed.add_field(name="💰 Gold", value=fmt_gold(s.gold), inline=True)
embed.add_field(
name="🔮 Spells", value=fmt_spells(s.spell1_id, s.spell2_id), inline=True
)
embed.add_field(name="👤 Player", value=f"{s.game_name}#{s.tag_line}", inline=False)
embed.set_thumbnail(
url=f"https://ddragon.leagueoflegends.com/cdn/{s.patch}/img/champion/{s.champion}.png"
)
embed.set_footer(text=f"Match ID: {s.match_id}")
return embed
def build_compact_embed(s: MatchSummary) -> discord.Embed:
"""Minimal one-liner embed, good for showing many matches at once."""
result = "✅" if s.won else "❌"
color = 0x2ECC71 if s.won else 0xE74C3C
kda = fmt_kda(s.kills, s.deaths, s.assists)
cs = fmt_cs(s.cs, s.duration)
embed = discord.Embed(
description=(
f"{result} **{s.champion}** · {fmt_queue(s.queue_id)} · {fmt_duration(s.duration)}\n"
f"⚔️ {kda} · 🌾 {cs} · 💥 {fmt_damage(s.damage)}"
),
color=color,
)
embed.set_thumbnail(
url=f"https://ddragon.leagueoflegends.com/cdn/{s.patch}/img/champion/{s.champion}.png"
)
return embed
def build_match_list_embed(
summaries: list[MatchSummary], game_name: str, tag_line: str
) -> discord.Embed:
"""Single embed summarizing multiple matches at once."""
total = len(summaries)
wins = sum(1 for s in summaries if s.won)
losses = total - wins
wr = f"{round(wins / total * 100)}%" if total > 0 else "N/A"
avg_kda = sum((s.kills + s.assists) / max(s.deaths, 1) for s in summaries) / max(
total, 1
)
embed = discord.Embed(
title=f"📋 Last {total} matches — {game_name}#{tag_line}",
description=f"**{wins}W / {losses}L** · {wr} WR · {avg_kda:.2f} avg KDA",
color=0x1A78C2,
)
for s in summaries:
result = "✅" if s.won else "❌"
embed.add_field(
name=f"{result} {s.champion} — {fmt_queue(s.queue_id)}",
value=(
f"⚔️ {fmt_kda(s.kills, s.deaths, s.assists)} · "
f"🌾 {fmt_cs(s.cs, s.duration)} · "
f"⏱️ {fmt_duration(s.duration)}"
),
inline=False,
)
return embed