feat: Add slashcommand api
This commit is contained in:
@@ -621,7 +621,7 @@ interface PluginApiCustomStreamRequest {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
type PluginApiActionSource = 'composerAction' | 'toolbarAction' | 'profileAction' | 'manual';
|
||||
type PluginApiActionSource = 'composerAction' | 'toolbarAction' | 'profileAction' | 'slashCommand' | 'manual';
|
||||
interface PluginApiActionContext {
|
||||
source: PluginApiActionSource;
|
||||
user: User | null;
|
||||
@@ -821,6 +821,10 @@ interface TojuClientPluginApi {
|
||||
registerEmbedRenderer: (id: string, contribution: PluginApiEmbedRendererContribution) => TojuPluginDisposable;
|
||||
mountElement: (id: string, request: PluginApiDomMountRequest) => TojuPluginDisposable;
|
||||
};
|
||||
readonly commands: {
|
||||
register: (id: string, contribution: PluginApiSlashCommandContribution) => TojuPluginDisposable;
|
||||
list: () => PluginApiSlashCommandContribution[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1178,6 +1182,8 @@ Capabilities:
|
||||
| `registerEmbedRenderer` | `ui.embeds` |
|
||||
| `mountElement` | `ui.dom` |
|
||||
|
||||
For `/` slash commands, use `api.commands.register` (capability `ui.commands`). See the Slash Commands subsection below.
|
||||
|
||||
Register side panel:
|
||||
|
||||
```js
|
||||
@@ -1310,6 +1316,36 @@ context.subscriptions.push(
|
||||
|
||||
`mountElement` tags the element with plugin ownership metadata, replaces duplicate mounts for the same plugin/id, and removes it on disposal/unload.
|
||||
|
||||
### Slash Commands
|
||||
|
||||
Capability: `commands.register` and `commands.list` both require `ui.commands`.
|
||||
|
||||
Register `/` slash commands that appear in the chat composer's autocomplete menu. Set `scope: 'global'` (default) for commands available in chat servers and direct messages, or `scope: 'server'` for commands only offered while a chat server is active. Declare `options` to parse arguments into `context.args` (use `type: 'rest'` to capture all trailing text). The `run` callback receives a context with `source: 'slashCommand'`, the parsed `args`, the invoked `command` name, the raw `rawArgs`, and the current user/server/channel.
|
||||
|
||||
```js
|
||||
context.subscriptions.push(
|
||||
api.commands.register('announce', {
|
||||
name: 'announce',
|
||||
description: 'Post an announcement to the current channel',
|
||||
icon: 'megaphone',
|
||||
scope: 'server',
|
||||
options: [{ name: 'message', type: 'rest', required: true }],
|
||||
run: (slash) => api.messages.send(`Announcement: ${slash.args.message}`, slash.textChannel?.id)
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
api.commands.register('shrug', {
|
||||
name: 'shrug',
|
||||
description: 'Append the shrug emoticon',
|
||||
scope: 'global',
|
||||
run: () => api.messages.send('shrug')
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
A command with no `options` runs immediately when picked; a command with options fills `/name ` so the user can type arguments before sending. Slash input is never posted as a chat message; unmatched `/text` falls through as a normal message.
|
||||
|
||||
## Capability Cheat Sheet
|
||||
|
||||
| API call group | Capabilities |
|
||||
@@ -1351,6 +1387,7 @@ context.subscriptions.push(
|
||||
| `ui.registerChannelSection` | `ui.channelsSection` |
|
||||
| `ui.registerEmbedRenderer` | `ui.embeds` |
|
||||
| `ui.mountElement` | `ui.dom` |
|
||||
| `commands.register`, `commands.list` | `ui.commands` |
|
||||
|
||||
## Complete Example Plugin
|
||||
|
||||
|
||||
@@ -318,6 +318,55 @@ interface PluginApiDomMountRequest {
|
||||
| `ui.registerEmbedRenderer(id, contribution)` | `ui.embeds` | Adds an embed renderer. |
|
||||
| `ui.mountElement(id, request)` | `ui.dom` | Mounts plugin-owned DOM into a target element or selector. |
|
||||
|
||||
## Slash Commands
|
||||
|
||||
Slash commands appear in a Discord-style autocomplete menu when a user types `/` in the chat composer. A command with `scope: 'global'` (the default) is offered in every chat surface, including direct messages; a command with `scope: 'server'` only appears while a chat server is active. The user picks a command from the menu (or types it and presses Enter) and the `run` callback executes with the parsed arguments and the current interaction context.
|
||||
|
||||
```ts
|
||||
type PluginApiSlashCommandScope = 'global' | 'server';
|
||||
|
||||
interface PluginApiSlashCommandOption {
|
||||
description?: string;
|
||||
name: string;
|
||||
required?: boolean;
|
||||
// 'rest' captures all remaining text; otherwise a single whitespace-delimited token
|
||||
type?: 'string' | 'number' | 'boolean' | 'rest';
|
||||
}
|
||||
|
||||
interface PluginApiSlashCommandContext extends PluginApiActionContext {
|
||||
args: Record<string, string>; // parsed values keyed by option name
|
||||
command: string; // invoked name without the leading slash
|
||||
rawArgs: string; // raw text typed after the command name
|
||||
}
|
||||
|
||||
interface PluginApiSlashCommandContribution {
|
||||
description?: string;
|
||||
icon?: string;
|
||||
name: string;
|
||||
options?: PluginApiSlashCommandOption[];
|
||||
run: (context: PluginApiSlashCommandContext) => Promise<void> | void;
|
||||
scope?: PluginApiSlashCommandScope;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| ----------------------------------- | -------------- | ------------------------------------------------------------------- |
|
||||
| `commands.register(id, command)` | `ui.commands` | Registers a `/` slash command for the chat composer. |
|
||||
| `commands.list()` | `ui.commands` | Lists every slash command currently registered across all plugins. |
|
||||
|
||||
```ts
|
||||
context.subscriptions.push(
|
||||
api.commands.register('shout', {
|
||||
description: 'Shout a message in uppercase',
|
||||
icon: '📢',
|
||||
name: 'shout',
|
||||
options: [{ name: 'message', required: true, type: 'rest' }],
|
||||
run: (slash) => api.messages.send(slash.args.message.toUpperCase()),
|
||||
scope: 'server'
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## Context and Logger
|
||||
|
||||
| Method | Capability | Description |
|
||||
|
||||
114
docs-site/docs/plugin-development/api/commands.md
Normal file
114
docs-site/docs/plugin-development/api/commands.md
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# Slash Commands API
|
||||
|
||||
The Commands API lets plugins register `/` slash commands. When a user types `/` in the chat composer, MetoYou shows a Discord-style autocomplete menu of available commands. Selecting a command (click, `Enter`, or `Tab`) runs it — either immediately when it declares no options, or after the user types the requested arguments.
|
||||
|
||||
## Required Capabilities
|
||||
|
||||
| Method | Capability |
|
||||
| --------------------------------- | ------------- |
|
||||
| `commands.register(id, command)` | `ui.commands` |
|
||||
| `commands.list()` | `ui.commands` |
|
||||
|
||||
Every registration returns a disposable. Push it into `context.subscriptions` so the command is removed when the plugin unloads.
|
||||
|
||||
## Command Scope
|
||||
|
||||
A command's `scope` controls where it appears:
|
||||
|
||||
| Scope | Available in |
|
||||
| ------------------- | --------------------------------------------- |
|
||||
| `global` (default) | Chat servers **and** direct messages |
|
||||
| `server` | Only while a chat server is the active surface |
|
||||
|
||||
Use `global` for commands that work without a server context (e.g. `/help`, `/shrug`). Use `server` for commands that act on the current server, channel, or members.
|
||||
|
||||
## Options and Argument Parsing
|
||||
|
||||
Declare `options` to describe the arguments a command accepts. MetoYou parses what the user typed after the command name and passes the result to `run` as `context.args`, keyed by option name.
|
||||
|
||||
```ts
|
||||
interface PluginApiSlashCommandOption {
|
||||
description?: string;
|
||||
name: string;
|
||||
required?: boolean;
|
||||
// 'rest' captures all remaining text; otherwise a single whitespace-delimited token
|
||||
type?: 'string' | 'number' | 'boolean' | 'rest';
|
||||
}
|
||||
```
|
||||
|
||||
- Positional options are filled left-to-right from whitespace-delimited tokens.
|
||||
- A `rest` option captures all remaining text verbatim (use it last, for free-form text).
|
||||
- Missing positional values are passed as empty strings.
|
||||
- The autocomplete menu shows required options as `<name>` and optional ones as `[name]`.
|
||||
|
||||
Values arrive as strings; convert `number`/`boolean` types yourself inside `run`.
|
||||
|
||||
## Command Context
|
||||
|
||||
`run` receives a context that extends the standard action context (`source: 'slashCommand'`) with the invocation details:
|
||||
|
||||
```ts
|
||||
interface PluginApiSlashCommandContext extends PluginApiActionContext {
|
||||
args: Record<string, string>; // parsed values keyed by option name
|
||||
command: string; // invoked name without the leading slash
|
||||
rawArgs: string; // raw text typed after the command name
|
||||
// inherited: server, textChannel, voiceChannel, user, source
|
||||
}
|
||||
```
|
||||
|
||||
## Register a Command
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const api = context.api;
|
||||
|
||||
// Server-scoped command with a free-form message argument.
|
||||
context.subscriptions.push(
|
||||
api.commands.register('announce', {
|
||||
name: 'announce',
|
||||
description: 'Post an announcement to the current channel',
|
||||
icon: '📢',
|
||||
scope: 'server',
|
||||
options: [{ name: 'message', type: 'rest', required: true }],
|
||||
run: (slash) => {
|
||||
api.messages.send(`📢 ${slash.args.message}`, slash.textChannel?.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Global command that works in servers and DMs.
|
||||
context.subscriptions.push(
|
||||
api.commands.register('shrug', {
|
||||
name: 'shrug',
|
||||
description: 'Append the shrug emoticon',
|
||||
scope: 'global',
|
||||
run: () => api.messages.send('¯\\_(ツ)_/¯')
|
||||
})
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`api.messages.send` requires the `messages.send` capability, so the example above declares both `ui.commands` and `messages.send` in its manifest.
|
||||
|
||||
## List Registered Commands
|
||||
|
||||
```js
|
||||
const allCommands = context.api.commands.list();
|
||||
```
|
||||
|
||||
Returns every slash command currently registered across all active plugins, including their scope and options.
|
||||
|
||||
## Built-in Commands
|
||||
|
||||
MetoYou ships first-party commands that are always available without any plugin, such as `/lenny` (posts `( ͡° ͜ʖ ͡°)`). They appear in the same autocomplete menu tagged as **Built-in**. Plugin commands are listed alongside them; if a plugin registers a command with the same name as a built-in, both appear and the user can pick either.
|
||||
|
||||
## How Input Is Handled
|
||||
|
||||
- Typing `/` opens the menu; typing more characters filters by command name (prefix matches rank first).
|
||||
- Picking a command **without options** runs it immediately and clears the composer.
|
||||
- Picking a command **with options** fills `/name ` so the user can type arguments, then `Enter` runs it.
|
||||
- Slash input is intercepted and never posted as a chat message. Text that starts with `/` but matches no registered command falls through and is sent as a normal message.
|
||||
@@ -6,6 +6,8 @@ sidebar_position: 11
|
||||
|
||||
The UI API lets plugins add pages, settings pages, side panels, channel sections, actions, embed renderers, and controlled DOM mounts.
|
||||
|
||||
For `/` slash commands in the chat composer, see the [Slash Commands API](./commands.md) (`api.commands`).
|
||||
|
||||
Prefer registered UI contributions over direct DOM mounting. Contribution APIs let Angular render the plugin UI when the matching app surface exists. Direct DOM mounting runs immediately and throws if the target selector is not present.
|
||||
|
||||
## Required Capabilities
|
||||
|
||||
@@ -37,6 +37,7 @@ Capabilities protect privileged app surfaces. A plugin must declare a capability
|
||||
| `ui.channelsSection` | `ui.registerChannelSection()` | Adds channel sections. |
|
||||
| `ui.embeds` | `ui.registerEmbedRenderer()` | Renders custom embeds. |
|
||||
| `ui.dom` | `ui.mountElement()` | Mounts plugin-owned DOM into app targets. |
|
||||
| `ui.commands` | `commands.register()`, `commands.list()` | Registers `/` slash commands (global or server scope) and lists registered commands. |
|
||||
| `storage.local` | `storage.*`, `clientData.*` | Reads and writes plugin-local data. |
|
||||
| `storage.serverData.read` | `serverData.read()` | Reads local per-user/per-server plugin data. |
|
||||
| `storage.serverData.write` | `serverData.write()`, `serverData.remove()` | Writes or removes local per-user/per-server plugin data. |
|
||||
|
||||
@@ -45,6 +45,61 @@ export function activate(context) {
|
||||
|
||||
The action appears as a tile in the server side panel's View plugins menu and runs with `source: 'toolbarAction'`.
|
||||
|
||||
## Slash Command Plugin
|
||||
|
||||
`toju-plugin.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "example.slash-commands",
|
||||
"title": "Slash Commands",
|
||||
"description": "Registers / commands available from the chat composer.",
|
||||
"version": "1.0.0",
|
||||
"kind": "client",
|
||||
"scope": "client",
|
||||
"apiVersion": "1.0.0",
|
||||
"compatibility": {
|
||||
"minimumTojuVersion": "1.0.0",
|
||||
"verifiedTojuVersion": "1.0.0"
|
||||
},
|
||||
"entrypoint": "./main.js",
|
||||
"capabilities": ["messages.send", "ui.commands"]
|
||||
}
|
||||
```
|
||||
|
||||
`main.js`
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const { api } = context;
|
||||
|
||||
// Global: works in chat servers and direct messages.
|
||||
context.subscriptions.push(
|
||||
api.commands.register('shrug', {
|
||||
name: 'shrug',
|
||||
description: 'Append the shrug emoticon',
|
||||
scope: 'global',
|
||||
run: () => api.messages.send('¯\\_(ツ)_/¯')
|
||||
})
|
||||
);
|
||||
|
||||
// Server-scoped: only offered while a chat server is active.
|
||||
context.subscriptions.push(
|
||||
api.commands.register('announce', {
|
||||
name: 'announce',
|
||||
description: 'Post an announcement to the current channel',
|
||||
icon: '📢',
|
||||
scope: 'server',
|
||||
options: [{ name: 'message', type: 'rest', required: true }],
|
||||
run: (slash) => api.messages.send(`📢 ${slash.args.message}`, slash.textChannel?.id)
|
||||
})
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Typing `/` in the composer opens the autocomplete menu. `/shrug` runs immediately; `/announce <message>` fills the composer so the user can type the announcement before sending. See the [Slash Commands API](./api/commands.md) for option parsing and the command context.
|
||||
|
||||
## Settings Page Plugin
|
||||
|
||||
```json
|
||||
|
||||
@@ -41,6 +41,7 @@ type PluginCapabilityId =
|
||||
| 'ui.channelsSection'
|
||||
| 'ui.embeds'
|
||||
| 'ui.dom'
|
||||
| 'ui.commands'
|
||||
| 'storage.local'
|
||||
| 'storage.serverData.read'
|
||||
| 'storage.serverData.write'
|
||||
|
||||
@@ -30,6 +30,8 @@ Server-scoped plugins installed to the server you are currently viewing are enab
|
||||
|
||||
When plugins add quick actions to a server, the server side panel shows a View plugins link in the plugin area. Open it to see a grid of plugin action tiles. Selecting a tile runs that plugin's action in the current server and channel context.
|
||||
|
||||
Plugins can also add `/` slash commands. Type `/` in the message box to open the command menu; plugin commands appear there tagged with the plugin name, alongside built-in commands like `/lenny`. See [Text and Direct Messages](./text-and-direct-messages.md#slash-commands) for how to use the menu.
|
||||
|
||||
## Install a Local Plugin
|
||||
|
||||
Desktop builds can discover local plugin folders from the app data plugins directory.
|
||||
@@ -65,7 +67,7 @@ Examples:
|
||||
| Messages | Send messages, read current messages, moderate messages, or render embeds. |
|
||||
| Users and roles | Read member lists, create plugin users, or manage users. |
|
||||
| Voice and media | Play audio, add an audio stream, add a video stream, or adjust volume. |
|
||||
| UI | Add pages, settings pages, side panels, toolbar buttons, or DOM elements. |
|
||||
| UI | Add pages, settings pages, side panels, toolbar buttons, slash commands, or DOM elements. |
|
||||
| Storage | Save plugin preferences locally or per server. |
|
||||
|
||||
Only grant capabilities to plugins you trust.
|
||||
|
||||
@@ -13,6 +13,7 @@ Text channels belong to a server. Everyone with access to that server and channe
|
||||
You can use text channels to:
|
||||
|
||||
- send normal messages;
|
||||
- run slash commands by typing `/`;
|
||||
- edit or delete your own messages when allowed;
|
||||
- react to messages;
|
||||
- send attachments;
|
||||
@@ -24,6 +25,17 @@ You can use text channels to:
|
||||
|
||||
Direct messages are private conversations outside a server channel. Use them when a message is meant for one person instead of the server.
|
||||
|
||||
## Slash Commands
|
||||
|
||||
Type `/` at the start of the message box to open the slash command menu. It lists the commands you can run, with a short description for each.
|
||||
|
||||
- Keep typing to filter the list (for example `/le`).
|
||||
- Use the up and down arrow keys to move through the list, then press `Enter` or `Tab` to pick a command. You can also click one.
|
||||
- Press `Escape` to close the menu.
|
||||
- A command that needs extra text fills the box with `/name ` so you can type the rest, then send it.
|
||||
|
||||
MetoYou includes built-in commands such as `/lenny`, which posts `( ͡° ͜ʖ ͡°)`. Plugins can add their own commands, which appear in the same menu (tagged with the plugin name). Slash commands are available in both text channels and direct messages; some plugin commands only appear inside a server. Text that starts with `/` but matches no command is sent as a normal message.
|
||||
|
||||
## Attachments and Media
|
||||
|
||||
Attachments can appear as files, images, audio, or video depending on the file type and what the app can preview. If an image or link cannot load directly, the app can use fallback paths where available.
|
||||
|
||||
@@ -50,7 +50,8 @@ const sidebars: SidebarsConfig = {
|
||||
'plugin-development/api/message-bus',
|
||||
'plugin-development/api/p2p-and-media',
|
||||
'plugin-development/api/storage',
|
||||
'plugin-development/api/ui'
|
||||
'plugin-development/api/ui',
|
||||
'plugin-development/api/commands'
|
||||
]
|
||||
},
|
||||
'plugin-development/examples'
|
||||
|
||||
Reference in New Issue
Block a user