Files
yusufipek 6bcbd5400b Rewrite dashboard and overlay, single /events stream, static client served by the backend
Backend: one SSE stream for messages, selection, poll and connection status; reconnect with backoff; no on-disk Innertube session cache; mock only with MOCK_CHAT=1; binds to 127.0.0.1; serves client/out so production runs on one port. Full URLs for links YouTube truncates in chat.

Client: components split out, Lucide icons, stream title and status, on-air strip, search, pause, keyboard shortcuts, overlay settings dialog. Overlay themes, position, size, animation and auto-hide via URL, sized for 1080p and scaled with the source. New blueprint theme for the brand background.

Removed Tailwind, better-sqlite3, zod, the timezone helpers, Cline memory bank and AGENTS.md. Added ESLint, node:test tests and CI.
2026-09-02 22:05:58 +03:00

70 lines
1.7 KiB
TypeScript

export type Badge = {
type: 'moderator' | 'member' | 'verified' | 'owner' | 'custom';
label?: string;
imageUrl?: string;
};
export type SuperChatInfo = {
amount: string;
currency: string;
color: string;
stickerUrl?: string;
stickerAlt?: string;
};
export type MessageRun = {
text?: string;
/** Full destination URL when YouTube rendered this run as a link. The visible text may be truncated. */
url?: string;
emojiUrl?: string;
emojiAlt?: string;
};
export type Poll = {
id: string;
active: boolean;
};
export type ChatMessage = {
id: string;
author: string;
authorPhoto?: string;
authorChannelId?: string;
text: string;
runs?: MessageRun[];
publishedAt: string;
badges?: Badge[];
isModerator?: boolean;
isMember?: boolean;
isVerified?: boolean;
superChat?: SuperChatInfo;
membershipGift?: boolean;
membershipGiftPurchase?: boolean;
membershipLevel?: string;
giftCount?: number;
leaderboardRank?: number;
};
export type ConnectionState = 'disconnected' | 'connecting' | 'live' | 'reconnecting';
export type ConnectionStatus = {
state: ConnectionState;
liveId: string | null;
title?: string | null;
error?: string | null;
};
/**
* Events pushed on the single `/events` SSE stream.
* `init` is sent once per connection with the full current state; the rest are deltas.
*/
export type ServerEvent =
| { type: 'init'; status: ConnectionStatus; messages: ChatMessage[]; selection: ChatMessage | null; poll: Poll | null }
| { type: 'message'; message: ChatMessage }
| { type: 'selection'; message: ChatMessage | null }
| { type: 'poll'; poll: Poll | null }
| { type: 'status'; status: ConnectionStatus }
| { type: 'clear' };
export type ServerEventType = ServerEvent['type'];