From bc109c4ae3cf34195d240237442dacd19a295839 Mon Sep 17 00:00:00 2001 From: Pranav Katariya Date: Tue, 27 Jan 2026 22:01:03 -0800 Subject: [PATCH] feat: register Zoom as core channel and fix critical issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Register Zoom in CHAT_CHANNEL_ORDER and CHAT_CHANNEL_META (core channel) - Add Zoom to channel dock with config/capabilities - Fix wrong default apiHost: https://api.zoom.us (was https://zoom.us) - Create normalize helpers (normalizeZoomMessagingTarget, looksLikeZoomTargetId) - Export normalize helpers from plugin-sdk - Update channel.ts to use normalize helpers from SDK - Fix version mismatch (2026.1.26 across all packages) Docs improvements: - Convert all config examples from YAML to JSON5 (matches Moltbot format) - Fix config path references (moltbot.yaml → ~/.clawdbot/moltbot.json) - Make LLM provider reference generic (not hardcoded to Anthropic) - Correct Zoom app setup: General App (not Team Chat App) - Add detailed steps: Production tab OAuth, Features tab Team Chat setup - Update onboarding wizard instructions to match actual Zoom UI flow --- docs/channels/zoom.md | 149 +++++++++++++++--------- extensions/zoom/package.json | 2 +- extensions/zoom/src/channel.ts | 18 +-- src/channels/dock.ts | 16 +++ src/channels/plugins/normalize/zoom.ts | 23 ++++ src/channels/plugins/onboarding/zoom.ts | 9 +- src/channels/registry.ts | 11 ++ src/plugin-sdk/index.ts | 4 + src/zoom/config.ts | 2 +- 9 files changed, 162 insertions(+), 72 deletions(-) create mode 100644 src/channels/plugins/normalize/zoom.ts diff --git a/docs/channels/zoom.md b/docs/channels/zoom.md index 18199a0bf..d0cc0d5e1 100644 --- a/docs/channels/zoom.md +++ b/docs/channels/zoom.md @@ -11,12 +11,11 @@ Status: production-ready plugin for Zoom Team Chat direct messages via Team Chat ## Quick setup 1) Create a 'General App' in [Zoom App Marketplace](https://marketplace.zoom.us/develop/create) -2) Enable the Team Chat surface and note your credentials (Client ID, Client Secret, Bot JID, Webhook Secret Token) -3) Install the plugin: `pnpm install` (the zoom extension is included in the workspace) -4) Configure URLs (see URL Configuration below): - - Webhook URL: `https://gateway-host/webhooks/zoom` - - OAuth Redirect URL: `https://gateway-host/api/zoomapp/auth` -5) Set credentials in config: +2) Production tab: Add OAuth Redirect URL (`https://gateway-host/api/zoomapp/auth`) +3) Features tab → Surfaces → Team Chat: Toggle on Team Chat Subscription, add Bot endpoint URL (`https://gateway-host/webhooks/zoom`) and Welcome Message +4) Note your credentials: Client ID, Client Secret, Bot JID, Secret Token +5) Install the plugin: `pnpm install` (the zoom extension is included in the workspace) +6) Set credentials in config: ```json5 { channels: { @@ -43,16 +42,23 @@ Status: production-ready plugin for Zoom Team Chat direct messages via Team Chat ## Setup (detailed) -### 1. Create Zoom Team Chat App +### 1. Create Zoom App 1) Go to [Zoom App Marketplace](https://marketplace.zoom.us/develop/create) -2) Click "Create" and select "Team Chat App" -3) Enable the **Bot** feature -4) Note your credentials from the app settings: - - **Client ID** - - **Client Secret** - - **Bot JID** (e.g., `bot@xmppdev.zoom.us`) - - **Secret Token** (for webhook verification) +2) Click "Create" and select "**General App**" +3) Fill in basic app information (name, description, etc.) +4) Go to the **Production** tab: + - Add **OAuth Redirect URL**: `https://gateway-host/api/zoomapp/auth` +5) Go to the **Features** tab: + - Under **Surfaces**, select "**Team Chat**" + - Toggle on "**Team Chat Subscription**" + - Add **Bot endpoint URL**: `https://gateway-host/webhooks/zoom` + - Set **Welcome Message** for the bot (e.g., "Hello! I'm your AI assistant.") +6) Note your credentials from the app settings: + - **Client ID** (from App Credentials) + - **Client Secret** (from App Credentials) + - **Bot JID** (from Team Chat section, e.g., `bot@xmppdev.zoom.us`) + - **Secret Token** (from Team Chat section, for webhook verification) ### 2. Configure URLs @@ -83,20 +89,25 @@ Subscribe to these event types: ### 4. Configure Moltbot -Add to `moltbot.yaml`: +Add to `~/.clawdbot/moltbot.json`: -```yaml -channels: - zoom: - enabled: true - clientId: YOUR_CLIENT_ID - clientSecret: YOUR_CLIENT_SECRET - botJid: YOUR_BOT_JID@xmppdev.zoom.us - secretToken: YOUR_SECRET_TOKEN - apiHost: https://zoomdev.us # Use https://api.zoom.us for production - oauthHost: https://zoomdev.us # Use https://zoom.us for production - dm: - policy: open # open | closed | allowlist +```json5 +{ + channels: { + zoom: { + enabled: true, + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + botJid: "YOUR_BOT_JID@xmppdev.zoom.us", + secretToken: "YOUR_SECRET_TOKEN", + apiHost: "https://zoomdev.us", // Use https://api.zoom.us for production + oauthHost: "https://zoomdev.us", // Use https://zoom.us for production + dm: { + policy: "open" // open | allowlist | pairing + } + } + } +} ``` ### 5. Start Gateway @@ -116,47 +127,77 @@ Send a direct message to your bot in Zoom Team Chat. The bot should respond with ### DM Policies **Open** (default): -```yaml -dm: - policy: open +```json5 +{ + channels: { + zoom: { + dm: { + policy: "open" + } + } + } +} ``` Anyone can message the bot. **Closed**: -```yaml -dm: - policy: closed +```json5 +{ + channels: { + zoom: { + dm: { + policy: "closed" + } + } + } +} ``` Bot rejects all DMs. **Allowlist**: -```yaml -dm: - policy: allowlist - allowFrom: - - user1@xmppdev.zoom.us - - user2@xmppdev.zoom.us +```json5 +{ + channels: { + zoom: { + dm: { + policy: "allowlist", + allowFrom: [ + "user1@xmppdev.zoom.us", + "user2@xmppdev.zoom.us" + ] + } + } + } +} ``` Only specified users can message the bot. ### Development vs Production **Development (zoomdev.us):** -```yaml -channels: - zoom: - apiHost: https://zoomdev.us - oauthHost: https://zoomdev.us - botJid: bot@xmppdev.zoom.us +```json5 +{ + channels: { + zoom: { + apiHost: "https://zoomdev.us", + oauthHost: "https://zoomdev.us", + botJid: "bot@xmppdev.zoom.us" + } + } +} ``` **Production (zoom.us):** -```yaml -channels: - zoom: - apiHost: https://api.zoom.us - oauthHost: https://zoom.us - botJid: bot@xmpp.zoom.us +```json5 +{ + channels: { + zoom: { + apiHost: "https://api.zoom.us", + oauthHost: "https://zoom.us", + botJid: "bot@xmpp.zoom.us" + } + } +} ``` ## Features @@ -205,8 +246,8 @@ If you see "port 3000 already in use": ### Bot Not Responding -1. Verify credentials in `moltbot.yaml` are correct -2. Check Anthropic API key is valid +1. Verify credentials in `~/.clawdbot/moltbot.json` are correct +2. Check LLM provider API key is configured (see `moltbot login`) 3. Ensure `botJid` matches your app's Bot JID exactly 4. Confirm app is installed to your Zoom account 5. Check gateway logs for authentication errors @@ -233,7 +274,7 @@ For implementation details, see the [plugin source code](https://github.com/molt **Never commit credentials!** -Store sensitive values in `moltbot.yaml` (gitignored) and use `moltbot-example.yaml` for documentation templates. +Store sensitive values in `~/.clawdbot/moltbot.json` (user's home directory, not in repo). Webhook events are verified using the `secretToken` from your Zoom app configuration. diff --git a/extensions/zoom/package.json b/extensions/zoom/package.json index 0b124b019..30fc607c6 100644 --- a/extensions/zoom/package.json +++ b/extensions/zoom/package.json @@ -1,6 +1,6 @@ { "name": "@moltbot/zoom", - "version": "2026.1.27", + "version": "2026.1.26", "type": "module", "description": "Moltbot Zoom Team Chat channel plugin", "moltbot": { diff --git a/extensions/zoom/src/channel.ts b/extensions/zoom/src/channel.ts index 69b72739b..937783919 100644 --- a/extensions/zoom/src/channel.ts +++ b/extensions/zoom/src/channel.ts @@ -1,6 +1,9 @@ import { buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, + getChatChannelMeta, + looksLikeZoomTargetId, + normalizeZoomMessagingTarget, type ChannelPlugin, type ResolvedZoomAccount, resolveZoomAccount, @@ -9,16 +12,7 @@ import { zoomOnboardingAdapter, } from "clawdbot/plugin-sdk"; -const meta = { - id: "zoom", - label: "Zoom", - selectionLabel: "Zoom Team Chat", - docsPath: "/channels/zoom", - docsLabel: "zoom", - blurb: "Zoom Team Chat via Team Chat Bot API.", - order: 45, - quickstartAllowFrom: true, -}; +const meta = getChatChannelMeta("zoom"); export const zoomPlugin: ChannelPlugin = { id: "zoom", @@ -88,9 +82,9 @@ export const zoomPlugin: ChannelPlugin = { collectWarnings: () => [], }, messaging: { - normalizeTarget: (target) => String(target ?? "").trim(), + normalizeTarget: (target) => normalizeZoomMessagingTarget(String(target ?? "")), targetResolver: { - looksLikeId: (input) => input.includes("@xmpp") || input.includes("@xmppdev"), + looksLikeId: (input) => looksLikeZoomTargetId(input), hint: "", }, }, diff --git a/src/channels/dock.ts b/src/channels/dock.ts index 600499091..47a22f954 100644 --- a/src/channels/dock.ts +++ b/src/channels/dock.ts @@ -9,6 +9,7 @@ import { normalizeAccountId } from "../routing/session-key.js"; import { normalizeE164 } from "../utils.js"; import { resolveWhatsAppAccount } from "../web/accounts.js"; import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js"; +import { resolveZoomAccount } from "../zoom/config.js"; import { requireActivePluginRegistry } from "../plugins/runtime.js"; import { resolveDiscordGroupRequireMention, @@ -369,6 +370,21 @@ const DOCKS: Record = { }, }, }, + zoom: { + id: "zoom", + capabilities: { + chatTypes: ["direct"], + }, + outbound: { textChunkLimit: 4000 }, + config: { + resolveAllowFrom: ({ cfg, accountId }) => + (resolveZoomAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) => + String(entry), + ), + formatAllowFrom: ({ allowFrom }) => + allowFrom.map((entry) => String(entry).trim()).filter(Boolean), + }, + }, }; function buildDockFromPlugin(plugin: ChannelPlugin): ChannelDock { diff --git a/src/channels/plugins/normalize/zoom.ts b/src/channels/plugins/normalize/zoom.ts new file mode 100644 index 000000000..d46de6ed4 --- /dev/null +++ b/src/channels/plugins/normalize/zoom.ts @@ -0,0 +1,23 @@ +export function normalizeZoomMessagingTarget(raw: string): string | undefined { + const trimmed = raw.trim(); + if (!trimmed) return undefined; + + // Strip optional zoom: prefix + const withoutPrefix = trimmed.replace(/^zoom:/i, "").trim(); + if (!withoutPrefix) return undefined; + + return withoutPrefix; +} + +export function looksLikeZoomTargetId(raw: string): boolean { + const trimmed = raw.trim(); + if (!trimmed) return false; + + // Zoom JIDs contain @xmpp or @xmppdev + if (trimmed.includes("@xmpp") || trimmed.includes("@xmppdev")) return true; + + // Or starts with zoom: prefix + if (/^zoom:/i.test(trimmed)) return true; + + return false; +} diff --git a/src/channels/plugins/onboarding/zoom.ts b/src/channels/plugins/onboarding/zoom.ts index 8cb7f1288..f6ea18100 100644 --- a/src/channels/plugins/onboarding/zoom.ts +++ b/src/channels/plugins/onboarding/zoom.ts @@ -33,13 +33,14 @@ async function noteZoomHelp(prompter: WizardPrompter): Promise { await prompter.note( [ "1) Go to Zoom App Marketplace (marketplace.zoom.us/develop/create)", - "2) Create a Team Chat App and enable Bot feature", - "3) Copy Client ID, Client Secret, Bot JID, and Secret Token", - "4) You'll configure webhook and OAuth redirect URLs after setup", + "2) Create a General App", + "3) Production tab: add OAuth Redirect URL", + "4) Features tab > Surfaces > Team Chat: enable subscription + bot endpoint", + "5) Copy Client ID, Client Secret, Bot JID, Secret Token", `Docs: ${formatDocsLink("/channels/zoom")}`, "Website: https://molt.bot", ].join("\n"), - "Zoom Team Chat setup", + "Zoom setup", ); } diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 6afe1996c..cd7fe1bbb 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -12,6 +12,7 @@ export const CHAT_CHANNEL_ORDER = [ "slack", "signal", "imessage", + "zoom", ] as const; export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number]; @@ -98,6 +99,16 @@ const CHAT_CHANNEL_META: Record = { blurb: "this is still a work in progress.", systemImage: "message.fill", }, + zoom: { + id: "zoom", + label: "Zoom", + selectionLabel: "Zoom Team Chat", + detailLabel: "Zoom Team Chat", + docsPath: "/channels/zoom", + docsLabel: "zoom", + blurb: "Zoom Team Chat via Team Chat Bot API.", + systemImage: "video", + }, }; export const CHAT_CHANNEL_ALIASES: Record = { diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index b0013df26..39e6fc0e8 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -312,6 +312,10 @@ export { resolveZoomAccount, type ResolvedZoomAccount, type ZoomConfig } from ". export { ZoomConfigSchema } from "../zoom/config.js"; export { monitorZoomProvider, type MonitorZoomOpts } from "../zoom/monitor.js"; export { zoomOnboardingAdapter } from "../channels/plugins/onboarding/zoom.js"; +export { + looksLikeZoomTargetId, + normalizeZoomMessagingTarget, +} from "../channels/plugins/normalize/zoom.js"; // Channel: Signal export { diff --git a/src/zoom/config.ts b/src/zoom/config.ts index ebf4ff186..71e9336cc 100644 --- a/src/zoom/config.ts +++ b/src/zoom/config.ts @@ -46,7 +46,7 @@ export function resolveZoomAccount(params: { clientSecret: config.clientSecret, botJid: config.botJid, secretToken: config.secretToken, - apiHost: config.apiHost || "https://zoom.us", + apiHost: config.apiHost || "https://api.zoom.us", oauthHost: config.oauthHost || "https://zoom.us", config, };