From a48cd5bc2202ee95ad75c8e73c88b8ba51786c05 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 06:10:25 +0100 Subject: [PATCH] Add user-specific command rule editor --- src/App.svelte | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/App.svelte b/src/App.svelte index 88429e5..f19d0ec 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -170,6 +170,11 @@ let commandWebUrl = ''; let commandWebBody = ''; let commandResponseFields = ''; + let commandRules: Array<{ user_id: string; response_template: string | null }> = []; + let commandRuleUserId = ''; + let commandRuleResponse = ''; + let commandRuleBusy = false; + let commandRuleError = ''; let commandFormBusy = false; let commandSimulationBusy = false; let commandSimulationError = ''; @@ -838,6 +843,10 @@ commandWebUrl = ''; commandWebBody = ''; commandResponseFields = ''; + commandRules = []; + commandRuleUserId = ''; + commandRuleResponse = ''; + commandRuleError = ''; } const commandVariables = ['user_id', 'user_name', 'message', 'args', ...Array.from({ length: 16 }, (_, index) => `arg_${index + 1}`)]; @@ -873,6 +882,54 @@ commandWebUrl = command.web_request?.url ?? ''; commandWebBody = command.web_request?.body ?? ''; commandResponseFields = (command.response_fields ?? []).map(([name, path]) => `${name}=${path}`).join('\n'); + void loadCommandRules(command.id); + } + + async function loadCommandRules(commandId: string) { + commandRuleError = ''; + try { + const response = await fetch(`${serverAddress}/api/v1/bot/commands/${encodeURIComponent(commandId)}/user-rules`, { headers: { authorization: `Bearer ${sessionToken}` } }); + if (!response.ok) throw new Error('User rules could not be loaded.'); + commandRules = await response.json(); + } catch (error) { + commandRuleError = error instanceof Error ? error.message : 'User rules could not be loaded.'; + } + } + + async function saveCommandRule() { + if (!commandFormId || !/^\d{1,64}$/.test(commandRuleUserId)) { + commandRuleError = 'Enter a valid Twitch user ID.'; + return; + } + commandRuleBusy = true; + commandRuleError = ''; + try { + const response = await fetch(`${serverAddress}/api/v1/bot/commands/${encodeURIComponent(commandFormId)}/user-rules`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` }, body: JSON.stringify({ user_id: commandRuleUserId, response_template: commandRuleResponse || null }) }); + const result = await response.json().catch(() => ({})); + if (!response.ok) throw new Error(result.error || 'The user rule could not be saved.'); + commandRuleUserId = ''; + commandRuleResponse = ''; + await loadCommandRules(commandFormId); + } catch (error) { + commandRuleError = error instanceof Error ? error.message : 'The user rule could not be saved.'; + } finally { + commandRuleBusy = false; + } + } + + async function deleteCommandRule(userId: string) { + if (!commandFormId) return; + commandRuleBusy = true; + commandRuleError = ''; + try { + const response = await fetch(`${serverAddress}/api/v1/bot/commands/${encodeURIComponent(commandFormId)}/user-rules/${encodeURIComponent(userId)}`, { method: 'DELETE', headers: { authorization: `Bearer ${sessionToken}` } }); + if (!response.ok) throw new Error('The user rule could not be deleted.'); + await loadCommandRules(commandFormId); + } catch (error) { + commandRuleError = error instanceof Error ? error.message : 'The user rule could not be deleted.'; + } finally { + commandRuleBusy = false; + } } async function saveCommand() { @@ -1392,6 +1449,7 @@
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.

+
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}
{:else if activeView === 'Collaboration'} {#if collaborationSession}