feat: register Zoom as core channel and fix critical issues

- 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
This commit is contained in:
Pranav Katariya 2026-01-27 22:01:03 -08:00
parent 7373b77baf
commit bc109c4ae3
9 changed files with 162 additions and 72 deletions

View File

@ -11,12 +11,11 @@ Status: production-ready plugin for Zoom Team Chat direct messages via Team Chat
## Quick setup ## Quick setup
1) Create a 'General App' in [Zoom App Marketplace](https://marketplace.zoom.us/develop/create) 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) 2) Production tab: Add OAuth Redirect URL (`https://gateway-host/api/zoomapp/auth`)
3) Install the plugin: `pnpm install` (the zoom extension is included in the workspace) 3) Features tab → Surfaces → Team Chat: Toggle on Team Chat Subscription, add Bot endpoint URL (`https://gateway-host/webhooks/zoom`) and Welcome Message
4) Configure URLs (see URL Configuration below): 4) Note your credentials: Client ID, Client Secret, Bot JID, Secret Token
- Webhook URL: `https://gateway-host/webhooks/zoom` 5) Install the plugin: `pnpm install` (the zoom extension is included in the workspace)
- OAuth Redirect URL: `https://gateway-host/api/zoomapp/auth` 6) Set credentials in config:
5) Set credentials in config:
```json5 ```json5
{ {
channels: { channels: {
@ -43,16 +42,23 @@ Status: production-ready plugin for Zoom Team Chat direct messages via Team Chat
## Setup (detailed) ## Setup (detailed)
### 1. Create Zoom Team Chat App ### 1. Create Zoom App
1) Go to [Zoom App Marketplace](https://marketplace.zoom.us/develop/create) 1) Go to [Zoom App Marketplace](https://marketplace.zoom.us/develop/create)
2) Click "Create" and select "Team Chat App" 2) Click "Create" and select "**General App**"
3) Enable the **Bot** feature 3) Fill in basic app information (name, description, etc.)
4) Note your credentials from the app settings: 4) Go to the **Production** tab:
- **Client ID** - Add **OAuth Redirect URL**: `https://gateway-host/api/zoomapp/auth`
- **Client Secret** 5) Go to the **Features** tab:
- **Bot JID** (e.g., `bot@xmppdev.zoom.us`) - Under **Surfaces**, select "**Team Chat**"
- **Secret Token** (for webhook verification) - 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 ### 2. Configure URLs
@ -83,20 +89,25 @@ Subscribe to these event types:
### 4. Configure Moltbot ### 4. Configure Moltbot
Add to `moltbot.yaml`: Add to `~/.clawdbot/moltbot.json`:
```yaml ```json5
channels: {
zoom: channels: {
enabled: true zoom: {
clientId: YOUR_CLIENT_ID enabled: true,
clientSecret: YOUR_CLIENT_SECRET clientId: "YOUR_CLIENT_ID",
botJid: YOUR_BOT_JID@xmppdev.zoom.us clientSecret: "YOUR_CLIENT_SECRET",
secretToken: YOUR_SECRET_TOKEN botJid: "YOUR_BOT_JID@xmppdev.zoom.us",
apiHost: https://zoomdev.us # Use https://api.zoom.us for production secretToken: "YOUR_SECRET_TOKEN",
oauthHost: https://zoomdev.us # Use https://zoom.us for production apiHost: "https://zoomdev.us", // Use https://api.zoom.us for production
dm: oauthHost: "https://zoomdev.us", // Use https://zoom.us for production
policy: open # open | closed | allowlist dm: {
policy: "open" // open | allowlist | pairing
}
}
}
}
``` ```
### 5. Start Gateway ### 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 ### DM Policies
**Open** (default): **Open** (default):
```yaml ```json5
dm: {
policy: open channels: {
zoom: {
dm: {
policy: "open"
}
}
}
}
``` ```
Anyone can message the bot. Anyone can message the bot.
**Closed**: **Closed**:
```yaml ```json5
dm: {
policy: closed channels: {
zoom: {
dm: {
policy: "closed"
}
}
}
}
``` ```
Bot rejects all DMs. Bot rejects all DMs.
**Allowlist**: **Allowlist**:
```yaml ```json5
dm: {
policy: allowlist channels: {
allowFrom: zoom: {
- user1@xmppdev.zoom.us dm: {
- user2@xmppdev.zoom.us policy: "allowlist",
allowFrom: [
"user1@xmppdev.zoom.us",
"user2@xmppdev.zoom.us"
]
}
}
}
}
``` ```
Only specified users can message the bot. Only specified users can message the bot.
### Development vs Production ### Development vs Production
**Development (zoomdev.us):** **Development (zoomdev.us):**
```yaml ```json5
channels: {
zoom: channels: {
apiHost: https://zoomdev.us zoom: {
oauthHost: https://zoomdev.us apiHost: "https://zoomdev.us",
botJid: bot@xmppdev.zoom.us oauthHost: "https://zoomdev.us",
botJid: "bot@xmppdev.zoom.us"
}
}
}
``` ```
**Production (zoom.us):** **Production (zoom.us):**
```yaml ```json5
channels: {
zoom: channels: {
apiHost: https://api.zoom.us zoom: {
oauthHost: https://zoom.us apiHost: "https://api.zoom.us",
botJid: bot@xmpp.zoom.us oauthHost: "https://zoom.us",
botJid: "bot@xmpp.zoom.us"
}
}
}
``` ```
## Features ## Features
@ -205,8 +246,8 @@ If you see "port 3000 already in use":
### Bot Not Responding ### Bot Not Responding
1. Verify credentials in `moltbot.yaml` are correct 1. Verify credentials in `~/.clawdbot/moltbot.json` are correct
2. Check Anthropic API key is valid 2. Check LLM provider API key is configured (see `moltbot login`)
3. Ensure `botJid` matches your app's Bot JID exactly 3. Ensure `botJid` matches your app's Bot JID exactly
4. Confirm app is installed to your Zoom account 4. Confirm app is installed to your Zoom account
5. Check gateway logs for authentication errors 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!** **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. Webhook events are verified using the `secretToken` from your Zoom app configuration.

