Metadata-Version: 2.4
Name: wechat-openclaw-sdk
Version: 0.1.0
Summary: Python SDK for WeChat iLink OpenClaw bot channel (HTTP long-polling)
Author-email: wangzhiyi <eliseowzy@hotmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/Eliseowzy/wechat-openclaw-sdk
Project-URL: Repository, https://github.com/Eliseowzy/wechat-openclaw-sdk
Project-URL: Issues, https://github.com/Eliseowzy/wechat-openclaw-sdk/issues
Keywords: wechat,ilink,openclaw,bot,sdk,long-polling
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.5
Requires-Dist: cryptography>=42.0
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == "redis"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: fakeredis>=2.21; extra == "dev"
Dynamic: license-file

# WeChat OpenClaw SDK（Python）

微信 iLink OpenClaw 机器人渠道的 Python 接入 SDK。消息 API 协议为 **HTTP 长轮询**（非 WebSocket），
5 个消息端点均为 POST JSON 到 `https://ilinkai.weixin.qq.com`；扫码登录网关为 GET 接口，
CDN 媒体上传为 POST octet-stream 密文。

> 面向 AI coding agents 的精简参考文档见 [llms.md](llms.md)。

## 核心能力

1. **任意 Agent 对接**：`BaseAgent` 异步事件流抽象，自研服务 / OpenAI 兼容接口 / 本地模型均可插拔；
2. **单机 / 分布式部署**：单机零外部依赖；分布式借助 Redis Leader 选举，保证同一 botToken 全局只有一个实例拉消息；
3. **主动发消息**：可在入站消息之外的时机推送文本 / 图片 / 文件 / 视频 / 语音，
   但需复用该用户最近一条入站消息的 `context_token`（见「主动推送」一节）；
4. **二维码登录**：内置扫码登录流程，产出 `WeChatCredentials` 直接构造客户端。

## 安装

```bash
# Python >= 3.10
pip install httpx "pydantic>=2" cryptography

# 分布式模式（可选）
pip install redis

# 或从源码安装
pip install .            # 单机
pip install ".[redis]"   # 含分布式依赖
```

## 快速上手

```python
import asyncio
from wechat_openclaw import (EchoAgent, NoopCoordinator, OpenClawClient,
                             QrLoginModule, SDKConfig)

async def main():
    # 基础地址（登录网关与消息 API 同源）唯一配置项：环境变量 WECHAT_OPENCLAW_BASE_URL，
    # 未配置时默认 https://ilinkai.weixin.qq.com；也可在此显式传入 base_url="..."
    config = SDKConfig()

    # 1. 扫码登录（二维码内容会打印到控制台）
    qr = QrLoginModule(config.base_url)
    credentials = await qr.login_with_qr(timeout=480)

    # 2. 构造客户端并注册 Agent
    client = OpenClawClient(credentials, NoopCoordinator(), config)
    client.register_agent(EchoAgent())

    # 3. 启动长轮询（阻塞直到 stop / 会话过期）
    await client.start()

asyncio.run(main())
```

### 地址配置

SDK 只有一个地址配置项，消息 API 与扫码登录网关共用：

```bash
export WECHAT_OPENCLAW_BASE_URL=https://ilinkai.weixin.qq.com
```

未设置时取默认值 `https://ilinkai.weixin.qq.com`；也可通过 `SDKConfig(base_url=...)`
在代码中覆盖。优先级：显式配置的非默认值 > 扫码登录返回的 `baseurl` > 默认值。

### 主动推送

`sendmessage` 的服务端 prepare 阶段要靠 `context_token` 定位会话，该 token 由服务端
随每条入站消息经 `getupdates` 下发，出站必须原样回带。**不带 / 带伪造值都会被拒**：
`{"ret":-2,"errmsg":"prepare failed"}`。因此机器人无法冷启动主动发消息，必须先收到
该用户的一条入站消息，记下它的 `from_user_id` 与 `context_token` 再推送：

```python
# 完整可运行版本见 examples/push_message.py
token = None
to_user_id = None

async def remember(msg):           # 每条新入站消息都会签发新 token，需刷新
    global token, to_user_id
    to_user_id, token = msg.from_user_id, msg.context_token

client.connection.on_message(remember)
asyncio.create_task(client.start())
# ...等 remember 被触发后...
await client.send_text(to_user_id, "您关注的任务已完成 ✅", token)
```

