Python client for the Weixin iLink Bot API
Project description
openilink-sdk-python
微信 iLink Bot API 的 Python SDK,覆盖完整生命周期:扫码登录、消息监听、文本发送、输入状态指示、主动推送。
安装
# 从源码安装
git clone https://github.com/openilink/openilink-sdk-python.git
cd openilink-sdk-python
pip install -e .
快速开始
最小 Echo Bot
from openilink import Client, LoginCallbacks, MonitorOptions, extract_text, print_qrcode
client = Client()
# 扫码登录
result = client.login_with_qr(
callbacks=LoginCallbacks(on_qrcode=print_qrcode)
)
print(f"登录成功! BotID={result.bot_id}")
# 收到什么就回复什么
client.monitor(
lambda msg: client.push(msg.from_user_id, "echo: " + extract_text(msg)),
opts=MonitorOptions(on_error=lambda e: print(f"错误: {e}")),
)
运行后终端会显示二维码,用微信扫描即可登录。
核心概念
1. 创建客户端
from openilink import Client
# 首次使用,需要扫码登录
client = Client()
# 已有 token,跳过扫码直接使用
client = Client(token="你保存的bot_token")
支持的参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
token |
"" |
Bot Token,为空则需要扫码登录 |
base_url |
https://ilinkai.weixin.qq.com |
API 地址 |
cdn_base_url |
https://novac2c.cdn.weixin.qq.com/c2c |
CDN 地址 |
bot_type |
"3" |
Bot 类型 |
version |
"1.0.0" |
客户端版本号 |
session |
None |
自定义 requests.Session |
2. 扫码登录
from openilink import Client, LoginCallbacks, print_qrcode
client = Client()
result = client.login_with_qr(
callbacks=LoginCallbacks(
on_qrcode=print_qrcode, # 二维码就绪
on_scanned=lambda: print("已扫码,请在手机上确认..."), # 用户已扫码
on_expired=lambda n, mx: print(f"二维码已过期,正在刷新 ({n}/{mx})..."),
),
timeout=480, # 登录超时时间(秒),默认 8 分钟
)
if result.connected:
print(f"登录成功! BotID={result.bot_id} UserID={result.user_id}")
# 保存 token,下次启动可以跳过扫码
save_token(result.bot_token)
else:
print(f"登录失败: {result.message}")
LoginResult 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
connected |
bool |
是否登录成功 |
bot_token |
str |
Bot Token,保存后下次可直接使用 |
bot_id |
str |
Bot ID |
base_url |
str |
服务端返回的 API 地址 |
user_id |
str |
用户 ID |
message |
str |
状态消息 |
3. 监听消息
from openilink import MonitorOptions
def handler(msg):
text = extract_text(msg)
if not text:
return
print(f"收到来自 {msg.from_user_id} 的消息: {text}")
# 回复消息
client.push(msg.from_user_id, f"你说了: {text}")
client.monitor(
handler,
opts=MonitorOptions(
initial_buf="", # 断点续传游标,空则从头开始
on_buf_update=lambda buf: save_to_file(buf), # 游标更新回调,持久化后可断点续传
on_error=lambda e: print(f"错误: {e}"), # 非致命错误回调
on_session_expired=lambda: print("会话已过期!"), # 会话过期回调
),
)
monitor() 会阻塞当前线程,自动处理重试和退避:
- 连续失败 3 次后退避 30 秒
- 会话过期(errcode -14)后等待 5 分钟
- 自动缓存每条消息的
context_token,供push()使用
4. 发送消息
# 方式一:push(推荐) —— 使用自动缓存的 context_token
client.push(user_id, "你好!")
# 方式二:send_text —— 手动传入 context_token
client.send_text(user_id, "你好!", context_token)
push()要求目标用户之前发过消息(SDK 会自动缓存其 context_token)。 如果用户从未发过消息,会抛出NoContextTokenError。
5. 输入状态指示
# 获取 typing_ticket
config = client.get_config(user_id, context_token)
# 显示"正在输入..."
client.send_typing(user_id, config.typing_ticket)
# 取消输入状态
from openilink import TypingStatus
client.send_typing(user_id, config.typing_ticket, TypingStatus.CANCEL)
6. 获取 CDN 上传地址
resp = client.get_upload_url({
"filekey": "my-file-key",
"media_type": 1, # 1=图片 2=视频 3=文件 4=语音
"to_user_id": user_id,
"rawsize": file_size,
"rawfilemd5": file_md5,
"filesize": file_size,
})
print(resp.upload_param)
7. 优雅停止
import signal
def on_signal(sig, frame):
client.stop() # 通知 monitor 停止
signal.signal(signal.SIGINT, on_signal)
signal.signal(signal.SIGTERM, on_signal)
消息结构
收到的每条消息是 WeixinMessage 对象:
msg.from_user_id # 发送者 ID
msg.to_user_id # 接收者 ID
msg.message_id # 消息 ID
msg.message_type # MessageType.USER(1) 或 MessageType.BOT(2)
msg.message_state # MessageState.NEW(0) / GENERATING(1) / FINISH(2)
msg.context_token # 上下文 token,回复时需要
msg.session_id # 会话 ID
msg.group_id # 群 ID(私聊为空)
msg.item_list # 消息内容列表 [MessageItem, ...]
每个 MessageItem 包含一种内容类型:
item.type # MessageItemType: TEXT(1) IMAGE(2) VOICE(3) FILE(4) VIDEO(5)
item.text_item # TextItem: .text
item.image_item # ImageItem: .url, .media, .thumb_media, ...
item.voice_item # VoiceItem: .media, .playtime, .text(语音转文字), ...
item.file_item # FileItem: .file_name, .media, .md5, ...
item.video_item # VideoItem: .media, .play_length, .thumb_media, ...
使用 extract_text(msg) 可以快速提取第一条文本内容。
完整示例:命令机器人
import signal
import sys
from pathlib import Path
from openilink import (
Client, LoginCallbacks, MonitorOptions,
extract_text, print_qrcode,
)
BUF_FILE = Path("sync_buf.dat")
TOKEN_FILE = Path("bot_token.dat")
def load_file(path: Path) -> str:
try:
return path.read_text().strip()
except FileNotFoundError:
return ""
def main():
token = load_file(TOKEN_FILE)
client = Client(token=token)
# 没有 token 则扫码登录
if not token:
result = client.login_with_qr(
callbacks=LoginCallbacks(
on_qrcode=print_qrcode,
on_scanned=lambda: print("已扫码,请确认..."),
)
)
if not result.connected:
print(f"登录失败: {result.message}", file=sys.stderr)
sys.exit(1)
TOKEN_FILE.write_text(result.bot_token)
print(f"登录成功! BotID={result.bot_id}")
# Ctrl+C 优雅退出
signal.signal(signal.SIGINT, lambda *_: client.stop())
def handler(msg):
text = extract_text(msg)
if not text:
return
user = msg.from_user_id
print(f"[{user}] {text}")
if text == "/help":
client.push(user, "支持的命令:\n/help - 帮助\n/ping - 测试\n/time - 当前时间")
elif text == "/ping":
client.push(user, "pong!")
elif text == "/time":
from datetime import datetime
client.push(user, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
else:
client.push(user, f"你说了: {text}")
print("开始监听消息... (Ctrl+C 退出)")
client.monitor(handler, opts=MonitorOptions(
initial_buf=load_file(BUF_FILE),
on_buf_update=lambda buf: BUF_FILE.write_text(buf),
on_error=lambda e: print(f"错误: {e}", file=sys.stderr),
on_session_expired=lambda: print("会话过期,需要重新登录", file=sys.stderr),
))
if __name__ == "__main__":
main()
错误处理
| 异常 | 场景 |
|---|---|
APIError |
API 返回错误(ret != 0),可通过 .is_session_expired() 判断会话是否过期 |
HTTPError |
HTTP 状态码 >= 400 |
NoContextTokenError |
调用 push() 时目标用户没有缓存的 context token |
from openilink import NoContextTokenError, APIError
try:
client.push(user_id, "你好")
except NoContextTokenError:
print("该用户还没有发过消息,无法主动推送")
except APIError as e:
if e.is_session_expired():
print("会话过期,请重新登录")
else:
print(f"API 错误: {e}")
断点续传
monitor() 通过 get_updates_buf 游标实现增量拉取。持久化这个游标,重启后可以从断点继续:
client.monitor(handler, opts=MonitorOptions(
initial_buf=Path("sync_buf.dat").read_text(), # 启动时读取
on_buf_update=lambda buf: Path("sync_buf.dat").write_text(buf), # 实时保存
))
API 参考
| 方法 | 说明 |
|---|---|
Client(token, base_url, ...) |
创建客户端 |
client.login_with_qr(callbacks, timeout) |
扫码登录 |
client.fetch_qr_code() |
单独获取二维码 |
client.poll_qr_status(qrcode) |
轮询扫码状态 |
client.monitor(handler, opts) |
长轮询消息监听(阻塞) |
client.get_updates(buf) |
单次拉取更新 |
client.send_message(msg) |
发送原始消息 |
client.send_text(to, text, context_token) |
发送文本消息 |
client.push(to, text) |
使用缓存 token 主动推送 |
client.get_config(user_id, context_token) |
获取 bot 配置 |
client.send_typing(user_id, ticket, status) |
发送输入状态 |
client.get_upload_url(req) |
获取 CDN 上传地址 |
client.set_context_token(user_id, token) |
手动缓存 context token |
client.get_context_token(user_id) |
获取缓存的 context token |
client.stop() |
停止监听循环 |
extract_text(msg) |
提取消息中第一条文本 |
print_qrcode(url) |
在终端打印二维码 |
项目结构
openilink-sdk-python/
├── openilink/
│ ├── __init__.py # 包入口,统一导出
│ ├── client.py # 核心客户端,HTTP 请求封装和 API 方法
│ ├── types.py # 数据类型定义(dataclass + enum)
│ ├── auth.py # 扫码登录流程
│ ├── monitor.py # 长轮询消息监听
│ ├── errors.py # 异常类型
│ └── helpers.py # 工具函数
├── examples/
│ └── echo_bot.py # Echo 机器人示例
├── pyproject.toml
└── README.md
依赖
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openilink_sdk_python-0.1.1.tar.gz.
File metadata
- Download URL: openilink_sdk_python-0.1.1.tar.gz
- Upload date:
- Size: 17.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1a27eb1c2a6900043df439a6b7f3bfdf1f71891a0638f3062a73f07cd0e4542
|
|
| MD5 |
582f1c375dc9b1a9cad0ad604df6d905
|
|
| BLAKE2b-256 |
f423548ea8a14183c71077a7135e8ec7892f5d92fcd31e337582122e35f1625a
|
Provenance
The following attestation bundles were made for openilink_sdk_python-0.1.1.tar.gz:
Publisher:
publish.yml on openilink/openilink-sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openilink_sdk_python-0.1.1.tar.gz -
Subject digest:
f1a27eb1c2a6900043df439a6b7f3bfdf1f71891a0638f3062a73f07cd0e4542 - Sigstore transparency entry: 1154890219
- Sigstore integration time:
-
Permalink:
openilink/openilink-sdk-python@e0307be4a3ad75b12f4b12a87622d499a418a0d7 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/openilink
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e0307be4a3ad75b12f4b12a87622d499a418a0d7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file openilink_sdk_python-0.1.1-py3-none-any.whl.
File metadata
- Download URL: openilink_sdk_python-0.1.1-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
138b828a910c8bd24ac5fa6742ca1d0994526d15689bed9bd753b348b19f53d4
|
|
| MD5 |
4e1e506e550637bab6204074c7b7f03b
|
|
| BLAKE2b-256 |
189b3c274d25a0bc81617c66598bf9166d0fb1206471c4b4e8bdb786cecc10c0
|
Provenance
The following attestation bundles were made for openilink_sdk_python-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on openilink/openilink-sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openilink_sdk_python-0.1.1-py3-none-any.whl -
Subject digest:
138b828a910c8bd24ac5fa6742ca1d0994526d15689bed9bd753b348b19f53d4 - Sigstore transparency entry: 1154890220
- Sigstore integration time:
-
Permalink:
openilink/openilink-sdk-python@e0307be4a3ad75b12f4b12a87622d499a418a0d7 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/openilink
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e0307be4a3ad75b12f4b12a87622d499a418a0d7 -
Trigger Event:
push
-
Statement type: