From 7f0d3fbb6d06913b2d8f66afb2748802a7d2b8a9 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 04:59:08 +0100 Subject: [PATCH] render incoming Twitch chat --- src/App.svelte | 10 ++++++++-- src/styles.css | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 20dfd33..5e41b3d 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -17,6 +17,7 @@ let realtimeState = 'DISCONNECTED'; let realtimeSocket: WebSocket | undefined; let activityEvents: Array<{ event_id: string; event_type: string; occurred_at: string }> = []; + let chatMessages: Array<{ event_id: string; user_name: string; text: string; sent_at: string }> = []; let notifications: Array<{ id: string; severity: string; title: string; message: string; seen_at: string | null; created_at: string }> = []; let showNotifications = false; let showStreamEditor = false; @@ -144,6 +145,7 @@ notifications = []; showNotifications = false; diagnostics = undefined; + chatMessages = []; } function connectRealtime() { @@ -160,10 +162,13 @@ const event: { event_type?: string } = JSON.parse(message.data); if (event.event_type === 'connection.ready') realtimeState = 'CONNECTED'; else if ('event_id' in event && event.event_type) { - const activity = event as { event_id: string; event_type: string; occurred_at: string }; + const activity = event as { event_id: string; event_type: string; occurred_at: string; payload?: { chatter_user_name?: string; message?: { text?: string } } }; if (!activityEvents.some((item) => item.event_id === activity.event_id)) { activityEvents = [activity, ...activityEvents].slice(0, 100); } + if (activity.event_type === 'twitch.channel.chat.message' && activity.payload?.message?.text) { + chatMessages = [{ event_id: activity.event_id, user_name: activity.payload.chatter_user_name ?? 'unknown', text: activity.payload.message.text, sent_at: activity.occurred_at }, ...chatMessages].slice(0, 100); + } } } catch { realtimeState = 'DISCONNECTED'; @@ -185,6 +190,7 @@ }); if (!response.ok) return; activityEvents = await response.json(); + chatMessages = []; } catch { activityEvents = []; } @@ -451,7 +457,7 @@
STREAM PREVIEW⋮
{#if playerSrc}{:else}
STREAM OFFLINE
CONNECT TWITCH TO BEGIN
{/if}
ACTIVITY FEED⋮
{#if activityEvents.length}
{#each activityEvents as event}
{event.event_type}{new Date(event.occurred_at).toLocaleTimeString()}
{/each}
{:else}
◌
Nothing here yetFollows, subscriptions, raids, and server events will appear here.
{/if}
-
MY CHAT⋮
⌁
Chat is waitingConnect a streamer or bot account to begin.
event.key === 'Enter' && sendChatMessage()} />
{#if chatError}
{chatError}
{/if}
+
MY CHAT⋮
{#if chatMessages.length}
{#each chatMessages as message}
{message.user_name}{message.text}
{/each}
{:else}
⌁
Chat is waitingConnect a streamer or bot account to begin.
{/if}
event.key === 'Enter' && sendChatMessage()} />
{#if chatError}
{chatError}
{/if}
QUICK ACTIONS⋮
{#if showStreamEditor}
searchCategories(event.currentTarget.value)} autocomplete="off" placeholder="Search Twitch categories" />{#if categoryOptions.length}
{#each categoryOptions as category}{/each}
{/if}{#if streamError}
{streamError}
{/if}
{/if}{#if showModeration}
{#if moderationError}
{moderationError}
{/if}
{/if}
{/if} diff --git a/src/styles.css b/src/styles.css index be214fc..51f695e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -238,6 +238,29 @@ button:hover:not(:disabled) { color: var(--ed-dim); font: 11px var(--font-data); } +.chat-message-list { + display: flex; + flex: 1; + flex-direction: column; + gap: 6px; + overflow: auto; + padding: 10px; +} +.chat-message { + display: flex; + gap: 7px; + font-size: 11px; + line-height: 1.35; +} +.chat-user { + color: var(--ed-accent); + font-family: var(--font-data); + font-weight: 600; +} +.chat-text { + color: var(--ed-fg); + overflow-wrap: anywhere; +} .workspace { display: grid; grid-template-columns: 214px 1fr;