Der billigste Hebel im Repo, und bis jetzt nur Vorarbeit: die Webhooks lagen seit der Discord-Einrichtung bereit, gepostet hat nie jemand. Ablauf: neuer Stream erkannt, eine Minute warten (Twitch setzt Titel und Kategorie erst kurz nach dem Start, wer sofort postet kuendigt den Titel des letzten Streams an), Daten frisch holen, Webhook posten, Rolle Live-Ping anpingen. Der Ping funktioniert nur mit `allowed_mentions` im Nachrichtenkoerper. `<@&ID>` allein faerbt die Erwaehnung lila ein und benachrichtigt niemanden - der Fehler, den man erst nach drei leeren Streams bemerkt. Dagegen steht ein eigener Test. Gegen den doppelten Post stehen drei Dinge, denn wer zweimal in zehn Minuten angepingt wird, nimmt sich die Rolle wieder ab und ist damit fuer immer stumm: - Tabelle `posts` mit eindeutigem Index je Stream und Plattform. Nur *gesendete* Posts sperren; ein fehlgeschlagener Versuch darf wiederholt werden, sonst kostet ein Netzwerkaussetzer die ganze Ankuendigung. - Abklingzeit von sechs Stunden, gegen die neue Stream-ID nach einem Verbindungsabbruch. - Ausgeloest wird nur beim erkannten Neustart, nie im Fortsetzungszweig. `stream_id` ist bewusst kein Fremdschluessel: ein Post ist die Quittung fuer etwas, das den Rechner verlassen hat, und muss auch dann schreibbar sein, wenn es die Sitzung nicht gibt - beim Probelauf etwa. Dazu ein vierter Webhook in #technik-log. Der Entwurfsmodus postet dieselbe Nachricht dorthin, ohne Ping und nur fuer Mods sichtbar. So sieht man das Ergebnis, bevor es der Server sieht. Die Rollenaufloesung aus selfroles.ts liegt jetzt in roles.ts, weil der Ping sie auch braucht: ein Webhook kann eine Rolle nicht ueber ihren Namen anpingen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
// Textbausteine fuer die Go-Live-Ankuendigung.
|
|
//
|
|
// Warum mehrere: eine Ankuendigung, die jeden Donnerstag Wort fuer Wort
|
|
// dieselbe ist, wird nach drei Wochen nicht mehr gelesen. Das ist kein
|
|
// Algorithmus-Aberglaube, sondern gilt genauso fuer Menschen, die die
|
|
// Nachricht in der Seitenleiste ueberfliegen.
|
|
//
|
|
// Die Auswahl ist nicht zufaellig, sondern haengt an der Stream-ID: derselbe
|
|
// Stream bekommt bei einem Wiederholungsversuch denselben Text, und Tests
|
|
// bekommen ein vorhersagbares Ergebnis. Zwei aufeinanderfolgende Streams
|
|
// bekommen verschiedene IDs und damit fast immer verschiedene Texte.
|
|
|
|
/** Die Zeile ueber dem Embed. Hier steht der Ping, also faellt sie ins Auge. */
|
|
export const OPENERS = [
|
|
'Abdu ist live.',
|
|
'Der Stream läuft.',
|
|
'Es geht los.',
|
|
'Abdu ist da.',
|
|
'Ab jetzt live.',
|
|
];
|
|
|
|
/** Die Zeile im Embed. Darf laenger sein, wird ruhiger gelesen. */
|
|
export const INVITATIONS = [
|
|
'Schau vorbei.',
|
|
'Komm rein, es ist noch Platz.',
|
|
'Jetzt einschalten.',
|
|
'Der Chat ist offen.',
|
|
'Bis gleich im Chat.',
|
|
];
|
|
|
|
/**
|
|
* Kleiner, stabiler Hash. Kein crypto: er muss nur gleichmaessig streuen und
|
|
* ueber Neustarts hinweg dasselbe Ergebnis liefern.
|
|
*/
|
|
export function pick<T>(list: readonly T[], seed: string): T {
|
|
if (list.length === 0) throw new Error('Keine Textbausteine hinterlegt.');
|
|
let h = 2166136261;
|
|
for (let i = 0; i < seed.length; i++) {
|
|
h ^= seed.charCodeAt(i);
|
|
h = Math.imul(h, 16777619);
|
|
}
|
|
return list[Math.abs(h) % list.length]!;
|
|
}
|
|
|
|
/**
|
|
* Platzhalter ersetzen. Unbekannte Platzhalter bleiben stehen - so sieht man
|
|
* im Ergebnis, dass einer falsch geschrieben ist, statt eine Luecke zu
|
|
* bekommen, die nach Absicht aussieht.
|
|
*/
|
|
export function fill(template: string, values: Record<string, string>): string {
|
|
return template.replace(/\{\{(\w+)\}\}/g, (all, key: string) =>
|
|
key in values ? values[key]! : all);
|
|
}
|