mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-04 21:06:03 +00:00
484 lines
16 KiB
Markdown
484 lines
16 KiB
Markdown
# 适配器新目录结构
|
||
|
||
## 1. 设计目标
|
||
|
||
- **模块化**:每个适配器从单文件拆分到独立目录,各模块职责清晰
|
||
- **可维护**:随着事件和 API 的增加,代码量会显著增长,目录结构有助于管理复杂度
|
||
- **一致性**:所有适配器遵循相同的目录布局和文件命名约定
|
||
- **兼容现有发现机制**:保持 YAML manifest + ComponentDiscoveryEngine 的注册体系
|
||
|
||
## 2. 新目录布局
|
||
|
||
### 2.1 整体结构
|
||
|
||
```
|
||
pkg/platform/
|
||
├── __init__.py
|
||
├── botmgr.py # PlatformManager + RuntimeBot(重构)
|
||
├── event_bus.py # EventBus(新增)
|
||
├── event_router.py # EventRouter(新增)
|
||
├── logger.py # EventLogger(保留)
|
||
├── webhook_pusher.py # WebhookPusher(重构为 WebhookHandler)
|
||
│
|
||
├── adapters/ # 适配器(新目录)
|
||
│ ├── __init__.py
|
||
│ │
|
||
│ ├── telegram/
|
||
│ │ ├── __init__.py
|
||
│ │ ├── adapter.py # TelegramAdapter 主类
|
||
│ │ ├── event_converter.py # 平台事件 → 统一事件
|
||
│ │ ├── message_converter.py # MessageChain 互转
|
||
│ │ ├── api_impl.py # 通用 API 实现
|
||
│ │ ├── platform_api.py # call_platform_api 的动作映射
|
||
│ │ ├── types.py # 平台特有类型定义
|
||
│ │ └── manifest.yaml # 适配器清单
|
||
│ │
|
||
│ ├── discord/
|
||
│ │ ├── __init__.py
|
||
│ │ ├── adapter.py
|
||
│ │ ├── event_converter.py
|
||
│ │ ├── message_converter.py
|
||
│ │ ├── api_impl.py
|
||
│ │ ├── platform_api.py
|
||
│ │ ├── types.py
|
||
│ │ ├── voice.py # Discord 语音连接管理(特有)
|
||
│ │ └── manifest.yaml
|
||
│ │
|
||
│ ├── aiocqhttp/ # OneBot v11 (QQ)
|
||
│ │ └── ...
|
||
│ ├── qqofficial/
|
||
│ │ └── ...
|
||
│ ├── lark/ # 飞书
|
||
│ │ └── ...
|
||
│ ├── dingtalk/
|
||
│ │ └── ...
|
||
│ ├── slack/
|
||
│ │ └── ...
|
||
│ ├── wechatpad/
|
||
│ │ └── ...
|
||
│ ├── officialaccount/ # 微信公众号
|
||
│ │ └── ...
|
||
│ ├── wecom/ # 企业微信
|
||
│ │ └── ...
|
||
│ ├── wecombot/
|
||
│ │ └── ...
|
||
│ ├── wecomcs/
|
||
│ │ └── ...
|
||
│ ├── kook/
|
||
│ │ └── ...
|
||
│ ├── line/
|
||
│ │ └── ...
|
||
│ ├── satori/
|
||
│ │ └── ...
|
||
│ ├── websocket/ # 内置 WebSocket 适配器
|
||
│ │ ├── __init__.py
|
||
│ │ ├── adapter.py
|
||
│ │ ├── manager.py # WebSocket 连接管理
|
||
│ │ └── manifest.yaml
|
||
│ │
|
||
│ └── legacy/ # 旧版适配器(保留一段时间后移除)
|
||
│ ├── gewechat/
|
||
│ ├── nakuru/
|
||
│ └── qqbotpy/
|
||
│
|
||
└── handlers/ # 事件处理器实现(新增)
|
||
├── __init__.py
|
||
├── base.py # AbstractEventHandler 基类
|
||
├── pipeline_handler.py # PipelineHandler
|
||
├── agent_handler.py # AgentHandler
|
||
├── webhook_handler.py # WebhookHandler
|
||
└── plugin_handler.py # PluginHandler
|
||
```
|
||
|
||
### 2.2 适配器目录内各文件职责
|
||
|
||
以 Telegram 为例:
|
||
|
||
| 文件 | 职责 | 关键类/函数 |
|
||
|------|------|------------|
|
||
| `adapter.py` | 主入口,继承 `AbstractPlatformAdapter`,组装其他模块 | `TelegramAdapter` |
|
||
| `event_converter.py` | 将 Telegram 原生事件转换为统一事件类型 | `TelegramEventConverter` — 支持 Message/Edit/Delete/Reaction/MemberJoin 等所有事件 |
|
||
| `message_converter.py` | `MessageChain` 与 Telegram 消息格式互转 | `TelegramMessageConverter.yiri2target()` / `target2yiri()` |
|
||
| `api_impl.py` | 实现通用 API 方法(edit_message, delete_message, get_group_info 等) | 各 API 方法的 Telegram 实现 |
|
||
| `platform_api.py` | 实现 `call_platform_api` 的动作分发表 | `PLATFORM_API_MAP = {"pin_message": ..., "unpin_message": ...}` |
|
||
| `types.py` | 平台特有的类型定义 | Telegram 特有的枚举、配置结构等 |
|
||
| `manifest.yaml` | 适配器清单:名称、配置 schema、支持的事件和 API 列表 | — |
|
||
|
||
## 3. 新基类设计
|
||
|
||
### 3.1 AbstractPlatformAdapter
|
||
|
||
新基类继承自现有 `AbstractMessagePlatformAdapter` 并扩展,位于 `langbot-plugin-sdk` 中:
|
||
|
||
```python
|
||
# langbot_plugin/api/definition/abstract/platform/adapter.py
|
||
|
||
class AbstractPlatformAdapter(pydantic.BaseModel, metaclass=abc.ABCMeta):
|
||
"""平台适配器基类(EBA 版本)
|
||
|
||
相比旧版 AbstractMessagePlatformAdapter:
|
||
- 新增通用 API 方法(edit_message, delete_message, get_group_info 等)
|
||
- 新增透传 API(call_platform_api)
|
||
- 新增能力声明(get_supported_events, get_supported_apis)
|
||
- 事件监听器支持所有事件类型,不仅限于消息事件
|
||
"""
|
||
|
||
bot_account_id: str = ""
|
||
config: dict
|
||
logger: AbstractEventLogger = pydantic.Field(exclude=True)
|
||
|
||
class Config:
|
||
arbitrary_types_allowed = True
|
||
|
||
# ---- 能力声明 ----
|
||
|
||
def get_supported_events(self) -> list[str]:
|
||
"""返回此适配器支持的事件类型列表
|
||
|
||
默认实现从 manifest.yaml 读取。
|
||
适配器也可以 override 此方法动态声明。
|
||
"""
|
||
return ["message.received"]
|
||
|
||
def get_supported_apis(self) -> list[str]:
|
||
"""返回此适配器支持的 API 列表
|
||
|
||
默认实现从 manifest.yaml 读取。
|
||
"""
|
||
return ["send_message", "reply_message"]
|
||
|
||
# ---- 必需方法(抽象) ----
|
||
|
||
@abc.abstractmethod
|
||
async def send_message(self, target_type, target_id, message) -> MessageResult:
|
||
...
|
||
|
||
@abc.abstractmethod
|
||
async def reply_message(self, event, message, quote_origin=False) -> MessageResult:
|
||
...
|
||
|
||
@abc.abstractmethod
|
||
async def run_async(self):
|
||
...
|
||
|
||
@abc.abstractmethod
|
||
async def kill(self) -> bool:
|
||
...
|
||
|
||
@abc.abstractmethod
|
||
def register_listener(self, event_type, callback):
|
||
...
|
||
|
||
@abc.abstractmethod
|
||
def unregister_listener(self, event_type, callback):
|
||
...
|
||
|
||
# ---- 可选方法(默认抛 NotSupportedError) ----
|
||
# edit_message, delete_message, forward_message,
|
||
# get_group_info, get_group_member_list, ...
|
||
# call_platform_api, ...
|
||
# (完整签名见 02-platform-api.md)
|
||
|
||
# ---- 流式输出(保留) ----
|
||
|
||
async def reply_message_chunk(self, event, bot_message, message,
|
||
quote_origin=False, is_final=False):
|
||
raise NotSupportedError("reply_message_chunk")
|
||
|
||
async def is_stream_output_supported(self) -> bool:
|
||
return False
|
||
|
||
# ---- 消息卡片(保留) ----
|
||
|
||
async def create_message_card(self, message_id, event) -> bool:
|
||
return False
|
||
|
||
async def is_muted(self, group_id) -> bool:
|
||
return False
|
||
```
|
||
|
||
### 3.2 AbstractMessagePlatformAdapter 兼容
|
||
|
||
旧的 `AbstractMessagePlatformAdapter` 保留为 `AbstractPlatformAdapter` 的类型别名:
|
||
|
||
```python
|
||
# 向后兼容
|
||
AbstractMessagePlatformAdapter = AbstractPlatformAdapter
|
||
```
|
||
|
||
现有适配器代码中的 `AbstractMessagePlatformAdapter` 引用不需要立即修改。
|
||
|
||
### 3.3 EventConverter 新设计
|
||
|
||
现有 `AbstractEventConverter` 只有 `target2yiri` 和 `yiri2target` 两个静态方法,且只处理消息事件。
|
||
|
||
新设计支持多种事件类型:
|
||
|
||
```python
|
||
class AbstractEventConverter:
|
||
"""事件转换器基类(EBA 版本)
|
||
|
||
适配器需要实现此转换器,将平台原生事件转换为统一事件。
|
||
"""
|
||
|
||
@staticmethod
|
||
def target2yiri(raw_event: typing.Any) -> typing.Optional[Event]:
|
||
"""将平台原生事件转换为统一事件
|
||
|
||
Args:
|
||
raw_event: 平台 SDK 回调传入的原始事件对象
|
||
|
||
Returns:
|
||
统一 Event 对象,如果无法转换或不需要处理则返回 None
|
||
"""
|
||
raise NotImplementedError
|
||
|
||
@staticmethod
|
||
def yiri2target(event: Event) -> typing.Any:
|
||
"""将统一事件转换为平台原生事件(一般不需要)"""
|
||
raise NotImplementedError
|
||
```
|
||
|
||
具体适配器的 EventConverter 实现会是一个分发式的结构:
|
||
|
||
```python
|
||
class TelegramEventConverter(AbstractEventConverter):
|
||
"""Telegram 事件转换器"""
|
||
|
||
@staticmethod
|
||
def target2yiri(update: telegram.Update) -> typing.Optional[Event]:
|
||
# 消息事件
|
||
if update.message:
|
||
return TelegramEventConverter._convert_message(update)
|
||
# 消息编辑
|
||
if update.edited_message:
|
||
return TelegramEventConverter._convert_edited_message(update)
|
||
# 成员变动
|
||
if update.chat_member:
|
||
return TelegramEventConverter._convert_chat_member(update)
|
||
# 回调查询(按钮点击等)
|
||
if update.callback_query:
|
||
return TelegramEventConverter._convert_callback_query(update)
|
||
# 其他 → PlatformSpecificEvent
|
||
return TelegramEventConverter._convert_platform_specific(update)
|
||
|
||
@staticmethod
|
||
def _convert_message(update) -> MessageReceivedEvent:
|
||
...
|
||
|
||
@staticmethod
|
||
def _convert_edited_message(update) -> MessageEditedEvent:
|
||
...
|
||
|
||
@staticmethod
|
||
def _convert_chat_member(update) -> typing.Union[
|
||
MemberJoinedEvent, MemberLeftEvent, ...
|
||
]:
|
||
...
|
||
|
||
@staticmethod
|
||
def _convert_platform_specific(update) -> PlatformSpecificEvent:
|
||
...
|
||
```
|
||
|
||
## 4. Manifest 文件格式扩展
|
||
|
||
现有 manifest.yaml 只声明 `kind`, `metadata`, `spec.config`, `execution`。
|
||
|
||
新增 `spec.supported_events` 和 `spec.supported_apis`:
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: MessagePlatformAdapter
|
||
|
||
metadata:
|
||
name: telegram
|
||
label:
|
||
en_US: Telegram
|
||
zh_Hans: Telegram
|
||
icon: telegram.svg
|
||
description:
|
||
en_US: Telegram Bot adapter
|
||
zh_Hans: Telegram Bot 适配器
|
||
|
||
spec:
|
||
config:
|
||
# 现有配置 schema(保持不变)
|
||
- key: token
|
||
label: { en_US: "Bot Token", zh_Hans: "Bot Token" }
|
||
type: string
|
||
required: true
|
||
sensitive: true
|
||
# ...
|
||
|
||
supported_events:
|
||
- message.received
|
||
- message.edited
|
||
- message.deleted
|
||
- message.reaction
|
||
- feedback.received
|
||
- group.member_joined
|
||
- group.member_left
|
||
- group.member_banned
|
||
- group.info_updated
|
||
- bot.invited_to_group
|
||
- bot.removed_from_group
|
||
- bot.muted
|
||
- bot.unmuted
|
||
- platform.specific
|
||
|
||
supported_apis:
|
||
required:
|
||
- send_message
|
||
- reply_message
|
||
optional:
|
||
- edit_message
|
||
- delete_message
|
||
- get_group_info
|
||
- get_group_member_list
|
||
- get_group_member_info
|
||
- get_user_info
|
||
- upload_file
|
||
- get_file_url
|
||
- call_platform_api
|
||
|
||
platform_specific_apis:
|
||
- action: pin_message
|
||
description: { en_US: "Pin a message", zh_Hans: "置顶消息" }
|
||
- action: unpin_message
|
||
description: { en_US: "Unpin a message", zh_Hans: "取消置顶" }
|
||
- action: get_chat_administrators
|
||
description: { en_US: "Get chat admins", zh_Hans: "获取群管理员列表" }
|
||
|
||
execution:
|
||
python:
|
||
path: pkg/platform/adapters/telegram/adapter.py
|
||
attr: TelegramAdapter
|
||
```
|
||
|
||
## 5. 适配器注册与发现
|
||
|
||
### 5.1 Blueprint 更新
|
||
|
||
`templates/components.yaml` 中更新扫描路径:
|
||
|
||
```yaml
|
||
kind: Blueprint
|
||
spec:
|
||
components:
|
||
MessagePlatformAdapter:
|
||
fromDirs:
|
||
- path: pkg/platform/adapters/ # 新路径
|
||
```
|
||
|
||
`ComponentDiscoveryEngine` 的递归扫描逻辑不变——它会扫描所有子目录中的 `.yaml` 文件。因此每个适配器目录下的 `manifest.yaml` 会被自动发现。
|
||
|
||
### 5.2 PlatformManager 适配
|
||
|
||
`PlatformManager.initialize()` 的核心逻辑基本不变:
|
||
|
||
```python
|
||
async def initialize(self):
|
||
# 1. 发现适配器组件(自动扫描新目录结构)
|
||
self.adapter_components = self.ap.discover.get_components_by_kind('MessagePlatformAdapter')
|
||
|
||
# 2. 动态导入适配器类
|
||
for component in self.adapter_components:
|
||
self.adapter_dict[component.metadata.name] = component.get_python_component_class()
|
||
|
||
# 3. 从数据库加载 Bot 并实例化适配器(不变)
|
||
await self.load_bots_from_db()
|
||
```
|
||
|
||
变更点:
|
||
- `execution.python.path` 从 `pkg/platform/sources/telegram.py` 变为 `pkg/platform/adapters/telegram/adapter.py`
|
||
- `get_python_component_class()` 正常工作,因为它按路径动态导入
|
||
|
||
## 6. RuntimeBot 重构
|
||
|
||
### 6.1 现有问题
|
||
|
||
当前 `RuntimeBot.initialize()` 硬编码注册了两个回调:
|
||
|
||
```python
|
||
# 现有代码
|
||
self.adapter.register_listener(platform_events.FriendMessage, on_friend_message)
|
||
self.adapter.register_listener(platform_events.GroupMessage, on_group_message)
|
||
```
|
||
|
||
### 6.2 新设计
|
||
|
||
`RuntimeBot` 改为注册一个通用的事件回调:
|
||
|
||
```python
|
||
class RuntimeBot:
|
||
async def initialize(self):
|
||
# 注册通用事件回调,接收所有事件类型
|
||
self.adapter.register_listener(Event, self._on_event)
|
||
|
||
async def _on_event(
|
||
self,
|
||
event: Event,
|
||
adapter: AbstractPlatformAdapter,
|
||
):
|
||
"""统一事件入口"""
|
||
|
||
# 1. 设置事件的 bot_uuid 和 adapter_name
|
||
event.bot_uuid = self.bot_entity.uuid
|
||
event.adapter_name = self.bot_entity.adapter
|
||
|
||
# 2. 日志记录
|
||
await self._log_event(event)
|
||
|
||
# 3. 提交给 EventBus
|
||
await self.ap.event_bus.emit(event, adapter)
|
||
```
|
||
|
||
适配器侧的 `register_listener` 实现也需调整:
|
||
- 当 `event_type` 为 `Event`(基类)时,注册为"接收所有事件"的通配回调
|
||
- 适配器在收到平台原生事件时,通过 `EventConverter.target2yiri()` 转换后,调用所有匹配的回调
|
||
|
||
## 7. 从现有单文件适配器迁移
|
||
|
||
### 7.1 迁移模式
|
||
|
||
以 Telegram 为例,从 `sources/telegram.py`(445 行)拆分:
|
||
|
||
| 原代码位置 | → 新文件 |
|
||
|-----------|----------|
|
||
| `TelegramMessageConverter` 类 | `telegram/message_converter.py` |
|
||
| `TelegramEventConverter` 类 | `telegram/event_converter.py`(扩展,支持更多事件) |
|
||
| `TelegramAdapter.__init__` / `run_async` / `kill` / `register_listener` | `telegram/adapter.py` |
|
||
| `TelegramAdapter.send_message` / `reply_message` / `reply_message_chunk` | `telegram/adapter.py`(消息方法保留在主类)+ `telegram/api_impl.py`(新增 API) |
|
||
| 新增代码 | `telegram/api_impl.py`(edit_message, delete_message, get_group_info 等) |
|
||
| 新增代码 | `telegram/platform_api.py`(pin_message, unpin_message 等的映射) |
|
||
| `telegram.yaml` | `telegram/manifest.yaml`(扩展 supported_events/apis) |
|
||
|
||
### 7.2 迁移顺序建议
|
||
|
||
1. **Telegram** — 功能最完整的适配器之一,适合作为模板
|
||
2. **Discord** — 第二个迁移,验证模式的通用性
|
||
3. **AioCQHTTP (OneBot)** — 国内最常用,确保兼容
|
||
4. **其他适配器** — 按使用频率排序
|
||
|
||
### 7.3 渐进式迁移
|
||
|
||
不需要一次性迁移所有适配器。可以采用渐进策略:
|
||
|
||
1. 先在 `adapters/` 下建立新适配器
|
||
2. `Blueprint` 同时扫描 `sources/` 和 `adapters/` 两个目录
|
||
3. 旧适配器在 `sources/` 中继续工作
|
||
4. 逐个迁移到新结构
|
||
5. 全部迁移完成后移除 `sources/` 目录
|
||
|
||
```yaml
|
||
# 过渡期的 Blueprint
|
||
kind: Blueprint
|
||
spec:
|
||
components:
|
||
MessagePlatformAdapter:
|
||
fromDirs:
|
||
- path: pkg/platform/sources/ # 旧路径(尚未迁移的适配器)
|
||
- path: pkg/platform/adapters/ # 新路径(已迁移的适配器)
|
||
```
|