import { test } from 'node:test'; import assert from 'node:assert/strict'; import { parseLine, toChatMessage, parseGifs } 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'); }); // ---------------------------------------------------------------- GIFs test('der gifs-Tag wird zerlegt', () => { // Echte Zeile aus #abdulive_, mitgeschnitten am 2026-09-17. const g = parseGifs( '0-22|Pnd7qLFuGCOntZcNKB|https://media0.giphy.com/media/Pnd7qLFuGCOntZcNKB/giphy.gif?cid=095d&ct=g'); assert.equal(g.length, 1); assert.equal(g[0]!.id, 'Pnd7qLFuGCOntZcNKB'); assert.equal(g[0]!.from, 0); assert.equal(g[0]!.to, 22); assert.match(g[0]!.url, /^https:\/\/media0\.giphy\.com\//); assert.ok(g[0]!.url.includes('&ct=g'), 'die Adresse darf nicht am & abgeschnitten werden'); }); test('der Bereich deckt genau den Platzhalter ab', () => { // Wenn das nicht stimmt, bleibt ein Rest der eckigen Klammer im Bild stehen. const text = '[Idk GIF by Respective]'; const g = parseGifs('0-22|abc|https://x/y.gif')[0]!; assert.equal([...text].slice(g.from, g.to + 1).join(''), text); }); test('eine Nachricht ohne GIF hat eine leere Liste', () => { const m = parseLine('@id=1;user-id=7;emotes= :a!a@a PRIVMSG #k :moin'); assert.deepEqual(toChatMessage(m!)!.gifs, []); }); test('eine Nachricht mit GIF traegt es bis in die ChatMessage', () => { const m = parseLine( '@id=1;user-id=7;emotes=;gifs=0-4|xy|https://media.giphy.com/a.gif ' + ':a!a@a PRIVMSG #k :[GIF]'); assert.equal(toChatMessage(m!)!.gifs[0]!.url, 'https://media.giphy.com/a.gif'); }); test('kein https, kein Bild', () => { // Der Tag kommt von Twitch, nicht vom Chatter - trotzdem landet die Adresse // als Bild im Stream, und dort gilt kein Vertrauensvorschuss. assert.deepEqual(parseGifs('0-4|xy|http://media.giphy.com/a.gif'), []); assert.deepEqual(parseGifs('0-4|xy|javascript:alert(1)'), []); }); test('unvollstaendige oder unsinnige Tags werden verworfen', () => { for (const kaputt of ['', '0-4', '0-4|nurid', 'abc|x|https://y/z.gif', '-1-4|x|https://y/z.gif', '9-4|x|https://y/z.gif']) { assert.deepEqual(parseGifs(kaputt), [], `"${kaputt}" haette verworfen gehoert`); } });