View File

@ -1,6 +1,6 @@
{ {
"name": "@moltbot/zoom", "name": "@moltbot/zoom",
"version": "2026.1.27", "version": "2026.1.26",
"type": "module", "type": "module",
"description": "Moltbot Zoom Team Chat channel plugin", "description": "Moltbot Zoom Team Chat channel plugin",
"moltbot": { "moltbot": {

View File

@ -1,6 +1,9 @@
import { import {
buildChannelConfigSchema, buildChannelConfigSchema,
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
getChatChannelMeta,
looksLikeZoomTargetId,
normalizeZoomMessagingTarget,
type ChannelPlugin, type ChannelPlugin,
type ResolvedZoomAccount, type ResolvedZoomAccount,
resolveZoomAccount, resolveZoomAccount,
@ -9,16 +12,7 @@ import {
zoomOnboardingAdapter, zoomOnboardingAdapter,
} from "clawdbot/plugin-sdk"; } from "clawdbot/plugin-sdk";
const meta = { const meta = getChatChannelMeta("zoom");
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,
};
export const zoomPlugin: ChannelPlugin<ResolvedZoomAccount> = { export const zoomPlugin: ChannelPlugin<ResolvedZoomAccount> = {
id: "zoom", id: "zoom",
@ -88,9 +82,9 @@ export const zoomPlugin: ChannelPlugin<ResolvedZoomAccount> = {
collectWarnings: () => [], collectWarnings: () => [],
}, },
messaging: { messaging: {
normalizeTarget: (target) => String(target ?? "").trim(), normalizeTarget: (target) => normalizeZoomMessagingTarget(String(target ?? "")),
targetResolver: { targetResolver: {
looksLikeId: (input) => input.includes("@xmpp") || input.includes("@xmppdev"), looksLikeId: (input) => looksLikeZoomTargetId(input),
hint: "<userJid>", hint: "<userJid>",
}, },
}, },

View File

@ -9,6 +9,7 @@ import { normalizeAccountId } from "../routing/session-key.js";
import { normalizeE164 } from "../utils.js"; import { normalizeE164 } from "../utils.js";
import { resolveWhatsAppAccount } from "../web/accounts.js"; import { resolveWhatsAppAccount } from "../web/accounts.js";
import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js"; import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
import { resolveZoomAccount } from "../zoom/config.js";
import { requireActivePluginRegistry } from "../plugins/runtime.js"; import { requireActivePluginRegistry } from "../plugins/runtime.js";
import { import {
resolveDiscordGroupRequireMention, resolveDiscordGroupRequireMention,
@ -369,6 +370,21 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
}, },
}, },
}, },
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 { function buildDockFromPlugin(plugin: ChannelPlugin): ChannelDock {

View File

@ -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;
}

View File

@ -33,13 +33,14 @@ async function noteZoomHelp(prompter: WizardPrompter): Promise<void> {
await prompter.note( await prompter.note(
[ [
"1) Go to Zoom App Marketplace (marketplace.zoom.us/develop/create)", "1) Go to Zoom App Marketplace (marketplace.zoom.us/develop/create)",
"2) Create a Team Chat App and enable Bot feature", "2) Create a General App",
"3) Copy Client ID, Client Secret, Bot JID, and Secret Token", "3) Production tab: add OAuth Redirect URL",
"4) You'll configure webhook and OAuth redirect URLs after setup", "4) Features tab > Surfaces > Team Chat: enable subscription + bot endpoint",
"5) Copy Client ID, Client Secret, Bot JID, Secret Token",
`Docs: ${formatDocsLink("/channels/zoom")}`, `Docs: ${formatDocsLink("/channels/zoom")}`,
"Website: https://molt.bot", "Website: https://molt.bot",
].join("\n"), ].join("\n"),
"Zoom Team Chat setup", "Zoom setup",
); );
} }

View File

@ -12,6 +12,7 @@ export const CHAT_CHANNEL_ORDER = [
"slack", "slack",
"signal", "signal",
"imessage", "imessage",
"zoom",
] as const; ] as const;
export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number]; export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number];
@ -98,6 +99,16 @@ const CHAT_CHANNEL_META: Record<ChatChannelId, ChannelMeta> = {
blurb: "this is still a work in progress.", blurb: "this is still a work in progress.",
systemImage: "message.fill", 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<string, ChatChannelId> = { export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = {

View File

@ -312,6 +312,10 @@ export { resolveZoomAccount, type ResolvedZoomAccount, type ZoomConfig } from ".
export { ZoomConfigSchema } from "../zoom/config.js"; export { ZoomConfigSchema } from "../zoom/config.js";
export { monitorZoomProvider, type MonitorZoomOpts } from "../zoom/monitor.js"; export { monitorZoomProvider, type MonitorZoomOpts } from "../zoom/monitor.js";
export { zoomOnboardingAdapter } from "../channels/plugins/onboarding/zoom.js"; export { zoomOnboardingAdapter } from "../channels/plugins/onboarding/zoom.js";
export {
looksLikeZoomTargetId,
normalizeZoomMessagingTarget,
} from "../channels/plugins/normalize/zoom.js";
// Channel: Signal // Channel: Signal
export { export {

View File

@ -46,7 +46,7 @@ export function resolveZoomAccount(params: {
clientSecret: config.clientSecret, clientSecret: config.clientSecret,
botJid: config.botJid, botJid: config.botJid,
secretToken: config.secretToken, secretToken: config.secretToken,
apiHost: config.apiHost || "https://zoom.us", apiHost: config.apiHost || "https://api.zoom.us",
oauthHost: config.oauthHost || "https://zoom.us", oauthHost: config.oauthHost || "https://zoom.us",
config, config,
}; };