Add typed argument command editor
This commit is contained in:
+15
-2
@@ -152,7 +152,7 @@
|
|||||||
let oauthIdentity: 'streamer' | 'bot' = 'streamer';
|
let oauthIdentity: 'streamer' | 'bot' = 'streamer';
|
||||||
let oauthBusy = false;
|
let oauthBusy = false;
|
||||||
let oauthError = '';
|
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 publicBotCommands: Array<{ id: string; name: string; description: string; permission_level: string; aliases: string[]; tags: string[] }> = [];
|
||||||
let botCommandError = '';
|
let botCommandError = '';
|
||||||
let commandFormId = '';
|
let commandFormId = '';
|
||||||
@@ -170,6 +170,7 @@
|
|||||||
let commandWebUrl = '';
|
let commandWebUrl = '';
|
||||||
let commandWebBody = '';
|
let commandWebBody = '';
|
||||||
let commandResponseFields = '';
|
let commandResponseFields = '';
|
||||||
|
let commandArgumentDefinitions = '[]';
|
||||||
let commandRules: Array<{ user_id: string; response_template: string | null }> = [];
|
let commandRules: Array<{ user_id: string; response_template: string | null }> = [];
|
||||||
let commandRuleUserId = '';
|
let commandRuleUserId = '';
|
||||||
let commandRuleResponse = '';
|
let commandRuleResponse = '';
|
||||||
@@ -843,6 +844,7 @@
|
|||||||
commandWebUrl = '';
|
commandWebUrl = '';
|
||||||
commandWebBody = '';
|
commandWebBody = '';
|
||||||
commandResponseFields = '';
|
commandResponseFields = '';
|
||||||
|
commandArgumentDefinitions = '[]';
|
||||||
commandRules = [];
|
commandRules = [];
|
||||||
commandRuleUserId = '';
|
commandRuleUserId = '';
|
||||||
commandRuleResponse = '';
|
commandRuleResponse = '';
|
||||||
@@ -882,6 +884,7 @@
|
|||||||
commandWebUrl = command.web_request?.url ?? '';
|
commandWebUrl = command.web_request?.url ?? '';
|
||||||
commandWebBody = command.web_request?.body ?? '';
|
commandWebBody = command.web_request?.body ?? '';
|
||||||
commandResponseFields = (command.response_fields ?? []).map(([name, path]) => `${name}=${path}`).join('\n');
|
commandResponseFields = (command.response_fields ?? []).map(([name, path]) => `${name}=${path}`).join('\n');
|
||||||
|
commandArgumentDefinitions = JSON.stringify(command.argument_definitions ?? [], null, 2);
|
||||||
void loadCommandRules(command.id);
|
void loadCommandRules(command.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,11 +941,20 @@
|
|||||||
const aliases = commandFormAliases.split(',').map((value) => value.trim()).filter(Boolean);
|
const aliases = commandFormAliases.split(',').map((value) => value.trim()).filter(Boolean);
|
||||||
const tags = commandFormTags.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]));
|
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 {
|
try {
|
||||||
const response = await fetch(commandFormId ? `${serverAddress}/api/v1/bot/commands/${encodeURIComponent(commandFormId)}` : `${serverAddress}/api/v1/bot/commands`, {
|
const response = await fetch(commandFormId ? `${serverAddress}/api/v1/bot/commands/${encodeURIComponent(commandFormId)}` : `${serverAddress}/api/v1/bot/commands`, {
|
||||||
method: commandFormId ? 'PATCH' : 'POST',
|
method: commandFormId ? 'PATCH' : 'POST',
|
||||||
headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` },
|
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.');
|
if (!response.ok) throw new Error('The command could not be saved. Check its name, aliases, and fields.');
|
||||||
resetCommandForm();
|
resetCommandForm();
|
||||||
@@ -1450,6 +1462,7 @@
|
|||||||
<section class="panel command-management-panel"><div class="panel-header"><span>COMMAND MANAGEMENT</span></div><div class="settings-body">{#each botCommands as command}<div class="bot-command-row"><div><strong>!{command.name}</strong><span class="bot-command-description">{command.enabled ? 'ENABLED' : 'DISABLED'} · {command.permission_level.toUpperCase()}</span></div><div class="bot-command-meta"><button class="secondary-action" onclick={() => editCommand(command)}>EDIT</button><button class="secondary-action danger" onclick={() => deleteCommand(command.id)}>DELETE</button></div></div>{/each}{#if !botCommands.length}<div class="empty-row">No commands are configured yet.</div>{/if}</div></section>
|
<section class="panel command-management-panel"><div class="panel-header"><span>COMMAND MANAGEMENT</span></div><div class="settings-body">{#each botCommands as command}<div class="bot-command-row"><div><strong>!{command.name}</strong><span class="bot-command-description">{command.enabled ? 'ENABLED' : 'DISABLED'} · {command.permission_level.toUpperCase()}</span></div><div class="bot-command-meta"><button class="secondary-action" onclick={() => editCommand(command)}>EDIT</button><button class="secondary-action danger" onclick={() => deleteCommand(command.id)}>DELETE</button></div></div>{/each}{#if !botCommands.length}<div class="empty-row">No commands are configured yet.</div>{/if}</div></section>
|
||||||
<section class="panel command-web-action-panel"><div class="panel-header"><span>WEB ACTION CONFIGURATION</span></div><div class="settings-body command-form"><p class="bot-secret-note">Optional HTTPS request and response variables. The server validates the URL and never executes template values as code.</p><div class="command-form-grid"><label class="micro" for="command-web-method">METHOD</label><select id="command-web-method" class="control" bind:value={commandWebMethod}><option value="GET">GET</option><option value="POST">POST</option></select><label class="micro" for="command-web-url">HTTPS URL</label><input id="command-web-url" class="control" bind:value={commandWebUrl} maxlength="2048" placeholder="https://example.com/status" /><label class="micro" for="command-web-body">JSON BODY (OPTIONAL)</label><textarea id="command-web-body" class="control command-template" bind:value={commandWebBody} maxlength="65536" placeholder="JSON body"></textarea><label class="micro" for="command-response-fields">RESPONSE VARIABLES (one name=path per line)</label><textarea id="command-response-fields" class="control command-template" bind:value={commandResponseFields} maxlength="4096" placeholder="status=$.status"></textarea></div></div></section>
|
<section class="panel command-web-action-panel"><div class="panel-header"><span>WEB ACTION CONFIGURATION</span></div><div class="settings-body command-form"><p class="bot-secret-note">Optional HTTPS request and response variables. The server validates the URL and never executes template values as code.</p><div class="command-form-grid"><label class="micro" for="command-web-method">METHOD</label><select id="command-web-method" class="control" bind:value={commandWebMethod}><option value="GET">GET</option><option value="POST">POST</option></select><label class="micro" for="command-web-url">HTTPS URL</label><input id="command-web-url" class="control" bind:value={commandWebUrl} maxlength="2048" placeholder="https://example.com/status" /><label class="micro" for="command-web-body">JSON BODY (OPTIONAL)</label><textarea id="command-web-body" class="control command-template" bind:value={commandWebBody} maxlength="65536" placeholder="JSON body"></textarea><label class="micro" for="command-response-fields">RESPONSE VARIABLES (one name=path per line)</label><textarea id="command-response-fields" class="control command-template" bind:value={commandResponseFields} maxlength="4096" placeholder="status=$.status"></textarea></div></div></section>
|
||||||
<section class="panel command-rules-panel"><div class="panel-header"><span>USER-SPECIFIC RULES</span><span class="micro">{commandFormId ? `COMMAND ${commandFormName}` : 'SELECT A COMMAND'}</span></div><div class="settings-body command-form"><p class="bot-secret-note">A command with rules is limited to the named Twitch users. Add an optional response for a personalized variant.</p><div class="chat-send-row"><input class="control" bind:value={commandRuleUserId} inputmode="numeric" maxlength="64" placeholder="Twitch user ID" disabled={!commandFormId} /><input class="control" bind:value={commandRuleResponse} maxlength="2000" placeholder="Optional personalized response" disabled={!commandFormId} /><button class="primary" onclick={saveCommandRule} disabled={commandRuleBusy || !commandFormId}>ADD / UPDATE</button></div>{#if commandRuleError}<div class="auth-error">{commandRuleError}</div>{/if}{#if commandRules.length}<div class="bot-command-list">{#each commandRules as rule}<div class="bot-command-row"><div><strong>{rule.user_id}</strong><span class="bot-command-description">{rule.response_template || 'Uses the command response template'}</span></div><button class="secondary-action danger" onclick={() => deleteCommandRule(rule.user_id)} disabled={commandRuleBusy}>DELETE</button></div>{/each}</div>{:else}<div class="empty-row">No user-specific rules configured.</div>{/if}</div></section>
|
<section class="panel command-rules-panel"><div class="panel-header"><span>USER-SPECIFIC RULES</span><span class="micro">{commandFormId ? `COMMAND ${commandFormName}` : 'SELECT A COMMAND'}</span></div><div class="settings-body command-form"><p class="bot-secret-note">A command with rules is limited to the named Twitch users. Add an optional response for a personalized variant.</p><div class="chat-send-row"><input class="control" bind:value={commandRuleUserId} inputmode="numeric" maxlength="64" placeholder="Twitch user ID" disabled={!commandFormId} /><input class="control" bind:value={commandRuleResponse} maxlength="2000" placeholder="Optional personalized response" disabled={!commandFormId} /><button class="primary" onclick={saveCommandRule} disabled={commandRuleBusy || !commandFormId}>ADD / UPDATE</button></div>{#if commandRuleError}<div class="auth-error">{commandRuleError}</div>{/if}{#if commandRules.length}<div class="bot-command-list">{#each commandRules as rule}<div class="bot-command-row"><div><strong>{rule.user_id}</strong><span class="bot-command-description">{rule.response_template || 'Uses the command response template'}</span></div><button class="secondary-action danger" onclick={() => deleteCommandRule(rule.user_id)} disabled={commandRuleBusy}>DELETE</button></div>{/each}</div>{:else}<div class="empty-row">No user-specific rules configured.</div>{/if}</div></section>
|
||||||
|
<section class="panel command-arguments-panel"><div class="panel-header"><span>TYPED ARGUMENTS</span><span class="micro">JSON DEFINITIONS</span></div><div class="settings-body command-form"><p class="bot-secret-note">Declare text, number, choice, username, or duration arguments. Values are validated as data and never executed.</p><textarea class="control command-template" bind:value={commandArgumentDefinitions} maxlength="12000" placeholder="[]" aria-label="Typed argument definitions"></textarea><div class="micro">Example: a JSON array containing count, number, required, minimum, and maximum fields.</div></div></section>
|
||||||
{:else if activeView === 'Collaboration'}
|
{:else if activeView === 'Collaboration'}
|
||||||
{#if collaborationSession}
|
{#if collaborationSession}
|
||||||
<section class="collaboration-page panel">
|
<section class="collaboration-page panel">
|
||||||
|
|||||||
Reference in New Issue
Block a user