S1: Einrichtungs-Assistent, Twitch-Auth, Datenbank und Chat-Aufnahme
Zugangsdaten kommen jetzt aus der App statt aus einer .env: ein Einrichtungs-Assistent auf http://localhost:4792/setup nimmt Client-ID, Secret und OBS-Passwort entgegen, der OAuth-Rueckruf laeuft auf denselben Server. Gespeichert wird verschlueselt (AES-256-GCM) in einem Volume, bewusst nicht in der Datenbank - die wird auf die Windows-Seite gesichert. Siehe ADR-0005. Technikbasis vereinfacht: Node 24 bringt node:sqlite, WebSocket, fetch und die Ausfuehrung von TypeScript mit. Das Projekt hat damit null Laufzeitabhaengigkeiten und keinen Build-Schritt; pnpm-Workspaces und die Paketmanifeste sind entfallen, Importe laufen ueber die imports-Map der Wurzel-package.json. erasableSyntaxOnly verhindert Syntax, die das Type Stripping stillschweigend entfernen wuerde. Siehe Nachtrag 2 in ADR-0001. Enthalten: - packages/core: Config, Layout, Logger mit Secret-Maskierung, Retry, YAML - packages/db: Migrationen, Repositories, alle SQL-Anweisungen an einem Ort - packages/twitch: Secret-Speicher, OAuth, Helix mit Token-Bucket, IRC - apps/ingest: Daemon, Einrichtungsseite, Spike-Erkennung - 20 Tests (IRC-Parser, Spike-Detektor, Datenbankschicht) Der Chat verbindet sich anonym, solange nichts hinterlegt ist - damit laesst sich die Datenaufnahme pruefen, bevor die Twitch-Apps existieren. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
This commit is contained in:
1 parent
4ceb61f095
commit
d07c18441b
82 files changed
+2775
-838
No files matched your search
@@ -0,0 +1,59 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseLine, toChatMessage } from './irc.ts';
|
||||
|
||||
const LINE =
|
||||
'@badge-info=subscriber/8;badges=broadcaster/1,subscriber/6;color=#1E90FF;' +
|
||||
'display-name=Nic_OOo;emotes=25:0-4/1902:6-10;first-msg=0;id=abc-123;mod=0;' +
|
||||
'returning-chatter=0;subscriber=1;tmi-sent-ts=1757404800000;user-id=98765 ' +
|
||||
':nic_ooo!nic_ooo@nic_ooo.tmi.twitch.tv PRIVMSG #abdulive_ :Kappa Keepo mehr Rost als Farbe';
|
||||
|
||||
test('zerlegt eine PRIVMSG mit Tags', () => {
|
||||
const m = parseLine(LINE);
|
||||
assert.ok(m);
|
||||
assert.equal(m.command, 'PRIVMSG');
|
||||
assert.equal(m.params[0], '#abdulive_');
|
||||
assert.equal(m.params[1], 'Kappa Keepo mehr Rost als Farbe');
|
||||
assert.equal(m.tags['user-id'], '98765');
|
||||
assert.equal(m.tags['display-name'], 'Nic_OOo');
|
||||
});
|
||||
|
||||
test('entschluesselt escapte Tag-Werte', () => {
|
||||
const m = parseLine('@system-msg=Ein\\sTest\\szur\\sSache;id=x :tmi.twitch.tv USERNOTICE #k');
|
||||
assert.equal(m?.tags['system-msg'], 'Ein Test zur Sache');
|
||||
});
|
||||
|
||||
test('uebersetzt in eine ChatMessage', () => {
|
||||
const msg = toChatMessage(parseLine(LINE)!);
|
||||
assert.ok(msg);
|
||||
assert.equal(msg.userId, '98765');
|
||||
assert.equal(msg.login, 'nic_ooo');
|
||||
assert.equal(msg.displayName, 'Nic_OOo');
|
||||
assert.equal(msg.isBroadcaster, true);
|
||||
assert.equal(msg.isSub, true);
|
||||
assert.equal(msg.isFirst, false);
|
||||
assert.equal(msg.isCommand, false);
|
||||
assert.equal(msg.ts, new Date(1757404800000).toISOString());
|
||||
// Emote-Namen werden aus den Positionsangaben zurueckgelesen
|
||||
assert.deepEqual(msg.emotes.map((e) => e.name).sort(), ['Kappa', 'Keepo']);
|
||||
});
|
||||
|
||||
test('erkennt Befehle und Erstchatter', () => {
|
||||
const line =
|
||||
'@first-msg=1;id=y;tmi-sent-ts=1757404800000;user-id=42;display-name=Neu ' +
|
||||
':neu!neu@neu.tmi.twitch.tv PRIVMSG #abdulive_ :!clip';
|
||||
const msg = toChatMessage(parseLine(line)!)!;
|
||||
assert.equal(msg.isFirst, true);
|
||||
assert.equal(msg.isCommand, true);
|
||||
});
|
||||
|
||||
test('ignoriert Nachrichten ohne user-id', () => {
|
||||
const line = ':tmi.twitch.tv PRIVMSG #abdulive_ :ohne Tags';
|
||||
assert.equal(toChatMessage(parseLine(line)!), null);
|
||||
});
|
||||
|
||||
test('PING wird als solches erkannt', () => {
|
||||
const m = parseLine('PING :tmi.twitch.tv');
|
||||
assert.equal(m?.command, 'PING');
|
||||
assert.equal(m?.params[0], 'tmi.twitch.tv');
|
||||
});
|
||||
Reference in new issue
Block a user