// Einheitliches Ereignisformat. Alles, was aus IRC, EventSub, Helix oder OBS // hereinkommt, wird darauf normalisiert und in `events` geschrieben. // Siehe docs/03-data-model.md. export type EventType = | 'stream.online' | 'stream.offline' | 'stream.segment_changed' | 'chat.message' | 'chat.first_message' | 'chat.spike' | 'user.arrived' | 'user.returning' | 'follow' | 'subscribe' | 'subgift' | 'cheer' | 'raid.in' | 'raid.out' | 'points.redeemed' | 'shop.redeemed' | 'poll.begin' | 'poll.end' | 'obs.scene_changed' | 'obs.replay_saved' | 'clip.requested' | 'clip.created' | 'clip.rendered'; export type StreamEvent = { id: string; ts: string; // ISO-8601, UTC type: EventType; streamId: string | null; userId: string | null; payload: unknown; }; export type ChatMessage = { id: string; // msg-id aus IRC ts: string; userId: string; login: string; displayName: string; text: string; emotes: Array<{ id: string; name: string; count: number }>; isFirst: boolean; // first-msg Tag isReturning: boolean; // returning-chatter Tag isCommand: boolean; isMod: boolean; isSub: boolean; isBroadcaster: boolean; bits: number; color: string | null; }; export type SpikeEvent = { score: number; rate: number; baseline: number; chatters: number; emoteHits: number; clipMentions: number; windowFrom: string; windowTo: string; overThreshold: boolean; }; /** ULID-artige, zeitlich sortierbare ID ohne Abhaengigkeit. */ const B32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; export function ulid(now = Date.now()): string { let time = ''; let t = now; for (let i = 0; i < 10; i++) { time = B32[t % 32]! + time; t = Math.floor(t / 32); } const rnd = new Uint8Array(16); crypto.getRandomValues(rnd); let tail = ''; for (const b of rnd) tail += B32[b % 32]!; return time + tail; }