diff --git a/src/App.svelte b/src/App.svelte index 5e41b3d..7bec005 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -17,7 +17,8 @@ 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 chatMessages: Array<{ event_id: string; message_id: string; user_name: string; text: string; sent_at: string; reply_parent_message_id?: string }> = []; + let chatReplyParent: { message_id: string; user_name: string } | undefined; let notifications: Array<{ id: string; severity: string; title: string; message: string; seen_at: string | null; created_at: string }> = []; let showNotifications = false; let showStreamEditor = false; @@ -146,6 +147,7 @@ showNotifications = false; diagnostics = undefined; chatMessages = []; + chatReplyParent = undefined; } function connectRealtime() { @@ -162,12 +164,12 @@ 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; payload?: { chatter_user_name?: string; message?: { text?: string } } }; + const activity = event as { event_id: string; event_type: string; occurred_at: string; payload?: { message_id?: string; 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); + chatMessages = [{ event_id: activity.event_id, message_id: activity.payload.message_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 { @@ -299,10 +301,11 @@ const response = await fetch(`${serverAddress}/api/v1/twitch/chat/message`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` }, - body: JSON.stringify({ identity: chatIdentity, message: chatMessage }), + body: JSON.stringify({ identity: chatIdentity, message: chatMessage, reply_parent_message_id: chatReplyParent?.message_id }), }); if (!response.ok) throw new Error('The selected Twitch identity could not send the message.'); chatMessage = ''; + chatReplyParent = undefined; } catch (error) { chatError = error instanceof Error ? error.message : 'Chat message failed.'; } finally { @@ -310,6 +313,10 @@ } } + function replyToChatMessage(message: { message_id: string; user_name: string }) { + chatReplyParent = { message_id: message.message_id, user_name: message.user_name }; + } + async function applyModeration() { moderationBusy = true; moderationError = ''; @@ -457,7 +464,7 @@