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:
parent
7373b77baf
commit
bc109c4ae3
@ -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.
|
||||
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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<ResolvedZoomAccount> = {
|
||||
id: "zoom",
|
||||
@ -88,9 +82,9 @@ export const zoomPlugin: ChannelPlugin<ResolvedZoomAccount> = {
|
||||
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: "<userJid>",
|
||||
},
|
||||
},
|
||||
|
||||
@ -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<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 {
|
||||
|
||||
23
src/channels/plugins/normalize/zoom.ts
Normal file
23
src/channels/plugins/normalize/zoom.ts
Normal 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;
|
||||
}
|
||||
@ -33,13 +33,14 @@ async function noteZoomHelp(prompter: WizardPrompter): Promise<void> {
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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<ChatChannelId, ChannelMeta> = {
|
||||
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<string, ChatChannelId> = {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user