# MoFox Wire 使用手册

MoFox Wire 是一个轻量级、高性能的消息线路（消息总线），为聊天机器人与消息驱动系统提供统一的消息模型、编解码、路由与运行时处理能力，并内置 HTTP/WebSocket 传输实现与平台适配器工具。本手册面向工程实践，覆盖从概念到落地的完整指南。

---

## 目录

- 概览与定位
- 核心概念
- 安装与要求
- 快速开始
- 运行时（MessageRuntime）详解
- 路由器（Router）与多平台分发
- 编解码（codec）与 schema
- 消息构建（MessageBuilder）
- 服务器与客户端（api）
- 传输层（transport）
- 平台适配器工具（adapter_utils）
- 错误处理、Hook 与中间件
- 性能与并发模型
- 常见问题与故障排查（FAQ）
- 示例命令与开发建议

---

## 概览与定位

- 目标：在不同平台之间，以统一的 `MessageEnvelope` 结构传递消息，并提供可扩展的路由与处理能力。
- 适用场景：聊天机器人、消息聚合服务、跨平台消息网关、事件驱动系统。
- 主要模块：
  - `types`：TypedDict 消息模型
  - `codec`：JSON 编解码与 schema 处理
  - `runtime`：消息运行时、路由、Hook、中间件
  - `router`：跨平台客户端管理与优先级分发
  - `api`：WebSocket Server/Client（基于 FastAPI 和 aiohttp）
  - `transport`：HTTP/WS 客户端与服务器（aiohttp）
  - `adapter_utils`：适配器基类与连接封装

---

## 核心概念

- `MessageEnvelope`：统一的消息信封，包含：
  - `direction`：`incoming`/`outgoing`
  - `message_info`：消息元信息（平台、用户、群组、时间等）
  - `message_segment`：消息内容段，兼容 `maim_message` 的分段结构
  - 可选字段：`raw_bytes`（原始二进制载荷）、`metadata`、`timestamp_ms`、`schema_version` 等
- `SegPayload`：消息分段，常见类型：`text`、`image`、`reply`、以及自定义类型。
- `MessageInfoPayload`：元信息，含 `platform`、`message_id`、时间戳、`user_info`/`group_info` 等。
- `schema_version`：序列化结构版本，当前默认 `1`。

---

## 安装与要求

- Python 3.11+
- 主要依赖：`aiohttp`、`fastapi`、`orjson`、`uvicorn`、`websockets`

安装：

```bash
pip install mofox-wire
```

开发环境：

```bash
pip install -e .
pip install -r requirements-dev.txt
```

---

## 快速开始

### 1）用 MessageBuilder 构造并处理一条消息

```python
import asyncio
from mofox_wire import MessageRuntime, MessageBuilder, MessageEnvelope

async def handle(envelope: MessageEnvelope):
    seg = envelope.get("message_segment")
    print("收到消息段:", seg)
    return None  # 返回处理结果（可选）

async def main():
    runtime = MessageRuntime()
    runtime.add_route(lambda m: True, handle)  # 简单匹配所有消息

    msg = (
        MessageBuilder()
        .platform("qq")
        .from_user("u123", nickname="Alice")
        .text("hello")
        .build()
    )
    await runtime.handle_message(msg)

asyncio.run(main())
```

### 2）WebSocket 服务器与客户端（api）

- 服务器（`MessageServer`，基于 FastAPI + uvicorn）

```python
import asyncio
import uvicorn
from mofox_wire import MessageServer

server = MessageServer(host="0.0.0.0", port=18000, path="/ws")

async def on_message(payload: dict):
    print("服务端收到:", payload.get("message_info", {}))

server.register_message_handler(on_message)

async def main():
    await server.run()

if __name__ == "__main__":
    asyncio.run(main())
```

- 客户端（`MessageClient`，带自动重连）

```python
import asyncio
from mofox_wire import MessageClient

async def main():
    client = MessageClient()
    await client.connect(url="ws://127.0.0.1:18000/ws", platform="qq")

    async def on_message(payload: dict):
        print("客户端收到:", payload.get("message_info", {}))

    client.register_message_handler(on_message)

    # 发送一条简单 JSON（也可发送 bytes）
    await client.send_message({"message_info": {"platform": "qq", "message_id": "mid-1"},
                              "message_segment": {"type": "text", "data": "ping"}})

    # 持续接收，或在需要时 client.stop()
    await client.run()

asyncio.run(main())
```

