# 多通道 Gateway 实施计划（Telegram + QQ）

> **Status:** Ready for implementation

## Context

用户没有专用的 QQ Bot 账号，NapCat 登录复杂。Telegram Bot 只需通过 @BotFather 创建、拿 token 即可使用，远比 QQ 方便。

需要将当前硬编码的 NapCatGateway 重构为多通道架构，支持 Telegram 和 QQ 两种 Gateway，共享同一套消息处理逻辑。

## 架构设计

```
Telegram Bot ──┐                    ┌── Telegram reply
               ├── _process_message ─┤
NapCat WS ─────┘   (共享逻辑)       └── NapCat send_private_msg
```

定义 `BaseGateway` 抽象类，各通道实现 `start()` / `stop()` / `send_message()`。

## 实施步骤

### Step 1: 创建 Gateway 抽象基类

**新建文件:** `src/butler/gateway/base.py`

```python
class BaseGateway(ABC):
    name: str                    # "telegram" / "napcat"
    message_handler: Callable    # 统一的消息回调

    @abstractmethod
    async def start(self) -> None: ...
    @abstractmethod
    async def stop(self) -> None: ...
    @abstractmethod
    async def send_message(self, user_id: str, text: str) -> None: ...

    def on_message(self, handler: Callable) -> None:
        self.message_handler = handler
```

回调签名统一为 `async (user_id: str, content: str, channel: str) -> None`，
不再依赖 NapCatEvent 等特定类型。

### Step 2: 重构 NapCatGateway 继承 BaseGateway

**修改文件:** `src/butler/gateway/napcat.py`

- 继承 `BaseGateway`
- `connect()` → `start()`
- `_listen()` 中调用 `self.message_handler(str(user_id), message, "napcat")`
- `send_private_message()` → `send_message()`

### Step 3: 创建 TelegramGateway

**新建文件:** `src/butler/gateway/telegram.py`

使用 `python-telegram-bot` 库（v20+，原生 async）：

```python
class TelegramGateway(BaseGateway):
    name = "telegram"

    async def start(self):
        # 用 polling 模式（无需公网 IP）
        app = Application.builder().token(self.token).build()
        app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self._on_message))
        await app.initialize()
        await app.start()
        await app.updater.start_polling()

    async def _on_message(self, update, context):
        user_id = str(update.effective_user.id)
        text = update.message.text
        if self.message_handler:
            await self.message_handler(user_id, text, "telegram")

    async def send_message(self, user_id: str, text: str):
        await self._app.bot.send_message(chat_id=user_id, text=text)
```

### Step 4: 添加 Telegram 配置

**修改文件:** `src/butler/config.py`

```python
# Telegram 配置
telegram_bot_token: str = Field(default="", description="Telegram Bot Token")
telegram_allowed_chats: list[str] = Field(default_factory=list, description="允许的聊天 ID")

# 启用的通道
enabled_channels: list[str] = Field(default_factory=lambda: ["telegram"], description="启用的通道")
```

**修改文件:** `.env`

```
TELEGRAM_BOT_TOKEN=your_bot_token
```

### Step 5: 重构 main.py Gateway 管理

**修改文件:** `src/butler/main.py`

- 用 `GatewayManager` 管理多个 Gateway 实例
- 根据配置自动启用对应通道
- 统一消息回调，处理完通过原通道回复

```python
class GatewayManager:
    gateways: dict[str, BaseGateway]

    async def start_all(self):
        for name, gw in self.gateways.items():
            gw.on_message(self._handle_message)
            asyncio.create_task(gw.start())

    async def _handle_message(self, user_id, content, channel):
        result = await _process_message(user_id, content)
        reply = result.get("reply", "")
        if reply:
            await self.gateways[channel].send_message(user_id, reply)
```

### Step 6: 安装依赖 + 测试

**修改文件:** `requirements.txt`

```
python-telegram-bot>=20.0
```

**新建测试文件:** `tests/test_gateway_base.py`

## 关键文件变更

| 文件 | 操作 | 说明 |
|------|------|------|
| `src/butler/gateway/base.py` | 新建 | Gateway 抽象基类 |
| `src/butler/gateway/telegram.py` | 新建 | Telegram Bot Gateway |
| `src/butler/gateway/napcat.py` | 修改 | 继承 BaseGateway |
| `src/butler/config.py` | 修改 | 添加 Telegram 配置 |
| `src/butler/main.py` | 修改 | GatewayManager 多通道管理 |
| `requirements.txt` | 修改 | 添加 python-telegram-bot |
| `.env` | 修改 | 添加 TELEGRAM_BOT_TOKEN |
| `tests/test_gateway_base.py` | 新建 | Gateway 基类测试 |

## 验证步骤

```bash
# 1. 安装依赖
pip install python-telegram-bot

# 2. 创建 Telegram Bot（通过 @BotFather）
#    拿到 token 填入 .env

# 3. 重启服务
python -m butler.main

# 4. 在 Telegram 给 Bot 发消息
#    期望：Bot 回复自然语言总结

# 5. Mock 测试仍然可用
python scripts/mock_test.py 当前目录有多少个文件

# 6. 运行测试
pytest tests/ -v
```
