Discord-Webhook für Streamer-Meldungen mit wählbaren Ereignissen
Meldungen zu Veto, Match-Start, Map- und Serienende, Matchende und Serverfehlern gehen an DISCORD_WEBHOOK_URL. Im Admin-Bereich wählt man, welche Ereignisse gemeldet werden, und kann eine Testmeldung senden. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5a332655b2
commit
9af83cb47c
12 files changed
+254
-2
No files matched your search
@@ -4,6 +4,7 @@ import { formatCode, hashToken, normalizeCode, randomCode, randomId, randomToken
|
||||
import { applyLogLines, initialLiveState, seriesScore, splitLogBody } from './liveScore.js';
|
||||
import { trimLiveState } from './rounds.js';
|
||||
import { sanitizeConsoleText } from './servers/cs2Config.js';
|
||||
import { NOTIFY_EVENTS } from './discord.js';
|
||||
|
||||
export class HttpError extends Error {
|
||||
constructor(status, message, code = 'error') {
|
||||
@@ -41,11 +42,12 @@ function cleanSteamId(value) {
|
||||
}
|
||||
|
||||
export class MatchService extends EventEmitter {
|
||||
constructor({ db, pools, config }) {
|
||||
constructor({ db, pools, config, notifier = { enabled: false, send: async () => {} } }) {
|
||||
super();
|
||||
this.db = db;
|
||||
this.pools = pools;
|
||||
this.config = config;
|
||||
this.notifier = notifier;
|
||||
this.presence = () => new Set();
|
||||
}
|
||||
|
||||
@@ -339,6 +341,7 @@ export class MatchService extends EventEmitter {
|
||||
.run(firstTeam, this.now(), this.now(), match.id);
|
||||
const how = match.veto_start === 'coin' ? 'per Münzwurf ausgelost' : 'vom Organisator festgelegt';
|
||||
this.log(match.id, 'veto', `Veto gestartet – ${this.teamName(match, firstTeam)} beginnt (${how})`);
|
||||
this.notify('veto_start', match.id, `Veto gestartet: ${this.matchTitle(match.id)}, ${this.teamName(match, firstTeam)} beginnt`);
|
||||
})();
|
||||
this.changed(viewer.matchId);
|
||||
}
|
||||
@@ -516,6 +519,8 @@ export class MatchService extends EventEmitter {
|
||||
const winner = state.winner ? this.teamName(match, state.winner) : 'Unentschieden';
|
||||
this.log(matchId, 'score',
|
||||
`Map ${row.number} (${this.mapName(match, row.map)}) beendet: ${match.team_a_name} ${state.score.A}:${state.score.B} ${match.team_b_name} – Sieger: ${winner}`);
|
||||
this.notify('map_end', matchId, `${this.matchTitle(matchId)}
|
||||
Map ${row.number} (${this.mapName(match, row.map)}) beendet: ${state.score.A}:${state.score.B}, Sieger ${winner}`);
|
||||
}
|
||||
})();
|
||||
if (ended) {
|
||||
@@ -524,6 +529,8 @@ export class MatchService extends EventEmitter {
|
||||
const needed = Math.floor(BEST_OF[ended.match.format] / 2) + 1;
|
||||
const decided = score.A >= needed ? 'A' : score.B >= needed ? 'B' : null;
|
||||
if (decided) this.log(matchId, 'score', `Serie entschieden: ${this.teamName(ended.match, decided)} gewinnt ${score[decided]}:${score[decided === 'A' ? 'B' : 'A']}`);
|
||||
if (decided) this.notify('series_end', matchId, `${this.matchTitle(matchId)}
|
||||
Serie entschieden: ${this.teamName(ended.match, decided)} gewinnt ${score[decided]}:${score[decided === 'A' ? 'B' : 'A']}`);
|
||||
const hasNext = maps.some((m) => m.number === ended.row.number + 1);
|
||||
this.changed(matchId);
|
||||
this.emit('mapEnded', { matchId, number: ended.row.number, decided, hasNext });
|
||||
@@ -555,6 +562,8 @@ export class MatchService extends EventEmitter {
|
||||
this.db.prepare("UPDATE series_maps SET status = 'done' WHERE match_id = ? AND status IN ('live','next')").run(match.id);
|
||||
}
|
||||
this.log(match.id, 'status', message ?? (status === 'finished' ? 'Match abgeschlossen' : 'Match abgebrochen'));
|
||||
this.notify('match_closed', match.id, `${this.matchTitle(match.id)}
|
||||
${message ?? (status === 'finished' ? 'Match abgeschlossen' : 'Match abgebrochen')}`);
|
||||
this.setStatus(match.id, status);
|
||||
this.emit('closed', match.id);
|
||||
}
|
||||
@@ -569,6 +578,45 @@ export class MatchService extends EventEmitter {
|
||||
this.db.prepare('UPDATE matches SET last_activity_at = ? WHERE id = ?').run(this.now(), matchId);
|
||||
}
|
||||
|
||||
/** Titel für Meldungen, z. B. „Team A vs. Team B (BO3)“. */
|
||||
matchTitle(matchId) {
|
||||
const m = this.getMatch(matchId);
|
||||
return `${m.team_a_name} vs. ${m.team_b_name} (${m.format.toUpperCase()})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung an den Discord-Webhook, falls konfiguriert. Ohne Stream-Schlüssel gehört die Übersicht
|
||||
* des Matches dazu, mit Schlüssel entfällt der Link, damit der Schlüssel nicht in den Kanal gelangt.
|
||||
*/
|
||||
/** Discord-Meldungen: der Webhook muss eingerichtet und das Ereignis ausgewählt sein. */
|
||||
notificationSettings() {
|
||||
const row = this.db.prepare("SELECT value FROM settings WHERE key = 'discord_events'").get();
|
||||
let stored = {};
|
||||
try {
|
||||
stored = row ? JSON.parse(row.value) : {};
|
||||
} catch {
|
||||
stored = {};
|
||||
}
|
||||
const events = Object.fromEntries(NOTIFY_EVENTS.map(({ key }) => [key, stored[key] !== false]));
|
||||
return { enabled: this.notifier.enabled, events, catalog: NOTIFY_EVENTS };
|
||||
}
|
||||
|
||||
setNotificationEvents(input) {
|
||||
if (!input || typeof input !== 'object') throw new HttpError(400, 'Ungültige Auswahl der Meldungen', 'invalid');
|
||||
const events = Object.fromEntries(NOTIFY_EVENTS.map(({ key }) => [key, input[key] === true]));
|
||||
this.db
|
||||
.prepare("INSERT INTO settings (key, value) VALUES ('discord_events', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
|
||||
.run(JSON.stringify(events));
|
||||
return this.notificationSettings();
|
||||
}
|
||||
|
||||
notify(type, matchId, text) {
|
||||
if (!this.notifier.enabled || !this.notificationSettings().events[type]) return;
|
||||
const link = this.config.streamKey ? '' : `
|
||||
${this.config.publicUrl}/stream/match/${matchId}`;
|
||||
this.notifier.send(`${text}${link}`);
|
||||
}
|
||||
|
||||
log(matchId, type, message) {
|
||||
this.db
|
||||
.prepare('INSERT INTO events (match_id, type, message, created_at) VALUES (?, ?, ?, ?)')
|
||||
|
||||
Reference in new issue
Block a user