说明：
- `MessageServer.broadcast_message()` 可广播到所有连接。
- `MessageServer.broadcast_to_platform(platform, message)` 定向到某平台连接。
- `MessageClient.send_message()` 支持 `use_raw_bytes=True` 直接发送二进制。

---

## 运行时（MessageRuntime）详解

`MessageRuntime` 负责：路由匹配、Hook 调用、批量处理、错误封装与中间件链。

关键接口：
- `add_route(predicate, handler, name=None, message_type=None, event_types=None, priority=DEFAULT_PRIORITY)`
  - `message_type` 支持 `str` 或 `list[str]`，用于按消息段类型过滤（如 `text`/`image`）。
  - `priority` 越大优先级越高；仅最高优先级的处理器接收消息；同优先级并发执行。
- 装饰器：
  - `route(predicate, name=None, priority=0)`：声明式注册普通路由。
  - `on_message(message_type=None, platform=None, predicate=None, name=None, priority=0)`：糖衣装饰器，常用于按类型/平台过滤。
- Hook：
  - `register_before_hook(hook)` / `register_after_hook(hook)` / `register_error_hook(hook)`
- 中间件：
  - `register_middleware(middleware: Middleware)`，中间件签名：`async def mw(msg, next): return await next(msg)`
- 批量处理：
  - `set_batch_handler(handler)` 与 `handle_batch(messages)`
- 单条处理：
  - `handle_message(message)`

示例：根据消息类型匹配，并记录前后处理：

```python
from mofox_wire import MessageRuntime, DEFAULT_PRIORITY
from mofox_wire.types import MessageEnvelope

runtime = MessageRuntime()

@runtime.on_message(message_type=["text", "image"], priority=DEFAULT_PRIORITY + 10)
async def handle_general(msg: MessageEnvelope):
    # 只处理 text/image 段
    return None

async def before_hook(msg: MessageEnvelope):
    print("before:", msg.get("message_info", {}).get("message_id"))

async def after_hook(msg: MessageEnvelope):
    print("after:", msg.get("message_info", {}).get("message_id"))

runtime.register_before_hook(before_hook)
runtime.register_after_hook(after_hook)
```

---

## 路由器（Router）与多平台分发

`Router` 管理多个平台的 `MessageClient` 连接，并按优先度将消息分发到你注册的处理器。

配置类型：
- `TargetConfig(url: str, token: str|None=None, ssl_verify: str|None=None)`
- `RouteConfig(route_config: dict[str, TargetConfig])`

核心用法：

```python
import asyncio
from mofox_wire import Router, RouteConfig, TargetConfig, DEFAULT_PRIORITY

config = RouteConfig(route_config={
    "qq": TargetConfig(url="ws://127.0.0.1:18000/ws", token=None),
    "discord": TargetConfig(url="ws://127.0.0.1:18001/ws"),
})

router = Router(config)

async def handler_qq(msg: dict):
    print("QQ 收到", msg.get("message_info", {}))

async def handler_discord(msg: dict):
    print("Discord 收到", msg.get("message_info", {}))

# 注册优先度路由处理器（仅最高优先级的组会收到；同优先级并发）
router.register_class_handler(handler_qq, priority=DEFAULT_PRIORITY + 1)
router.register_class_handler(handler_discord, priority=DEFAULT_PRIORITY + 1)

async def main():
    await router.run()  # 建立所有平台连接并开始接收

asyncio.run(main())
```

要点：
- 内部使用 `MessageClient` 与服务端建立 WebSocket 连接。
- 若断连，会自动重连（可在 `MessageClient` 配置重连间隔与最大次数）。
- 可通过 `Router.remove_platform(platform)` 动态移除某平台连接。

---

## 编解码（codec）与 schema

- 单条消息：`dumps_message(msg) -> bytes` / `loads_message(data) -> MessageEnvelope`
- 批量消息：`dumps_messages(msgs) -> bytes` / `loads_messages(data) -> list[MessageEnvelope]`
- 默认 `schema_version = 1`，包含在序列化结果中。
- 若消息内有 `raw_bytes` 字段，序列化时会被移除（避免无效的二进制进入 JSON）。

