weflow-cli 1.9.0 → 1.11.0
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.
- package/ARCHITECTURE.md +62 -26
- package/CHANGELOG.md +1051 -0
- package/OPERATIONS.md +99 -13
- package/README.en.md +14 -5
- package/README.md +23 -8
- package/bin/weflow-cli.ts +364 -40
- package/dist/bin/weflow-cli.js +381 -69
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +19 -1
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/dbPathService.d.ts +12 -0
- package/dist/src/core/dbPathService.d.ts.map +1 -1
- package/dist/src/core/dbPathService.js +20 -3
- package/dist/src/core/dbPathService.js.map +1 -1
- package/dist/src/panel/server.d.ts.map +1 -1
- package/dist/src/panel/server.js +35 -0
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/services/assistantService.d.ts +19 -0
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +20 -0
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +27 -1
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +169 -35
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/configService.d.ts +2 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +3 -2
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/exportService.d.ts +43 -0
- package/dist/src/services/exportService.d.ts.map +1 -1
- package/dist/src/services/exportService.js +116 -11
- package/dist/src/services/exportService.js.map +1 -1
- package/dist/src/services/messageQuery.d.ts +18 -0
- package/dist/src/services/messageQuery.d.ts.map +1 -1
- package/dist/src/services/messageQuery.js +29 -0
- package/dist/src/services/messageQuery.js.map +1 -1
- package/dist/src/services/wechatClient.d.ts +125 -0
- package/dist/src/services/wechatClient.d.ts.map +1 -0
- package/dist/src/services/wechatClient.js +344 -0
- package/dist/src/services/wechatClient.js.map +1 -0
- package/dist/src/services/wechatMedia.d.ts +44 -0
- package/dist/src/services/wechatMedia.d.ts.map +1 -0
- package/dist/src/services/wechatMedia.js +155 -0
- package/dist/src/services/wechatMedia.js.map +1 -0
- package/dist/src/services/wechatMessageService.d.ts +11 -0
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +55 -11
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -1
- package/docs/CONTACT_DB_SCHEMA.md +1085 -0
- package/docs/DECISIONS.md +1274 -0
- package/docs/EXTENDING.md +69 -7
- package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +27 -0
- package/docs/HEALTH-CHECK.md +12 -7
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +2 -2
- package/docs/MCP.md +2 -2
- package/docs/PROJECT_STATE.md +64 -5
- package/docs/RELEASING.md +128 -1
- package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
- package/logo.png +0 -0
- package/mcp-server/index.ts +18 -3
- package/package.json +5 -3
- package/resources/js/graph3d/NOTICE.txt +35 -0
- package/resources/js/graph3d/d3-binarytree.min.js +2 -0
- package/resources/js/graph3d/d3-dispatch.min.js +2 -0
- package/resources/js/graph3d/d3-force-3d.min.js +2 -0
- package/resources/js/graph3d/d3-octree.min.js +2 -0
- package/resources/js/graph3d/d3-quadtree.min.js +2 -0
- package/resources/js/graph3d/d3-timer.min.js +2 -0
- package/resources/js/graph3d/layout.mjs +63 -0
- package/resources/js/graph3d/three.min.js +6 -0
- package/resources/js/graph3d/viewer2d.js +448 -0
- package/resources/panel/index.html +12 -0
- package/resources/panel/mascot-blink-1.png +0 -0
- package/resources/panel/mascot-blink-2.png +0 -0
- package/resources/panel/mascot-lift-down-1.png +0 -0
- package/resources/panel/mascot-lift-down-2.png +0 -0
- package/resources/panel/mascot-lift-down-3.png +0 -0
- package/resources/panel/mascot-lift-down-4.png +0 -0
- package/resources/panel/mascot-lift-down-5.png +0 -0
- package/resources/panel/mascot-lift-down-6.png +0 -0
- package/resources/panel/mascot-lift-up-1.png +0 -0
- package/resources/panel/mascot-lift-up-2.png +0 -0
- package/resources/panel/mascot-lift-up-3.png +0 -0
- package/resources/panel/mascot-lift-up-4.png +0 -0
- package/resources/panel/mascot-lift-up-5.png +0 -0
- package/resources/panel/mascot-lift-up-6.png +0 -0
- package/resources/panel/mascot-tickle-1.png +0 -0
- package/resources/panel/mascot-tickle-2.png +0 -0
- package/resources/panel/mascot-tickle-3.png +0 -0
- package/resources/panel/mascot-tickle-4.png +0 -0
- package/resources/panel/panel.css +111 -0
- package/resources/panel/renderer.js +480 -8
- package/scripts/_utils.py +1203 -977
- package/scripts/biz_daily.py +25 -10
- package/scripts/chat_notes.py +15 -5
- package/scripts/check_readme_commands.py +426 -0
- package/scripts/compile_wiki.py +163 -57
- package/scripts/contact_schema.py +339 -0
- package/scripts/data/wechat_emoji.json +225 -0
- package/scripts/export_chat_html.py +346 -6
- package/scripts/fav_notes.py +4 -1
- package/scripts/graph_2d.py +151 -0
- package/scripts/graph_3d.py +646 -0
- package/scripts/health_check.py +163 -26
- package/scripts/nt_decrypt.py +3 -3
- package/scripts/panel_frames.py +683 -0
- package/scripts/pipeline.py +21 -10
- package/scripts/quality_eval.py +5 -0
- package/scripts/rag_chat.py +33 -16
- package/scripts/reply_debt.py +26 -6
- package/scripts/semantic_search.py +212 -58
- package/scripts/vault_rag.py +42 -16
- package/scripts/vault_search.py +44 -8
- package/scripts/wechat_emoticon.py +6 -0
- package/scripts/wiki_lint.py +94 -17
- package/src/core/dbPathService.ts +22 -4
- package/src/panel/server.ts +33 -0
- package/src/services/assistantService.ts +26 -0
- package/src/services/assistantTools.ts +2452 -2319
- package/src/services/configService.ts +5 -2
- package/src/services/exportService.ts +129 -12
- package/src/services/messageQuery.ts +29 -0
- package/src/services/wechatClient.ts +380 -0
- package/src/services/wechatMedia.ts +166 -0
- package/src/services/wechatMessageService.ts +62 -11
- package/src/types.ts +4 -0
- package/dist/src/utils/wechatEmoji.d.ts +0 -5
- package/dist/src/utils/wechatEmoji.d.ts.map +0 -1
- package/dist/src/utils/wechatEmoji.js +0 -35
- package/dist/src/utils/wechatEmoji.js.map +0 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **这台机器上的微信客户端,运行时到底加载的是哪一份。**
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:`check` / `health_check` 一直在报"数据版本 4.x"(那是**数据库格式**),
|
|
5
|
+
* 而"客户端是哪个版本"从来没人报过 —— 出问题时(数据读不出来、客户端起不来)这恰恰是
|
|
6
|
+
* 第一个要问的。
|
|
7
|
+
*
|
|
8
|
+
* **判据来自一份实测的启动器逆向分析**(报告在仓库外),它纠正了一个很自然的错法:
|
|
9
|
+
*
|
|
10
|
+
* - `Weixin.exe` 里**确实**硬编码着一个版本字面量,而且**恰好与本机的版本目录同名** ——
|
|
11
|
+
* 看起来"读它就能知道版本"。但控制流显示它只是**兜底**:主路径读
|
|
12
|
+
* `%APPDATA%\Tencent\xwechat\update\update.data` 里记的目录,只有那次失败才会用到它。
|
|
13
|
+
* ⇒ **读字面量判版本是错的**,哪怕它在这台机器上碰巧对。(报告里那条"2/2 实测"就是先
|
|
14
|
+
* 写错、后被控制流推翻的。)
|
|
15
|
+
* - 正确的判据是**活进程命令行里的 `--user-lib-dir`** —— 那是真的被加载的那个目录。
|
|
16
|
+
* - 而"磁盘上有个版本目录"**不代表它能用**:本机 `D:\WeiXin\4.1.15.11\` 是升级残留,
|
|
17
|
+
* 只剩 `crashpad_handler.exe` 一个文件(**本机实测** 1 个文件 / 2.1 MB;报告 §2.3 记了同一件事)。
|
|
18
|
+
*
|
|
19
|
+
* 所以这个模块只做两件事,且都**不猜**:从进程命令行取实际目录;把一个安装根下的版本目录
|
|
20
|
+
* 分成"能用/不能用"(判据是**有没有 `Weixin.dll`** —— 启动器加载的就是它)。
|
|
21
|
+
* 取不到就是 `null` + 一句 `note`,不拿别的来源顶替。
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
24
|
+
import { join, win32 } from 'node:path';
|
|
25
|
+
import { execFile } from 'node:child_process';
|
|
26
|
+
import { promisify } from 'node:util';
|
|
27
|
+
const execFileAsync = promisify(execFile);
|
|
28
|
+
/** 版本目录名:`4.1.15.13` 这种。别的一律不当版本目录(不猜)。 */
|
|
29
|
+
const VERSION_DIR = /^\d+(\.\d+)+$/;
|
|
30
|
+
/**
|
|
31
|
+
* PowerShell 一行取所有 `Weixin.exe` 的 pid / 可执行路径 / 命令行。
|
|
32
|
+
*
|
|
33
|
+
* 与 `keyService.isProcessRunning` 用同一种调法(`-NoProfile -NonInteractive`):这台机器上
|
|
34
|
+
* 那是仓库既有做法,不引第二种。`ConvertTo-Json` 在只有一个结果时会给对象而不是数组,
|
|
35
|
+
* 所以下面 `parseProcessRows` 两种都收。
|
|
36
|
+
*/
|
|
37
|
+
// **两个镜像名都要问**:`--user-lib-dir` 在 `Weixin.exe` 那组上,而 `--wechat-files-path`
|
|
38
|
+
// 只在 `WeChatAppEx.exe`(小程序运行时那组)的命令行上(**本机实测**:拿它去筛 `Weixin.exe` 会一条都取不到,
|
|
39
|
+
// 于是"客户端自报数据目录"这一项**永远是空的**而看不出来)。版本那一支只认 `Weixin.exe`(见 `summarize`)。
|
|
40
|
+
const PS_LIST = "$p = Get-CimInstance Win32_Process -Filter (\"Name='Weixin.exe' or Name='WeChatAppEx.exe'\") | "
|
|
41
|
+
+ 'Select-Object ProcessId, ExecutablePath, CommandLine; '
|
|
42
|
+
+ 'if ($p) { $p | ConvertTo-Json -Compress }';
|
|
43
|
+
export function parseProcessRows(raw) {
|
|
44
|
+
const text = String(raw || '').trim();
|
|
45
|
+
if (!text)
|
|
46
|
+
return [];
|
|
47
|
+
let data;
|
|
48
|
+
try {
|
|
49
|
+
data = JSON.parse(text);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const rows = Array.isArray(data) ? data : [data];
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
if (!row || typeof row !== 'object')
|
|
58
|
+
continue;
|
|
59
|
+
const r = row;
|
|
60
|
+
const pid = Number(r.ProcessId);
|
|
61
|
+
if (!Number.isFinite(pid))
|
|
62
|
+
continue;
|
|
63
|
+
const commandLine = String(r.CommandLine || '');
|
|
64
|
+
out.push({
|
|
65
|
+
pid,
|
|
66
|
+
exe: String(r.ExecutablePath || ''),
|
|
67
|
+
loadedDir: userLibDir(commandLine),
|
|
68
|
+
dataDir: wechatFilesPath(commandLine),
|
|
69
|
+
versionToken: clientVersionToken(commandLine),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 从一条命令行里取 `--user-lib-dir` 的值。**两种写法都认**(`=` 与空格、带引号与不带),
|
|
76
|
+
* 取不到返回 null。
|
|
77
|
+
*
|
|
78
|
+
* 注意这个参数**不在 `Weixin.exe` 的字符串表里**(**逆向报告实测**:0 命中)—— 它是被加载的 DLL
|
|
79
|
+
* 自己解析的。也就是说:只有**进程**能告诉我们它,二进制里搜不到。
|
|
80
|
+
*/
|
|
81
|
+
export function userLibDir(commandLine) {
|
|
82
|
+
const m = /--user-lib-dir\s*=\s*(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(String(commandLine || ''));
|
|
83
|
+
if (!m)
|
|
84
|
+
return null;
|
|
85
|
+
const value = (m[1] || m[2] || m[3] || '').trim().replace(/["']/g, '');
|
|
86
|
+
return value || null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 从命令行里取 `--wechat-files-path`:**客户端自己说数据目录在哪**。
|
|
90
|
+
*
|
|
91
|
+
* 用处在排查:它和配置里的数据目录不一致时,说明两边指向的不是同一份库(本机数据目录
|
|
92
|
+
* 还有 D 盘软链,同一份库两条路径)。只报出来,**不拿它去改配置**。
|
|
93
|
+
*/
|
|
94
|
+
export function wechatFilesPath(commandLine) {
|
|
95
|
+
const m = /--wechat-files-path\s*=\s*(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(String(commandLine || ''));
|
|
96
|
+
if (!m)
|
|
97
|
+
return null;
|
|
98
|
+
const value = (m[1] || m[2] || m[3] || '').trim().replace(/["']/g, '');
|
|
99
|
+
return value || null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 命令行里的 `--client_version`:客户端**自己的版本 token**(十进制)。
|
|
103
|
+
*
|
|
104
|
+
* 为什么要报它:它是"哪个构建"的**另一个独立来源** —— 版本目录名(`4.1.15.13`)是装载位置,
|
|
105
|
+
* 而这个是客户端自报的构建标识,两者来自不同的地方。顺带:实测本机这个值 `4065599245`
|
|
106
|
+
* 正是 `0xF2541F0D`,也就是那份逆向报告从三处(反编译 / 插件 manifest / UA)定下来的版本号。
|
|
107
|
+
*
|
|
108
|
+
* ⚠️ **不给"版本号"**:token 与三段式版本号之间的编码规则**没人解出来**(报告只确认"旧版
|
|
109
|
+
* `0xF2541D0C` → 新版 `0xF2541F0D`,高 16 位两版都是 `F254`")。所以它只作为**并列的第二来源**
|
|
110
|
+
* 报出去,不拿它去推版本号。
|
|
111
|
+
*/
|
|
112
|
+
export function clientVersionToken(commandLine) {
|
|
113
|
+
const m = /--client_version\s*=\s*(\d+)/.exec(String(commandLine || ''));
|
|
114
|
+
if (!m)
|
|
115
|
+
return null;
|
|
116
|
+
const value = Number(m[1]);
|
|
117
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
118
|
+
return null;
|
|
119
|
+
return '0x' + value.toString(16).toUpperCase().padStart(8, '0');
|
|
120
|
+
}
|
|
121
|
+
/** 客户端崩溃记录:只数文件、只取最近时间,**不读任何内容**。 */
|
|
122
|
+
export function crashSummary(root) {
|
|
123
|
+
const reports = countFiles(join(root, 'reports'));
|
|
124
|
+
const records = countFiles(join(root, 'records')) + countFiles(join(root, 'metadata'));
|
|
125
|
+
let newest = null;
|
|
126
|
+
try {
|
|
127
|
+
for (const sub of ['reports', 'records', 'metadata']) {
|
|
128
|
+
const dir = join(root, sub);
|
|
129
|
+
if (!existsSync(dir))
|
|
130
|
+
continue;
|
|
131
|
+
for (const name of readdirSync(dir)) {
|
|
132
|
+
const at = statSync(join(dir, name)).mtime;
|
|
133
|
+
if (!newest || at > new Date(newest))
|
|
134
|
+
newest = at.toISOString();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// 读不到时间就给 null —— 不拿当前时间顶替(那会造出一个假的"最近崩溃")
|
|
140
|
+
}
|
|
141
|
+
return { dir: root, reports, records, newest };
|
|
142
|
+
}
|
|
143
|
+
function countFiles(dir) {
|
|
144
|
+
try {
|
|
145
|
+
return readdirSync(dir).filter(name => {
|
|
146
|
+
try {
|
|
147
|
+
return statSync(join(dir, name)).isFile();
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}).length;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** 两条路径是不是同一个位置。**按真身比**:本机数据目录有 D 盘软链,同一份库有两条路径。 */
|
|
159
|
+
export function samePath(a, b) {
|
|
160
|
+
try {
|
|
161
|
+
return realpathSync(a).toLowerCase() === realpathSync(b).toLowerCase();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** `%APPDATA%\Tencent\xwechat` —— 崩溃记录与 `update.data` 都在这棵树下。 */
|
|
168
|
+
export function xwechatRoot(appData = process.env.APPDATA) {
|
|
169
|
+
return appData ? join(appData, 'Tencent', 'xwechat') : null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* `update.data` 在场时**必须一起说出去**的那句话。
|
|
173
|
+
*
|
|
174
|
+
* 报告把这条明确留成未解:"若该文件存在,exe 侧第一次 `LoadLibraryExW` 就会用它,
|
|
175
|
+
* 根本不会看命令行 —— 两条路谁优先【未知】"。所以这个函数的存在意义是:
|
|
176
|
+
* 别让新报出去的那个版本在那种机器上**悄悄变成错的**。
|
|
177
|
+
*/
|
|
178
|
+
export function updateDataCaveats(present) {
|
|
179
|
+
if (!present)
|
|
180
|
+
return [];
|
|
181
|
+
return ['update\\update.data 存在:加载路径**可能**由它决定,命令行给的版本未必是实际加载的那个'
|
|
182
|
+
+ '(那份逆向没解开它的内部格式,也没判定两条路谁优先)'];
|
|
183
|
+
}
|
|
184
|
+
/** 一个版本目录能不能用:**有没有 `Weixin.dll`**(启动器加载的就是它)。 */
|
|
185
|
+
export function versionDirFiles(dir) {
|
|
186
|
+
try {
|
|
187
|
+
if (!statSync(dir).isDirectory())
|
|
188
|
+
return null;
|
|
189
|
+
return readdirSync(dir);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** 把一堆目录名分成"能用的版本目录 / 不能用的 / 与版本无关的"。纯函数,便于测。 */
|
|
196
|
+
export function classifyVersionDirs(entries) {
|
|
197
|
+
const usable = [];
|
|
198
|
+
const unusable = [];
|
|
199
|
+
for (const entry of entries) {
|
|
200
|
+
if (!VERSION_DIR.test(entry.name))
|
|
201
|
+
continue;
|
|
202
|
+
if (entry.files.includes('Weixin.dll'))
|
|
203
|
+
usable.push(entry.name);
|
|
204
|
+
else
|
|
205
|
+
unusable.push({ dir: entry.name, files: entry.files.length });
|
|
206
|
+
}
|
|
207
|
+
return { usable, unusable };
|
|
208
|
+
}
|
|
209
|
+
/** 安装根(`D:\WeiXin`)→ 它的版本目录分类。读不出来就当空(不猜)。 */
|
|
210
|
+
export function versionDirsUnder(installRoot) {
|
|
211
|
+
let names;
|
|
212
|
+
try {
|
|
213
|
+
names = readdirSync(installRoot);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return { usable: [], unusable: [] };
|
|
217
|
+
}
|
|
218
|
+
const entries = names.map(name => ({ name, files: versionDirFiles(join(installRoot, name)) || [] }));
|
|
219
|
+
return classifyVersionDirs(entries);
|
|
220
|
+
}
|
|
221
|
+
export function summarize(processes, unusable) {
|
|
222
|
+
// 去重按**规范形**(去掉尾部分隔符、大小写折叠 —— Windows 路径大小写不敏感):
|
|
223
|
+
// 同一个目录写成 `D:\WeiXin\4.1.15.13` 与 `…\4.1.15.13\` 是**一个**目录,
|
|
224
|
+
// 不折算的话会被当成"两个进程指向不同目录"从而拒绝给出版本号。
|
|
225
|
+
// **版本只跟 `Weixin.exe` 有关**:`WeChatAppEx.exe`(小程序那组)与它在同一张进程表里
|
|
226
|
+
// (后者的命令行上带着 `--wechat-files-path`),但它不加载 `Weixin.dll` —— 拿它的
|
|
227
|
+
// "没带 `--user-lib-dir`"去说事会把"客户端在跑、版本取得到"讲成"取不到"。
|
|
228
|
+
// 用 `win32` 而不是平台 `path`:这里拆的**永远是 Windows 路径**(进程表/命令行给的),
|
|
229
|
+
// 而平台 `basename` 在别的平台上会把 `D:\WeiXin\4.1.15.13\Weixin.exe` 当成**一个文件名**
|
|
230
|
+
// —— 于是过滤出 0 个启动器、版本退回"未知",在 Windows 上还看不出来(CI 的 ubuntu 上红了 4 条)。
|
|
231
|
+
// 在 Windows 上 `win32.*` 与平台版完全等价,所以目标机器的行为一点没变。
|
|
232
|
+
const launchers = processes.filter(p => win32.basename(p.exe).toLowerCase() === 'weixin.exe');
|
|
233
|
+
const seen = new Map();
|
|
234
|
+
for (const p of launchers) {
|
|
235
|
+
if (!p.loadedDir)
|
|
236
|
+
continue;
|
|
237
|
+
const shown = p.loadedDir.replace(/[\\/]+$/, '');
|
|
238
|
+
if (!shown)
|
|
239
|
+
continue;
|
|
240
|
+
const key = shown.toLowerCase();
|
|
241
|
+
if (!seen.has(key))
|
|
242
|
+
seen.set(key, shown);
|
|
243
|
+
}
|
|
244
|
+
const loadedDirs = [...seen.values()];
|
|
245
|
+
let version = null;
|
|
246
|
+
let note = '';
|
|
247
|
+
if (!launchers.length) {
|
|
248
|
+
note = '客户端没在运行 ⇒ 版本未知(`--user-lib-dir` 只有进程知道)';
|
|
249
|
+
}
|
|
250
|
+
else if (!loadedDirs.length) {
|
|
251
|
+
note = `在跑 ${launchers.length} 个进程,但命令行里都没有 --user-lib-dir`;
|
|
252
|
+
}
|
|
253
|
+
else if (loadedDirs.length > 1) {
|
|
254
|
+
// **不猜**:多个目录各有各的来源(子进程可以指到不同的库),挑一个报出去就是编。
|
|
255
|
+
// 目录本身照旧列出来(那是实测到的),只是不给"版本号"这个单一结论。
|
|
256
|
+
note = `有 ${loadedDirs.length} 个不同的目录在使用,不挑一个报版本`;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
version = win32.basename(loadedDirs[0]);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
running: processes.length > 0,
|
|
263
|
+
loadedDirs,
|
|
264
|
+
version,
|
|
265
|
+
note,
|
|
266
|
+
unusableVersionDirs: unusable,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* **只要那一句话**:客户端自报的数据目录。
|
|
271
|
+
*
|
|
272
|
+
* 给 `init` 的数据目录发现用(`DbPathService.autoDetect({ hintedRoots })`)—— 它是唯一
|
|
273
|
+
* **来自客户端自己**的来源,别的都是按目录名猜的。找不到就返回空数组(调用方走老路),
|
|
274
|
+
* **不抛**:这只是个线索,不该让初始化失败。
|
|
275
|
+
*/
|
|
276
|
+
export async function reportedDataDirs(run = defaultRunner) {
|
|
277
|
+
if (process.platform !== 'win32')
|
|
278
|
+
return [];
|
|
279
|
+
let raw = '';
|
|
280
|
+
try {
|
|
281
|
+
raw = await run(PS_LIST);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
const dirs = parseProcessRows(raw).map(p => p.dataDir).filter((d) => !!d);
|
|
287
|
+
return [...new Set(dirs.map(d => d.replace(/[\\/]+$/, '')).filter(Boolean))];
|
|
288
|
+
}
|
|
289
|
+
const defaultRunner = async (command) => {
|
|
290
|
+
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], { timeout: 15_000 });
|
|
291
|
+
return String(stdout || '');
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* 汇总:进程画像 + 每个安装根下"不能用的版本目录"。
|
|
295
|
+
*
|
|
296
|
+
* 非 Windows 直接返回"平台不支持"(`Get-CimInstance` 是 Windows 的),**不是**返回"没问题"。
|
|
297
|
+
*/
|
|
298
|
+
export async function detectWechatClient(run = defaultRunner) {
|
|
299
|
+
const root = xwechatRoot();
|
|
300
|
+
const nothing = {
|
|
301
|
+
running: false, loadedDirs: [], version: null, note: '', unusableVersionDirs: [],
|
|
302
|
+
versionTokens: [],
|
|
303
|
+
dataDirs: [], updateData: { path: root ? join(root, 'update', 'update.data') : '', present: false },
|
|
304
|
+
crashRecords: { dir: root ? join(root, 'crashinfo') : '', reports: 0, records: 0, newest: null },
|
|
305
|
+
caveats: [],
|
|
306
|
+
};
|
|
307
|
+
if (process.platform !== 'win32') {
|
|
308
|
+
return { ...nothing, note: `判版本只做了 Windows(当前平台 ${process.platform})` };
|
|
309
|
+
}
|
|
310
|
+
let raw = '';
|
|
311
|
+
try {
|
|
312
|
+
raw = await run(PS_LIST);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
raw = '';
|
|
316
|
+
}
|
|
317
|
+
const processes = parseProcessRows(raw);
|
|
318
|
+
// 安装根从**启动器**的可执行路径推(**别去猜**:注册表与常见路径都不可靠 ——
|
|
319
|
+
// 注册表那条空的是**我们自己**的旧结论,出处是 `docs/DECISIONS.md` 里那句"`WeChat Files`
|
|
320
|
+
// 与注册表 InstallPath 都为空"(讲的是 3.x);**别把它记成那份逆向报告的结论**,
|
|
321
|
+
// 报告里没有注册表这一段)。只取 `Weixin.exe` 的目录:小程序的目录是插件目录,
|
|
322
|
+
// 那里的版本号子目录不是客户端版本。
|
|
323
|
+
const roots = [...new Set(processes
|
|
324
|
+
.filter(p => win32.basename(p.exe).toLowerCase() === 'weixin.exe')
|
|
325
|
+
.map(p => win32.dirname(p.exe))
|
|
326
|
+
.filter(dir => dir && existsSync(dir)))];
|
|
327
|
+
const unusable = roots.flatMap(r => versionDirsUnder(r).unusable);
|
|
328
|
+
const version = summarize(processes, unusable);
|
|
329
|
+
// `update.data` 在场 ⇒ **它可能才是决定加载路径的那个**,命令行给的版本就未必是加载的那个。
|
|
330
|
+
// 报告把这条明确留成未解("两条路谁优先 —— 【未知】"),所以这里**不猜**:只说存在,
|
|
331
|
+
// 并把不确定性跟着版本一起带出去(否则新报的这个版本会在那种机器上悄悄变成错的)。
|
|
332
|
+
const updateFile = nothing.updateData.path;
|
|
333
|
+
const present = !!updateFile && existsSync(updateFile);
|
|
334
|
+
return {
|
|
335
|
+
...version,
|
|
336
|
+
// 客户端自报的构建 token(与"目录名"并列的第二来源)——**不拿它推版本号**,编码规则没人解出来
|
|
337
|
+
versionTokens: [...new Set(processes.map(p => p.versionToken).filter((t) => !!t))],
|
|
338
|
+
dataDirs: [...new Set(processes.map(p => p.dataDir).filter((d) => !!d))],
|
|
339
|
+
updateData: { path: updateFile, present },
|
|
340
|
+
crashRecords: crashSummary(nothing.crashRecords.dir),
|
|
341
|
+
caveats: updateDataCaveats(present),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=wechatClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechatClient.js","sourceRoot":"","sources":["../../../src/services/wechatClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** 单件上限。够大(手机拍的视频也能过),又不至于一次 CDN 事故把磁盘写满 */
|
|
2
|
+
export declare const WECHAT_MEDIA_MAX_BYTES: number;
|
|
3
|
+
/** 落地目录:默认仓库相对 `output/wechat-media/`(`output/` 已在 .gitignore 里),可用环境变量改(测试用临时目录) */
|
|
4
|
+
export declare function wechatMediaDir(env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
export type InboundMediaType = 'image' | 'record' | 'file' | 'video';
|
|
6
|
+
/** item.type 的数字编码(与服务端 item_list 一致) */
|
|
7
|
+
export declare function mediaTypeOf(itemType: number): InboundMediaType | null;
|
|
8
|
+
/** 服务端给的名字只当作"一个字符串"用:取 basename、去掉控制字符与路径字符、去掉前导点、限长 */
|
|
9
|
+
export declare function sanitizeFileName(name: string): string;
|
|
10
|
+
/** 按魔数认扩展名。认不出就返回空串(调用方退回按类型给的后缀),不猜 */
|
|
11
|
+
export declare function extFromMagic(buf: Buffer): string;
|
|
12
|
+
/** 下载参数(`encrypt_query_param`)。取不到就没法下载——**这时候不猜、不留空文件** */
|
|
13
|
+
export declare function pickEncryptParam(item: any, mediaType: InboundMediaType): string;
|
|
14
|
+
/**
|
|
15
|
+
* AES key 的取值优先级。**图片这一支必须照厂商实现**:`image_item.aeskey` 是十六进制字符串,
|
|
16
|
+
* 比 `media.aes_key`(base64)更靠前;漏了这个优先级,图片会解出一堆乱码却不报错。
|
|
17
|
+
*/
|
|
18
|
+
export declare function pickAesKeyValue(item: any, mediaType: InboundMediaType): string;
|
|
19
|
+
export interface SaveMediaOptions {
|
|
20
|
+
item: any;
|
|
21
|
+
mediaType: InboundMediaType;
|
|
22
|
+
/** 同一条消息里的第几项(一条消息可以带多张图) */
|
|
23
|
+
index: number;
|
|
24
|
+
/** 名字前缀:优先用服务端给的 client_id */
|
|
25
|
+
messageId: string;
|
|
26
|
+
dir?: string;
|
|
27
|
+
/** 注入的下载器(默认由调用方传 `client.downloadMedia`),测试里换成假的 */
|
|
28
|
+
download: (encryptedQueryParam: string, aesKeyValue: string) => Promise<Buffer>;
|
|
29
|
+
/** 文件类消息里服务端给的文件名 */
|
|
30
|
+
serverFileName?: string;
|
|
31
|
+
maxBytes?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface SaveMediaResult {
|
|
34
|
+
/** 成功是磁盘路径;失败是空串(**绝不返回"看起来成功"的半成品**) */
|
|
35
|
+
path: string;
|
|
36
|
+
/** 失败原因,进日志用 */
|
|
37
|
+
reason: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 下载一件入站媒体并落盘。失败一律返回 `{ path: '', reason }` 且**不留下文件**:
|
|
41
|
+
* 助手与导出拿到空 path 时知道"没有这玩意",比拿到一个 0 字节文件强。
|
|
42
|
+
*/
|
|
43
|
+
export declare function saveInboundMedia(opts: SaveMediaOptions): Promise<SaveMediaResult>;
|
|
44
|
+
//# sourceMappingURL=wechatMedia.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechatMedia.d.ts","sourceRoot":"","sources":["../../../src/services/wechatMedia.ts"],"names":[],"mappings":"AAoBA,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,QAAmB,CAAA;AAEtD,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG3E;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;AAEpE,yCAAyC;AACzC,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAMrE;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgBhD;AAUD,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAG/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAI9E;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAA;IACT,SAAS,EAAE,gBAAgB,CAAA;IAC3B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qDAAqD;IACrD,QAAQ,EAAE,CAAC,mBAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/E,qBAAqB;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA8CvF"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 官方通道(ilink)**入站媒体**的落地:把 CDN 上的密文拉下来、解密、写到本地磁盘。
|
|
3
|
+
*
|
|
4
|
+
* 为什么值得做:入站图片/语音/文件/视频此前只解析出"有这一项",`filePath` 是空串,于是助手拿到消息
|
|
5
|
+
* 也不知道图里是什么(那四项的落地当时是显式延后的)。下载与解密的本事本来就有
|
|
6
|
+
* (`WechatClient.downloadMedia`:`<cdn>/download?encrypted_query_param=…` + AES-128-ECB + 去 PKCS#7),
|
|
7
|
+
* 缺的只是把它接上入站这条路。
|
|
8
|
+
*
|
|
9
|
+
* 字段名与优先级照**厂商自己的实现**(`third-party/WeKnora/internal/im/wechat/longpoll.go`):
|
|
10
|
+
* 图片优先用 `image_item.aeskey`(十六进制字符串,**不走 base64**),退回 `media.aes_key`(base64);
|
|
11
|
+
* 语音/文件/视频只有 `media.aes_key`。两种编码都由 `WechatClient.parseMediaAesKey` 认。
|
|
12
|
+
*
|
|
13
|
+
* 三条边界写在这里,不指望调用方记得:
|
|
14
|
+
* 1. **它不是默认行为** —— 打开 `wechatMediaDownload` 才发生网络访问;
|
|
15
|
+
* 2. **文件名永远由本地决定** —— 服务端给的名字只取 basename 并过滤字符,`../` 之类必须在这里被吃掉;
|
|
16
|
+
* 3. **超过上限就不写**(`WECHAT_MEDIA_MAX_BYTES`),失败不留半个文件 —— 半个文件比没有更难查。
|
|
17
|
+
*/
|
|
18
|
+
import { basename, join } from 'path';
|
|
19
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
20
|
+
/** 单件上限。够大(手机拍的视频也能过),又不至于一次 CDN 事故把磁盘写满 */
|
|
21
|
+
export const WECHAT_MEDIA_MAX_BYTES = 25 * 1024 * 1024;
|
|
22
|
+
/** 落地目录:默认仓库相对 `output/wechat-media/`(`output/` 已在 .gitignore 里),可用环境变量改(测试用临时目录) */
|
|
23
|
+
export function wechatMediaDir(env = process.env) {
|
|
24
|
+
const fromEnv = env.WEFLOW_WECHAT_MEDIA_DIR;
|
|
25
|
+
return fromEnv && fromEnv.trim() ? fromEnv : join('output', 'wechat-media');
|
|
26
|
+
}
|
|
27
|
+
/** item.type 的数字编码(与服务端 item_list 一致) */
|
|
28
|
+
export function mediaTypeOf(itemType) {
|
|
29
|
+
if (itemType === 2)
|
|
30
|
+
return 'image';
|
|
31
|
+
if (itemType === 3)
|
|
32
|
+
return 'record';
|
|
33
|
+
if (itemType === 4)
|
|
34
|
+
return 'file';
|
|
35
|
+
if (itemType === 5)
|
|
36
|
+
return 'video';
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/** 服务端给的名字只当作"一个字符串"用:取 basename、去掉控制字符与路径字符、去掉前导点、限长 */
|
|
40
|
+
export function sanitizeFileName(name) {
|
|
41
|
+
const base = basename(String(name || '').replace(/\\/g, '/')).trim();
|
|
42
|
+
return base
|
|
43
|
+
.replace(/[\u0000-\u001f<>:"|?*]/g, '_')
|
|
44
|
+
.replace(/^\.+/, '')
|
|
45
|
+
.trim()
|
|
46
|
+
.slice(0, 80);
|
|
47
|
+
}
|
|
48
|
+
/** 按魔数认扩展名。认不出就返回空串(调用方退回按类型给的后缀),不猜 */
|
|
49
|
+
export function extFromMagic(buf) {
|
|
50
|
+
const b = buf;
|
|
51
|
+
if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
|
|
52
|
+
return '.jpg';
|
|
53
|
+
if (b.length >= 8 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47)
|
|
54
|
+
return '.png';
|
|
55
|
+
if (b.length >= 6 && b.subarray(0, 6).toString('latin1').startsWith('GIF8'))
|
|
56
|
+
return '.gif';
|
|
57
|
+
if (b.length >= 12 && b.subarray(0, 4).toString('latin1') === 'RIFF'
|
|
58
|
+
&& b.subarray(8, 12).toString('latin1') === 'WEBP')
|
|
59
|
+
return '.webp';
|
|
60
|
+
if (b.length >= 2 && b[0] === 0x42 && b[1] === 0x4d)
|
|
61
|
+
return '.bmp';
|
|
62
|
+
if (b.length >= 12 && b.subarray(4, 8).toString('latin1') === 'ftyp')
|
|
63
|
+
return '.mp4';
|
|
64
|
+
if (b.length >= 4 && b[0] === 0x1a && b[1] === 0x45 && b[2] === 0xdf && b[3] === 0xa3)
|
|
65
|
+
return '.webm';
|
|
66
|
+
if (b.length >= 5 && b.subarray(0, 5).toString('latin1') === '#!AMR')
|
|
67
|
+
return '.amr';
|
|
68
|
+
if (b.length >= 4 && b.subarray(0, 4).toString('latin1') === 'OggS')
|
|
69
|
+
return '.ogg';
|
|
70
|
+
if (b.length >= 2 && b[0] === 0x49 && b[1] === 0x44 && b[2] === 0x33)
|
|
71
|
+
return '.mp3';
|
|
72
|
+
if (b.length >= 12 && b.subarray(0, 4).toString('latin1') === 'RIFF'
|
|
73
|
+
&& b.subarray(8, 12).toString('latin1') === 'WAVE')
|
|
74
|
+
return '.wav';
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
/** 该媒体类型在服务端字段树里的节点名 */
|
|
78
|
+
function mediaNode(item, mediaType) {
|
|
79
|
+
if (mediaType === 'image')
|
|
80
|
+
return item?.image_item;
|
|
81
|
+
if (mediaType === 'record')
|
|
82
|
+
return item?.voice_item;
|
|
83
|
+
if (mediaType === 'file')
|
|
84
|
+
return item?.file_item;
|
|
85
|
+
return item?.video_item;
|
|
86
|
+
}
|
|
87
|
+
/** 下载参数(`encrypt_query_param`)。取不到就没法下载——**这时候不猜、不留空文件** */
|
|
88
|
+
export function pickEncryptParam(item, mediaType) {
|
|
89
|
+
const node = mediaNode(item, mediaType);
|
|
90
|
+
return String(node?.media?.encrypt_query_param || '');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* AES key 的取值优先级。**图片这一支必须照厂商实现**:`image_item.aeskey` 是十六进制字符串,
|
|
94
|
+
* 比 `media.aes_key`(base64)更靠前;漏了这个优先级,图片会解出一堆乱码却不报错。
|
|
95
|
+
*/
|
|
96
|
+
export function pickAesKeyValue(item, mediaType) {
|
|
97
|
+
const node = mediaNode(item, mediaType);
|
|
98
|
+
if (mediaType === 'image' && node?.aeskey)
|
|
99
|
+
return String(node.aeskey);
|
|
100
|
+
return String(node?.media?.aes_key || '');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 下载一件入站媒体并落盘。失败一律返回 `{ path: '', reason }` 且**不留下文件**:
|
|
104
|
+
* 助手与导出拿到空 path 时知道"没有这玩意",比拿到一个 0 字节文件强。
|
|
105
|
+
*/
|
|
106
|
+
export async function saveInboundMedia(opts) {
|
|
107
|
+
const { item, mediaType, index, messageId, download } = opts;
|
|
108
|
+
const cap = opts.maxBytes ?? WECHAT_MEDIA_MAX_BYTES;
|
|
109
|
+
const encryptParam = pickEncryptParam(item, mediaType);
|
|
110
|
+
if (!encryptParam)
|
|
111
|
+
return { path: '', reason: '服务端没给 encrypt_query_param' };
|
|
112
|
+
const aesKeyValue = pickAesKeyValue(item, mediaType);
|
|
113
|
+
if (!aesKeyValue)
|
|
114
|
+
return { path: '', reason: '服务端没给 aes key' };
|
|
115
|
+
let bytes;
|
|
116
|
+
try {
|
|
117
|
+
bytes = await download(encryptParam, aesKeyValue);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
return { path: '', reason: `下载或解密失败:${error?.message || error}` };
|
|
121
|
+
}
|
|
122
|
+
if (!bytes || bytes.length === 0)
|
|
123
|
+
return { path: '', reason: '下载结果为空' };
|
|
124
|
+
if (bytes.length > cap) {
|
|
125
|
+
return { path: '', reason: `超过上限(${bytes.length} > ${cap} 字节)` };
|
|
126
|
+
}
|
|
127
|
+
const byMagic = extFromMagic(bytes);
|
|
128
|
+
const fallbackExt = mediaType === 'image' ? '.jpg' : '';
|
|
129
|
+
const prefix = sanitizeFileName(messageId) || 'msg';
|
|
130
|
+
let name;
|
|
131
|
+
if (mediaType === 'file' && opts.serverFileName) {
|
|
132
|
+
const safe = sanitizeFileName(opts.serverFileName);
|
|
133
|
+
if (safe) {
|
|
134
|
+
const hasExt = /\.[A-Za-z0-9]{1,8}$/.test(safe);
|
|
135
|
+
name = `${prefix}-${index}-${hasExt || !byMagic ? safe : safe + byMagic}`;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
name = `${prefix}-${index}${byMagic}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
name = `${prefix}-${index}${byMagic || fallbackExt}`;
|
|
143
|
+
}
|
|
144
|
+
const dir = opts.dir || wechatMediaDir();
|
|
145
|
+
const full = join(dir, name);
|
|
146
|
+
try {
|
|
147
|
+
await mkdir(dir, { recursive: true });
|
|
148
|
+
await writeFile(full, bytes);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return { path: '', reason: `写盘失败:${error?.message || error}` };
|
|
152
|
+
}
|
|
153
|
+
return { path: full, reason: '' };
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=wechatMedia.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechatMedia.js","sourceRoot":"","sources":["../../../src/services/wechatMedia.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,10 @@ export declare class WechatMessageService {
|
|
|
30
30
|
private messageCallbacks;
|
|
31
31
|
/** 服务端说过 token 失效了。**只由长轮询置位**(见 `classifyPollResult`),恢复时清掉 */
|
|
32
32
|
private tokenExpired;
|
|
33
|
+
/** 入站媒体是否落地。undefined = 每次去读配置(默认关,见 `wechatMedia.ts` 的边界 1) */
|
|
34
|
+
private mediaDownload?;
|
|
35
|
+
/** 落地目录。undefined = 用 `wechatMediaDir()`(可被环境变量改,测试用临时目录) */
|
|
36
|
+
private mediaDirOverride?;
|
|
33
37
|
constructor(config?: WechatOCConfig);
|
|
34
38
|
startLogin(): Promise<{
|
|
35
39
|
qrcodeUrl: string;
|
|
@@ -48,6 +52,13 @@ export declare class WechatMessageService {
|
|
|
48
52
|
onMessage(callback: (msg: WechatInboundMessage) => void): void;
|
|
49
53
|
isLoggedIn(): boolean;
|
|
50
54
|
getAccountId(): string | null;
|
|
55
|
+
/** 入站媒体落地的开关:构造时注入的优先,其次读配置。默认关 —— 关着就不发任何网络请求 */
|
|
56
|
+
private mediaDownloadEnabled;
|
|
57
|
+
/**
|
|
58
|
+
* 下载一件入站媒体并落盘,返回路径;**没开这个功能、或下载失败,都返回空串**。
|
|
59
|
+
* 失败会往日志里写一行原因 —— "没开"和"开了但失败了"必须分得开,否则出事时两种证据全无。
|
|
60
|
+
*/
|
|
61
|
+
private inboundMediaPath;
|
|
51
62
|
private parseInboundMessage;
|
|
52
63
|
}
|
|
53
64
|
//# sourceMappingURL=wechatMessageService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wechatMessageService.d.ts","sourceRoot":"","sources":["../../../src/services/wechatMessageService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wechatMessageService.d.ts","sourceRoot":"","sources":["../../../src/services/wechatMessageService.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,oBAAoB,EAA0B,MAAM,aAAa,CAAA;AAUnH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACzE,UAAU,CAAC,EAAE,MAAM,GAClB,IAAI,GAAG,eAAe,GAAG,WAAW,CAStC;AAED,mDAAmD;AACnD,eAAO,MAAM,eAAe,UAAyC,CAAA;AAErE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,gBAAgB,CAAiD;IACzE,gEAAgE;IAChE,OAAO,CAAC,YAAY,CAAQ;IAC5B,gEAAgE;IAChE,OAAO,CAAC,aAAa,CAAC,CAAkB;IACxC,6DAA6D;IAC7D,OAAO,CAAC,gBAAgB,CAAC,CAAQ;gBAErB,MAAM,GAAE,cAAmB;IA2BjC,UAAU,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAmBnE,YAAY,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAkC3C,YAAY,CAAC,cAAc,SAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkChE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAyEnC,yDAAyD;IACzD,cAAc,IAAI,OAAO;IAInB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BxD,SAAS,CACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GACpC,OAAO,CAAC,OAAO,CAAC;IA0Fb,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI9D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMtF,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI;IAM9D,UAAU,IAAI,OAAO;IAIrB,YAAY,IAAI,MAAM,GAAG,IAAI;IAM7B,kDAAkD;IAClD,OAAO,CAAC,oBAAoB;IAK5B;;;OAGG;YACW,gBAAgB;YAuBhB,mBAAmB;CAqElC"}
|