Stream-Übersicht aller laufenden Vetos und Matches
/stream zeigt alle aktiven Matches als Karten (Veto mit Map-Board, Match mit Live-Stand und Serie), dazu offene Lobbys und Ergebnisse. Raster passt sich der Anzahl an, ab 7 Matches wird geblättert. Live per Socket, optional mit STREAM_KEY geschützt, ohne Codes oder Verbindungsdaten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54bfe198d8
commit
8996294d7e
11 files changed
+495
-3
No files matched your search
@@ -561,6 +561,76 @@ export class MatchService extends EventEmitter {
|
||||
this.emit('changed', matchId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Übersicht aller aktiven Matches für die Stream-Seite: laufende Vetos und Matches mit Details,
|
||||
* Lobbys und kürzlich beendete Matches knapp. Enthält nur, was Zuschauer sehen dürfen –
|
||||
* keine Codes, keine Verbindungsdaten.
|
||||
*/
|
||||
streamOverview({ resultsWithinMs = 3 * 3_600_000 } = {}) {
|
||||
const rows = this.db.prepare('SELECT * FROM matches ORDER BY created_at DESC LIMIT 200').all();
|
||||
const now = this.now();
|
||||
const active = [];
|
||||
const upcoming = [];
|
||||
const results = [];
|
||||
|
||||
for (const m of rows) {
|
||||
const pool = this.pools[m.mode];
|
||||
const maps = JSON.parse(m.maps);
|
||||
const mapInfo = (id) => {
|
||||
const map = maps.find((x) => x.id === id);
|
||||
return { id, name: map?.name ?? id, image: map?.image ?? null, color: map?.color ?? null };
|
||||
};
|
||||
const base = {
|
||||
id: m.id,
|
||||
teams: { A: m.team_a_name, B: m.team_b_name },
|
||||
mode: m.mode,
|
||||
modeLabel: pool?.label ?? m.mode,
|
||||
format: m.format,
|
||||
status: m.status,
|
||||
statusLabel: MATCH_STATUS[m.status],
|
||||
};
|
||||
|
||||
if (m.status === 'lobby') {
|
||||
const readiness = this.teamReadiness(m);
|
||||
upcoming.push({ ...base, ready: { A: readiness.A.ready, B: readiness.B.ready } });
|
||||
continue;
|
||||
}
|
||||
const seriesRows = this.seriesMaps(m.id);
|
||||
const series = seriesRows.map((s) => {
|
||||
const live = s.live ? JSON.parse(s.live) : null;
|
||||
return {
|
||||
number: s.number,
|
||||
...mapInfo(s.map),
|
||||
pickedBy: s.picked_by,
|
||||
status: s.status,
|
||||
score: { A: s.score_a, B: s.score_b },
|
||||
winner: s.winner,
|
||||
live: live && { status: live.status, sides: live.sides, roundsPlayed: live.rounds.length },
|
||||
};
|
||||
});
|
||||
const item = { ...base, seriesScore: seriesScore(seriesRows), currentMapNumber: m.current_map_number, series };
|
||||
|
||||
if (CLOSED_STATUSES.includes(m.status)) {
|
||||
if (m.status === 'finished' && now - (m.finished_at ?? 0) < resultsWithinMs) results.push(item);
|
||||
continue;
|
||||
}
|
||||
if (m.status === 'veto') {
|
||||
const veto = this.vetoState(m);
|
||||
item.veto = {
|
||||
current: veto.current,
|
||||
maps: veto.maps.map((x) => ({ ...mapInfo(x.id), status: x.status, by: x.by })),
|
||||
stepsDone: veto.steps.filter((x) => x.done).length,
|
||||
stepsTotal: veto.steps.length,
|
||||
};
|
||||
}
|
||||
active.push(item);
|
||||
}
|
||||
// Laufende Matches zuerst, dann Vetos, dann Serverstart – jeweils älteste zuerst
|
||||
const order = { live: 0, veto: 1, server_setup: 2 };
|
||||
active.sort((a, b) => (order[a.status] - order[b.status]) || a.id.localeCompare(b.id));
|
||||
return { active, upcoming: upcoming.slice(0, 8), results: results.slice(0, 8), updatedAt: now };
|
||||
}
|
||||
|
||||
listMatches() {
|
||||
const rows = this.db.prepare('SELECT * FROM matches ORDER BY created_at DESC LIMIT 200').all();
|
||||
const counts = this.db.prepare('SELECT team, COUNT(*) AS n FROM players WHERE match_id = ? AND active = 1 GROUP BY team');
|
||||
|
||||
Reference in new issue
Block a user