宿主应用需自行持久化 `user_id -> context_token` 映射（官方 JS SDK 也是落盘保存），
才能在进程重启后继续推送。注意微信频控，建议合并推送。

### 自定义 Agent

```python
from wechat_openclaw import AgentEvent, AgentEventType, BaseAgent

class MyAgent(BaseAgent):
    async def run(self, request):
        yield AgentEvent(AgentEventType.TEXT_DELTA, {"text": f"收到：{request.user_input}"})
        yield AgentEvent(AgentEventType.MESSAGE_COMPLETED)
```

对接 OpenAI 兼容接口可直接使用内置 `OpenAICompatAgent(base_url, api_key, model)`。

### 分布式部署

```python
import redis.asyncio as aioredis
from wechat_openclaw import OpenClawClient, RedisCoordinator

r = aioredis.from_url("redis://localhost:6379/0")
coordinator = RedisCoordinator(r, credentials.account_id)
client = OpenClawClient(credentials, coordinator, config, redis_client=r)
```

Leader 选举语义：锁 key `distributed:connection:wechat:{account_id}`，
租约 1500ms、Leader 每 500ms 续期、Follower 每 300ms 抢占；
故障切换时游标不迁移，新 Leader 从服务端最新位置开始拉取。

## 模块结构

```
wechat_openclaw/
├── models.py        # pydantic 协议模型 + 枚举（字段名对齐协议线上格式）
├── config.py        # 统一基础地址配置（WECHAT_OPENCLAW_BASE_URL）
├── api.py           # OpenClawAPI：5 个 HTTP 端点 + CDN 上传
├── client.py        # OpenClawClient：编排入口（收发/typing/媒体）+ SDKConfig
├── connection.py    # 长轮询 ConnectionManager（游标/过滤/去重/会话过期）
├── coordinator.py   # NoopCoordinator / RedisCoordinator（SET NX PX + Lua）
├── qrlogin.py       # 二维码登录模块
├── agent.py         # BaseAgent + AgentEvent + Dispatcher + 参考实现
└── utils/
    ├── markdown.py  # 流式 Markdown -> 纯文本过滤器（微信不渲染 Markdown）
    ├── crypto.py    # AES-128-ECB 加解密（CDN 媒体协议强制）
    ├── ids.py       # client_id / X-WECHAT-UIN 生成与日志脱敏
    └── retry.py     # 指数退避异步重试（3 次 / 0.5s 起 / x2.0）
```

## 关键协议要点

- 长轮询 `getupdates` 超时 35s（客户端超时视为正常空响应）；`sendmessage`/`getuploadurl` 15s；`getconfig`/`sendtyping` 10s；
- `errcode == -14` 表示会话过期（`SessionExpiredError`），不重试，需重新扫码登录；
- 出站文本必须经 Markdown 过滤器转纯文本（微信仅原生渲染 `**加粗**`/`__加粗__`）；
- 长耗时 Agent 处理需 typing 保活（每 8–15s 随机刷新、上限 5 分钟），`status=1/2` 必须配对；
- 本 SDK 仅支持单聊，群聊行为未验证。

## 示例

- `examples/echo_bot.py` —— 扫码登录 + 回声机器人（单机）
- `examples/llm_bot.py` —— 扫码登录 + DashScope（OpenAI 兼容接口）LLM 机器人
- `examples/push_message.py` —— 主动推送
- `examples/distributed_bot.py` —— Redis Leader 选举的分布式部署

本地调试可将凭据写入仓库根目录 `.env.local`（已在 `.gitignore` 中忽略），`llm_bot.py` 与
`push_message.py` 启动时自动加载；`echo_bot.py` / `distributed_bot.py` 不加载该文件，需显式
`export` 环境变量。已 `export` 的环境变量优先级更高，不会被文件覆盖。

## 安全说明

- `WECHAT_OPENCLAW_BASE_URL` 应使用 HTTPS；若自建部署只提供明文 HTTP，需置于可信内网或加 HTTPS 反向代理；
- 日志中 bot_token / 用户 ID 一律脱敏（前 4 后 4，中间 `*`），URL 去 query；
- API Key 等凭据不得硬编码进源码，仅通过环境变量或 `.env.local` 提供；
- AES-128-ECB 为 OpenClaw CDN 协议强制规定，密钥为每文件一次性随机密钥。
