Files
Toju/docs-site/docs/plugin-development/examples.md
2026-06-05 17:12:26 +02:00

267 lines
6.7 KiB
Markdown

---
sidebar_position: 5
---
# Examples
## Toolbar Message Plugin
`toju-plugin.json`
```json
{
"schemaVersion": 1,
"id": "example.toolbar-message",
"title": "Toolbar Message",
"description": "Adds a View plugins menu action that sends a reusable message.",
"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.pages"]
}
```
`main.js`
```js
export function activate(context) {
const { api } = context;
context.subscriptions.push(
api.ui.registerToolbarAction('standup-message', {
icon: 'ST',
label: 'Standup',
run: (actionContext) => api.messages.send('Standup: yesterday, today, blocked', actionContext.textChannel?.id)
})
);
}
```
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
{
"schemaVersion": 1,
"id": "example.settings-page",
"title": "Settings Page Example",
"description": "Adds a plugin settings page and stores a local preference.",
"version": "1.0.0",
"kind": "client",
"apiVersion": "1.0.0",
"compatibility": { "minimumTojuVersion": "1.0.0" },
"entrypoint": "./main.js",
"capabilities": ["ui.settings", "storage.local"],
"settings": {
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": true }
}
}
}
```
```js
export function activate(context) {
const { api } = context;
context.subscriptions.push(
api.ui.registerSettingsPage('preferences', {
label: 'Example Preferences',
render: () => {
const root = document.createElement('section');
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Remember preference';
button.onclick = () => api.storage.set('enabled', true);
root.append(button);
return root;
}
})
);
}
```
## Server-Scoped Soundboard
A server-scoped plugin can be installed as a server requirement and auto-installed for server members when marked required.
```json
{
"schemaVersion": 1,
"id": "example.soundboard",
"title": "Server Soundboard",
"description": "Adds a soundboard side panel and announces played sounds.",
"version": "1.0.0",
"kind": "client",
"scope": "server",
"apiVersion": "1.0.0",
"compatibility": { "minimumTojuVersion": "1.0.0" },
"entrypoint": "./main.js",
"capabilities": ["server.read", "users.manage", "ui.sidePanel", "media.playAudio", "messages.send"],
"pluginUser": {
"displayName": "Soundboard",
"label": "Audio helper"
}
}
```
```js
export function activate(context) {
const { api } = context;
const botId = api.server.registerPluginUser({
id: 'soundboard-bot',
displayName: 'Soundboard'
});
context.subscriptions.push(
api.ui.registerSidePanel('sounds', {
label: 'Soundboard',
render: () => {
const panel = document.createElement('div');
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Play chime';
button.onclick = async () => {
await api.media.playAudioClip({ url: './chime.wav', volume: 0.7 });
api.messages.sendAsPluginUser({ pluginUserId: botId, content: 'Played chime' });
};
panel.append(button);
return panel;
}
})
);
}
```
## Message Bus Plugin
```json
{
"schemaVersion": 1,
"id": "example.poll-bus",
"title": "Poll Bus",
"description": "Uses the plugin message bus for lightweight P2P poll votes.",
"version": "1.0.0",
"kind": "client",
"apiVersion": "1.0.0",
"compatibility": { "minimumTojuVersion": "1.0.0" },
"entrypoint": "./main.js",
"capabilities": ["events.p2p.publish", "events.p2p.subscribe", "messages.read"]
}
```
```js
export function activate(context) {
const { api } = context;
context.subscriptions.push(
api.messageBus.subscribe({
topic: 'poll:votes',
replayLatest: true,
latestMessageLimit: 20,
handler: (event) => api.logger.info('Vote received', event.payload)
})
);
api.messageBus.publish({
topic: 'poll:votes',
payload: { option: 'A' },
includeLatestMessages: true,
includeSelf: true,
latestMessageLimit: 20
});
}
```
## Custom DOM Mount
Use `ui.dom` sparingly and cleanly. The runtime tags mounted elements with plugin ownership metadata and removes remaining mounted elements when the plugin unloads.
```js
export function activate(context) {
const badge = document.createElement('div');
badge.textContent = 'Plugin active';
badge.style.position = 'absolute';
badge.style.right = '1rem';
badge.style.bottom = '1rem';
context.subscriptions.push(
context.api.ui.mountElement('active-badge', {
target: 'body',
element: badge
})
);
}
```
## All-API Fixture
The repo includes an E2E fixture at `toju-app/public/plugins/e2e-all-api/`. It intentionally calls every public plugin API surface so Playwright coverage can validate the runtime. Use it as a compatibility reference, not as the minimal style for production plugins.