---
title: 添加新的 Bot 平台
description: 了解如何向 LobeHub 的渠道系统添加新的 Bot 平台（如 Slack、WhatsApp），包括 Schema 定义、客户端实现和平台注册。
tags:
  - Bot 平台
  - 消息渠道
  - 集成
  - 开发指南
---

# 添加新的 Bot 平台

本指南介绍如何向 LobeHub 的渠道系统添加新的 Bot 平台。平台架构是模块化的 —— 每个平台是 `src/server/services/bot/platforms/` 下的一个独立目录。

## 架构概览

```
src/server/services/bot/platforms/
├── types.ts            # 核心接口（FieldSchema、PlatformClient、ClientFactory 等）
├── registry.ts         # PlatformRegistry 类
├── index.ts            # 单例注册表 + 平台注册
├── utils.ts            # 共享工具函数
├── discord/            # 示例：Discord 平台
│   ├── definition.ts   # PlatformDefinition 导出
│   ├── schema.ts       # 凭据和设置的 FieldSchema[]
│   ├── client.ts       # ClientFactory + PlatformClient 实现
│   └── api.ts          # 平台 API 辅助类
└── <your-platform>/    # 你的新平台
```

**核心概念：**

- **FieldSchema** — 声明式 Schema，同时驱动服务端校验和前端表单自动生成
- **PlatformClient** — 与平台交互的运行时接口（消息收发、生命周期管理）
- **ClientFactory** — 创建 PlatformClient 实例并验证凭据
- **PlatformDefinition** — 元数据 + Schema + 工厂，注册到全局注册表
- **Chat SDK Adapter** — 将平台的 Webhook / 事件桥接到统一的 Chat SDK

## 前置条件：Chat SDK Adapter

