diff --git a/src/App.svelte b/src/App.svelte index 1ef17d5..88429e5 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -152,7 +152,7 @@ let oauthIdentity: 'streamer' | 'bot' = 'streamer'; let oauthBusy = false; let oauthError = ''; - let botCommands: Array<{ id: string; name: string; description: string; permission_level: string; aliases: string[]; tags: string[]; cooldown_seconds: number; cooldown_scope: string; response_template: string; error_template: string | null; enabled: boolean }> = []; + let botCommands: Array<{ id: string; name: string; description: string; permission_level: string; aliases: string[]; tags: string[]; cooldown_seconds: number; cooldown_scope: string; response_template: string; error_template: string | null; enabled: boolean; web_request?: { method: string; url: string; body?: string | null } | null; response_fields?: Array<[string, string]> }> = []; let publicBotCommands: Array<{ id: string; name: string; description: string; permission_level: string; aliases: string[]; tags: string[] }> = []; let botCommandError = ''; let commandFormId = ''; @@ -166,6 +166,10 @@ let commandFormResponse = ''; let commandFormErrorResponse = ''; let commandFormEnabled = true; + let commandWebMethod = 'GET'; + let commandWebUrl = ''; + let commandWebBody = ''; + let commandResponseFields = ''; let commandFormBusy = false; let commandSimulationBusy = false; let commandSimulationError = ''; @@ -830,6 +834,10 @@ commandSimulationError = ''; commandSimulationResult = null; commandFormEnabled = true; + commandWebMethod = 'GET'; + commandWebUrl = ''; + commandWebBody = ''; + commandResponseFields = ''; } const commandVariables = ['user_id', 'user_name', 'message', 'args', ...Array.from({ length: 16 }, (_, index) => `arg_${index + 1}`)]; @@ -861,6 +869,10 @@ commandFormResponse = command.response_template; commandFormErrorResponse = command.error_template ?? ''; commandFormEnabled = command.enabled; + commandWebMethod = command.web_request?.method ?? 'GET'; + commandWebUrl = command.web_request?.url ?? ''; + commandWebBody = command.web_request?.body ?? ''; + commandResponseFields = (command.response_fields ?? []).map(([name, path]) => `${name}=${path}`).join('\n'); } async function saveCommand() { @@ -868,11 +880,12 @@ botCommandError = ''; const aliases = commandFormAliases.split(',').map((value) => value.trim()).filter(Boolean); const tags = commandFormTags.split(',').map((value) => value.trim()).filter(Boolean); + const responseFields = commandResponseFields.split('\n').map((value) => value.trim()).filter(Boolean).map((value) => value.split('=').map((part) => part.trim())).filter((parts): parts is [string, string] => parts.length === 2 && Boolean(parts[0]) && Boolean(parts[1])); try { const response = await fetch(commandFormId ? `${serverAddress}/api/v1/bot/commands/${encodeURIComponent(commandFormId)}` : `${serverAddress}/api/v1/bot/commands`, { method: commandFormId ? 'PATCH' : 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` }, - body: JSON.stringify({ name: commandFormName, description: commandFormDescription, permission_level: commandFormPermission, aliases, tags, cooldown_seconds: Number(commandFormCooldown), cooldown_scope: commandFormCooldownScope, response_template: commandFormResponse, error_template: commandFormErrorResponse || null, enabled: commandFormEnabled }), + body: JSON.stringify({ name: commandFormName, description: commandFormDescription, permission_level: commandFormPermission, aliases, tags, cooldown_seconds: Number(commandFormCooldown), cooldown_scope: commandFormCooldownScope, response_template: commandFormResponse, error_template: commandFormErrorResponse || null, enabled: commandFormEnabled, web_request: commandWebUrl ? { method: commandWebMethod, url: commandWebUrl, body: commandWebBody || null } : null, response_fields: responseFields }), }); if (!response.ok) throw new Error('The command could not be saved. Check its name, aliases, and fields.'); resetCommandForm(); @@ -1378,6 +1391,7 @@
{commandFormId ? 'EDIT COMMAND' : 'NEW COMMAND'}
{#if commandVariableSuggestions.length}
{#each commandVariableSuggestions as variable}{/each}
{/if}
SIMULATION

Preview with literal sample data. Real requests are opt-in and use the same server-side safety checks.

{#if commandSimulationError}
{commandSimulationError}
{/if}{#if commandSimulationResult}
{commandSimulationResult.rendered ?? commandSimulationResult.error ?? 'Simulation completed.'}{#if commandSimulationResult.extracted_variables && Object.keys(commandSimulationResult.extracted_variables).length}Extracted: {formatExtractedVariables(commandSimulationResult.extracted_variables)}{/if}
{/if}
BOT COMMANDS
PUBLIC COMMAND CATALOG
{#if botCommandError}
{botCommandError}
{:else if botCommands.length}
{#each botCommands as command}
!{command.name}{command.description}{#if command.tags.length}
{command.tags.join(' · ')}
{/if}
{command.permission_level.toUpperCase()}{#if command.cooldown_seconds}{command.cooldown_seconds}s / {command.cooldown_scope}{/if}
{/each}
{:else}
No enabled commandsCreate commands on the server to make them available here.
{/if}
SECRETS MANAGEMENT
Values are encrypted and cannot be read after saving.
{#if botSecrets.length}
{#each botSecrets as secret}
{secret.name}{secret.description || 'No description'}{#if secret.used_by.length}
USED BY: {secret.used_by.join(', ')}
{/if}
{/each}
{:else}
There are no secrets yet.
{/if}{#if secretError}
{secretError}
{/if}
COMMAND MANAGEMENT
{#each botCommands as command}
!{command.name}{command.enabled ? 'ENABLED' : 'DISABLED'} · {command.permission_level.toUpperCase()}
{/each}{#if !botCommands.length}
No commands are configured yet.
{/if}
+
WEB ACTION CONFIGURATION

Optional HTTPS request and response variables. The server validates the URL and never executes template values as code.

{:else if activeView === 'Collaboration'} {#if collaborationSession}