示例：

```python
from mofox_wire import dumps_message, loads_message
from mofox_wire import MessageBuilder

msg = MessageBuilder().platform("qq").text("hi").build()
payload = dumps_message(msg)
restored = loads_message(payload)
```

---

## 消息构建（MessageBuilder）

流式构建统一信封：

常用方法：
- `platform(value)` 指定平台
- `from_user(user_id, nickname=..., cardname=..., user_avatar=...)`
- `from_group(group_id, name=...)`
- `text(content)` / `image(url)` / `reply(target_message_id)` / `seg(type, data)` / `raw_segment(seg)`
- `format_info(content_format, accept_format)`
- `metadata(dict)` / `timestamp_ms(value)` / `message_id(value)` / `direction(value)`
- `seg_list(list[SegPayload])` 批量添加段
- `build()` 返回 `MessageEnvelope`

规则：
- 必须至少有一个 `segment`
- 若未设置 `message_id`，会自动生成 UUID
- 若设置了 `platform`，会自动补充到 `user_info.platform` / `group_info.platform`

---

## 服务器与客户端（api）

- `MessageServer`
  - FastAPI WebSocket 端点，默认路径 `"/ws"`
  - 令牌校验（`enable_token=True` + `add_valid_token()`）
  - 消息排队与后台 worker 消费（`queue_maxsize`、`worker_count`）
  - 广播：`broadcast_message()`、按平台：`broadcast_to_platform()`
  - 定向发送：`send_message()`（依据 `message_info.platform`）
  - 运行：`run()` / 同步封装 `run_sync()`

- `MessageClient`
  - 连接：`connect(url, platform, token=None, ssl_verify=None)`
  - 接收循环：`run()`；断开：`stop()`
  - 自动重连：`reconnect_interval` 与 `max_reconnect_attempts`
  - 发送：`send_message(message, use_raw_bytes=False)`

---

## 传输层（transport）

基于 `aiohttp` 的轻量实现，适合与现有服务整合：

- `mofox_wire.transport.HttpMessageServer`
  - 初始化：`HttpMessageServer(handler, path="/messages")`
  - 生成 `aiohttp` 应用：`make_app()`，或挂载到既有 `aiohttp` app：`add_to_app(app)`
- `mofox_wire.transport.HttpMessageClient`
  - 批量发送：`send_messages(list[MessageEnvelope])`
- `mofox_wire.transport.WsMessageServer`
  - 路由：`path="/ws"`
  - 广播：`broadcast(messages)`
- `mofox_wire.transport.WsMessageClient`
  - 连接：`connect()`；发送单条/批量：`send_message()` / `send_messages()`；接收循环由用户自行处理（或结合适配器）。

示例：HTTP Server 嵌入现有 `aiohttp` 应用：

```python
from aiohttp import web
from mofox_wire.transport import HttpMessageServer

async def handle(envelope):
    print("HTTP 收到:", envelope.get("message_info", {}))

server = HttpMessageServer(handle, path="/messages")
app = server.make_app()
web.run_app(app, host="0.0.0.0", port=8000)
```

---

## 平台适配器工具（adapter_utils）

用于将“平台原始消息”<->“统一信封”互转，并管理连接：

- `AdapterBase`：适配器基类
  - 传输选项：`WebSocketAdapterOptions` 或 `HttpAdapterOptions`
  - 实现方法：`from_platform_message(raw) -> MessageEnvelope` 和 `_send_platform_message(envelope)`
  - 启停：`start()` / `stop()`；连接状态：`is_connected()` / `wait_connected()`
- `CoreSink` 协议：核心消息入口（进程内或跨进程）
  - `InProcessCoreSink(handler)`：将适配器送入的统一消息转交给你的处理逻辑
  - `set_outgoing_handler(handler)`：当核心产生 `outgoing` 时回调适配器

示例：最小适配器（仅演示结构）：

