From df6fa0fa429c0f2ec562b6ece8b084ae67a4e853 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Wed, 22 Apr 2026 00:39:43 +1000 Subject: [PATCH] moved docs --- PLUGINS.md | 157 ---- cinny | 2 +- docs/API-QUICKSTART.md | 172 ---- docs/API.md | 537 ----------- docs/PLUGIN_API.md | 1282 -------------------------- docs/PLUGIN_BUTTON_API.md | 432 --------- docs/PLUGIN_SYSTEM_IMPLEMENTATION.md | 207 ----- 7 files changed, 1 insertion(+), 2788 deletions(-) delete mode 100644 PLUGINS.md delete mode 100644 docs/API-QUICKSTART.md delete mode 100644 docs/API.md delete mode 100644 docs/PLUGIN_API.md delete mode 100644 docs/PLUGIN_BUTTON_API.md delete mode 100644 docs/PLUGIN_SYSTEM_IMPLEMENTATION.md diff --git a/PLUGINS.md b/PLUGINS.md deleted file mode 100644 index 7a21844..0000000 --- a/PLUGINS.md +++ /dev/null @@ -1,157 +0,0 @@ -# Paarrot Plugin System - -The Paarrot desktop client includes a powerful plugin system that allows you to extend and customize your Matrix experience. - -## What are Plugins? - -Plugins are small JavaScript modules that run inside Paarrot and can: -- Add custom commands -- Modify the UI -- React to Matrix events -- Add new settings sections -- Enhance functionality - -## Installing Plugins - -### From the Plugin Marketplace - -1. Open **Settings** → **Plugins** -2. Browse the **Marketplace** tab -3. Click **Install** on any plugin you want to add -4. The plugin will be downloaded and enabled automatically - -### Manual Installation - -1. Download a plugin ZIP file -2. Extract it to your plugins directory: - - **Windows**: `%APPDATA%\paarrot\plugins\` - - **Linux**: `~/.config/Paarrot/plugins/` - - **macOS**: `~/Library/Application Support/Paarrot/plugins/` -3. Restart Paarrot -4. Enable the plugin in **Settings** → **Plugins** → **Installed** - -## Managing Plugins - -### Viewing Installed Plugins - -Go to **Settings** → **Plugins** → **Installed** to see all installed plugins. - -### Enabling/Disabling Plugins - -Click the toggle switch next to any plugin to enable or disable it. Disabled plugins won't run or use resources. - -### Uninstalling Plugins - -Click the **Uninstall** button next to any plugin to remove it completely. - -### Searching Plugins - -Use the search bar to filter plugins by name, description, author, or tags. - -## Plugin Security - -⚠️ **Important Security Information** - -Plugins run with access to your Matrix client and can: -- Read your messages -- Send messages on your behalf -- Access your room list -- Modify app behavior - -**Only install plugins from trusted sources!** - -### Safety Tips - -1. **Check the author**: Install plugins from known developers -2. **Read reviews**: Look for community feedback -3. **Review permissions**: Understand what the plugin can do -4. **Keep updated**: Update plugins regularly for security fixes -5. **Report issues**: If a plugin behaves suspiciously, report it immediately - -## Developing Plugins - -Want to create your own plugin? Check out the [Plugin API Reference](docs/PLUGIN_API.md). - -### Quick Start - -1. Create a plugin directory with: - - `index.js` - Your plugin code - - `plugin-metadata.json` - Plugin information - -2. Implement the plugin interface: -```javascript -module.exports = { - onLoad: async (context) => { - // Your plugin initialization - console.log('Plugin loaded!'); - }, - onUnload: async () => { - // Cleanup when disabled - } -}; -``` - -3. Test locally by copying to your plugins directory - -4. Publish to the [Plugin Directory](https://github.com/Paarrot/Plugin-Directory) - -## Plugin API - -Plugins have access to: -- **Matrix Client**: Full matrix-js-sdk client instance -- **React**: For building UI components -- **Commands**: Register custom slash commands -- **UI Buttons**: Inject buttons into 11 locations across the app (nav lists, toolbars, headers, sidebar) -- **UI Renderers**: Custom message and content renderers -- **Settings**: Add custom settings sections per plugin -- **Themes**: Register custom themes that appear in the theme selector -- **Matrix Events**: Hook into raw Matrix events -- **Timers**: Background intervals and timeouts, auto-cleaned on unload -- **Notifications**: System notifications - -See the [Plugin API Reference](docs/PLUGIN_API.md) and [Button Registration API](docs/PLUGIN_BUTTON_API.md) for complete documentation. - -## Example Plugins - -Check out the `example-plugin/` directory for a simple example, or the `plugins/example-showcase-plugin/` for a full demonstration of all 11 UI button locations. - -## Troubleshooting - -### Plugin Won't Load - -1. Check the browser console for errors (F12 → Console) -2. Look for `[PluginLoader]` messages -3. Verify `index.js` exists in the plugin directory -4. Ensure `plugin-metadata.json` is valid JSON - -### Plugin Crashes - -1. Disable the plugin in Settings → Plugins -2. Check console for error stack traces -3. Report the issue to the plugin author -4. Try reinstalling the plugin - -### Performance Issues - -If Paarrot becomes slow: -1. Disable plugins one by one to identify the culprit -2. Check for memory leaks in the console -3. Contact the plugin author with details - -## Plugin Directory - -The official Paarrot Plugin Directory is maintained at: -https://github.com/Paarrot/Plugin-Directory - -Submit your plugins there to make them available in the marketplace! - -## Support - -- **Plugin API Reference**: [docs/PLUGIN_API.md](docs/PLUGIN_API.md) -- **Button API Reference**: [docs/PLUGIN_BUTTON_API.md](docs/PLUGIN_BUTTON_API.md) -- **Issues**: https://github.com/Paarrot/cinny-desktop/issues -- **Community**: Join our Matrix room for plugin development help - -## License - -Plugins are independent works and have their own licenses. Check each plugin's metadata for license information. diff --git a/cinny b/cinny index 64e252d..d4cb0b7 160000 --- a/cinny +++ b/cinny @@ -1 +1 @@ -Subproject commit 64e252da94b4839dcae6c0a2002cde32d7ac5713 +Subproject commit d4cb0b743590fd4cdc5ce82613c4b41b1cf1f003 diff --git a/docs/API-QUICKSTART.md b/docs/API-QUICKSTART.md deleted file mode 100644 index ffbfaf6..0000000 --- a/docs/API-QUICKSTART.md +++ /dev/null @@ -1,172 +0,0 @@ -# Paarrot API Quick Start - -A local HTTP API server for controlling Paarrot from external devices like Stream Deck, scripts, and automation tools. - -## 🚀 Getting Started - -The API server starts automatically when you run Paarrot. It listens on `http://127.0.0.1:33384`. - -### Test the API - -**Option 1: Using Postman** - -Import the Postman collection for easy testing: -1. Open Postman -2. Click **Import** → **File** -3. Select `paarrot-api.postman_collection.json` -4. All endpoints will be ready to use! - -The collection is automatically updated on git push. - -**Option 2: Using the test script** -```bash -node test-api.js -``` - -**Option 3: Using curl** -```bash -./test-api.sh -``` - -**Option 4: Manual curl commands** -```bash -# Health check -curl http://127.0.0.1:33384/health - -# Toggle mute -curl -X POST http://127.0.0.1:33384/mute/toggle - -# Send message -curl -X POST http://127.0.0.1:33384/message/current \ - -H "Content-Type: application/json" \ - -d '{"message": "Hello from API!"}' -``` - -## 📝 Quick Reference - -### Common Endpoints - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/health` | GET | Check if API is running | -| `/status` | GET | Get app status (mute, deafen, etc.) | -| `/mute/toggle` | POST | Toggle microphone mute | -| `/deafen/toggle` | POST | Toggle deafen | -| `/channels` | GET | Get list of rooms | -| `/channel` | POST | Switch to a room | -| `/message/current` | POST | Send message to current room | - -## 🎮 Stream Deck Integration - -1. Install the "API Ninja" or "HTTP Request" plugin -2. Create buttons with these settings: - -**Mute Toggle Button:** -- URL: `http://127.0.0.1:33384/mute/toggle` -- Method: POST - -**Quick Message Button:** -- URL: `http://127.0.0.1:33384/message/current` -- Method: POST -- Body: `{"message": "BRB!"}` - -## 🔧 Integration with Paarrot - -To enable full functionality, you need to implement the action handlers in the Cinny frontend: - -1. Import the API handler in your app initialization: -```typescript -import { initPaarrotAPI } from './app/paarrot-api'; - -// After Matrix client is initialized -initPaarrotAPI(matrixClient); -``` - -2. Implement the TODO items in `cinny/src/app/paarrot-api.ts`: - - Mute/unmute logic (WebRTC audio) - - Deafen logic (WebRTC audio output) - - Navigation to rooms (router integration) - - Get current room from URL/state - -## 📖 Full Documentation - -See [API.md](API.md) for complete API documentation including: -- All available endpoints -- Request/response formats -- Error handling -- Code examples in multiple languages -- Detailed integration guide - -## 🛠️ Configuration - -Edit `electron/api-server.js` to customize: -- Port number (default: 33384) -- Timeout duration (default: 10s) -- CORS settings - -## 🐛 Troubleshooting - -**API not responding:** -1. Check if Paarrot is running -2. Look for "Paarrot API server listening" in console logs -3. Verify port 33384 is not in use: `lsof -i :33384` - -**Actions not working:** -1. Check browser console for errors -2. Ensure `initPaarrotAPI()` is called in your app -3. Implement the TODO items in `paarrot-api.ts` - -**Port in use:** -Edit `electron/api-server.js` and change the port number in the constructor. - -## 📦 Files - -- `electron/api-server.js` - API server implementation -- `cinny/src/app/paarrot-api.ts` - Client-side handler (needs implementation) -- `test-api.js` - Node.js test script -- `test-api.sh` - Bash test script -- `API.md` - Full documentation - -## 🔐 Security - -- API only listens on localhost (127.0.0.1) -- Not accessible from network -- No authentication required (local only) -- CORS enabled for all origins (safe since localhost only) - -## 💡 Examples - -**Python Script:** -```python -import requests - -def toggle_mute(): - r = requests.post('http://127.0.0.1:33384/mute/toggle') - print(r.json()) - -toggle_mute() -``` - -**JavaScript:** -```javascript -async function sendQuickMessage(msg) { - const res = await fetch('http://127.0.0.1:33384/message/current', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message: msg }) - }); - return res.json(); -} -``` - -## 🎯 Next Steps - -1. Run Paarrot: `npm run dev` -2. Test the API: `node test-api.js` -3. Integrate the handler: Import and call `initPaarrotAPI()` -4. Implement the TODO items in `paarrot-api.ts` -5. Create Stream Deck buttons or automation scripts! - ---- - -For more details, see [API.md](API.md) diff --git a/docs/API.md b/docs/API.md deleted file mode 100644 index 14ec122..0000000 --- a/docs/API.md +++ /dev/null @@ -1,537 +0,0 @@ -# Paarrot API Server Documentation - -The Paarrot API server provides a local HTTP API for controlling the Paarrot desktop app from external applications like Stream Deck, keyboard macros, scripts, and other automation tools. - -## Server Details - -- **Base URL**: `http://127.0.0.1:33384` -- **Protocol**: HTTP -- **Content-Type**: `application/json` -- **CORS**: Enabled for all origins - -## Authentication - -Currently, no authentication is required. The API server only listens on localhost (127.0.0.1) for security. - -## API Endpoints - -### Health Check - -Check if the API server is running. - -**Endpoint**: `GET /health` - -**Response**: -```json -{ - "status": "ok", - "app": "Paarrot API", - "version": "1.0.0" -} -``` - ---- - -### Get Status - -Get the current status of the app (mute, deafen, current room, etc.). - -**Endpoint**: `GET /status` - -**Response**: -```json -{ - "success": true, - "data": { - "muted": false, - "deafened": false, - "currentRoom": "!abc123:matrix.org", - "connected": true - } -} -``` - ---- - -### Mute/Unmute - -Set the mute status. - -**Endpoint**: `POST /mute` - -**Request Body**: -```json -{ - "muted": true -} -``` - -**Response**: -```json -{ - "success": true, - "data": { - "muted": true - } -} -``` - ---- - -### Toggle Mute - -Toggle the current mute status. - -**Endpoint**: `POST /mute/toggle` - -**Response**: -```json -{ - "success": true, - "data": { - "muted": true - } -} -``` - ---- - -### Deafen/Undeafen - -Set the deafen status (deafening also mutes). - -**Endpoint**: `POST /deafen` - -**Request Body**: -```json -{ - "deafened": true -} -``` - -**Response**: -```json -{ - "success": true, - "data": { - "deafened": true - } -} -``` - ---- - -### Toggle Deafen - -Toggle the current deafen status. - -**Endpoint**: `POST /deafen/toggle` - -**Response**: -```json -{ - "success": true, - "data": { - "deafened": true - } -} -``` - ---- - -### Change Channel/Room - -Switch to a different Matrix room. - -**Endpoint**: `POST /channel` - -**Request Body**: -```json -{ - "roomId": "!abc123:matrix.org" -} -``` - -**Response**: -```json -{ - "success": true, - "data": { - "roomId": "!abc123:matrix.org", - "roomName": "General" - } -} -``` - ---- - -### Get Channels/Rooms - -Get a list of available rooms/channels. - -**Endpoint**: `GET /channels` - -**Response**: -```json -{ - "success": true, - "data": [ - { - "roomId": "!abc123:matrix.org", - "name": "General", - "server": "matrix.org", - "isDirect": false, - "avatar": null - }, - { - "roomId": "!def456:matrix.org", - "name": "Random", - "server": "matrix.org", - "isDirect": false, - "avatar": null - }, - { - "roomId": "!xyz789:example.com", - "name": "General", - "server": "example.com", - "isDirect": false, - "avatar": null - } - ] -} -``` - -**Note**: The `server` field helps distinguish between rooms with the same name on different servers. - ---- - -### Send Message - -Send a message to a specific room. - -**Endpoint**: `POST /message` - -**Request Body**: -```json -{ - "roomId": "!abc123:matrix.org", - "message": "Hello from the API!" -} -``` - -**Response**: -```json -{ - "success": true, - "data": { - "eventId": "$abc123", - "roomId": "!abc123:matrix.org" - } -} -``` - ---- - -### Send Message to Current Room - -Send a message to the currently active room. - -**Endpoint**: `POST /message/current` - -**Request Body**: -```json -{ - "message": "Hello from the API!" -} -``` - -**Response**: -```json -{ - "success": true, - "data": { - "eventId": "$abc123", - "roomId": "!abc123:matrix.org" - } -} -``` - ---- - -### Get Current Room - -Get information about the currently active room. - -**Endpoint**: `GET /room/current` - -**Response**: -```json -{ - "success": true, - "data": { - "roomId": "!abc123:matrix.org", - "name": "General", - "server": "matrix.org", - "avatar": "mxc://matrix.org/abc123", - "isDirect": false - } -} -``` - ---- - -## Error Responses - -All endpoints return error responses in the following format: - -```json -{ - "success": false, - "error": "Error message here" -} -``` - -Common HTTP status codes: -- `200 OK` - Request successful -- `400 Bad Request` - Missing or invalid parameters -- `404 Not Found` - Endpoint not found -- `500 Internal Server Error` - Server error - ---- - -## Example Usage - -### cURL - -```bash -# Toggle mute -curl -X POST http://127.0.0.1:33384/mute/toggle - -# Send message to current room -curl -X POST http://127.0.0.1:33384/message/current \ - -H "Content-Type: application/json" \ - -d '{"message": "Hello!"}' - -# Change channel -curl -X POST http://127.0.0.1:33384/channel \ - -H "Content-Type: application/json" \ - -d '{"roomId": "!abc123:matrix.org"}' -``` - -### JavaScript/Node.js - -```javascript -const fetch = require('node-fetch'); - -// Toggle mute -async function toggleMute() { - const response = await fetch('http://127.0.0.1:33384/mute/toggle', { - method: 'POST' - }); - const data = await response.json(); - console.log('Muted:', data.data.muted); -} - -// Send message -async function sendMessage(roomId, message) { - const response = await fetch('http://127.0.0.1:33384/message', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ roomId, message }) - }); - const data = await response.json(); - console.log('Message sent:', data.data.eventId); -} -``` - -### Python - -```python -import requests - -# Toggle mute -def toggle_mute(): - response = requests.post('http://127.0.0.1:33384/mute/toggle') - data = response.json() - print('Muted:', data['data']['muted']) - -# Send message -def send_message(room_id, message): - response = requests.post('http://127.0.0.1:33384/message', json={ - 'roomId': room_id, - 'message': message - }) - data = response.json() - print('Message sent:', data['data']['eventId']) -``` - ---- - -## Stream Deck Integration - -To integrate with Stream Deck: - -1. Install the "API Ninja" or "HTTP Request" plugin for Stream Deck -2. Create a button with the following configuration: - - **URL**: `http://127.0.0.1:33384/mute/toggle` - - **Method**: POST - - **Headers**: `Content-Type: application/json` - -Example configurations: - -**Mute Toggle Button**: -- URL: `http://127.0.0.1:33384/mute/toggle` -- Method: POST - -**Deafen Toggle Button**: -- URL: `http://127.0.0.1:33384/deafen/toggle` -- Method: POST - -**Send Quick Message**: -- URL: `http://127.0.0.1:33384/message/current` -- Method: POST -- Body: `{"message": "BRB!"}` - ---- - -## Client-Side Implementation - -To enable the API functionality in the Paarrot app, you need to implement the action handlers in your Matrix client code. The API server sends actions via IPC to the renderer process, which should handle them and respond. - -### Example Handler (TypeScript/React) - -Add this to your app initialization: - -```typescript -// Listen for API actions from the Electron API server -if (window.electron?.api?.onAction) { - window.electron.api.onAction(async (action: { - action: string; - params: any; - responseChannel: string; - }) => { - try { - let result; - - switch (action.action) { - case 'toggle-mute': - // Toggle microphone mute - result = await toggleMicrophone(); - break; - - case 'set-mute': - // Set microphone mute state - result = await setMicrophone(action.params.muted); - break; - - case 'toggle-deafen': - // Toggle deafen (mute speakers) - result = await toggleDeafen(); - break; - - case 'set-deafen': - // Set deafen state - result = await setDeafen(action.params.deafened); - break; - - case 'change-channel': - // Navigate to a different room - result = await changeRoom(action.params.roomId); - break; - - case 'get-channels': - // Get list of rooms - result = await getRoomList(); - break; - - case 'send-message': - // Send message to specific room - result = await sendMessage(action.params.roomId, action.params.message); - break; - - case 'send-message-current': - // Send message to current room - result = await sendMessageToCurrent(action.params.message); - break; - - case 'get-current-room': - // Get current room info - result = await getCurrentRoomInfo(); - break; - - case 'get-status': - // Get current app status - result = await getAppStatus(); - break; - - default: - throw new Error(`Unknown action: ${action.action}`); - } - - // Send success response back - window.electron.api.sendResponse(action.responseChannel, { - success: true, - data: result - }); - } catch (error) { - // Send error response back - window.electron.api.sendResponse(action.responseChannel, { - success: false, - error: error.message - }); - } - }); -} -``` - ---- - -## Configuration - -The API server can be configured by modifying `electron/api-server.js`: - -- **Default Port**: 33384 (can be changed in the constructor) -- **Bind Address**: 127.0.0.1 (localhost only for security) -- **Timeout**: 10 seconds for actions - ---- - -## Security Notes - -1. The API server only listens on localhost (127.0.0.1) and is not accessible from the network -2. No authentication is implemented - anyone with local access can control the app -3. Consider adding API key authentication if needed for your use case -4. CORS is enabled for all origins since it's localhost only - ---- - -## Troubleshooting - -**API server not starting**: -- Check if port 33384 is already in use -- Check console logs for errors -- Verify Express is installed: `npm install express cors body-parser` - -**Actions not working**: -- Ensure the client-side handler is implemented -- Check browser console for errors -- Verify the action names match between API server and client - -**Timeout errors**: -- Actions must respond within 10 seconds -- Check if the Matrix client is initialized -- Verify the action handler is registered - ---- - -## Future Enhancements - -Potential features to add: -- WebSocket support for real-time events -- Authentication with API keys -- Voice channel controls (join/leave) -- User status updates -- Notification controls -- Screen sharing controls diff --git a/docs/PLUGIN_API.md b/docs/PLUGIN_API.md deleted file mode 100644 index b1d89c4..0000000 --- a/docs/PLUGIN_API.md +++ /dev/null @@ -1,1282 +0,0 @@ -# Paarrot Plugin API Documentation - -Complete reference for developing plugins for Paarrot/Cinny Desktop. - -## Table of Contents - -- [Getting Started](#getting-started) -- [Plugin Structure](#plugin-structure) -- [Plugin Context API](#plugin-context-api) - - [Commands](#commands) - - [Message Interceptors](#message-interceptors) - - [UI: Button Registration](#ui-button-registration) - - [UI: Custom Renderers](#custom-renderers) - - [Settings](#settings) - - [Themes](#themes) - - [Matrix Events](#matrix-events) - - [Background Tasks](#background-tasks) - - [Notifications](#notifications) - - [Logging](#logging) - - [Plugin Exports](#plugin-exports) - - [Matrix Client](#matrix-client) - ---- - -## Getting Started - -### Plugin Location - -Plugins are installed in: -- **Windows**: `%APPDATA%\paarrot\plugins\\` -- **Linux**: `~/.config/Paarrot/plugins//` -- **macOS**: `~/Library/Application Support/Paarrot/plugins//` - -### Required Files - -- `index.js` - Main plugin file (required) -- `plugin-metadata.json` - Plugin metadata (required) -- `README.md` - Documentation (recommended) - -### plugin-metadata.json - -```json -{ - "name": "My Awesome Plugin", - "version": "1.0.0", - "description": "Does something cool", - "author": "Your Name", - "homepage": "https://github.com/username/plugin", - "thumbnail": "https://example.com/icon.png", - "tags": ["utility", "messaging", "fun"] -} -``` - ---- - -## Plugin Structure - -### Basic Plugin - -```javascript -module.exports = { - name: "My Plugin", - version: "1.0.0", - dependencies: { - // "other-plugin": "^1.0.0" - }, - - /** - * Called when plugin loads - * @param {PluginContext} ctx - Plugin context - */ - onLoad: async (ctx) => { - ctx.log('Plugin loaded!'); - - // Initialize your plugin here - ctx.commands.register({ - name: "hello", - description: "Say hello", - run: () => "Hello, world!" - }); - }, - - /** - * Called when plugin unloads - */ - onUnload: async () => { - console.log('Plugin unloaded'); - }, - - /** - * API for other plugins - */ - exports: null -}; -``` - ---- - -## Plugin Context API - -The `PluginContext` provides access to all plugin APIs: - -```typescript -interface PluginContext { - pluginId: string; - matrixClient: MatrixClient; - React: typeof import('react'); - - commands: CommandsAPI; - messages: MessagesAPI; - ui: UIAPI; - settings: SettingsAPI; - matrix: MatrixEventsAPI; - timers: TimersAPI; - notify: NotificationFunction; - log: LogFunction; - warn: LogFunction; - error: LogFunction; - require: RequireFunction; -} -``` - -### Commands - -Register slash commands that users can type in chat. - -#### Register a Command - -```javascript -ctx.commands.register({ - name: "mycommand", - description: "Does something cool", - args: ["arg1", "arg2"], // Optional - run: (args) => { - // Return string to send as message - return `Got args: ${args.arg1}, ${args.arg2}`; - } -}); -``` - -#### Examples - -**Simple command:** -```javascript -ctx.commands.register({ - name: "shrug", - description: "Send a shrug emoji", - run: () => "¯\\_(ツ)_/¯" -}); -// Usage: /shrug -``` - -**Command with arguments:** -```javascript -ctx.commands.register({ - name: "greet", - description: "Greet someone", - args: ["name"], - run: ({ name }) => `Hello, ${name || 'stranger'}!` -}); -// Usage: /greet Alice -``` - -**Advanced command:** -```javascript -ctx.commands.register({ - name: "calc", - description: "Simple calculator", - args: ["expression"], - run: ({ expression }) => { - try { - const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, ''); - const result = Function(`'use strict'; return (${sanitized})`)(); - return `${expression} = ${result}`; - } catch (err) { - return "Invalid expression"; - } - } -}); -// Usage: /calc 2 + 2 * 3 -``` - -#### API Reference - -```typescript -interface CommandsAPI { - register(command: PluginCommand): void; - unregister(name: string): void; - execute(name: string, args: Record): Promise; -} - -interface PluginCommand { - name: string; - description: string; - args?: string[]; - run: (args: Record) => string | void | Promise; -} -``` - ---- - -### Message Interceptors - -Intercept and modify messages before they're sent or after they're received. - -#### Before Send - -Modify outgoing messages before they're sent to the server. - -```javascript -ctx.messages.onBeforeSend((msg) => { - // Modify msg.content - if (msg.content.includes('secret')) { - msg.content = msg.content.replace(/secret/g, '█████'); - } - - // Add prefix - msg.content = `[Bot] ${msg.content}`; -}); -``` - -#### On Receive - -Process incoming messages (read-only monitoring). - -```javascript -ctx.messages.onReceive((msg) => { - // Monitor for keywords - if (msg.content.includes('important')) { - ctx.notify({ - title: 'Important Message', - body: msg.content, - type: 'info' - }); - } -}); -``` - -#### Real-World Examples - -**Auto-expand abbreviations:** -```javascript -ctx.messages.onBeforeSend((msg) => { - const expansions = { - 'brb': 'be right back', - 'omw': 'on my way', - 'gtg': 'got to go', - 'afk': 'away from keyboard' - }; - - Object.entries(expansions).forEach(([abbr, full]) => { - const regex = new RegExp(`\\b${abbr}\\b`, 'gi'); - msg.content = msg.content.replace(regex, full); - }); -}); -``` - -**Add auto-shrug to questions:** -```javascript -ctx.messages.onBeforeSend((msg) => { - if (msg.content.trim().endsWith('?')) { - msg.content += ' ¯\\_(ツ)_/¯'; - } -}); -``` - -**Track mentions:** -```javascript -ctx.messages.onReceive((msg) => { - const userId = ctx.matrixClient.getUserId(); - if (msg.content.includes(userId)) { - ctx.log('You were mentioned!'); - ctx.notify('Someone mentioned you!'); - } -}); -``` - -#### API Reference - -```typescript -interface MessagesAPI { - onBeforeSend(handler: (msg: Message) => void): void; - onReceive(handler: (msg: Message) => void): void; -} - -interface Message { - content: string; - roomId?: string; - sender?: string; - eventId?: string; -} -``` - ---- - -### Custom Renderers - -Register custom UI renderers for messages, images, and other content types. - -```javascript -// Custom message renderer -ctx.ui.registerRenderer("message", (msg, defaultRenderer) => { - // Custom logic - if (msg.sender?.includes('bot')) { - // Return custom React component - return ctx.React.createElement('div', { - style: { background: 'yellow' } - }, msg.content); - } - - // Fall back to default - return defaultRenderer?.(); -}); - -// Custom image renderer -ctx.ui.registerRenderer("image", (data, defaultRenderer) => { - // Add custom effects, borders, etc. - return defaultRenderer?.(); -}); -``` - -#### API Reference - -```typescript -interface UIAPI { - registerButton(button: UIButtonDefinition): void; - unregisterButton(id: string): void; - registerRenderer( - type: string, - renderer: (data: any, defaultRenderer?: () => ReactNode) => ReactNode - ): void; -} - -type CustomRenderer = ( - data: any, - defaultRenderer?: () => ReactNode -) => ReactNode; -``` - ---- - -### UI: Button Registration - -Inject buttons into various parts of the Paarrot UI. Buttons render in two visual styles depending on location — **nav list rows** (icon + label, full width) or **icon buttons** (compact, toolbar-style). - -See [PLUGIN_BUTTON_API.md](PLUGIN_BUTTON_API.md) for the complete reference including positioning and grouping examples. - -#### Register a Button - -```javascript -ctx.ui.registerButton({ - id: 'my-button', - location: 'text-composer-toolbar', - label: 'My Tool', - icon: '🔧', - onClick: () => ctx.log('clicked!') -}); -``` - -#### UI Locations — Nav List Rows - -| Location | Where it appears | -|---|---| -| `channel-list` | Space channel list, below Lobby and Message Search | -| `home-section` | Home panel, inside the Rooms category above the room list | -| `direct-messages` | DMs panel, below "Create Chat" and above the CHATS dropdown | - -#### UI Locations — Icon Buttons - -| Location | Where it appears | -|---|---| -| `text-composer-toolbar` | Message composer toolbar (alongside emoji, sticker buttons) | -| `composer-actions` | Left side of the composer, beside the `+` attach button | -| `room-header` | Top room header bar, before the ⋮ menu | -| `room-menu` | Room ⋮ dropdown menu | -| `message-actions` | Message hover action bar | -| `user-menu` | Right-click popup on the user avatar | -| `search-notification-section` | Notifications page header (right side) | -| `sidebar-actions` | Left sidebar — above Explore Servers icon, and above Search icon in the sticky bottom section | - -#### Positioning - -```javascript -ctx.ui.registerButton({ - id: 'my-button', - location: 'text-composer-toolbar', - label: 'My Tool', - icon: '🔧', - position: { - after: 'emoji-picker-button', - group: 'my-tools', - order: 1 - }, - onClick: () => ctx.log('clicked!') -}); -``` - -#### Unregister - -```javascript -ctx.ui.unregisterButton('my-button'); -// All buttons are automatically unregistered on plugin unload -``` - -#### API Reference - -```typescript -type UILocation = - | 'channel-list' | 'direct-messages' | 'home-section' - | 'text-composer-toolbar' | 'composer-actions' - | 'room-header' | 'room-menu' | 'message-actions' - | 'user-menu' | 'search-notification-section' | 'sidebar-actions'; - -interface UIButtonDefinition { - id: string; - location: UILocation; - label: string; - icon?: string; - position?: UIButtonPosition; - onClick?: () => void | Promise; -} - -interface UIButtonPosition { - before?: string; - after?: string; - group?: string; - order?: number; -} -``` - ---- - -### Settings - -Define settings that users can configure in the UI. - -#### Define Settings Schema - -```javascript -ctx.settings.define({ - // Boolean toggle - enableFeature: { - type: "boolean", - label: "Enable Feature", - description: "Toggle this feature on/off", - default: true - }, - - // String input - apiKey: { - type: "string", - label: "API Key", - description: "Your API key", - default: "" - }, - - // Number input - maxRetries: { - type: "number", - label: "Max Retries", - description: "Maximum retry attempts", - default: 3 - }, - - // Select dropdown - theme: { - type: "select", - label: "Theme", - description: "Choose a theme", - options: [ - { value: "dark", label: "Dark Mode" }, - { value: "light", label: "Light Mode" } - ], - default: "dark" - }, - - // Color picker - accentColor: { - type: "color", - label: "Accent Color", - description: "Choose your accent color", - default: "#6366f1" - } -}); -``` - -#### Get/Set Settings - -```javascript -// Get a setting -const theme = ctx.settings.get('theme'); -const enabled = ctx.settings.get('enableFeature'); - -// Set a setting (usually done by user in UI) -ctx.settings.set('theme', 'dark'); -``` - -#### API Reference - -```typescript -interface SettingsAPI { - define(schema: SettingsSchema): void; - get(key: string): T | undefined; - set(key: string, value: any): void; -} - -interface SettingDefinition { - type: 'string' | 'number' | 'boolean' | 'select' | 'color'; - label?: string; - description?: string; - default?: any; - options?: Array<{ value: any; label: string }>; -} - -type SettingsSchema = Record; -``` - ---- - -### Themes - -Register custom themes that appear in the theme selector dropdown. Themes use CSS that's injected once when the plugin loads. - -#### Register a Theme - -```javascript -ctx.themes.register({ - id: "my-theme", - name: "My Custom Theme", - kind: "dark", // "light" or "dark" - css: ` - body.plugin-my-plugin-my-theme { - background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); - } - - .plugin-my-plugin-my-theme #root { - background: linear-gradient(180deg, rgba(22, 33, 62, 0.5) 0%, rgba(15, 52, 96, 0.3) 100%); - } - - .plugin-my-plugin-my-theme { - --tc-link: hsl(213, 100%, 80%); - } - ` -}); -``` - -#### How It Works - -1. CSS is injected as a `