cortico-world-wechat 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +244 -0
  3. package/package.json +52 -52
package/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  本文件的 `## <版本号>` 段落会显示在扩展市场的发布时间线上(索引仓的 Action 按同样规则切段)。
4
4
 
5
+ ## 0.1.2
6
+
7
+ - 文档重写:安装给出三条路径(npm / 离线包 / 源码,按推荐顺序)、配置项逐条说明、
8
+ "没反应时按顺序查"的排查表、以及"消息正文会进入模型上下文"的明确提示。
9
+ - 无功能变化。
5
10
  ## 0.1.1
6
11
 
7
12
  - **打包内容调整**(无功能变化):
@@ -21,3 +26,4 @@
21
26
  - **取密钥**:微信的库密钥只存在于其进程内存中,由 `tools/fetch-keys.py` 按需取出
22
27
  (短命进程,取完即退;失效时自动重取)。这是本项目唯一用到 Python 的地方。
23
28
  - 实测:真机库解密 `PRAGMA integrity_check = ok`;测试 26 例(7 监听逻辑 + 6 文本格式化 + 13 World 行为)。
29
+
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # wechat-notifier · 微信消息桌宠提示器
2
+
3
+ 把 Windows 微信新消息播报给桌宠(Coopanion / 大肥鱼):她看到后用一句话汇报,并能用 TTS 念出来。
4
+
5
+ > **它是一个 Coopanion World 扩展。** 监听、解密、轮询、限流、推送**全部在扩展里**(Node),
6
+ > 随 Coopanion 一起启动 —— 没有独立常驻进程、没有 exe、没有开机自启任务。
7
+ >
8
+ > 唯一的例外是**取密钥**:微信的库密钥只存在于微信进程内存中,需要一个短命脚本把它取出来
9
+ > (几秒完成、取完即退,失效时由扩展自动重拉)。这是本项目唯一用到 Python 的地方,
10
+ > 取舍理由见下文「为什么取密钥保留 Python」。
11
+
12
+ | | |
13
+ |---|---|
14
+ | **npm 包** | [`cortico-world-wechat`](https://www.npmjs.com/package/cortico-world-wechat) |
15
+ | **源码仓** | https://gitee.com/ydcy_studio/wechat-notifier |
16
+ | **平台** | Windows x64 | 微信 4.x(需已登录) | Coopanion 0.2.0+ |
17
+
18
+ ---
19
+
20
+ ## 快速开始(三步)
21
+
22
+ ```powershell
23
+ # ① 装扩展:在 Coopanion 的扩展页搜 cortico-world-wechat 点安装
24
+ # (或命令行:进 Coopanion 的 data\extensions 目录执行下面这行)
25
+ pnpm add cortico-world-wechat
26
+
27
+ # ② 准备密钥(取密钥需要 Python;扩展启动时会自动拉起它,也可手动跑)
28
+ # 见下方「准备密钥」
29
+
30
+ # ③ 重启 Coopanion(扩展在启动时装载,不热更)
31
+ ```
32
+
33
+ 装好并重启后,随便发一条微信消息,桌宠应当冒泡汇报。
34
+
35
+ ---
36
+
37
+ ## 架构
38
+
39
+ ```text
40
+ 微信 4.x ──加密库──▶ Coopanion World 扩展(主体)
41
+ (已登录) ├─ 解密:纯 node:crypto(AES-256-CBC raw CBC)+ WAL 增量合并
42
+ ├─ 轮询:sort_seq 水位(落盘,重启不重复)
43
+ ├─ 规范化 + 限流(全局间隔 / 会话冷却 / 静音时段)
44
+ └─ host.pushEvent ──▶ 桌宠(气泡 / TTS)
45
+ ▲
46
+ └── keys.json ◀── tools/fetch-keys.py(短命,仅取密钥)
47
+ ```
48
+
49
+ | 部件 | 位置 | 说明 |
50
+ |---|---|---|
51
+ | **World 扩展(主体)** | `cortico-world-wechat/src/` | 监听、解密、轮询、推送全在这里 |
52
+ | 取密钥工具(例外) | `cortico-world-wechat/tools/fetch-keys.py` | 密钥缺失/失效时被自动拉起,几秒后退出 |
53
+ | 密钥与状态 | `%LOCALAPPDATA%\WeChatNotifier\`(`keys.json`、`watermarks.json`、`cache\`) | 含密钥,**切勿入库** |
54
+
55
+ ### 为什么取密钥保留 Python
56
+
57
+ 密钥只在微信进程内存里,"把它找出来"需要**版本相关的结构锚点**
58
+ (`com.Tencent.WCDB.Config.Cipher` 标记 + 多层指针 + XOR 解码)。上游 `wechatauto-replica`
59
+ 已把这套做得很稳,而我们在 Node 里试过的替代方案(宽扫内存候选 + 页 HMAC 验真)
60
+ **性能不可接受**(约 4000 万次 HMAC,实测 10 分钟跑不完)。
61
+
62
+ 所以取舍是:**取密钥复用已验证实现(短命、按需),其余全部在扩展里**。
63
+ 如果将来要彻底去掉 Python,正确路径是复刻上游那套"标记 + 指针链"(只扫 Weixin.dll,秒级),
64
+ 而不是宽扫 —— 这条路记录在 `设计文档.md` 里备用。
65
+
66
+ ---
67
+
68
+ ## 安装(三种方式,按推荐顺序)
69
+
70
+ ### 方式一:从 npm 安装(推荐)
71
+
72
+ Coopanion 的扩展页会用 `keywords:cortico-world` 搜 npm。搜 **cortico-world-wechat** 点安装即可;
73
+ 它内部执行 pnpm,依赖(含原生 sqlite 绑定)会自动装好。
74
+
75
+ 命令行等价做法:
76
+
77
+ ```powershell
78
+ cd 'D:\MyApp\Coopanion\data\extensions'
79
+ pnpm add cortico-world-wechat
80
+ ```
81
+
82
+ ### 方式二:离线发布包(不想装 npm / 内网)
83
+
84
+ 下载 `wechat-notifier-v<版本>-win-x64.zip`,解压后:
85
+
86
+ ```powershell
87
+ pwsh -File install.ps1 # 默认装到 D:\MyApp\Coopanion
88
+ pwsh -File install.ps1 -CoopanionRoot 'C:\Apps\Coopanion' # 自定义安装目录
89
+ pwsh -File install.ps1 -KeysOnly # 只看"怎么准备密钥"
90
+ ```
91
+
92
+ 它会:复制扩展到 `extensions/node_modules/`、复制**原生 sqlite 绑定**(离线包已内置,
93
+ 只保留 win32-x64)、把 `worlds.wechat-notifier` 写进配置(改前自动备份)。
94
+
95
+ ### 方式三:从源码(开发者)
96
+
97
+ ```powershell
98
+ $src = '<本仓库>\cortico-world-wechat'
99
+ $dst = 'D:\MyApp\Coopanion\data\extensions\node_modules\cortico-world-wechat'
100
+ robocopy $src $dst /E /XD node_modules
101
+ # 依赖装到 Coopanion 的 extensions 下(不要装进扩展目录,避免嵌套)
102
+ cd 'D:\MyApp\Coopanion\data\extensions'
103
+ pnpm add better-sqlite3-multiple-ciphers
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 配置
109
+
110
+ `D:\MyApp\Coopanion\data\home\companion\config.json` 的 `worlds.wechat-notifier`:
111
+
112
+ ```json
113
+ "wechat-notifier": {
114
+ "mode": "in-process",
115
+ "enabled": true,
116
+ "notifySelf": false,
117
+ "notifyGroup": true,
118
+ "autoFetchKeys": true,
119
+ "pollIntervalSec": 1,
120
+ "previewMaxChars": 60,
121
+ "throttle": { "global_min_gap_sec": 20, "per_chat_cooldown_sec": 120 },
122
+ "quietHours": { "enabled": true, "start": "23:30", "end": "08:00" }
123
+ }
124
+ ```
125
+
126
+ | 键 | 默认 | 含义 |
127
+ |---|---|---|
128
+ | `mode` | `in-process` | `in-process`=扩展自己读库(推荐);`bridge`=收外部进程推来的事件(兼容旧架构) |
129
+ | `notifySelf` | `false` | 是否提醒"自己发的消息" |
130
+ | `notifyGroup` | `true` | 是否提醒群消息 |
131
+ | `autoFetchKeys` | `true` | 密钥缺失/失效时自动拉起取密钥工具 |
132
+ | `pollIntervalSec` | `1` | 轮询间隔(秒) |
133
+ | `previewMaxChars` | `60` | 正文截断长度(隐私 + 上下文成本) |
134
+ | `throttle.*` | 20 / 120 | 全局最短间隔、同会话冷却(秒) |
135
+ | `quietHours.*` | `23:30–08:00` | 静音时段;**被吞掉即丢弃、不补发** |
136
+
137
+ ---
138
+
139
+ ## 准备密钥
140
+
141
+ 密钥只在微信进程内存里,需要一个 64 位 Python 环境来取(**建议装在 `%LOCALAPPDATA%` 下**)。
142
+
143
+ ```powershell
144
+ # 建环境(一次性)
145
+ python -m venv "$env:LOCALAPPDATA\wechat-notifier-venv"
146
+ $py = "$env:LOCALAPPDATA\wechat-notifier-venv\Scripts\python.exe"
147
+ & $py -m pip install -U pip
148
+ & $py -m pip install "wechatauto-replica>=1.2.6.1" `
149
+ -i https://mirrors.cloud.tencent.com/pypi/simple --no-build-isolation
150
+
151
+ # 取密钥(微信需已登录)
152
+ & $py "<扩展目录>\tools\fetch-keys.py"
153
+ # 产出 %LOCALAPPDATA%\WeChatNotifier\keys.json(24 个库密钥)
154
+ ```
155
+
156
+ **一般不用手动跑**:扩展启动时发现没有 `keys.json`,会自己拉起这个脚本(实测冷启动 4 秒完成)。
157
+ 微信重新登录 / 升级导致密钥变化时会自动重取。
158
+
159
+ ---
160
+
161
+ ## 确认在工作
162
+
163
+ 日志在 `D:\MyApp\Coopanion\data\logs\core.log`,搜 `wechat-notifier`:
164
+
165
+ ```
166
+ [wechat-notifier] 读库模式:db_storage=...\xwechat_files\<账号>\db_storage
167
+ [wechat-notifier] 消息分片 4 个
168
+ [wechat-notifier] [listener] 联系人 8269 条
169
+ [wechat-notifier] [listener] 放行 chat=文件传输助手 sender=胡闹 preview=测试3
170
+ [wechat-notifier] 收到 文件传输助手 的消息 → 推给她:【微信】…
171
+ ```
172
+
173
+ > 状态端点(`http://127.0.0.1:19391/wechat/status`)**只在 `mode: "bridge"` 下存在**
174
+ > (那种模式才起 WS/HTTP 服务)。`in-process` 模式靠上面的日志判断。
175
+
176
+ ### 没反应时按顺序查
177
+
178
+ | 现象 | 原因 | 处理 |
179
+ |---|---|---|
180
+ | 日志里 `keysError` 非空 | 取不到密钥 | 确认微信**已登录**;把 Python 换成 64 位;杀软放行读内存 |
181
+ | 日志停在"密钥缓存不存在" | 取密钥工具起不来 | 检查 Python 环境是否存在、`wechatauto-replica` 是否装好 |
182
+ | 有"放行"但桌宠不吭声 | 问题在桌宠侧 | 看 core.log 有无 `收到 … 推给她`;有则说明已推,检查她的 persona / TTS 开关 |
183
+ | 完全没日志 | 扩展没装载 | 扩展在**启动时**装载:改完配置或代码必须**重启 Coopanion** |
184
+ | 几分钟没有任何"放行" | 可能确实没人发消息 | 用手机发一条;记得看是否被限流吞掉(日志会写 `限流吞掉 … global_gap`) |
185
+
186
+ ---
187
+
188
+ ## 测试
189
+
190
+ ```bash
191
+ cd cortico-world-wechat
192
+
193
+ # 纯逻辑(26 例:正文规范化、限流、类型映射、WS 服务、状态端点、事件推送)
194
+ node test/listener.test.mjs
195
+ node test/format.test.mjs
196
+ node test/world.test.mjs
197
+
198
+ # 集成(需要真机微信库 + keys.json):验证解密正确性
199
+ # 微信升级后先跑这个 —— integrity_check = ok 才说明页布局仍被正确解密
200
+ node test/decrypt-verify.mjs 'contact\contact.db'
201
+ ```
202
+
203
+ > 在受限沙箱里不要用 `node --test` / `npm test`(可能 `spawn EPERM`),**逐个文件直跑**。
204
+
205
+ ---
206
+
207
+ ## 发布与分发
208
+
209
+ - npm:`npm publish`(版本号改 `package.json` 并在 `CHANGELOG.md` 加 `## <版本>` 段)
210
+ - 离线包:`pwsh -File release/build-package.ps1 -Version <版本>`
211
+ - 官方扩展市场:机制与清单见 [docs/发布到官方市场.md](docs/发布到官方市场.md)
212
+
213
+ ---
214
+
215
+ ## 已知限制
216
+
217
+ 1. 仅 Windows x64;微信必须**已登录**(密钥在其进程内存里)。
218
+ 2. 取密钥需要读微信进程内存,**可能被杀软拦截**(当前未遇)。
219
+ 3. 依赖上游 [`wechatauto-replica`](https://pypi.org/project/wechatauto-replica/) 取密钥;
220
+ 微信大版本升级后若取不到密钥,先跑它的体检命令排查。
221
+ 4. 只读微信库:不回消息、不写库、不驱动微信窗口。
222
+ 5. 消息频率高,**限流是必需的**(每次推送都会真实唤醒桌宠、走一次 LLM 调用)。
223
+ 6. **消息正文会进入桌宠的模型上下文**(随 `pushEvent` 交给她处理)——扩展在市场里已声明
224
+ `sensitive-data` 警示标签,装之前请知悉。
225
+ 7. 主要在**一台机器、一个微信版本(4.1.15.13)、单账号**上验证过;市场标签为 `experimental`。
226
+
227
+ ---
228
+
229
+ ## 实现要点(踩过的坑,改代码前先读)
230
+
231
+ | 坑 | 现象 | 正解 |
232
+ |---|---|---|
233
+ | **水位必须用 `sort_seq`** | 用 `local_id` 会漏消息:`local_id` 在每个会话表内各自计数,跨 `Msg_*` 表不单调(实测一条新消息 `local_id=2392` 小于"分片最大 2564"被永久跳过) | 水位存最大 `sort_seq`(毫秒) |
234
+ | **IV 在页内 `[4016:4032]`** | 当成文件头盐或页尾 IV 会解出乱码 | 保留区前 16 字节 |
235
+ | **第 2 页起密文从 offset 0 开始** | 第 1 页才是 `[16:4016)`;一律从 16 开始会在第 2 页起解错 | 按页号分支 |
236
+ | **必须 `setAutoPadding(false)`** | Node 默认校验 PKCS#7,SQLCipher 页是 raw CBC → `bad decrypt` | 关闭自动填充 |
237
+ | **WAL 帧只合并同 salt 的** | checkpoint 会重置 WAL,旧世代帧合并会用过期页覆盖新数据 | 比对 WAL 头 salt |
238
+ | **`real_sender_id` 要按所在分片解析** | 用别的表解析会把发送者显示成无关的人(上游 issue #34) | 查该分片自己的 `Name2Id` |
239
+
240
+ ---
241
+
242
+ ## License
243
+
244
+ MIT
package/package.json CHANGED
@@ -1,52 +1,52 @@
1
- {
2
- "name": "cortico-world-wechat",
3
- "version": "0.1.1",
4
- "description": "把 Windows 微信新消息播报给桌宠:扩展自己解密并轮询微信本地库,把新消息推进桌宠的事件流,由桌宠决定怎么汇报。",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "src/index.js",
8
- "exports": {
9
- ".": "./src/index.js",
10
- "./package.json": "./package.json"
11
- },
12
- "files": [
13
- "src",
14
- "tools",
15
- "test",
16
- "describe.json",
17
- "CHANGELOG.md",
18
- "README.md"
19
- ],
20
- "os": [
21
- "win32"
22
- ],
23
- "engines": {
24
- "node": "^22.19.0 || >=24.0.0"
25
- },
26
- "dependencies": {
27
- "better-sqlite3-multiple-ciphers": "^13.0.3",
28
- "ws": "^8.21.3"
29
- },
30
- "keywords": [
31
- "cortico-world",
32
- "cortico",
33
- "wechat",
34
- "weixin",
35
- "notifier"
36
- ],
37
- "cortico": {
38
- "displayName": "微信消息提示",
39
- "kind": "world",
40
- "api": 6,
41
- "describe": "describe.json",
42
- "tags": [
43
- "im",
44
- "external-setup",
45
- "experimental",
46
- "sensitive-data"
47
- ]
48
- },
49
- "scripts": {
50
- "test": "node --test test/format.test.mjs test/world.test.mjs"
51
- }
52
- }
1
+ {
2
+ "name": "cortico-world-wechat",
3
+ "version": "0.1.2",
4
+ "description": "把 Windows 微信新消息播报给桌宠:扩展自己解密并轮询微信本地库,把新消息推进桌宠的事件流,由桌宠决定怎么汇报。",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "src/index.js",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "tools",
15
+ "test",
16
+ "describe.json",
17
+ "CHANGELOG.md",
18
+ "README.md"
19
+ ],
20
+ "os": [
21
+ "win32"
22
+ ],
23
+ "engines": {
24
+ "node": "^22.19.0 || >=24.0.0"
25
+ },
26
+ "dependencies": {
27
+ "better-sqlite3-multiple-ciphers": "^13.0.3",
28
+ "ws": "^8.21.3"
29
+ },
30
+ "keywords": [
31
+ "cortico-world",
32
+ "cortico",
33
+ "wechat",
34
+ "weixin",
35
+ "notifier"
36
+ ],
37
+ "cortico": {
38
+ "displayName": "微信消息提示",
39
+ "kind": "world",
40
+ "api": 6,
41
+ "describe": "describe.json",
42
+ "tags": [
43
+ "im",
44
+ "external-setup",
45
+ "experimental",
46
+ "sensitive-data"
47
+ ]
48
+ },
49
+ "scripts": {
50
+ "test": "node --test test/format.test.mjs test/world.test.mjs"
51
+ }
52
+ }