diff --git a/src/App.svelte b/src/App.svelte index f19d0ec..edec988 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; web_request?: { method: string; url: string; body?: string | null } | null; response_fields?: Array<[string, string]> }> = []; + 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]>; argument_definitions?: unknown[] }> = []; let publicBotCommands: Array<{ id: string; name: string; description: string; permission_level: string; aliases: string[]; tags: string[] }> = []; let botCommandError = ''; let commandFormId = ''; @@ -170,6 +170,7 @@ let commandWebUrl = ''; let commandWebBody = ''; let commandResponseFields = ''; + let commandArgumentDefinitions = '[]'; let commandRules: Array<{ user_id: string; response_template: string | null }> = []; let commandRuleUserId = ''; let commandRuleResponse = ''; @@ -843,6 +844,7 @@ commandWebUrl = ''; commandWebBody = ''; commandResponseFields = ''; + commandArgumentDefinitions = '[]'; commandRules = []; commandRuleUserId = ''; commandRuleResponse = ''; @@ -882,6 +884,7 @@ commandWebUrl = command.web_request?.url ?? ''; commandWebBody = command.web_request?.body ?? ''; commandResponseFields = (command.response_fields ?? []).map(([name, path]) => `${name}=${path}`).join('\n'); + commandArgumentDefinitions = JSON.stringify(command.argument_definitions ?? [], null, 2); void loadCommandRules(command.id); } @@ -938,11 +941,20 @@ 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])); + let argumentDefinitions: unknown[] = []; + try { + argumentDefinitions = JSON.parse(commandArgumentDefinitions); + if (!Array.isArray(argumentDefinitions)) throw new Error('Argument definitions must be a JSON array.'); + } catch (error) { + botCommandError = error instanceof Error ? error.message : 'Argument definitions must be valid JSON.'; + commandFormBusy = false; + return; + } 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, web_request: commandWebUrl ? { method: commandWebMethod, url: commandWebUrl, body: commandWebBody || null } : null, response_fields: responseFields }), + 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, argument_definitions: argumentDefinitions }), }); if (!response.ok) throw new Error('The command could not be saved. Check its name, aliases, and fields.'); resetCommandForm(); @@ -1450,6 +1462,7 @@
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.

USER-SPECIFIC RULES{commandFormId ? `COMMAND ${commandFormName}` : 'SELECT A COMMAND'}

A command with rules is limited to the named Twitch users. Add an optional response for a personalized variant.

{#if commandRuleError}
{commandRuleError}
{/if}{#if commandRules.length}
{#each commandRules as rule}
{rule.user_id}{rule.response_template || 'Uses the command response template'}
{/each}
{:else}
No user-specific rules configured.
{/if}
+
TYPED ARGUMENTSJSON DEFINITIONS

Declare text, number, choice, username, or duration arguments. Values are validated as data and never executed.

Example: a JSON array containing count, number, required, minimum, and maximum fields.
{:else if activeView === 'Collaboration'} {#if collaborationSession}