// Meldungen an einen Discord-Webhook, z. B. für Streamer. Fehler werden nur protokolliert // und bremsen den Matchablauf nie aus. const WEBHOOK_PATTERN = /^https:\/\/(discord\.com|discordapp\.com)\/api\/webhooks\/\d+\/[\w-]+$/; const TIMEOUT_MS = 5000; const MAX_LENGTH = 1900; /** Ereignisse, die gemeldet werden können. Alle sind standardmäßig aktiv. */ export const NOTIFY_EVENTS = [ { key: 'veto_start', label: 'Veto gestartet' }, { key: 'match_start', label: 'Match gestartet (Aufwärmphase beendet)' }, { key: 'map_end', label: 'Map beendet (mit Ergebnis)' }, { key: 'series_end', label: 'Serie entschieden' }, { key: 'match_closed', label: 'Match abgeschlossen oder abgebrochen' }, { key: 'server_error', label: 'Serverfehler' }, ]; /** * @param {string} url Webhook-URL aus DISCORD_WEBHOOK_URL, leer = keine Meldungen * @param {object} options fetchImpl und logger, vor allem für Tests */ export function createDiscordNotifier(url, { fetchImpl = fetch, logger = console } = {}) { if (!url) return { enabled: false, send: async () => false }; // Die URL enthält ein Geheimnis. Sie wird deshalb nie in Fehlermeldungen ausgegeben. if (!WEBHOOK_PATTERN.test(url)) throw new Error('DISCORD_WEBHOOK_URL ist keine gültige Discord-Webhook-URL'); return { enabled: true, /** Liefert true, wenn Discord die Nachricht angenommen hat. */ async send(message) { // Text oder vollständige Discord-Payload (z. B. mit embeds) const payload = typeof message === 'string' ? { content: message.slice(0, MAX_LENGTH) } : message; try { const res = await fetchImpl(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(TIMEOUT_MS), }); if (res.ok) return true; logger.warn(`Discord-Webhook antwortet mit HTTP ${res.status}`); } catch (err) { logger.warn(`Discord-Meldung fehlgeschlagen: ${err.name}`); } return false; }, }; }