```python
import asyncio
from mofox_wire.adapter_utils import AdapterBase, InProcessCoreSink, WebSocketAdapterOptions
from mofox_wire import MessageBuilder

class DemoAdapter(AdapterBase):
    platform = "qq"

    async def from_platform_message(self, raw):
        # 假设平台原始结构为 {"text": "..."}
        txt = raw.get("text", "")
        return MessageBuilder().platform("qq").text(txt).build()

    async def _send_platform_message(self, envelope):
        # 这里只 demo：打印要发往平台的内容
        print("发送到平台:", envelope.get("message_segment"))

async def core_handler(env):
    print("核心收到:", env.get("message_segment"))

sink = InProcessCoreSink(core_handler)
adapter = DemoAdapter(sink, transport=WebSocketAdapterOptions(url="ws://127.0.0.1:18000/ws"))

async def main():
    await adapter.start()
    await adapter.wait_connected()
    # 模拟从核心向平台发送
    await sink.push_outgoing(MessageBuilder().platform("qq").text("hello from core").build())
    await asyncio.sleep(1)
    await adapter.stop()

asyncio.run(main())
```

---

## 错误处理、Hook 与中间件

- 错误封装：`MessageProcessingError(message, original)`，在处理链路抛出异常时可用于日志与上报。
- Hook：
  - `before/after`：在处理前后执行，签名 `async def hook(msg)`
  - `error`：异常时执行，签名 `async def hook(msg, exc)`
- 中间件：链式包裹处理函数，可用于鉴权、过滤、统计、超时控制等。

---

## 性能与并发模型

- `MessageRuntime` 内部支持并发执行同优先级处理器，避免单个阻塞拖垮事件循环。
- `api.MessageServer` 采用队列 + worker 模式：
  - `queue_maxsize` 控制排队上限；满队列会丢弃消息并告警。
  - `worker_count` 控制并发消费任务数。
- `MessageClient`/`Router` 在断连时会按间隔重连，避免大量重连风暴；可设最大重试次数。

---

## 常见问题与故障排查（FAQ）

- 收到 `Invalid JSON payload`：检查上游是否发送了合法 JSON；或使用 `use_raw_bytes=True` 发送二进制。
- 队列满导致丢消息：调大 `queue_maxsize` 或优化处理器速度；也可增加 `worker_count`。
- 无法定向到平台：确保消息的 `message_info.platform` 正确；`send_message()` 依赖该字段。
- WebSocket 收发过大：调整 `DEFAULT_WS_MAX_MSG_SIZE` 或传输层 `max_message_size`。
- SSL 验证失败：在客户端 `connect()` 传入 `ssl_verify=cafile`（CA 文件路径）。

---

## 示例命令与开发建议

- 运行测试：

```bash
pytest
pytest --cov=mofox_wire
```

- 代码格式：

```bash
ruff check mofox_wire
black mofox_wire
isort mofox_wire
```

- 启动示例 WebSocket 服务：

```bash
python -m examples.ws_server  # 如你创建了示例脚本
```

- Windows 下建议使用 PowerShell 或 `python -m venv venv` 创建虚拟环境。

---

## 附：模块索引（可直接导入）

`mofox_wire.__init__` 暴露的常用符号：
- 类型与模型：`MessageDirection`, `MessageEnvelope`, `SegPayload`, `UserInfoPayload`, `GroupInfoPayload`, `FormatInfoPayload`, `TemplateInfoPayload`, `MessageInfoPayload`
- 编解码：`codec`, `dumps_message`, `dumps_messages`, `loads_message`, `loads_messages`
- 构建器：`MessageBuilder`
- 运行时与路由：`MessageRoute`, `MessageRuntime`, `MessageProcessingError`, `Middleware`, `Router`, `RouteConfig`, `TargetConfig`, `HandlerEntry`, `DEFAULT_PRIORITY`
- 服务器/客户端：`MessageServer`, `MessageClient`
- 适配器工具：`AdapterTransportOptions`, `AdapterBase`, `CoreSink`, `CoreMessageSink`, `InProcessCoreSink`, `ProcessCoreSink`, `ProcessCoreSinkServer`, `WebSocketLike`, `WebSocketAdapterOptions`, `HttpAdapterOptions`

如需进一步扩展，请参阅源码中的 docstring 与 tests/ 下的使用示例。
