:::warning 使用边界 本示例仅面向用户本人控制的低频辅助操作。请勿用于批量营销、垃圾信息、骚扰或向大量不特定用户发送信息。开始前请阅读可接受使用政策。 :::
:::tip 前置条件
容器已经在跑,并且微信已登录。没登录就先执行 wejam login。
:::
from wejam import WeChat
# 初始化微信实例
wx = WeChat()默认连本机 127.0.0.1:7700,换地址传参即可:WeChat("192.168.1.9:7700")。
# 发送消息
wx.send("你好", chat="文件传输助手")返回 bool。服务端会回读输入框确认真的发出去了 —— 微信发送失败时不会有任何提示,
所以这里不猜。
# 获取当前聊天窗口消息
msgs = wx.messages()
for msg in msgs:
print('==' * 30)
print(msg.text)msg 是 dataclass,可直接打印:
| 字段 | 含义 |
|---|---|
msg.text |
正文 |
msg.sender |
群聊发送者(单聊为空) |
msg.chat |
所属会话 |
msg.from_self |
服务端溯源:这条是自己发的 |
msg.preview |
来自会话列表预览,可能被截断 |
msg.kind |
1 正文 / 2 时间 / 3 系统 |
:::tip ✅ 恭喜,你已经成功进行了自动化操作,接下来你可以继续探索更多功能。 :::
for chat in wx.chats():
print(chat)1145 克里斯蒂娜: 你一定要活下去。。 16:30 [置顶 未读12]
文件传输助手 最终复测:无前缀 16:21
公众号 荆楚网: 零跑汽车发声明… 16:03
chat 字段:name / last_message / time / unread / pinned / muted。
给 chat 传会话名即可。服务端会自动打开它 —— 微信一次只渲染一个会话,
不先打开就读不到。
for msg in wx.messages(chat="文件传输助手", limit=20):
print(msg.text)覆盖所有会话,且完全不打开任何会话(不动界面、不清你的未读):
for chat, msg in wx.watch(seconds=60):
print(f"[{chat}] {msg.sender}{msg.text}")代价:只能拿到会话列表的预览(msg.preview 为真,可能被截断)。
所以事件里还带了该会话的未读总数,用来判断有没有漏。
能拿到完整正文,代价是服务端要保持该会话打开 → 会清掉它的未读。
for chat, msg in wx.watch(chat="文件传输助手", seconds=60):
print(msg.text)msg.from_self 由服务端标注,不需要给消息加 [bot] 之类的前缀:
for chat, msg in wx.watch(seconds=60):
if msg.from_self: # 自己发的,跳过
continue
print("收到新消息:", msg.text)消息列表在无障碍树里是扁平文本,没有发送者节点 —— 界面上那条昵称是画出来的, 悬停也不出提示。但点发送者头像会弹资料卡,卡片里的节点是暴露的:
msgs = wx.messages(chat="工作群")
for m in msgs[-3:]:
print(m.text, "←", wx.sender_of(m.index, chat="工作群"))吃爽了 ← 黑百合的花语
好事儿 ← 黑百合的花语
拿到的同时也是资料卡上的其他信息:
r = wx._c.message_sender("工作群", 3)
print(r.name) # 群昵称
print(r.fields) # ['地区:', '所罗门群岛', '朋友圈', '添加到通讯录']:::warning 两条限制
- 每次约 1.5~2 秒(要开一次资料卡再关掉),不适合对整屏消息批量调用 —— 它是给「需要知道这条是谁发的」这种按需场景用的。
- 被滚出可视区的消息取不到:那种行的头像点不到,服务端会明确报错
(
该消息已被滚出可视区域),而不是返回空名字让你以为这条没有发送者。 :::
b = wx.badges()
print(b["unread_sessions"], "个会话有未读,共", b["unread_messages"], "条")
print(b["nav"]["微信"]) # True = 导航图标上有红点:::details 徽标为什么这么读
导航图标上那个小红点是画出来的装饰,不在无障碍树里 —— 有徽标与没徽标在
节点结构上完全一样。所以位置取自无障碍节点,是否变红由像素颜色判定。
既不是 OCR,也不依赖分辨率。徽标里的数字读不出来(那是 OCR 的活),
但会话级未读数 chat.unread 是精确的结构化字段。
:::
:::warning 只在需要时用 已经登录的实例不需要重复执行。微信的登录态与设备指纹绑定, 服务端会在状态卷里持久化,容器重建也不会掉。 :::
wx.login_state() # 'LOGGED_IN' / 'QR_READY' / 'WAIT_PHONE' / ...
wx.qr_code() # 二维码**内容**字符串,自己渲染
wx.submit_login() # 「记住账号」界面下推进登录查看状态:
print(wx.status())
# {'version': '4.1.13.23', 'login': 'LOGGED_IN', 'uptime_ms': 123456}契约里还有两个原子接口,给需要深度定制的场景:
# 1. 直接 dump 无障碍节点树
for node in wx.tree().nodes:
if node.text or node.name:
print(f"{' ' * node.depth}[{node.role}] {node.text or node.name}")
# 2. 按角色/名字点击节点(位置由服务端运行时算出,无静态坐标)
ok, detail = wx.click_node("push button", "登录"):::tip 逃生舱是给「契约里还没覆盖的能力」用的。如果你发现某个常用操作必须靠它, 那多半是契约该补一个业务语义接口了 —— 欢迎提 issue。 :::
门面只是薄包装。需要并发、订阅细节或非 Python 语言时,直接用契约:
from wejam import Client
with Client("127.0.0.1:7700") as c:
for chat in c.list_chats().chats:
print(chat.name, chat.unread_count)契约定义在 proto/wejam/v1/wejam.proto,任何语言都能生成自己的 stub:
service WeJam {
rpc GetStatus(GetStatusRequest) returns (Status);
rpc GetLoginState(GetLoginStateRequest) returns (LoginState);
rpc WatchLogin(WatchLoginRequest) returns (stream LoginState);
rpc GetLoginQRCode(GetLoginQRCodeRequest) returns (LoginQRCode);
rpc SubmitLogin(SubmitLoginRequest) returns (LoginState);
rpc ListChats(ListChatsRequest) returns (ListChatsResponse);
rpc ListMessages(ListMessagesRequest) returns (ListMessagesResponse);
rpc WatchMessages(WatchMessagesRequest) returns (stream MessageEvent);
rpc SendText(SendTextRequest) returns (SendResult);
rpc GetBadges(GetBadgesRequest) returns (GetBadgesResponse);
rpc DumpA11yTree(DumpA11yTreeRequest) returns (DumpA11yTreeResponse);
rpc InvokeNode(InvokeNodeRequest) returns (InvokeNodeResponse);
}