diff --git a/src/App.svelte b/src/App.svelte index 0776589..d938601 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -55,6 +55,7 @@ let collaborationBusy = false; let collaborationError = ''; let showCollaboration = false; + let collaborationGuestId = ''; let oauthLink = ''; let oauthIdentity: 'streamer' | 'bot' = 'streamer'; let oauthBusy = false; @@ -281,6 +282,39 @@ } } + async function sendCollaborationInvite() { + if (!collaborationSession || !collaborationGuestId.trim()) return; + collaborationBusy = true; + collaborationError = ''; + try { + const query = `session_id=${encodeURIComponent(collaborationSession.id)}&guest_id=${encodeURIComponent(collaborationGuestId.trim())}`; + const response = await fetch(`${serverAddress}/api/v1/twitch/collaboration/invites/send?${query}`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } }); + if (!response.ok) throw new Error('Twitch could not send the collaboration invite.'); + collaborationGuestId = ''; + await loadCollaboration(); + } catch (error) { + collaborationError = error instanceof Error ? error.message : 'Invite could not be sent.'; + } finally { + collaborationBusy = false; + } + } + + async function cancelCollaborationInvite(guestId: string) { + if (!collaborationSession) return; + collaborationBusy = true; + collaborationError = ''; + try { + const query = `session_id=${encodeURIComponent(collaborationSession.id)}&guest_id=${encodeURIComponent(guestId)}`; + const response = await fetch(`${serverAddress}/api/v1/twitch/collaboration/invites/cancel?${query}`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } }); + if (!response.ok) throw new Error('Twitch could not cancel the collaboration invite.'); + await loadCollaboration(); + } catch (error) { + collaborationError = error instanceof Error ? error.message : 'Invite could not be cancelled.'; + } finally { + collaborationBusy = false; + } + } + async function markNotificationSeen(notificationId: string) { const response = await fetch(`${serverAddress}/api/v1/admin/notifications/${notificationId}/seen`, { method: 'POST', @@ -566,7 +600,7 @@
STREAM PREVIEW⋮
{#if playerSrc}{:else}
STREAM OFFLINE
CONNECT TWITCH TO BEGIN
{/if}
ACTIVITY FEED⋮
{#if activityEvents.filter((event) => activityFilter === 'all' || event.event_type.startsWith('moderation.')).length}
{#each activityEvents.filter((event) => activityFilter === 'all' || event.event_type.startsWith('moderation.')) as event}
{event.event_type}{new Date(event.occurred_at).toLocaleTimeString()}
{/each}
{:else}
◌
Nothing here yet{activityFilter === 'moderation' ? 'Moderation events will appear here.' : 'Follows, subscriptions, raids, and server events will appear here.'}
{/if}
MY CHAT⋮
{#if pinnedMessageIds.length}
PINNED{#each chatMessages.filter((message) => pinnedMessageIds.includes(message.message_id)) as message}
{message.user_name}{message.text}
{/each}
{/if}{#if chatMessages.length}
{#each chatMessages as message}
{#if message.fragments.length}{#each message.fragments as fragment}{#if fragment.type === 'emote' && fragment.emote_id}{fragment.text}{:else}{fragment.text}{/if}{/each}{:else}{message.text}{/if}
{/each}
{:else}
⌁
Chat is waitingConnect a streamer or bot account to begin.
{/if}
{#if chatReplyParent}
REPLYING TO @{chatReplyParent.user_name}
{/if}
{#if chatEmoteOptions.length}
{#each chatEmoteOptions as emote}{/each}
{/if}
searchChatEmotes(chatMessage)} onkeydown={(event) => event.key === 'Enter' && sendChatMessage()} />
{#if chatError}
{chatError}
{/if}
-
QUICK ACTIONS⋮
{#if showCollaboration}
GUEST STAR SESSION
{#if collaborationSession}
ACTIVE · {collaborationSession.guests.length} GUESTS
{#if collaborationInvites.length}
PENDING INVITES
{#each collaborationInvites as invite}
{invite.user_id}{invite.status}
{/each}{/if}{:else}
No active collaboration.
{/if}{#if collaborationError}
{collaborationError}
{/if}
{/if}{#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}
+
QUICK ACTIONS⋮
{#if showCollaboration}
GUEST STAR SESSION
{#if collaborationSession}
ACTIVE · {collaborationSession.guests.length} GUESTS
{#if collaborationInvites.length}
PENDING INVITES
{#each collaborationInvites as invite}
{invite.user_id} · {invite.status}
{/each}{/if}{:else}
No active collaboration.
{/if}{#if collaborationError}
{collaborationError}
{/if}
{/if}{#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}