每个平台都需要一个 **Chat SDK Adapter**，用于将平台的 Webhook 事件桥接到统一的 [Vercel Chat SDK](https://github.com/vercel/chat)（`chat` npm 包）。在实现平台之前，需要确定使用哪个 Adapter：

### 方案 A：使用已有的 npm Adapter

部分平台已有官方 Adapter 发布在 `@chat-adapter/*` 下：

- `@chat-adapter/discord` — Discord
- `@chat-adapter/slack` — Slack
- `@chat-adapter/telegram` — Telegram

可以通过 `npm view @chat-adapter/<platform>` 检查是否存在。

### 方案 B：在 `packages/` 中开发自定义 Adapter

如果没有现成的 npm Adapter，你需要在工作区中创建一个 Adapter 包。可参考现有实现：

- `packages/chat-adapter-feishu` — 飞书 / Lark Adapter（`@lobechat/chat-adapter-feishu`）
- `packages/chat-adapter-qq` — QQ Adapter（`@lobechat/chat-adapter-qq`）

每个 Adapter 包遵循以下结构：

```
packages/chat-adapter-<platform>/
├── package.json        # name: @lobechat/chat-adapter-<platform>
├── tsconfig.json
├── tsup.config.ts
└── src/
    ├── index.ts            # 公共导出：createXxxAdapter、XxxApiClient 等
    ├── adapter.ts          # 实现 chat SDK 的 Adapter 接口的适配器类
    ├── api.ts              # 平台 API 客户端（Webhook 验证、消息解析）
    ├── crypto.ts           # 请求签名验证
    ├── format-converter.ts # 消息格式转换（平台格式 ↔ Chat SDK AST）
    └── types.ts            # 平台特定的类型定义
```

开发自定义 Adapter 的要点：

- Adapter 必须实现 `chat` 包中的 `Adapter` 接口
- 需要处理 Webhook 请求验证、事件解析和消息格式转换
- `createXxxAdapter(config)` 工厂函数是 `PlatformClient.createAdapter()` 调用的入口
- 在 `package.json` 中添加 `"chat": "^4.14.0"` 作为依赖

## 第一步：创建平台目录

```bash
mkdir src/server/services/bot/platforms/<platform-name>
```

需要创建四个文件：

| 文件              | 用途                                    |
| --------------- | ------------------------------------- |
| `schema.ts`     | 凭据和设置的字段定义                            |
| `api.ts`        | 用于出站消息的轻量 API 客户端                     |
| `client.ts`     | `ClientFactory` + `PlatformClient` 实现 |
| `definition.ts` | `PlatformDefinition` 导出               |

## 第二步：定义 Schema（`schema.ts`）

Schema 是一个 `FieldSchema` 对象数组，包含两个顶层部分：`credentials`（凭据）和 `settings`（设置）。

```ts
import type { FieldSchema } from '../types';

export const schema: FieldSchema[] = [
  {
    key: 'credentials',
    label: 'channel.credentials',
    properties: [
      {
        key: 'applicationId',
        description: 'channel.applicationIdHint',
        label: 'channel.applicationId',
        required: true,
        type: 'string',
      },
      {
        key: 'botToken',
        description: 'channel.botTokenEncryptedHint',
        label: 'channel.botToken',
        required: true,
        type: 'password', // 存储时加密，UI 中遮蔽显示
      },
    ],
    type: 'object',
  },
  {
    key: 'settings',
    label: 'channel.settings',
    properties: [
      {
        key: 'charLimit',
        default: 4000,
        description: 'channel.charLimitHint',
        label: 'channel.charLimit',
        minimum: 100,
        type: 'number',
      },
      // 添加平台特定设置...
    ],
    type: 'object',
  },
];
```

**Schema 约定：**

- `type: 'password'` 字段会被加密存储，在表单中以密码形式显示
- 共享字段使用已有的 i18n 键（如 `channel.botToken`、`channel.charLimit`）
- 平台特有字段使用 `channel.<platform>.<key>` 命名
- `devOnly: true` 的字段仅在 `NODE_ENV === 'development'` 时显示
- 凭据中必须包含一个能解析为 `applicationId` 的字段 —— 可以是显式的 `applicationId` 字段、`appId` 字段，或从 `botToken` 中提取（参见渠道详情页的 `resolveApplicationId`）

## 第三步：创建 API 客户端（`api.ts`）

用于回调服务（Chat SDK Adapter 之外）的出站消息操作的轻量类：

```ts
import debug from 'debug';

const log = debug('bot-platform:<platform>:client');

export const API_BASE = 'https://api.example.com';

export class PlatformApi {
  private readonly token: string;

  constructor(token: string) {
    this.token = token;
  }

  async sendMessage(channelId: string, text: string): Promise<{ id: string }> {
    log('sendMessage: channel=%s', channelId);
    return this.call('messages.send', { channel: channelId, text });
  }

  async editMessage(channelId: string, messageId: string, text: string): Promise<void> {
    log('editMessage: channel=%s, message=%s', channelId, messageId);
    await this.call('messages.update', { channel: channelId, id: messageId, text });
  }

  // ... 其他操作（输入指示器、表情回应等）

  private async call(method: string, body: Record<string, unknown>): Promise<any> {
    const response = await fetch(`${API_BASE}/${method}`, {
      body: JSON.stringify(body),
      headers: {
        Authorization: `Bearer ${this.token}`,
        'Content-Type': 'application/json',
      },
      method: 'POST',
    });

    if (!response.ok) {
      const text = await response.text();
      log('API error: method=%s, status=%d, body=%s', method, response.status, text);
      throw new Error(`API ${method} failed: ${response.status} ${text}`);
    }

    return response.json();
  }
}
```

## 第四步：实现客户端（`client.ts`）

实现 `PlatformClient` 并继承 `ClientFactory`：

```ts
import { createPlatformAdapter } from '@chat-adapter/<platform>';
import debug from 'debug';

import {
  type BotPlatformRuntimeContext,
  type BotProviderConfig,
  ClientFactory,
  type PlatformClient,
  type PlatformMessenger,
  type ValidationResult,
} from '../types';
import { PlatformApi } from './api';

const log = debug('bot-platform:<platform>:bot');

class MyPlatformClient implements PlatformClient {
  readonly id = '<platform>';
  readonly applicationId: string;

  private config: BotProviderConfig;
  private context: BotPlatformRuntimeContext;

  constructor(config: BotProviderConfig, context: BotPlatformRuntimeContext) {
    this.config = config;
    this.context = context;
    this.applicationId = config.applicationId;
  }

  // --- 生命周期 ---

  async start(): Promise<void> {
    // 注册 webhook 或开始监听
    // Webhook 平台：通过平台 API 配置 webhook URL
    // 网关平台：打开持久连接
  }

  async stop(): Promise<void> {
    // 清理：移除 webhook 注册或关闭连接
  }

  // --- 运行时操作 ---

  createAdapter(): Record<string, any> {
    // 返回 Chat SDK adapter 实例用于入站消息处理
    return {
      '<platform>': createPlatformAdapter({
        botToken: this.config.credentials.botToken,
        // ... adapter 特定配置
      }),
    };
  }

  getMessenger(platformThreadId: string): PlatformMessenger {
    const api = new PlatformApi(this.config.credentials.botToken);
    const channelId = platformThreadId.split(':')[1];

    return {
      createMessage: (content) => api.sendMessage(channelId, content).then(() => {}),
      editMessage: (messageId, content) => api.editMessage(channelId, messageId, content),
      removeReaction: (messageId, emoji) => api.removeReaction(channelId, messageId, emoji),
      triggerTyping: () => Promise.resolve(),
    };
  }

  extractChatId(platformThreadId: string): string {
    return platformThreadId.split(':')[1];
  }

  parseMessageId(compositeId: string): string {
    return compositeId;
  }

  // --- 可选方法 ---

  // sanitizeUserInput(text: string): string { ... }
  // shouldSubscribe(threadId: string): boolean { ... }
  // formatReply(body: string, stats?: UsageStats): string { ... }
}

export class MyPlatformClientFactory extends ClientFactory {
  createClient(config: BotProviderConfig, context: BotPlatformRuntimeContext): PlatformClient {
    return new MyPlatformClient(config, context);
  }

  async validateCredentials(credentials: Record<string, string>): Promise<ValidationResult> {
    // 调用平台 API 验证凭据有效性
    try {
      const res = await fetch('https://api.example.com/auth.test', {
        headers: { Authorization: `Bearer ${credentials.botToken}` },
        method: 'POST',
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return { valid: true };
    } catch {
      return {
        errors: [{ field: 'botToken', message: 'Failed to authenticate' }],
        valid: false,
      };
    }
  }
}
```

**需要实现的关键接口：**

| 方法                    | 用途                           |
| --------------------- | ---------------------------- |
| `start()`             | 注册 webhook 或启动网关监听           |
| `stop()`              | 关闭时清理资源                      |
| `createAdapter()`     | 返回 Chat SDK adapter 用于入站事件处理 |
| `getMessenger()`      | 返回指定会话的出站消息接口                |
| `extractChatId()`     | 从复合会话 ID 中解析平台频道 ID          |
| `parseMessageId()`    | 将复合消息 ID 转换为平台原生格式           |
| `sanitizeUserInput()` | \*（可选）\* 去除用户输入中的 Bot 提及标记   |
| `shouldSubscribe()`   | \*（可选）\* 控制会话自动订阅行为          |
| `formatReply()`       | \*（可选）\* 在回复中追加平台特定的格式化内容    |

## 第五步：导出定义（`definition.ts`）

```ts
import type { PlatformDefinition } from '../types';
import { MyPlatformClientFactory } from './client';
import { schema } from './schema';

export const myPlatform: PlatformDefinition = {
  id: '<platform>',
  name: 'Platform Name',
  connectionMode: 'webhook', // 'webhook' | 'websocket' | 'polling'
  description: 'Connect a Platform bot',
  documentation: {
    portalUrl: 'https://developers.example.com',
    setupGuideUrl: 'https://lobehub.com/docs/usage/channels/<platform>',
  },
  schema,
  showWebhookUrl: true, // 如果用户需要手动复制 webhook URL 则设为 true
  clientFactory: new MyPlatformClientFactory(),
};
```

**`showWebhookUrl`：** 对于需要用户手动粘贴 webhook URL 的平台（如 Slack、飞书）设为 `true`。对于通过 API 自动注册 webhook 的平台（如 Telegram）设为 `false` 或省略。

## 第六步：注册平台

编辑 `src/server/services/bot/platforms/index.ts`：

```ts
import { myPlatform } from './<platform>/definition';

// 添加到导出
export { myPlatform } from './<platform>/definition';

// 注册
platformRegistry.register(myPlatform);
```

## 第七步：添加 i18n 键

### 默认键（`src/locales/default/agent.ts`）

添加平台特有键。尽量复用通用键：

```ts
// 可复用（已存在）：
// 'channel.botToken'、'channel.applicationId'、'channel.charLimit' 等

// 平台特有：
'channel.<platform>.description': 'Connect this assistant to Platform for ...',
'channel.<platform>.someFieldHint': 'Description of this field.',
```

### 翻译文件（`locales/zh-CN/agent.json`、`locales/en-US/agent.json`）

在两个语言文件中添加所有新键的对应翻译。

## 第八步：添加用户文档

在 `docs/usage/channels/` 下创建配置教程：

- `<platform>.mdx` — 英文教程
- `<platform>.zh-CN.mdx` — 中文教程

参考现有文档的结构（如 `discord.mdx`）：前置条件 → 创建应用 → 在 LobeHub 中配置 → 配置 Webhook → 测试连接 → 配置参考 → 故障排除。

## 前端：自动 UI 生成

前端会根据 Schema 自动生成配置表单，无需修改前端代码（除非你的平台需要自定义图标）。图标解析通过将平台 `name` 与 `@lobehub/ui/icons` 中的已知图标匹配来实现：

```
// src/routes/(main)/agent/channel/const.ts
const ICON_NAMES = ['Discord', 'GoogleChat', 'Lark', 'Slack', 'Telegram', ...];
```

如果你的平台 `name` 与图标名称匹配（不区分大小写），图标会自动使用。否则需要在 `ICON_ALIASES` 中添加别名。

## Webhook URL 模式

所有平台共享同一个 Webhook 路由：

```
POST /api/agent/webhooks/[platform]/[appId]
```

`BotMessageRouter` 会自动处理路由分发、按需加载 Bot 和 Chat SDK 集成。

## 检查清单

- [ ] 确保 Chat SDK Adapter 可用（npm 上的 `@chat-adapter/*` 或自定义的 `packages/chat-adapter-<platform>`）
- [ ] 创建 `src/server/services/bot/platforms/<platform>/`
  - [ ] `schema.ts` — 凭据和设置的字段定义
  - [ ] `api.ts` — 出站 API 客户端
  - [ ] `client.ts` — `ClientFactory` + `PlatformClient`
  - [ ] `definition.ts` — `PlatformDefinition` 导出
- [ ] 在 `src/server/services/bot/platforms/index.ts` 中注册
- [ ] 在 `src/locales/default/agent.ts` 中添加 i18n 键
- [ ] 在 `locales/zh-CN/agent.json` 和 `locales/en-US/agent.json` 中添加翻译
- [ ] 在 `docs/usage/channels/<platform>.mdx` 中添加配置教程（中英文）
- [ ] 验证图标在 `const.ts` 中能正确解析（或添加别名）
