# API Reference Complete reference for the Enhanced Mattermost Extension API. ## Table of Contents - [Actions](#actions) - [Interactive Directives](#interactive-directives) - [Configuration Types](#configuration-types) - [Error Handling](#error-handling) - [Security Audit](#security-audit) - [Client API](#client-api) ## Actions ### editMessage Edit an existing message posted by the bot. ```typescript import { editMessage } from '@lumbridgecorp/openclaw-mattermost'; const result = await editMessage({ cfg: OpenClawConfig, postId: string, channelId: string, message?: string, props?: Record, accountId?: string | null, fetchImpl?: MattermostFetch }); ``` **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration object | | `postId` | `string` | Yes | ID of the post to edit | | `channelId` | `string` | Yes | Channel ID containing the post | | `message` | `string` | No | New message content | | `props` | `Record` | No | Additional post properties | | `accountId` | `string \| null` | No | Specific account to use | | `fetchImpl` | `MattermostFetch` | No | Custom fetch implementation | **Returns:** ```typescript type EditMessageResult = | { ok: true; postId: string; channelId: string } | { ok: false; error: string; errorCode?: string }; ``` **Error Codes:** - `PERMISSION_DENIED` - Cannot edit other users' messages or system messages - `POST_NOT_FOUND` - Post was deleted or doesn't exist - `RATE_LIMITED` - Too many edit requests - `AUTH_FAILED` - Invalid or expired token **Example:** ```typescript const result = await editMessage({ cfg: openclawConfig, postId: 'abc123def456ghi789jkl012', channelId: 'channel123', message: 'Updated message content' }); if (result.ok) { console.log(`Edited post ${result.postId}`); } else { console.error(`Edit failed: ${result.error} (${result.errorCode})`); } ``` --- ### deleteMessage Delete a message posted by the bot. ```typescript import { deleteMessage } from '@lumbridgecorp/openclaw-mattermost'; const result = await deleteMessage({ cfg: OpenClawConfig, postId: string, channelId?: string, accountId?: string | null, fetchImpl?: MattermostFetch }); ``` **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration object | | `postId` | `string` | Yes | ID of the post to delete | | `channelId` | `string` | No | Channel ID (for validation) | | `accountId` | `string \| null` | No | Specific account to use | | `fetchImpl` | `MattermostFetch` | No | Custom fetch implementation | **Returns:** ```typescript type DeleteMessageResult = { ok: true } | { ok: false; error: string }; ``` **Notes:** - Returns `ok: true` if post is already deleted (idempotent) - Cannot delete system messages - Can only delete bot's own messages **Example:** ```typescript const result = await deleteMessage({ cfg: openclawConfig, postId: 'abc123def456ghi789jkl012' }); if (result.ok) { console.log('Message deleted successfully'); } else { console.error(`Delete failed: ${result.error}`); } ``` --- ### downloadFile Download a file from Mattermost with security validations. ```typescript import { downloadFile } from '@lumbridgecorp/openclaw-mattermost'; const result = await downloadFile({ cfg: OpenClawConfig, fileId: string, destinationPath: string, maxSize?: number, accountId?: string | null, fetchImpl?: MattermostFetch, allowedMimePrefixes?: string[] }); ``` **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `cfg` | `OpenClawConfig` | Yes | - | OpenClaw configuration | | `fileId` | `string` | Yes | - | Mattermost file ID | | `destinationPath` | `string` | Yes | - | Local path to save file | | `maxSize` | `number` | No | 100MB | Maximum file size in bytes | | `accountId` | `string \| null` | No | - | Specific account to use | | `fetchImpl` | `MattermostFetch` | No | - | Custom fetch implementation | | `allowedMimePrefixes` | `string[]` | No | Built-in list | Allowed MIME types | **Returns:** ```typescript type DownloadFileResult = | { ok: true; filePath: string; fileId: string; metadata: FileMetadata } | { ok: false; error: string; errorCode?: string }; type FileMetadata = { id: string; name: string; mimeType: string; size: number; extension: string; }; ``` **Error Codes:** - `CONFIG_ERROR` - Missing botToken or baseUrl - `INVALID_PARAMS` - Missing fileId or destinationPath - `FILE_TOO_LARGE` - File exceeds maxSize - `EMPTY_FILE` - File has no content - `BLOCKED_FILE_TYPE` - Executable file extension - `BLOCKED_MIME_TYPE` - MIME type not in allowlist - `FILE_NOT_FOUND` - File doesn't exist - `PERMISSION_DENIED` - No access to file - `AUTH_FAILED` - Invalid token - `DOWNLOAD_FAILED` - Network or server error **Security Restrictions:** Blocked file extensions: ``` .exe, .sh, .bat, .cmd, .com, .msi, .dll, .so, .dylib, .app, .dmg, .pkg, .deb, .rpm, .apk, .ipa, .jar, .war, .ear, .py, .rb, .pl, .php, .js, .ts, .vbs, .ps1, .psm1, .scr, .hta, .bin, .run, .out, .elf ``` Default allowed MIME types: ``` image/*, video/*, audio/*, text/* application/pdf, application/json, application/xml application/csv, application/zip, application/gzip, application/tar ``` **Example:** ```typescript const result = await downloadFile({ cfg: openclawConfig, fileId: 'file123abc', destinationPath: '/tmp/report.pdf', maxSize: 50 * 1024 * 1024 // 50MB }); if (result.ok) { console.log(`Downloaded: ${result.filePath}`); console.log(`Size: ${result.metadata.size} bytes`); console.log(`Type: ${result.metadata.mimeType}`); } ``` --- ### sendMessage Send a message to a Mattermost channel. ```typescript import { sendMessage } from '@lumbridgecorp/openclaw-mattermost'; await sendMessage({ cfg: OpenClawConfig, channelId: string, message: string, rootId?: string, fileIds?: string[], props?: Record, accountId?: string | null }); ``` **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration | | `channelId` | `string` | Yes | Target channel ID | | `message` | `string` | Yes | Message content (supports Markdown) | | `rootId` | `string` | No | Parent post ID for threaded replies | | `fileIds` | `string[]` | No | Array of uploaded file IDs to attach | | `props` | `Record` | No | Additional post properties | | `accountId` | `string \| null` | No | Specific account to use | **Example:** ```typescript await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message: 'Hello **World**!', // Markdown supported rootId: 'parent-post-id' // Thread reply }); ``` --- ### addReaction Add an emoji reaction to a message. ```typescript import { addReaction } from '@lumbridgecorp/openclaw-mattermost'; await addReaction({ cfg: OpenClawConfig, postId: string, emoji: string, // e.g., 'thumbsup', 'white_check_mark' accountId?: string | null }); ``` **Example:** ```typescript await addReaction({ cfg: openclawConfig, postId: 'post123', emoji: 'thumbsup' }); ``` --- ### uploadFile Upload a file to Mattermost. ```typescript import { uploadFile } from '@lumbridgecorp/openclaw-mattermost'; const fileInfo = await uploadFile({ cfg: OpenClawConfig, channelId: string, buffer: Buffer, fileName: string, contentType?: string, accountId?: string | null }); ``` **Returns:** `MattermostFileInfo` object with `id`, `name`, `mime_type`, `size` **Example:** ```typescript import { readFileSync } from 'fs'; const buffer = readFileSync('./document.pdf'); const fileInfo = await uploadFile({ cfg: openclawConfig, channelId: 'channel123', buffer, fileName: 'document.pdf', contentType: 'application/pdf' }); // Use the file ID when sending a message await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message: 'Here is the document:', fileIds: [fileInfo.id] }); ``` --- ## Interactive Directives Interactive directives allow you to embed buttons and dropdowns in messages using a special syntax. These are parsed and converted to Mattermost interactive components. ### Button Directive Syntax: ``` [[mattermost_buttons: Label1:value1:style1, Label2:value2:style2, ...]] ``` **Format:** - `Label`: Display text on the button (max 30 chars) - `value`: Value sent when button is clicked - `style`: Button style - `default`, `primary`, or `danger` (optional, defaults to `default`) **Maximum:** 5 buttons per directive **Example:** ```typescript const message = ` Please review this request: [[mattermost_buttons: Approve:approve_request:primary, Reject:reject_request:danger, Review Later:review_later:default ]] `; await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message }); ``` --- ### Select/Dropdown Directive Syntax: ``` [[mattermost_select: Placeholder | Label1:value1, Label2:value2, ...]] ``` **Format:** - `Placeholder`: Text shown when nothing selected (optional, defaults to "Choose an option") - `Label`: Display text for the option - `value`: Value sent when option is selected **Maximum:** 100 options per directive **Example:** ```typescript const message = ` Select a priority level: [[mattermost_select: Select Priority | Low:low, Medium:medium, High:high, Critical:critical ]] `; await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message }); ``` --- ### Multiple Directives You can combine multiple directives in a single message: ```typescript const message = ` Deployment ready for production. [[mattermost_buttons: Deploy:deploy:primary, Cancel:cancel:danger]] Select deployment region: [[mattermost_select: Choose Region | US East:us-east, US West:us-west, EU:eu, APAC:apac]] `; ``` --- ### Handling Interactions When a user interacts with buttons or selects, Mattermost sends a callback to your configured `interactions.callbackBaseUrl`: ```json { "type": "button" | "select", "value": "the_button_or_option_value", "user_id": "mattermost_user_id", "channel_id": "channel_id", "post_id": "original_post_id", "context": { ... } } ``` **Configuration:** ```json { "channels": { "mattermost": { "interactions": { "callbackBaseUrl": "https://your-bot.example.com/callbacks", "allowedSourceIps": ["10.0.0.0/8", "192.168.1.0/24"] } } } } ``` --- ## Configuration Types ### MattermostAccountConfig ```typescript interface MattermostAccountConfig { // Core settings baseUrl?: string; botToken?: SecretInput; pat?: SecretInput; enabled?: boolean; name?: string; // Access control dmPolicy?: 'open' | 'allowlist' | 'pairing'; allowFrom?: Array; groupPolicy?: 'open' | 'allowlist'; groupAllowFrom?: Array; requireMention?: boolean; // Chat modes chatmode?: 'oncall' | 'onmessage' | 'onchar'; oncharPrefixes?: string[]; // Message settings textChunkLimit?: number; chunkMode?: 'length' | 'newline'; blockStreaming?: boolean; blockStreamingCoalesce?: BlockStreamingConfig; replyToMode?: 'off' | 'first' | 'all'; responsePrefix?: string; // Slash commands commands?: { native?: boolean | 'auto'; nativeSkills?: boolean | 'auto'; callbackPath?: string; callbackUrl?: string; }; // Interactions interactions?: { callbackBaseUrl?: string; allowedSourceIps?: string[]; }; // Actions actions?: { reactions?: boolean; downloadFile?: boolean; delete?: boolean; }; // Security allowPrivateNetwork?: boolean; configWrites?: boolean; dangerouslyAllowNameMatching?: boolean; // Retry dmChannelRetry?: { maxRetries?: number; initialDelayMs?: number; maxDelayMs?: number; timeoutMs?: number; }; // Group-specific settings groups?: Record; // Capabilities capabilities?: string[] | { interactiveReplies?: boolean }; markdown?: MarkdownConfig; } ``` ### SecretInput Secret values can be provided in multiple formats: ```typescript type SecretInput = | string // Raw value (not recommended for production) | { $env: string } // Environment variable reference | { $file: string } // File path containing secret | { $secretRef: string }; // Secret manager reference ``` **Examples:** ```json { "botToken": "${MATTERMOST_BOT_TOKEN}", "botToken": { "$env": "MATTERMOST_BOT_TOKEN" }, "botToken": { "$file": "/run/secrets/bot_token" } } ``` --- ## Error Handling ### Error Classes All errors extend `MattermostError` with structured information: ```typescript class MattermostError extends Error { readonly code: ErrorCode; readonly context: ErrorContext; readonly retryable: boolean; readonly userMessage: string; readonly retryAfterMs?: number; toJSON(): Record; toLogString(): string; } ``` ### Error Codes | Code | Description | Retryable | |------|-------------|-----------| | `MATTERMOST_API_ERROR` | General API error | Depends on status | | `MATTERMOST_API_TIMEOUT` | Request timeout | Yes | | `MATTERMOST_API_RATE_LIMIT` | Rate limited | Yes | | `AUTHENTICATION_FAILED` | Invalid credentials | No | | `TOKEN_INVALID` | Token revoked | No | | `TOKEN_EXPIRED` | Token expired | No | | `PERMISSION_DENIED` | Access denied | No | | `POST_EDIT_DENIED` | Cannot edit post | No | | `CHANNEL_ACCESS_DENIED` | Channel access denied | No | | `RESOURCE_NOT_FOUND` | Resource not found | No | | `VALIDATION_ERROR` | Invalid input | No | | `CONFIGURATION_ERROR` | Bad configuration | No | | `MISSING_BOT_TOKEN` | No bot token | No | | `MISSING_BASE_URL` | No base URL | No | | `NETWORK_ERROR` | Network failure | Yes | | `CONNECTION_ERROR` | Cannot connect | Yes | | `TIMEOUT_ERROR` | Operation timed out | Yes | | `FILE_TOO_LARGE` | File exceeds limit | No | | `BLOCKED_FILE_TYPE` | Executable file | No | | `DOWNLOAD_FAILED` | Download error | Yes | ### Using Error Boundaries ```typescript import { globalErrorBoundary, MattermostError } from '@lumbridgecorp/openclaw-mattermost'; // Register error handler const unsubscribe = globalErrorBoundary.onError((error) => { if (error instanceof MattermostError) { console.error(`[${error.code}] ${error.toLogString()}`); if (error.retryable) { console.log(`Retry after: ${error.retryAfterMs}ms`); } } }); // Wrap operations const safeOperation = globalErrorBoundary.wrap( riskyOperation, 'my-operation', { accountId: 'default' } ); // Later: unsubscribe disunsubscribe(); ``` ### Retry Configuration ```typescript import { withRetry } from '@lumbridgecorp/openclaw-mattermost'; const result = await withRetry( 'my-operation', async () => { // Your operation here return await someApiCall(); }, { maxRetries: 5, initialDelayMs: 1000, maxDelayMs: 30000, timeoutMs: 60000, onRetry: ({ attempt, maxRetries, delayMs, error }) => { console.log(`Retry ${attempt}/${maxRetries} after ${delayMs}ms: ${error.message}`); } } ); ``` --- ## Security Audit ### collectMattermostSecurityAuditFindings Run a comprehensive security audit on your configuration: ```typescript import { collectMattermostSecurityAuditFindings } from '@lumbridgecorp/openclaw-mattermost'; const findings = await collectMattermostSecurityAuditFindings({ cfg: OpenClawConfig, accountId?: string | null, account: ResolvedMattermostAccount }); ``` **Returns:** Array of `SecurityAuditFinding`: ```typescript type SecurityAuditFinding = { checkId: string; // Unique check identifier severity: 'info' | 'warn' | 'critical'; title: string; detail: string; remediation?: string; }; ``` ### Audit Checks | Check ID | Severity | Description | |----------|----------|-------------| | `channels.mattermost.pat.hardcoded_token` | Critical | Bot token hardcoded in config | | `channels.mattermost.pat.short_token` | Warn | Token unusually short | | `channels.mattermost.pat.missing_token` | Critical | No bot token configured | | `channels.mattermost.https.insecure_url` | Critical | HTTP instead of HTTPS | | `channels.mattermost.https.missing_url` | Critical | No base URL configured | | `channels.mattermost.input.insecure_callback` | Critical | Callback URL uses HTTP | | `channels.mattermost.input.no_source_ip_restriction` | Warn | No IP allowlist on callbacks | | `channels.mattermost.network.private_access_enabled` | Warn | Private network access enabled | | `channels.mattermost.commands.access_groups_disabled` | Critical | Commands bypass access groups | | `channels.mattermost.commands.no_allowlist` | Warn | No allowlist for slash commands | | `channels.mattermost.config.legacy_dm_policy` | Info | Legacy dm.policy config found | | `channels.mattermost.config.legacy_allow_from` | Info | Legacy dm.allowFrom config found | | `channels.mattermost.allowlist.mutable_entries` | Warn | Non-ID entries in allowlist | | `channels.mattermost.actions.download_file_enabled` | Info | File download enabled | | `channels.mattermost.actions.delete_enabled` | Info | Message delete enabled | | `channels.mattermost.groups.open_policy` | Warn | Open group policy | | `channels.mattermost.dm.open_policy` | Warn | Open DM policy | | `channels.mattermost.dm.pairing_mode` | Info | DM pairing mode active | **Example:** ```typescript import { collectMattermostSecurityAuditFindings, collectAllMattermostSecurityAuditFindings } from '@lumbridgecorp/openclaw-mattermost'; // Audit single account const findings = await collectMattermostSecurityAuditFindings({ cfg: openclawConfig, accountId: 'default', account: resolvedAccount }); // Audit all accounts const allFindings = await collectAllMattermostSecurityAuditFindings({ cfg: openclawConfig, listAccounts: (cfg) => resolveAllAccounts(cfg) }); // Display findings by severity const critical = findings.filter(f => f.severity === 'critical'); const warnings = findings.filter(f => f.severity === 'warn'); console.log(`Critical issues: ${critical.length}`); for (const finding of critical) { console.log(`\n[!] ${finding.title}`); console.log(` ${finding.detail}`); console.log(` Fix: ${finding.remediation}`); } ``` --- ## Client API ### createMattermostClient Create a low-level Mattermost API client: ```typescript import { createMattermostClient } from '@lumbridgecorp/openclaw-mattermost'; const client = createMattermostClient({ baseUrl: string; botToken: string; pat?: string; fetchImpl?: MattermostFetch; allowPrivateNetwork?: boolean; accountId?: string; }); ``` **Client Methods:** | Method | Description | |--------|-------------| | `request(path, init?)` | Make authenticated API request | | `fetchImpl(input, init?)` | Low-level fetch with guards | **Request Examples:** ```typescript // Get current user const me = await client.request('/users/me'); // Get a channel const channel = await client.request(`/channels/${channelId}`); // Create a post const post = await client.request('/posts', { method: 'POST', body: JSON.stringify({ channel_id: channelId, message: 'Hello World' }) }); ``` ### Helper Functions ```typescript // User operations fetchMattermostMe(client) -> MattermostUser fetchMattermostUser(client, userId) -> MattermostUser fetchMattermostUserByUsername(client, username) -> MattermostUser // Channel operations fetchMattermostChannel(client, channelId) -> MattermostChannel fetchMattermostChannelByName(client, teamId, channelName) -> MattermostChannel createMattermostDirectChannel(client, userIds) -> MattermostChannel createMattermostDirectChannelWithRetry(client, userIds, options) -> MattermostChannel // Post operations createMattermostPost(client, params) -> MattermostPost updateMattermostPost(client, postId, params) -> MattermostPost // File operations uploadMattermostFile(client, params) -> MattermostFileInfo // Other sendMattermostTyping(client, { channelId, parentId }) -> void fetchMattermostUserTeams(client, userId) -> MattermostTeam[] ``` --- ## Type Exports ```typescript import { // Config types MattermostAccountConfig, MattermostConfig, SecretInput, // Client types MattermostClient, MattermostFetch, MattermostUser, MattermostChannel, MattermostPost, MattermostFileInfo, MattermostTeam, // Action types EditMessageParams, EditMessageResult, DeleteMessageParams, DeleteMessageResult, DownloadFileParams, DownloadFileResult, FileMetadata, // Error types MattermostError, MattermostErrorOptions, ErrorContext, ErrorCode, MattermostAPIError, AuthenticationError, ValidationError, PermissionError, RateLimitError, ResourceNotFoundError, NetworkError, ConfigurationError, // Security types SecurityAuditFinding, // Retry types RetryConfig, RetryAttemptInfo } from '@lumbridgecorp/openclaw-mattermost'; ```