dsh-napcat-keeper 2.5.1 → 2.7.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/CHANGELOG.md +45 -1
- package/README.md +46 -25
- package/lib/client.js +2 -2
- package/lib/index.js +216 -120
- package/package.json +1 -1
- package/scripts/selftest-setup.mjs +6 -6
- package/scripts/smoke-client.mjs +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.0 — 插件自己的文件收进 `~/.dsh/dsh-napcat-keeper/`
|
|
4
|
+
- **为什么要动这个**:以前插件产生的东西全都摊在 `~/.dsh` 根上(配置、日志、状态、二维码、生成的 bat/vbs、提权脚本),和 DSH 自己的 `AGENT.md`、`profiles/`、一堆别的插件的文件混在一起,压根看不出哪些是它写的。现在一个目录装完自己的全部家当。
|
|
5
|
+
- **新布局**(`~/.dsh/dsh-napcat-keeper/`):
|
|
6
|
+
|
|
7
|
+
| 文件 | 是什么 | 老名字 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `config.json` | 设置页存下来的配置 | `napcat-keeper-config.json` |
|
|
10
|
+
| `keeper.log` | 看门狗自己的日志 | `napcat-keeper.log` |
|
|
11
|
+
| `launch.log` | 拉起 QQ 那一路的输出 | `napcat-keeper-launch.log` |
|
|
12
|
+
| `state.json` | 给人看的运行状态快照 | `napcat-keeper-state.json` |
|
|
13
|
+
| `lock.json` | 跨进程拉起锁 | `napcat-keeper.lock.json` |
|
|
14
|
+
| `qr.png` | 待扫二维码的副本 | `napcat-qr.png` |
|
|
15
|
+
| `env.bat` | 「记住密码」中间层(含密码明文) | `napcat-keeper-env.bat` |
|
|
16
|
+
| `launch.vbs` | 无窗口壳,计划任务调它 | `napcat-keeper-launch.vbs` |
|
|
17
|
+
| `autogen.bat` / `autogen-node.bat` | 自动生成的启动脚本 | `napcat-autogen*.bat` |
|
|
18
|
+
| `setup/` | 提权交接脚本(apply.ps1 等) | `napcat-keeper-setup/` |
|
|
19
|
+
| `legacy/` | 老版本遗留的配置备份等,不参与运行 | —(原来散在根上) |
|
|
20
|
+
|
|
21
|
+
- **自动搬迁**:`migrateLegacyFiles()` 在启动解析配置时跑一次,能 rename 就 rename(同卷原子),跨卷才退回复制再删;目标已存在时不动旧文件,旧文件自己会落到 `legacy/`。已经搬过就不再重复(按目录记住)。
|
|
22
|
+
- **计划任务跟着改**:任务动作里写死的是 `wscript.exe "<vbs>"` 这种绝对路径,文件搬家后指向不存在的脚本 = 开机静默失败。新增 `repairTaskActions()`:只在动作对不上时用 `schtasks /change /tr` 就地改一次,**不 `/end`**,正在跑的 QQ 不会被踢下线。
|
|
23
|
+
- **不搬的**:`qq-bridge-state.json` 属于 `dsh-qq-bridge`(我们只是读一下它的时间戳),`profiles/`、`package.json` 属于 DSH 自身,全都留在原地。
|
|
24
|
+
- **验证**:`npm run smoke:client` PASS、`selftest:scan` 15/15、`selftest:setup` 45 通过 0 失败、`npm run check`(清单 + 隐私)双绿;真实环境里跑一次搬迁,配置/日志/二维码/中间层全部落到新目录,`napcat_status` 仍在线。
|
|
25
|
+
- 版本 2.7.0。
|
|
26
|
+
|
|
27
|
+
## 2.6.0 — 清死代码 + README 与代码对齐
|
|
28
|
+
- **删掉的死代码**(都确认过没有任何调用点,删了行为不变):
|
|
29
|
+
- v2.2.2 起停用的目录自动探测三件套 `looksLikeShellDir()` / `findShellDirs()` / `detectShellDir()` —— 从那次起 `shellDir` 只认用户在设置页选的,这几个函数只被彼此和调试用的 `__internals` 引用;
|
|
30
|
+
- `napcatRoot(cfg)` —— 只在声明处出现过一次;
|
|
31
|
+
- `__internals` 里同步摘掉 `detectShellDir`。
|
|
32
|
+
合计 `lib/index.js` 从 3332 行降到 3262 行。
|
|
33
|
+
- **PowerShell 调用收口**:新增 `psArgs()` / `runPs()` / `runPsSync()` 三个薄封装,原先 7 个调用点各自手抄一份 `['-NoProfile', '-NonInteractive', '-Command', …]`(漏一个参数就难查)。`where`/`tasklist`/`schtasks` 这些非 PowerShell 的调用保持原样。
|
|
34
|
+
- **措辞跟现状对齐**:3 处还写着「自动探测」的注释/字段说明改成「在目录里挑」「点分盘扫描找」;`detectLauncher` 的注释原先和函数挤在同一行,拆开。
|
|
35
|
+
- **README 统一**(这段是这回发版的主因):
|
|
36
|
+
- 安装代码块末尾多出的空行去掉;
|
|
37
|
+
- 「真假判定」那段把「可能误判」从括号里拿出来正着说,兼修「一边 → 一遍」;
|
|
38
|
+
- 「多个可能的 shell」标题与正文口径对齐(不再写死「两个目录」);
|
|
39
|
+
- 计划任务那一栏「留空 = 不用计划任务」语义是反的,改成「留空 = 走 cscript,跟着 DSH 生灭;填了才脱离 DSH」;
|
|
40
|
+
- `npm run check:privacy` 那条命令原先悬空(解释隐私体检的段落被删了),补回一整段说明;
|
|
41
|
+
- 块引用里孤零零一行 `[IO.File]::WriteAllText(...)` 补上引导语;
|
|
42
|
+
- Shell 型自动生成脚本的路径写错(`~/Dsh/…`),改成实际落点 `~/.dsh/napcat-autogen-node.bat`;文件清单同时补上 `napcat-autogen.bat`;
|
|
43
|
+
- 配置表补齐 `hideConsole` / `qrFreshSec` / `heartbeatSec` / `maxQqPerChain` / `maxLogBytes`,并注明路径类字段默认落在 `~/.dsh/`。
|
|
44
|
+
- **验证**:`npm run smoke:client` PASS、`selftest:scan` 15/15、`selftest:setup` 45 通过 0 失败、`npm run check`(清单 + 隐私)双绿。
|
|
45
|
+
- 版本 2.6.0。
|
|
46
|
+
|
|
3
47
|
## 2.5.1 — README 按维护者口径重写(只有文档改动)
|
|
4
48
|
- **为什么发这一版**:npm 上的 README 是发布那一刻的快照,改 GitHub 不会跟着变 —— 只有发一个新版本号,npm 页面才会重新抓。代码一行没动,纯为了把两个人看的那份对齐。
|
|
5
49
|
- **README 改了什么**(作者手改,我照搬):去掉「从本地目录装」那条只对开发者有意义的路径;分盘扫描的真假判定、多个 shell 并存这两段按实际重装经历改写;快速登录令牌失效时间改成实测的「几小时左右」;记住密码的风险清单删掉 MD5 那句;计划任务两处说明改成「留空 = 不建任务」的说法;开头补了一句吐槽。
|
|
@@ -14,7 +58,7 @@
|
|
|
14
58
|
3. 系统 `PATH`(`where node`);
|
|
15
59
|
4. 常见安装目录(`Program Files\nodejs`、`%LOCALAPPDATA%\Programs\nodejs`);
|
|
16
60
|
5. NapCat 目录里自带的 `node.exe` 兜底。
|
|
17
|
-
- **补上「Shell 型」NapCat**:目录**顶层**有 `napcat.mjs` 的(官方 NapCat.Shell 包那种),现在会用上面这份 node 生成
|
|
61
|
+
- **补上「Shell 型」NapCat**:目录**顶层**有 `napcat.mjs` 的(官方 NapCat.Shell 包那种),现在会用上面这份 node 生成 `~/.dsh/napcat-autogen-node.bat` 去跑 `node napcat.mjs`;以前只认 `NapCatWinBootMain.exe`,遇到 node 型安装直接报「找不到启动脚本」。注入版 `versions\*\resources\app\napcat\napcat.mjs` **不会**被当成入口(那份是等 QQ 注入的,普通 node 跑必崩)。
|
|
18
62
|
- **注入型(OneKey)不变**:NapCat 跑在 `QQ.exe --type=utility --utility-sub-type=node.mojom.NodeService` 里,磁盘上没有 node.exe,外部 node 替不了它 —— 这条链路插件只负责调 bootmain。
|
|
19
63
|
- **可见性**:设置页新增「Node 运行时」一栏 + 「测一下 node」按钮;状态条显示「哪份 node / 什么版本 / 路径」;`napcat_status` 返回值里多了 `node` 字段;启动日志 `boot:` 那行带上 node 来源。
|
|
20
64
|
- **发版前隐私清洗**:`lib/client.js` 的「接口没响应」提示原来硬编码了本机家目录,改成 `~/.dsh/napcat-keeper.log`;`scripts/selftest-scan.mjs` / `scripts/smoke-client.mjs` 里写死的本机绝对路径改成按脚本自身位置定位;`smoke-client` 的 `launchAccount` fixture 换成合成占位 `1000000000`。现在换台机器 clone 下来,自测脚本照样跑得起来。
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-napcat-keeper
|
|
2
2
|
|
|
3
|
-
发现没有能够随着
|
|
3
|
+
发现没有能够随着 DSH 打开自动启动 NapCat 的插件,遂使用大肥鱼制作此插件,建议搭配[Yuuyuko-uu 的 dsh-qq-bridge](https://github.com/Yuuyuko-uu/dsh-qq-bridge)使用~
|
|
4
4
|
|
|
5
5
|
给 [NapCat](https://napneko.github.io/)(QQ 协议端)配一个看门狗:**DSH 在,QQ 就在**。
|
|
6
6
|
|
|
@@ -41,9 +41,7 @@
|
|
|
41
41
|
## 安装
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
# 从 npm 装
|
|
45
44
|
dsh plugin --profile <profile> add dsh-napcat-keeper
|
|
46
|
-
|
|
47
45
|
```
|
|
48
46
|
|
|
49
47
|
装完重启一次 DSH(第一次装载才生效),然后去 **设置 → napcat桥**。
|
|
@@ -52,11 +50,11 @@ dsh plugin --profile <profile> add dsh-napcat-keeper
|
|
|
52
50
|
|
|
53
51
|
| 填什么 | 说明 |
|
|
54
52
|
| --- | --- |
|
|
55
|
-
| **NapCat Shell 目录** | 用下面的「分盘扫描」找:
|
|
53
|
+
| **NapCat Shell 目录** | 用下面的「分盘扫描」找:NapCat 装哪块盘就点哪块,扫到后点「用这个」直接填进去;也能手填,例如 `C:/Users/你/NapCat/OneKey/NapCat.Shell` |
|
|
56
54
|
| **要快登的 QQ 号** | 填了它,插件优先挑「填的这个号」的启动脚本;连脚本都没有时,自动生成一个直接调 `NapCatWinBootMain.exe <号>` 的脚本 |
|
|
57
55
|
| **OneBot HTTP 端口** | 跟 NapCat 网络配置里的 httpServers 一致(默认 3000) |
|
|
58
56
|
|
|
59
|
-
点 **保存设置** → 存到 `~/.dsh/napcat-keeper
|
|
57
|
+
点 **保存设置** → 存到 `~/.dsh/dsh-napcat-keeper/config.json`,立刻热生效。
|
|
60
58
|
回「状态」看:在线会显示昵称和 QQ 号;离线就点「拉起 NapCat」。
|
|
61
59
|
|
|
62
60
|
## 分盘扫描:装在哪都能找到
|
|
@@ -72,11 +70,13 @@ dsh plugin --profile <profile> add dsh-napcat-keeper
|
|
|
72
70
|
命中后不再往它下面钻(QQ 目录里几千个文件,钻进去只会变慢)。
|
|
73
71
|
- **命中之后再分真假**:目录里只要有 `versions/`、`QQ.exe`、`napcat.mjs` 里的任意一样,就是**真 Shell**
|
|
74
72
|
(标绿 `✓ 像真 Shell:versions/ · QQ.exe`,按钮「用这个」);三样都没有的标黄
|
|
75
|
-
`⚠ 没看到 versions/QQ.exe/napcat.mjs
|
|
73
|
+
`⚠ 没看到 versions/QQ.exe/napcat.mjs`,按钮是「仍要用这个」。
|
|
74
|
+
这个判据只是经验值,精简版布局有可能三样都不带(我自己重装了一遍 NapCat 就没这仨,不管了 bro),
|
|
75
|
+
所以「标黄」不等于「不能用」,点「仍要用这个」照样能填进去。
|
|
76
76
|
真 Shell 一律排在前面,不受磁盘遍历顺序影响。
|
|
77
|
-
-
|
|
77
|
+
- **为什么会有好几个候选**:OneKey 安装器会先把壳模板解到 `NapCat\OneKey\bootmain`(只有 `NapCatWinBootMain.exe`
|
|
78
78
|
+ `napcat.quick.bat`,**没有 QQ 本体**),再把真正的版本装成 `NapCat\<版本>.Shell`(有 `versions/` 和 `QQ.exe`)。
|
|
79
|
-
|
|
79
|
+
一次安装可能留下多个目录,只有带 QQ 本体的那个能拉起来。
|
|
80
80
|
- 每命中一条就列一行:路径、挑出来的启动脚本、是否含 `NapCatWinBootMain.exe`、真假判定,右边按钮一键填入。
|
|
81
81
|
- 到时间/到数量上限会明确写「结果可能不全」,不会闷头扫一小时。
|
|
82
82
|
|
|
@@ -115,7 +115,7 @@ DSH 自己是普通权限。插件要碰「装系统的位置」(往防火墙
|
|
|
115
115
|
| 事 | 会做什么 | 要不要管理员 |
|
|
116
116
|
| --- | --- | --- |
|
|
117
117
|
| 自动启动脚本 | 在 `shellDir` 里补一个 `napcat.quick.auto.bat`(写死账号和 `NapCatWinBootMain.exe` 路径) | 看那个目录你能不能写,不能写才需要 |
|
|
118
|
-
| 中间层 | 写 `
|
|
118
|
+
| 中间层 | 写 `env.bat`:把密码塞进环境变量再 call 启动脚本 | 不需要 |
|
|
119
119
|
| 计划任务 | 用 `schtasks` 建用户级任务(要不要隐藏黑框跟着设置走) | 不需要 |
|
|
120
120
|
| 防火墙放行 | `New-NetFirewallRule` 放行 OneBot 端口入站 | 需要 |
|
|
121
121
|
|
|
@@ -126,7 +126,7 @@ DSH 自己是普通权限。插件要碰「装系统的位置」(往防火墙
|
|
|
126
126
|
## 桌面上那个黑框
|
|
127
127
|
|
|
128
128
|
cscript / cmd 直接拉 NapCat,会多出一个控制台窗口挂在桌面上。默认行为是**不显示**:
|
|
129
|
-
计划任务的动作写的是 `wscript.exe "<dsh
|
|
129
|
+
计划任务的动作写的是 `wscript.exe "<DSH 家目录>\dsh-napcat-keeper\launch.vbs"`(绝对路径),这个壳里用
|
|
130
130
|
`sh.Run cmd, 0, False` 把真正的 cmd 以隐藏窗口启动(`0` = SW_HIDE)。壳文件用 UTF-16LE 存,中文路径也认。
|
|
131
131
|
|
|
132
132
|
不想要隐藏就把「拉起时不显示黑框」关掉,任务会退回直接跑 `cmd.exe`;已经跑起来的那条链想收窗口,
|
|
@@ -139,7 +139,7 @@ NapCat 的**快速登录令牌会失效**(实测几小时左右),失效后
|
|
|
139
139
|
|
|
140
140
|
设置页「自动登录(可不填)」里勾 **记住密码** 时会先弹一段风险提示,要你自己点「我知道风险,仍然开启」才会生效。开启前请确认你接受这几条:
|
|
141
141
|
|
|
142
|
-
1. 密码以**明文**存进 `~/.dsh/napcat-keeper
|
|
142
|
+
1. 密码以**明文**存进 `~/.dsh/dsh-napcat-keeper/config.json` 和同目录下插件生成的 `env.bat`,不加密。
|
|
143
143
|
2. 任何能读你用户目录的程序都能捡走:同步盘、备份工具、其它软件、远控、临时借电脑的人。
|
|
144
144
|
3. QQ 密码往往和 QQ 邮箱、部分游戏、微信登录入口共用一套身份,泄漏面不止一个号。
|
|
145
145
|
4. 插件不打印密码,`/state` 也只回「存没存 + 长度」,但那只是少开一个口子,不等于安全。
|
|
@@ -155,7 +155,7 @@ QQ 那条进程链挂在本 DSH 实例下面——DSH 开着它就在,DSH 关
|
|
|
155
155
|
|
|
156
156
|
「刚重启 DSH,QQ 就掉了」不是毛病,是这个默认行为:新实例会自己重新拉起,扫不上就自动密码登录。
|
|
157
157
|
|
|
158
|
-
要是反过来想要「DSH 关了 QQ
|
|
158
|
+
要是反过来想要「DSH 关了 QQ 还活着」,才去填「计划任务名」那一栏(比如 `NapCat-Keeper-Launch`;**留空 = 走 cscript,跟着 DSH 生灭**)+ 勾「自动创建计划任务」,
|
|
159
159
|
那时插件改用 `schtasks /run` 拉,父进程是任务计划服务,DSH 重启牵连不到它。
|
|
160
160
|
|
|
161
161
|
## 用哪份 Node:默认借 DSH 自带的
|
|
@@ -172,7 +172,7 @@ QQ 那条进程链挂在本 DSH 实例下面——DSH 开着它就在,DSH 关
|
|
|
172
172
|
|
|
173
173
|
两种 NapCat 装法的区别:
|
|
174
174
|
|
|
175
|
-
- **Shell 型**(官方 `NapCat.Shell` 包、目录**顶层**有 `napcat.mjs`):插件会用上面这份 Node 生成
|
|
175
|
+
- **Shell 型**(官方 `NapCat.Shell` 包、目录**顶层**有 `napcat.mjs`):插件会用上面这份 Node 生成 `~/.dsh/dsh-napcat-keeper/autogen-node.bat` 去跑 `node napcat.mjs` —— 这是 Node 运行时真正派上用场的地方;
|
|
176
176
|
- **注入型**(OneKey 那种):NapCat 跑在 `QQ.exe` 自己的 Node service 里(`--type=utility --utility-sub-type=node.mojom.NodeService`),磁盘上根本没有 `node.exe`,外面的 Node 也替不了它。这条链路插件只负责调 `NapCatWinBootMain.exe`,跟 Node 无关。
|
|
177
177
|
|
|
178
178
|
> 顺带一提:`versions\*\resources\app\napcat\napcat.mjs` 那份**不会**被当成入口——它是等 QQ 注入用的,拿普通 Node 直接跑必崩。
|
|
@@ -201,28 +201,47 @@ QQ 那条进程链挂在本 DSH 实例下面——DSH 开着它就在,DSH 关
|
|
|
201
201
|
| `accessToken` | 空 | OneBot access token |
|
|
202
202
|
| `taskName` | 空(= 用 cscript,QQ 跟着 DSH 生灭) | 想脱离 DSH 才填计划任务名 |
|
|
203
203
|
| `autoCreateTask` | `false` | 填了 `taskName` 且任务不存在时自动创建 |
|
|
204
|
+
| `hideConsole` | `true` | 拉起时不显示黑框(用 wscript 壳藏窗口) |
|
|
204
205
|
| `probeIntervalSec` | `45` | 复查间隔 |
|
|
205
206
|
| `launchGraceSec` | `150` | 拉起后的宽限期 |
|
|
207
|
+
| `qrFreshSec` | `300` | 二维码新鲜度窗口:比这新 = 还在等人扫 |
|
|
206
208
|
| `scanTimeoutSec` | `300` | 扫码等待上限 |
|
|
207
209
|
| `maxScanRetries` | `2` | 扫码重试上限 |
|
|
208
210
|
| `stuckSec` | `420` | 卡死判定 |
|
|
211
|
+
| `heartbeatSec` | `600` | 一切正常时的心跳日志间隔 |
|
|
209
212
|
| `chainGapSec` | `120` | 重复链判定窗口 |
|
|
213
|
+
| `maxQqPerChain` | `4` | 单条链该有几条 `QQ.exe`,判重时的参考值 |
|
|
210
214
|
| `procPathMatch` | 空(由 shellDir 推两层) | 只在这个路径前缀下找 QQ/NapCat 进程 |
|
|
211
|
-
| `bridgeStaleSec` | `180` | `qq-bridge` 的 state 多久没更新就警告 |
|
|
215
|
+
| `bridgeStaleSec` | `180` | `dsh-qq-bridge` 的 state 多久没更新就警告 |
|
|
216
|
+
| `maxLogBytes` | `1048576` | 日志超这么多字节就滚动一次 |
|
|
217
|
+
|
|
218
|
+
剩下几个是路径类字段(`configPath` / `logPath` / `launcherLog` / `lockPath` / `statePath` / `qrCopyPath` / `bridgeStatePath`),默认都落在 `~/.dsh/dsh-napcat-keeper/` 下(`bridgeStatePath` 例外,那是 `dsh-qq-bridge` 的文件,留在 `~/.dsh/` 根上),一般不用碰。
|
|
212
219
|
|
|
213
220
|
## 文件都在哪
|
|
214
221
|
|
|
222
|
+
插件产生的东西全在 `~/.dsh/dsh-napcat-keeper/` 一个目录里,DSH 自己的目录不会被它塞满:
|
|
223
|
+
|
|
215
224
|
```
|
|
216
|
-
~/.dsh/napcat-keeper
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
225
|
+
~/.dsh/dsh-napcat-keeper/
|
|
226
|
+
├── config.json 设置页保存的配置
|
|
227
|
+
├── keeper.log 看门狗日志(超 1MB 自动滚动)
|
|
228
|
+
├── launch.log 启动脚本的原始输出(扫码提示都在这)
|
|
229
|
+
├── state.json 每轮 tick 的状态快照
|
|
230
|
+
├── lock.json 跨进程拉起锁(防两个 DSH 同时拉)
|
|
231
|
+
├── qr.png 最近一张二维码副本(扫码时更新)
|
|
232
|
+
├── env.bat 只在「记住密码」开启时生成:把密码塞进环境变量给启动脚本
|
|
233
|
+
├── launch.vbs 「自动创建计划任务」时生成的无窗口壳,计划任务调它
|
|
234
|
+
├── autogen-node.bat Shell 型专用:拿 DSH 自带的 node 跑 napcat.mjs
|
|
235
|
+
├── autogen.bat 没现成启动脚本时生成:直接调 NapCatWinBootMain.exe
|
|
236
|
+
├── setup/ 提权交接目录:plan.json / apply.ps1 / apply-admin.cmd
|
|
237
|
+
│ / undo-admin.cmd / result.json / run.log / README.txt
|
|
238
|
+
└── legacy/ 老版本遗留的配置备份等,不参与运行,整个删掉也没事
|
|
224
239
|
```
|
|
225
240
|
|
|
241
|
+
- `env.bat` 只在「记住密码」开启时才有;两份 `autogen*.bat` 只在需要时出现(每次拉起前按当前配置重写,删掉也会自己长回来)。整个目录删掉 = 插件回到没配置过的状态。
|
|
242
|
+
- **从老版本升上来(< 2.7.0)**:第一次启动会自动把原来散在 `~/.dsh` 根上的文件搬进这个目录(`napcat-keeper-config.json` → `config.json` 这种),改名一次就固定;计划任务里写死的旧路径也会一并改指新位置 —— 那一步用 `schtasks /change` 就地改,不结束正在跑的 QQ。老备份之类的零碎挪进 `legacy/`。
|
|
243
|
+
- `~/.dsh/qq-bridge-state.json` **不在**这个目录里,也不归这个插件:它由 `dsh-qq-bridge` 写,插件只是读一下它的时间戳,判断那边轮询还活着没有。
|
|
244
|
+
|
|
226
245
|
## 出事了先跑体检:`npm run check`
|
|
227
246
|
|
|
228
247
|
插件整个没加载、DSH 日志里连一个字都不提它、QQ 掉线后再没人拉起来 —— 十有八九是 `package.json` 被动过:BOM、双重编码乱码、或者 JSON 语法坏掉。清单读不动,DSH 连插件代码都不会 import,看门狗自然一起消失。
|
|
@@ -238,19 +257,21 @@ npm run check:privacy # 只查隐私(发版前必跑)
|
|
|
238
257
|
|
|
239
258
|
**清单体检**查这几样:清单能不能 `JSON.parse`、有没有 BOM、是不是合法 UTF-8、有没有出现「鐔欑殑澶ц偉楸」这种双重编码乱码、入口文件在不在、清单版本和 `lib/index.js` 里的 `export const version` 同不同步。JSON 坏掉时直接告诉你第几行第几列,并用 `^` 指到那个字符上(位置 899 那种报错不用再数换行)。
|
|
240
259
|
|
|
260
|
+
**隐私体检**是发版前那道闸:把包里所有文本文件过一遍,找 Windows / Unix 家目录路径、邮箱、`ghp_` / `npm_` / `sk-` / `AKIA` 这类凭证、`Bearer` 后面的真串、DSH 会话 id,以及自测里那种 `launchAccount: '1234567890'` 的真实 QQ 号。命中就按 `文件:行` 列出来并返回非 0;确实是文档里必须留的示例,在那行末尾写 `check-privacy:ignore` 放过。占位写法(`C:/Users/你/…`、`C:/Users/x/…`)本来就放行。
|
|
261
|
+
|
|
241
262
|
另外三个自测:`npm run smoke:client`(把 `lib/client.js` 塞进假 React 渲染一遍)、`npm run selftest:scan`(造假树验扫盘判定)、`npm run selftest:setup`(临时目录里真跑一遍 apply / 重跑 / undo)。都不依赖本机路径,clone 下来就能跑。
|
|
242
263
|
|
|
243
|
-
> **改这个插件的文件的规矩**:别用 `Get-Content` 读、`Set-Content -Encoding utf8` 写 —— PowerShell 5.1 会按 ANSI 读、再按 UTF-8 写,一趟往返中文全变乱码,BOM 还照写。用编辑器另存为「UTF-8 无 BOM
|
|
264
|
+
> **改这个插件的文件的规矩**:别用 `Get-Content` 读、`Set-Content -Encoding utf8` 写 —— PowerShell 5.1 会按 ANSI 读、再按 UTF-8 写,一趟往返中文全变乱码,BOM 还照写。用编辑器另存为「UTF-8 无 BOM」;非要脚本写,就走
|
|
244
265
|
> `[IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))`。
|
|
245
266
|
|
|
246
267
|
## 常见问题
|
|
247
268
|
|
|
248
269
|
**「离线」但进程明明还在?**
|
|
249
|
-
端口没通。看
|
|
270
|
+
端口没通。看 `~/.dsh/dsh-napcat-keeper/launch.log`,多半是卡在扫码,或者 QQ 弹了验证窗。
|
|
250
271
|
|
|
251
272
|
**刚关掉 DSH,QQ 就跟着退了?**
|
|
252
273
|
正常,默认就是这样:QQ 挂在 DSH 下面,DSH 一走它就走。下次开 DSH 会重新拉一条。
|
|
253
|
-
想反过来(DSH 关了
|
|
274
|
+
想反过来(DSH 关了 NapCat 还活着)去填「计划任务名」+ 勾「自动创建计划任务」。
|
|
254
275
|
|
|
255
276
|
**两个 QQ 客户端互相踢?**
|
|
256
277
|
去重会兜住,但根因通常是有人又点了一次 `napcat.bat`。点「去重复链」立刻收敛。
|
package/lib/client.js
CHANGED
|
@@ -253,7 +253,7 @@ window.__ModuleLoader__.load({
|
|
|
253
253
|
setTimeout(拉, 900)
|
|
254
254
|
return
|
|
255
255
|
}
|
|
256
|
-
set说('接口没响应,读不到状态(日志见 ~/.dsh/napcat-keeper.log)')
|
|
256
|
+
set说('接口没响应,读不到状态(日志见 ~/.dsh/dsh-napcat-keeper/keeper.log)')
|
|
257
257
|
set态({ ok: false, config: null, runtime: {}, scan: {}, drives: [], log: [] })
|
|
258
258
|
}
|
|
259
259
|
拉()
|
|
@@ -576,7 +576,7 @@ window.__ModuleLoader__.load({
|
|
|
576
576
|
|
|
577
577
|
/* ---------- 「记住密码」那一块:先讲风险,再让人自己决定 ---------- */
|
|
578
578
|
const 风险条目 = [
|
|
579
|
-
'密码会以明文写进本机两个地方:~/.dsh/napcat-keeper
|
|
579
|
+
'密码会以明文写进本机两个地方:~/.dsh/dsh-napcat-keeper/config.json,以及同目录下插件生成的 env.bat。不加密、不做任何变换(MD5 那份是给 NapCat 兜底用的,同样等于密码)。',
|
|
580
580
|
'任何能读你这个用户目录的东西都能直接捡走:同步盘、备份工具、其它软件、远控木马、临时借你电脑的人。',
|
|
581
581
|
'QQ 密码通常和 QQ 邮箱、部分游戏、微信的登录入口共用一套身份,泄漏的影响面不止这一个号。',
|
|
582
582
|
'插件本身不打印密码、/state 接口也只回「存没存」和长度,但那只是少开一个泄漏口,不等于安全。',
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-napcat-keeper —— NapCat 看门狗 v2.
|
|
2
|
+
* dsh-napcat-keeper —— NapCat 看门狗 v2.7.0(可发布通用版)
|
|
3
3
|
*
|
|
4
4
|
* 解决的问题:DSH 是桌面应用,重启 DSH 时 NapCat(QQ 协议端) 不会跟着起来,
|
|
5
5
|
* 于是 dsh-qq-bridge 一直轮询一个死端口,你在 QQ 里说的话就石沉大海。
|
|
6
6
|
*
|
|
7
|
+
* v2.7.0:自己的东西收进自己的目录(~/.dsh/dsh-napcat-keeper/)
|
|
8
|
+
* - 以前配置、日志、状态、二维码、生成的 bat/vbs、提权脚本全散在 ~/.dsh 根上,
|
|
9
|
+
* 和 DSH 自己的文件混在一起,一眼看不出哪些是插件产生的;
|
|
10
|
+
* - 现在统一落在 ~/.dsh/dsh-napcat-keeper/ 一个目录里,文件名去掉重复前缀:
|
|
11
|
+
* config.json / keeper.log / launch.log / state.json / lock.json / qr.png /
|
|
12
|
+
* env.bat / launch.vbs / autogen.bat / autogen-node.bat / setup/;
|
|
13
|
+
* - 老版本升级时 migrateLegacyFiles() 自动搬一次(能 rename 就 rename),
|
|
14
|
+
* 遗留的 *.bak、旧备份目录挪进 legacy/ 留档;计划任务里写死的旧路径由
|
|
15
|
+
* repairTaskActions() 用 /change 改指新位置,不 /end、不打扰正在跑的 QQ;
|
|
16
|
+
* - qq-bridge-state.json 不搬:那是 dsh-qq-bridge 的文件,只是被读一下。
|
|
17
|
+
*
|
|
18
|
+
* v2.6.0:清死代码 + README 与代码对齐(行为一字没变)
|
|
19
|
+
* - 删掉 v2.2.2 起就没人再调用的目录自动探测三件套(looksLikeShellDir / findShellDirs /
|
|
20
|
+
* detectShellDir)和只被写过一次的 napcatRoot():配置不再有「悄悄替你填」的暗路,
|
|
21
|
+
* 文件也短了 70 行;
|
|
22
|
+
* - PowerShell 调用收成 psArgs / runPs / runPsSync 三个薄封装,原先 7 处手抄的
|
|
23
|
+
* ['-NoProfile','-NonInteractive','-Command', …] 不再各写一遍(参数漏一个就查半天的那种坑);
|
|
24
|
+
* - README:补回隐私体检那段说明(命令原先悬空)、修正计划任务「留空」的语义、
|
|
25
|
+
* 把 Shell 型自动生成的 bat 路径写对、配置表补齐 hideConsole / heartbeatSec 等漏掉的键;
|
|
26
|
+
* - 发版流程不变:npm run check(清单 + 隐私)+ 三个自测全绿才发。
|
|
27
|
+
*
|
|
7
28
|
* v2.5.0:统一用 DSH 自带的 Node 运行时
|
|
8
29
|
* - 以前凡是「要跑一段 JS」的地方都默认那台机器上有 node(或者干脆蹭 NapCat / QQ 的私有环境),
|
|
9
30
|
* 换台机器、换个装法就哑火;现在统一走 resolveNode():
|
|
@@ -35,7 +56,7 @@
|
|
|
35
56
|
*
|
|
36
57
|
* v2.3.0:提权交接 —— 需要管理员的活,交给「你点一下」的脚本
|
|
37
58
|
* - 插件不再自己尝试要管理员(它本来也要不到):把要做的事摊成步骤写进
|
|
38
|
-
* ~/.dsh/napcat-keeper
|
|
59
|
+
* ~/.dsh/dsh-napcat-keeper/setup/plan.json,生成 apply.ps1 + apply-admin.cmd + undo-admin.cmd;
|
|
39
60
|
* - 脚本先数一遍有没有真需要管理员的步骤:没有就直接干;有才 Start-Process -Verb RunAs
|
|
40
61
|
* 自我提权 —— UAC 那个「是」就是用户亲手按的确认键,插件全程普通权限;
|
|
41
62
|
* - 每步结果写 result.json,设置页「读结果」说清楚哪条成了、哪条没成、有没有拿到管理员;
|
|
@@ -57,12 +78,12 @@
|
|
|
57
78
|
* - NapCat 的快速登录 token 会失效(实测几小时就掉),失效后只剩手机扫码一条路;
|
|
58
79
|
* - 打开 rememberPassword 后,拉起时会通过环境变量 ACCOUNT / NAPCAT_QUICK_PASSWORD
|
|
59
80
|
* (外加 MD5 兜底)交给 NapCat,token 一失效它自己用密码重新登录,不用人管;
|
|
60
|
-
* - 密码只落在本机两处:~/.dsh/napcat-keeper
|
|
81
|
+
* - 密码只落在本机两处:~/.dsh/dsh-napcat-keeper/config.json 与生成的 env.bat,
|
|
61
82
|
* 所以设置页打开它之前会先摆出风险说明,要人自己点确认;关掉或点「清除密码」就删干净。
|
|
62
83
|
* - 密码绝不进日志、绝不进 /state 返回值(只回「已保存」和长度)。
|
|
63
84
|
*
|
|
64
85
|
* v2.0.0:从「一台机器专用」改成通用款
|
|
65
|
-
* - 所有机器相关的路径都不再写死,改为「设置 → napcat
|
|
86
|
+
* - 所有机器相关的路径都不再写死,改为「设置 → napcat桥」里填,或点分盘扫描找;
|
|
66
87
|
* - 新增设置界面(lib/client.js):状态 / 配置 / 一键动作 / 日志,保存即热生效;
|
|
67
88
|
* - 计划任务不存在时自动创建(用户级,无需管理员),保证拉起的 QQ 链不随 DSH 退出;
|
|
68
89
|
* - 只填 QQ 号也能用:自动生成直接调 NapCatWinBootMain.exe 的启动脚本。
|
|
@@ -90,6 +111,7 @@ import {
|
|
|
90
111
|
readFileSync,
|
|
91
112
|
readSync,
|
|
92
113
|
readdirSync,
|
|
114
|
+
renameSync,
|
|
93
115
|
statSync,
|
|
94
116
|
unlinkSync,
|
|
95
117
|
writeFileSync,
|
|
@@ -100,7 +122,7 @@ import { homedir, tmpdir } from 'node:os'
|
|
|
100
122
|
import path from 'node:path'
|
|
101
123
|
|
|
102
124
|
export const name = 'napcat-keeper'
|
|
103
|
-
export const version = '2.
|
|
125
|
+
export const version = '2.7.0'
|
|
104
126
|
|
|
105
127
|
/** tools 用来注册 napcat_* 工具;webServer 用来挂设置界面接口。拿不到也不至于把 DSH 拖崩。 */
|
|
106
128
|
export const inject = ['tools', 'webServer']
|
|
@@ -109,7 +131,7 @@ const DEFAULTS = {
|
|
|
109
131
|
enabled: true,
|
|
110
132
|
/** NapCat Shell 目录(启动脚本所在目录);不再自动探测,留空就表示还没选 */
|
|
111
133
|
shellDir: '',
|
|
112
|
-
/**
|
|
134
|
+
/** 启动脚本名或绝对路径;留空则在 shellDir 里自动挑(quick / napcat*.bat) */
|
|
113
135
|
launcherScript: '',
|
|
114
136
|
/**
|
|
115
137
|
* Node 运行时(node.exe)的位置;留空 = 自动。
|
|
@@ -217,15 +239,102 @@ function dshHome() {
|
|
|
217
239
|
return process.env.DSH_HOME || path.join(homedir(), '.dsh')
|
|
218
240
|
}
|
|
219
241
|
|
|
220
|
-
/**
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
242
|
+
/**
|
|
243
|
+
* 插件自己的家:~/.dsh/dsh-napcat-keeper。
|
|
244
|
+
* 配置、日志、状态、二维码、生成的 bat / vbs、提权脚本全在这一个目录里 ——
|
|
245
|
+
* 老版本是直接摊在 ~/.dsh 根上的,2.7.0 起收拢,升级时 migrateLegacyFiles() 自动搬。
|
|
246
|
+
*/
|
|
247
|
+
function keeperDir() {
|
|
248
|
+
const dir = path.join(dshHome(), 'dsh-napcat-keeper')
|
|
249
|
+
try {
|
|
250
|
+
mkdirSync(dir, { recursive: true })
|
|
251
|
+
} catch {}
|
|
252
|
+
return dir
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* 老名字 → 新名字。搬过一次就不再动(同目录 rename 是原子的)。 */
|
|
256
|
+
const LEGACY_FILES = [
|
|
257
|
+
['napcat-keeper-config.json', 'config.json'],
|
|
258
|
+
['napcat-keeper.log', 'keeper.log'],
|
|
259
|
+
['napcat-keeper-launch.log', 'launch.log'],
|
|
260
|
+
['napcat-keeper.lock.json', 'lock.json'],
|
|
261
|
+
['napcat-keeper-state.json', 'state.json'],
|
|
262
|
+
['napcat-qr.png', 'qr.png'],
|
|
263
|
+
['napcat-keeper-env.bat', 'env.bat'],
|
|
264
|
+
['napcat-keeper-launch.vbs', 'launch.vbs'],
|
|
265
|
+
['napcat-autogen-node.bat', 'autogen-node.bat'],
|
|
266
|
+
['napcat-autogen.bat', 'autogen.bat'],
|
|
267
|
+
]
|
|
268
|
+
const LEGACY_DIRS = [['napcat-keeper-setup', 'setup']]
|
|
269
|
+
/** 老版本留下的零碎(配置备份、bom-backup 之类):扫一遍挪进 legacy/,不参与运行 */
|
|
270
|
+
const LEGACY_LEFTOVER = /^(napcat-keeper[.-]|napcat-qr\.|napcat-autogen)/
|
|
271
|
+
const CANONICAL_LEGACY = new Set([...LEGACY_FILES, ...LEGACY_DIRS].map(([oldName]) => oldName))
|
|
272
|
+
|
|
273
|
+
let migratedFor = ''
|
|
274
|
+
/** 上一次搬迁搬了几个(给 apply 写一行日志用) */
|
|
275
|
+
let lastMigrated = 0
|
|
276
|
+
|
|
277
|
+
function moveOne(from, to) {
|
|
278
|
+
try {
|
|
279
|
+
if (existsSync(to)) return false
|
|
280
|
+
renameSync(from, to)
|
|
281
|
+
return true
|
|
282
|
+
} catch {
|
|
283
|
+
try {
|
|
284
|
+
copyFileSync(from, to)
|
|
285
|
+
unlinkSync(from)
|
|
286
|
+
return true
|
|
287
|
+
} catch {
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
}
|
|
225
291
|
}
|
|
226
292
|
|
|
227
|
-
|
|
228
|
-
|
|
293
|
+
/** 把老版本散在 ~/.dsh 根上的文件搬进 keeperDir;重复调用是空操作,返回搬了几个 */
|
|
294
|
+
function migrateLegacyFiles(dir) {
|
|
295
|
+
const target = dir || keeperDir()
|
|
296
|
+
if (migratedFor === target) return 0
|
|
297
|
+
migratedFor = target
|
|
298
|
+
const home = dshHome()
|
|
299
|
+
let moved = 0
|
|
300
|
+
for (const [oldName, newName] of LEGACY_FILES) {
|
|
301
|
+
try {
|
|
302
|
+
if (!existsSync(path.join(home, oldName))) continue
|
|
303
|
+
} catch {
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
if (moveOne(path.join(home, oldName), path.join(target, newName))) moved += 1
|
|
307
|
+
}
|
|
308
|
+
for (const [oldName, newName] of LEGACY_DIRS) {
|
|
309
|
+
try {
|
|
310
|
+
const from = path.join(home, oldName)
|
|
311
|
+
if (existsSync(from) && !existsSync(path.join(target, newName)) && moveOne(from, path.join(target, newName))) {
|
|
312
|
+
moved += 1
|
|
313
|
+
}
|
|
314
|
+
} catch {}
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
for (const entry of readdirSync(home)) {
|
|
318
|
+
if (CANONICAL_LEGACY.has(entry) || !LEGACY_LEFTOVER.test(entry)) continue
|
|
319
|
+
try {
|
|
320
|
+
mkdirSync(path.join(target, 'legacy'), { recursive: true })
|
|
321
|
+
if (moveOne(path.join(home, entry), path.join(target, 'legacy', entry))) moved += 1
|
|
322
|
+
} catch {}
|
|
323
|
+
}
|
|
324
|
+
} catch {}
|
|
325
|
+
lastMigrated = moved
|
|
326
|
+
return moved
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** 设置界面写的那份配置(JSON,无 BOM):<keeperDir>/config.json */
|
|
330
|
+
function configFilePath(cfgOrDir) {
|
|
331
|
+
if (cfgOrDir && typeof cfgOrDir === 'object' && cfgOrDir.configPath) return cfgOrDir.configPath
|
|
332
|
+
const dir = typeof cfgOrDir === 'string' ? cfgOrDir : keeperDir()
|
|
333
|
+
return path.join(dir, 'config.json')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function readConfigFile(dir) {
|
|
337
|
+
const file = configFilePath(dir)
|
|
229
338
|
try {
|
|
230
339
|
if (!existsSync(file)) return {}
|
|
231
340
|
} catch {
|
|
@@ -247,37 +356,6 @@ function writeConfigFile(home, data) {
|
|
|
247
356
|
return file
|
|
248
357
|
}
|
|
249
358
|
|
|
250
|
-
/* ---- 自动探测:让新用户不用手填一堆路径 ---------------------------- */
|
|
251
|
-
|
|
252
|
-
function looksLikeShellDir(dir) {
|
|
253
|
-
try {
|
|
254
|
-
if (!statSync(dir).isDirectory()) return false
|
|
255
|
-
} catch {
|
|
256
|
-
return false
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
return readdirSync(dir).some((f) => /\.bat$/i.test(f))
|
|
260
|
-
} catch {
|
|
261
|
-
return false
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function findShellDirs(root, depth, out) {
|
|
266
|
-
if (depth < 0 || out.length > 12) return
|
|
267
|
-
let entries = []
|
|
268
|
-
try {
|
|
269
|
-
entries = readdirSync(root, { withFileTypes: true })
|
|
270
|
-
} catch {
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
for (const entry of entries) {
|
|
274
|
-
if (!entry.isDirectory()) continue
|
|
275
|
-
const full = path.join(root, entry.name)
|
|
276
|
-
if (/shell$/i.test(entry.name) && looksLikeShellDir(full)) out.push(full)
|
|
277
|
-
else findShellDirs(full, depth - 1, out)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
359
|
/**
|
|
282
360
|
* 同步跑一条命令抓 stdout。失败一律返回空串 —— 这里全是「问一下系统」的活,
|
|
283
361
|
* 问不到就走兜底,绝不能影响看门狗本身。
|
|
@@ -297,6 +375,16 @@ function captureSync(file, args, timeoutMs = 4000) {
|
|
|
297
375
|
}
|
|
298
376
|
}
|
|
299
377
|
|
|
378
|
+
/* PowerShell 调用只有一种形状:-NoProfile -NonInteractive -Command <脚本>。
|
|
379
|
+
* 下面三个薄封装把这段样板收在一处,调用点只剩「脚本 + 超时」,也省得每处手抄漏参数。 */
|
|
380
|
+
const psArgs = (script) => ['-NoProfile', '-NonInteractive', '-Command', script]
|
|
381
|
+
|
|
382
|
+
/** 同步问一次系统(盘符这种一开机就该知道的廉价查询) */
|
|
383
|
+
const runPsSync = (script, timeoutMs = 4000) => captureSync('powershell.exe', psArgs(script), timeoutMs)
|
|
384
|
+
|
|
385
|
+
/** 异步跑一段 PowerShell,返回 stdout;失败返回空串 */
|
|
386
|
+
const runPs = (script, timeoutMs = 20000) => runCapture('powershell.exe', psArgs(script), timeoutMs)
|
|
387
|
+
|
|
300
388
|
/**
|
|
301
389
|
* 本机有哪些盘 —— 系统里有几块就报几块。
|
|
302
390
|
*
|
|
@@ -312,12 +400,7 @@ function listDrives() {
|
|
|
312
400
|
if (match) found.add(`${match[1].toUpperCase()}:`)
|
|
313
401
|
}
|
|
314
402
|
|
|
315
|
-
const out =
|
|
316
|
-
'-NoProfile',
|
|
317
|
-
'-NonInteractive',
|
|
318
|
-
'-Command',
|
|
319
|
-
'[System.IO.DriveInfo]::GetDrives() | ForEach-Object { $_.Name }',
|
|
320
|
-
])
|
|
403
|
+
const out = runPsSync('[System.IO.DriveInfo]::GetDrives() | ForEach-Object { $_.Name }')
|
|
321
404
|
for (const line of out.split(/\r?\n/)) add(line)
|
|
322
405
|
|
|
323
406
|
if (!found.size) {
|
|
@@ -330,45 +413,6 @@ function listDrives() {
|
|
|
330
413
|
return [...found].sort()
|
|
331
414
|
}
|
|
332
415
|
|
|
333
|
-
/**
|
|
334
|
-
* v2.2.2 起已停用:设置页不再替用户「自动探测」目录,只在用户点某个盘时深扫。
|
|
335
|
-
* 函数留着是给 __internals / 调试脚本用,正常运行路径(resolveConfig、/state、/action)
|
|
336
|
-
* 都不会再调用它,配置里的 shellDir 也不会被它悄悄填上。
|
|
337
|
-
*/
|
|
338
|
-
function detectShellDir() {
|
|
339
|
-
const home = homedir()
|
|
340
|
-
const roots = [
|
|
341
|
-
path.join(home, 'NapCat'),
|
|
342
|
-
path.join(home, 'Documents', 'NapCat'),
|
|
343
|
-
path.join(home, 'Desktop', 'NapCat'),
|
|
344
|
-
path.join(home, 'Downloads', 'NapCat'),
|
|
345
|
-
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'NapCat') : '',
|
|
346
|
-
process.env.ProgramData ? path.join(process.env.ProgramData, 'NapCat') : '',
|
|
347
|
-
process.env.ProgramFiles ? path.join(process.env.ProgramFiles, 'NapCat') : '',
|
|
348
|
-
'C:\\Program Files (x86)\\NapCat',
|
|
349
|
-
].filter(Boolean)
|
|
350
|
-
for (const drive of listDrives()) {
|
|
351
|
-
for (const sub of ['NapCat', 'Tools\\NapCat', 'Soft\\NapCat', 'Program Files\\NapCat', 'Program Files (x86)\\NapCat']) {
|
|
352
|
-
roots.push(`${drive}\\${sub}`)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
for (const root of roots) {
|
|
356
|
-
const out = []
|
|
357
|
-
try {
|
|
358
|
-
if (!statSync(root).isDirectory()) continue
|
|
359
|
-
} catch {
|
|
360
|
-
continue
|
|
361
|
-
}
|
|
362
|
-
findShellDirs(root, 3, out)
|
|
363
|
-
if (out.length) {
|
|
364
|
-
// 有多个就挑路径最短(=层级最浅)的那个
|
|
365
|
-
out.sort((a, b) => a.length - b.length)
|
|
366
|
-
return out[0].replace(/\\/g, '/')
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return ''
|
|
370
|
-
}
|
|
371
|
-
|
|
372
416
|
/* ---- 分盘深扫:用户点「扫 C 盘 / 扫 D 盘」,边扫边报进度 ------------- */
|
|
373
417
|
/**
|
|
374
418
|
* 为什么要有这个:常见位置只有那么几个,装到「D:\Games\NapCat\OneKey\NapCat.x.Shell」
|
|
@@ -582,7 +626,8 @@ async function startScan(cfg, driveSpec) {
|
|
|
582
626
|
}
|
|
583
627
|
}
|
|
584
628
|
|
|
585
|
-
/** 在 shellDir 里挑启动脚本:先挑「写死了这个 QQ 号」的,其次 quick,其次 napcat*.bat */
|
|
629
|
+
/** 在 shellDir 里挑启动脚本:先挑「写死了这个 QQ 号」的,其次 quick,其次 napcat*.bat */
|
|
630
|
+
function detectLauncher(shellDir, account = '') {
|
|
586
631
|
if (!shellDir) return ''
|
|
587
632
|
let files = []
|
|
588
633
|
try {
|
|
@@ -661,18 +706,22 @@ function normalizeConfig(cfg) {
|
|
|
661
706
|
|
|
662
707
|
function resolveConfig(raw) {
|
|
663
708
|
const home = dshHome()
|
|
709
|
+
const dir = keeperDir()
|
|
710
|
+
// 老版本的东西摊在 ~/.dsh 根上,先搬进 dir 再读配置(否则读的是搬走前的那份)
|
|
711
|
+
migrateLegacyFiles(dir)
|
|
664
712
|
// 优先级:内置默认 < cordis 里的 config < 设置界面存下来的 json
|
|
665
|
-
const cfg = { ...DEFAULTS, ...(raw || {}), ...readConfigFile(
|
|
713
|
+
const cfg = { ...DEFAULTS, ...(raw || {}), ...readConfigFile(dir) }
|
|
666
714
|
normalizeConfig(cfg)
|
|
667
715
|
// v2.2.2:shellDir 不再自动探测/自动填,空着就等用户在设置页里扫盘选或者手填
|
|
668
716
|
if (!cfg.launcherScript) cfg.launcherScript = detectLauncher(cfg.shellDir, cfg.launchAccount)
|
|
669
|
-
if (!cfg.logPath) cfg.logPath = path.join(
|
|
670
|
-
if (!cfg.launcherLog) cfg.launcherLog = path.join(
|
|
671
|
-
if (!cfg.lockPath) cfg.lockPath = path.join(
|
|
672
|
-
if (!cfg.statePath) cfg.statePath = path.join(
|
|
673
|
-
if (!cfg.qrCopyPath) cfg.qrCopyPath = path.join(
|
|
717
|
+
if (!cfg.logPath) cfg.logPath = path.join(dir, 'keeper.log')
|
|
718
|
+
if (!cfg.launcherLog) cfg.launcherLog = path.join(dir, 'launch.log')
|
|
719
|
+
if (!cfg.lockPath) cfg.lockPath = path.join(dir, 'lock.json')
|
|
720
|
+
if (!cfg.statePath) cfg.statePath = path.join(dir, 'state.json')
|
|
721
|
+
if (!cfg.qrCopyPath) cfg.qrCopyPath = path.join(dir, 'qr.png')
|
|
722
|
+
// qq-bridge-state.json 是 dsh-qq-bridge 写的,不归我们管,留在 ~/.dsh 根上
|
|
674
723
|
if (!cfg.bridgeStatePath) cfg.bridgeStatePath = path.join(home, 'qq-bridge-state.json')
|
|
675
|
-
if (!cfg.configPath) cfg.configPath = configFilePath(
|
|
724
|
+
if (!cfg.configPath) cfg.configPath = configFilePath(dir)
|
|
676
725
|
return cfg
|
|
677
726
|
}
|
|
678
727
|
|
|
@@ -804,11 +853,6 @@ async function probe(cfg) {
|
|
|
804
853
|
/* 进程链 / 二维码 / 桥接新鲜度 */
|
|
805
854
|
/* ------------------------------------------------------------------ */
|
|
806
855
|
|
|
807
|
-
function napcatRoot(cfg) {
|
|
808
|
-
// .../NapCat/OneKey/NapCat.52230.Shell -> .../NapCat
|
|
809
|
-
return path.dirname(path.dirname(cfg.shellDir))
|
|
810
|
-
}
|
|
811
|
-
|
|
812
856
|
const PS_PROCS =
|
|
813
857
|
"$ErrorActionPreference='SilentlyContinue';" +
|
|
814
858
|
// node 也要算:无头版(Node 包)只有 node.exe 一条进程,没有 QQ.exe
|
|
@@ -824,7 +868,7 @@ const PS_PROCS =
|
|
|
824
868
|
*/
|
|
825
869
|
async function probeProcs(cfg) {
|
|
826
870
|
const root = procRoot(cfg)
|
|
827
|
-
const out = await
|
|
871
|
+
const out = await runPs(PS_PROCS, 15000)
|
|
828
872
|
const result = { boot: 0, qq: 0, chains: 0, dup: false, newestStartMs: 0, chainStarts: [], pids: [], source: 'ps' }
|
|
829
873
|
let list = []
|
|
830
874
|
if (out && out.trim()) {
|
|
@@ -1010,7 +1054,7 @@ function taskReady(cfg) {
|
|
|
1010
1054
|
return state.taskReady
|
|
1011
1055
|
}
|
|
1012
1056
|
|
|
1013
|
-
/** 找启动脚本:配置的绝对/相对路径 →
|
|
1057
|
+
/** 找启动脚本:配置的绝对/相对路径 → 在 shellDir 里挑 → 用 QQ 号现生成一个 */
|
|
1014
1058
|
function resolveLauncherPath(cfg) {
|
|
1015
1059
|
const raw = String(cfg.launcherScript || '').trim()
|
|
1016
1060
|
if (raw) {
|
|
@@ -1022,7 +1066,7 @@ function resolveLauncherPath(cfg) {
|
|
|
1022
1066
|
if (standaloneMjs) {
|
|
1023
1067
|
const node = resolveNode(cfg)
|
|
1024
1068
|
if (node.path) {
|
|
1025
|
-
const file = path.join(
|
|
1069
|
+
const file = path.join(keeperDir(), 'autogen-node.bat')
|
|
1026
1070
|
const args = String(cfg.launchArgs || '').trim()
|
|
1027
1071
|
const body =
|
|
1028
1072
|
'@echo off\r\n' +
|
|
@@ -1050,7 +1094,7 @@ function resolveLauncherPath(cfg) {
|
|
|
1050
1094
|
// 用户只填了 QQ 号:直接调 NapCatWinBootMain.exe,不依赖别人的 quick bat
|
|
1051
1095
|
const boot = findBootMain(cfg.shellDir)
|
|
1052
1096
|
if (boot) {
|
|
1053
|
-
const file = path.join(
|
|
1097
|
+
const file = path.join(keeperDir(), 'autogen.bat')
|
|
1054
1098
|
const body =
|
|
1055
1099
|
'@echo off\r\n' +
|
|
1056
1100
|
'chcp 65001 >nul\r\n' +
|
|
@@ -1252,16 +1296,16 @@ function findStandaloneNapcatMjs(dir) {
|
|
|
1252
1296
|
return ''
|
|
1253
1297
|
}
|
|
1254
1298
|
|
|
1255
|
-
/** 无窗口壳 .vbs
|
|
1299
|
+
/** 无窗口壳 .vbs 的路径(跟日志放同一个目录) */
|
|
1256
1300
|
function taskWrapperPath(cfg) {
|
|
1257
|
-
const dir = path.dirname(cfg.launcherLog || path.join(
|
|
1258
|
-
return path.join(dir, '
|
|
1301
|
+
const dir = path.dirname(cfg.launcherLog || path.join(keeperDir(), 'launch.log'))
|
|
1302
|
+
return path.join(dir, 'launch.vbs')
|
|
1259
1303
|
}
|
|
1260
1304
|
|
|
1261
1305
|
/** 「记住密码」时生成的那个 bat:设好环境变量,再 call 真正的启动脚本 */
|
|
1262
1306
|
function envBatPath(cfg) {
|
|
1263
|
-
const dir = path.dirname(cfg.launcherLog || path.join(
|
|
1264
|
-
return path.join(dir, '
|
|
1307
|
+
const dir = path.dirname(cfg.launcherLog || path.join(keeperDir(), 'launch.log'))
|
|
1308
|
+
return path.join(dir, 'env.bat')
|
|
1265
1309
|
}
|
|
1266
1310
|
|
|
1267
1311
|
/** 密码是否真的在生效(三个条件缺一不可) */
|
|
@@ -1414,10 +1458,55 @@ function ensureTask(cfg) {
|
|
|
1414
1458
|
`$t=Get-ScheduledTask -TaskName '${cfg.taskName.replace(/'/g, "''")}';` +
|
|
1415
1459
|
`if($t){$s=$t.Settings;$s.ExecutionTimeLimit='PT0S';$s.DisallowStartIfOnBatteries=$false;` +
|
|
1416
1460
|
`$s.StopIfGoingOnBatteries=$false;Set-ScheduledTask -TaskName '${cfg.taskName.replace(/'/g, "''")}' -Settings $s | Out-Null}`
|
|
1417
|
-
void
|
|
1461
|
+
void runPs(ps)
|
|
1418
1462
|
return { ok: true, created: true }
|
|
1419
1463
|
}
|
|
1420
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* 计划任务的动作是写死的绝对路径(wscript.exe "<vbs>")。文件搬了家、或者日志目录换了,
|
|
1467
|
+
* 任务就指向一个不存在的脚本 —— 开机静默失败,人还以为是别的原因。
|
|
1468
|
+
*
|
|
1469
|
+
* 这里只在「动作里的路径已经不是当前该用的那个」时改一次,用 /change 就地改:
|
|
1470
|
+
* 不动 /end,正在跑的那条 QQ 进程链不会被踢下来。
|
|
1471
|
+
*/
|
|
1472
|
+
function repairTaskActions(cfg) {
|
|
1473
|
+
if (!cfg.taskName) return { ok: true, skipped: true }
|
|
1474
|
+
if (state.taskActionsChecked) return { ok: true, skipped: true }
|
|
1475
|
+
let xml = ''
|
|
1476
|
+
try {
|
|
1477
|
+
xml = captureSync('schtasks.exe', ['/query', '/tn', cfg.taskName, '/xml'], 8000)
|
|
1478
|
+
} catch {
|
|
1479
|
+
xml = ''
|
|
1480
|
+
}
|
|
1481
|
+
// 任务不存在(或查不动):不用管,真要建是 ensureTask 的活
|
|
1482
|
+
if (!xml || !xml.trim()) return { ok: true, skipped: true }
|
|
1483
|
+
const bat = resolveLauncherPath(cfg)
|
|
1484
|
+
if (!bat) return { ok: true, skipped: true }
|
|
1485
|
+
const wanted = cfg.hideConsole !== false ? taskWrapperPath(cfg) : passwordActive(cfg) ? envBatPath(cfg) : bat
|
|
1486
|
+
state.taskActionsChecked = true
|
|
1487
|
+
if (xml.includes(wanted)) return { ok: true, skipped: true }
|
|
1488
|
+
try {
|
|
1489
|
+
let inner = ''
|
|
1490
|
+
if (cfg.hideConsole !== false) {
|
|
1491
|
+
const vbs = writeTaskWrapper(cfg, bat, true)
|
|
1492
|
+
inner = `wscript.exe "${vbs}"`
|
|
1493
|
+
} else {
|
|
1494
|
+
inner = taskCommandLine(cfg, bat)
|
|
1495
|
+
}
|
|
1496
|
+
execFileSync('schtasks.exe', ['/change', '/tn', cfg.taskName, '/tr', inner], {
|
|
1497
|
+
stdio: 'ignore',
|
|
1498
|
+
windowsHide: true,
|
|
1499
|
+
timeout: 15000,
|
|
1500
|
+
})
|
|
1501
|
+
state.taskReady = undefined
|
|
1502
|
+
log(cfg, `task "${cfg.taskName}" 动作已改指到 ${wanted}`)
|
|
1503
|
+
return { ok: true, repaired: true }
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
state.taskActionsChecked = false
|
|
1506
|
+
return { ok: false, error: String((error && error.message) || error) }
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1421
1510
|
/* ── 提权交接:插件不自己去要管理员,改成给你一个双击就行的脚本 ──────────────
|
|
1422
1511
|
*
|
|
1423
1512
|
* 为什么要这样:DSH 自己是普通权限进程。插件偶尔要做几件「可能要管理员」的事
|
|
@@ -1433,7 +1522,7 @@ function ensureTask(cfg) {
|
|
|
1433
1522
|
* 4. 每步结果写 result.json,设置页读回来说清楚哪条成了、哪条没成;
|
|
1434
1523
|
* 5. 另外给一个 undo-admin.cmd,反着把插件建过的东西删掉。
|
|
1435
1524
|
*
|
|
1436
|
-
* 所有产物只在 ~/.dsh/napcat-keeper
|
|
1525
|
+
* 所有产物只在 ~/.dsh/dsh-napcat-keeper/setup 一个目录里,整个删掉就等于撤回。
|
|
1437
1526
|
*/
|
|
1438
1527
|
|
|
1439
1528
|
const SETUP_NEEDS = {
|
|
@@ -1456,7 +1545,8 @@ const SETUP_NEEDS = {
|
|
|
1456
1545
|
}
|
|
1457
1546
|
|
|
1458
1547
|
function setupPaths() {
|
|
1459
|
-
const dir = path.join(
|
|
1548
|
+
const dir = path.join(keeperDir(), 'setup')
|
|
1549
|
+
migrateLegacyFiles()
|
|
1460
1550
|
return {
|
|
1461
1551
|
dir,
|
|
1462
1552
|
ps1: path.join(dir, 'apply.ps1'),
|
|
@@ -1633,7 +1723,7 @@ function buildSetupSteps(cfg, wants) {
|
|
|
1633
1723
|
].join('\r\n')
|
|
1634
1724
|
steps.push({
|
|
1635
1725
|
id: 'task-wrapper',
|
|
1636
|
-
label: '写无窗口壳
|
|
1726
|
+
label: '写无窗口壳 launch.vbs',
|
|
1637
1727
|
kind: 'writeFile',
|
|
1638
1728
|
path: vbs,
|
|
1639
1729
|
admin: !dirWritable(path.dirname(vbs)),
|
|
@@ -2073,7 +2163,7 @@ function hideConsoleWindows(cfg) {
|
|
|
2073
2163
|
'[void][K.U]::EnumWindows($cb, [IntPtr]::Zero)',
|
|
2074
2164
|
'Write-Output ("hidden=" + $global:ktHidden + " targets=" + ($global:ktTargets -join ","))',
|
|
2075
2165
|
].join('\n')
|
|
2076
|
-
const out =
|
|
2166
|
+
const out = runPs(ps)
|
|
2077
2167
|
const hidden = /hidden=(\d+)/.exec(String(out || ''))
|
|
2078
2168
|
const n = hidden ? Number(hidden[1]) : 0
|
|
2079
2169
|
log(cfg, `hide console: ${n} 个窗口收起`)
|
|
@@ -2185,7 +2275,7 @@ async function killNewerChains(cfg, oldestLastMs) {
|
|
|
2185
2275
|
'Get-Process QQ,NapCatWinBootMain,node | ' +
|
|
2186
2276
|
`Where-Object { $_.Path -like '${root}*' -and $_.StartTime -gt $cut } | ` +
|
|
2187
2277
|
'ForEach-Object { try { Stop-Process -Id $_.Id -Force -ErrorAction Stop } catch {} }'
|
|
2188
|
-
await
|
|
2278
|
+
await runPs(script)
|
|
2189
2279
|
}
|
|
2190
2280
|
|
|
2191
2281
|
/** 杀掉 root 下所有 QQ / NapCatWinBootMain;返回是否真的清干净(没清干净就别拉新链) */
|
|
@@ -2200,7 +2290,7 @@ async function killNapcat(cfg) {
|
|
|
2200
2290
|
"Get-Process QQ,NapCatWinBootMain,node -ErrorAction SilentlyContinue | " +
|
|
2201
2291
|
`Where-Object { $_.Path -like '${root}*' } | ` +
|
|
2202
2292
|
'ForEach-Object { try { Stop-Process -Id $_.Id -Force -ErrorAction Stop } catch {} }'
|
|
2203
|
-
await
|
|
2293
|
+
await runPs(script)
|
|
2204
2294
|
// 等进程真的消失,别在旧客户端还没退干净时就拉新的(这就是互踢的根源)
|
|
2205
2295
|
for (let i = 0; i < 20; i += 1) {
|
|
2206
2296
|
const procs = await probeProcs(cfg)
|
|
@@ -2211,7 +2301,7 @@ async function killNapcat(cfg) {
|
|
|
2211
2301
|
let final = await probeProcs(cfg)
|
|
2212
2302
|
for (let round = 0; round < 2 && (final.boot > 0 || final.qq > 0); round += 1) {
|
|
2213
2303
|
log(cfg, `KILL round ${round + 1}: 仍有 boot=${final.boot} qq=${final.qq},再清一遍`)
|
|
2214
|
-
await
|
|
2304
|
+
await runPs(script)
|
|
2215
2305
|
await sleep(1500)
|
|
2216
2306
|
final = await probeProcs(cfg)
|
|
2217
2307
|
}
|
|
@@ -3264,6 +3354,13 @@ export function apply(ctx, config) {
|
|
|
3264
3354
|
`scanTimeout=${cfg.scanTimeoutSec}s, shell=${cfg.shellDir || '(未配置)'}, task=${cfg.taskName || '(不用)'}, ` +
|
|
3265
3355
|
`node=${node.path ? `${node.version || '?'}(${node.source})` : '(没找到)'})`,
|
|
3266
3356
|
)
|
|
3357
|
+
if (lastMigrated) {
|
|
3358
|
+
log(cfg, `已把 ${lastMigrated} 个文件从 ~/.dsh 根上搬进 ${keeperDir()}(老版本的散装布局)`)
|
|
3359
|
+
}
|
|
3360
|
+
if (cfg.taskName) {
|
|
3361
|
+
const repaired = repairTaskActions(cfg)
|
|
3362
|
+
if (repaired.ok && repaired.repaired) log(cfg, '计划任务已跟着文件位置改好')
|
|
3363
|
+
}
|
|
3267
3364
|
registerTools(ctx, cfg)
|
|
3268
3365
|
registerRoutes(ctx)
|
|
3269
3366
|
armTimer(cfg)
|
|
@@ -3282,7 +3379,6 @@ export const __internals = {
|
|
|
3282
3379
|
resolveConfig,
|
|
3283
3380
|
normalizeConfig,
|
|
3284
3381
|
doAction,
|
|
3285
|
-
detectShellDir,
|
|
3286
3382
|
detectLauncher,
|
|
3287
3383
|
resolveLauncherPath,
|
|
3288
3384
|
resolveNode,
|
package/package.json
CHANGED
|
@@ -24,7 +24,7 @@ writeFileSync(path.join(fakeShell, 'NapCatWinBootMain.exe'), '', 'utf8')
|
|
|
24
24
|
const cfg = internals.resolveConfig({})
|
|
25
25
|
cfg.shellDir = fakeShell
|
|
26
26
|
cfg.launcherScript = 'napcat.quick2.bat'
|
|
27
|
-
cfg.launcherLog = path.join(fakeLogDir, '
|
|
27
|
+
cfg.launcherLog = path.join(fakeLogDir, 'launch.log')
|
|
28
28
|
cfg.taskName = 'NapCat-Keeper-Selftest'
|
|
29
29
|
cfg.launchAccount = '10001'
|
|
30
30
|
cfg.launchArgs = ''
|
|
@@ -139,10 +139,10 @@ if (first) {
|
|
|
139
139
|
ok(first.results.length === 4, '4 条结果')
|
|
140
140
|
}
|
|
141
141
|
ok(existsSync(path.join(fakeShell, 'napcat.quick.auto.bat')), 'launcher: 启动脚本写出来了')
|
|
142
|
-
ok(existsSync(path.join(fakeLogDir, '
|
|
143
|
-
ok(existsSync(path.join(fakeLogDir, '
|
|
142
|
+
ok(existsSync(path.join(fakeLogDir, 'env.bat')), 'envbat: 中间层写出来了')
|
|
143
|
+
ok(existsSync(path.join(fakeLogDir, 'launch.vbs')), 'task-wrapper: vbs 写出来了')
|
|
144
144
|
|
|
145
|
-
const envBat = readFileSync(path.join(fakeLogDir, '
|
|
145
|
+
const envBat = readFileSync(path.join(fakeLogDir, 'env.bat'), 'utf8')
|
|
146
146
|
ok(envBat.includes('NAPCAT_QUICK_PASSWORD=selftest-pass-123'), 'env bat 里带密码(这是它的用途)')
|
|
147
147
|
ok(envBat.includes('call "'), 'env bat 会 call 真启动脚本')
|
|
148
148
|
|
|
@@ -186,8 +186,8 @@ const undone = internals.readSetupResult()
|
|
|
186
186
|
ok(undone && undone.mode === 'undo', 'result 里 mode = undo')
|
|
187
187
|
ok(undone && undone.ok === true, '撤回每步都成', JSON.stringify(undone && undone.results.filter((r) => !r.ok)))
|
|
188
188
|
ok(!existsSync(path.join(fakeShell, 'napcat.quick.auto.bat')), '启动脚本删掉了')
|
|
189
|
-
ok(!existsSync(path.join(fakeLogDir, '
|
|
190
|
-
ok(!existsSync(path.join(fakeLogDir, '
|
|
189
|
+
ok(!existsSync(path.join(fakeLogDir, 'env.bat')), '中间层删掉了(密码不留在磁盘)')
|
|
190
|
+
ok(!existsSync(path.join(fakeLogDir, 'launch.vbs')), 'vbs 删掉了')
|
|
191
191
|
try {
|
|
192
192
|
execFileSync('schtasks.exe', ['/query', '/tn', cfg.taskName], { stdio: 'ignore', windowsHide: true })
|
|
193
193
|
ok(false, '计划任务还在(应该删掉)')
|
package/scripts/smoke-client.mjs
CHANGED
|
@@ -60,9 +60,9 @@ function collect(tree) {
|
|
|
60
60
|
render(); // 第一次:态=null 的 loading 分支
|
|
61
61
|
states[0] = {
|
|
62
62
|
ok: true,
|
|
63
|
-
version: '2.
|
|
63
|
+
version: '2.7.0',
|
|
64
64
|
config: {
|
|
65
|
-
configPath: 'C:/x/napcat-keeper
|
|
65
|
+
configPath: 'C:/Users/x/.dsh/dsh-napcat-keeper/config.json', logPath: 'C:/Users/x/.dsh/dsh-napcat-keeper/keeper.log',
|
|
66
66
|
shellDir: 'C:/Users/x/NapCat/OneKey/NapCat.52230.Shell', launcherScript: 'napcat.quick2.bat',
|
|
67
67
|
launchAccount: '1000000000', launchArgs: '', onebotPort: 3000, accessToken: '',
|
|
68
68
|
taskName: 'NapCat-Keeper-Launch', rememberPassword: true, autoCreateTask: true,
|
|
@@ -78,10 +78,10 @@ states[0] = {
|
|
|
78
78
|
scan: { running: false, found: [], drives: ['C:'], visited: 0, elapsedMs: 0, limitHit: false, cancelled: false },
|
|
79
79
|
drives: ['C:', 'D:', 'E:'],
|
|
80
80
|
setup: {
|
|
81
|
-
dir: 'C:/Users/x/.dsh/napcat-keeper
|
|
82
|
-
cmdPath: 'C:/Users/x/.dsh/napcat-keeper
|
|
83
|
-
ps1Path: 'C:/Users/x/.dsh/napcat-keeper
|
|
84
|
-
undoCmdPath: 'C:/Users/x/.dsh/napcat-keeper
|
|
81
|
+
dir: 'C:/Users/x/.dsh/dsh-napcat-keeper/setup',
|
|
82
|
+
cmdPath: 'C:/Users/x/.dsh/dsh-napcat-keeper/setup/apply-admin.cmd',
|
|
83
|
+
ps1Path: 'C:/Users/x/.dsh/dsh-napcat-keeper/setup/apply.ps1',
|
|
84
|
+
undoCmdPath: 'C:/Users/x/.dsh/dsh-napcat-keeper/setup/undo-admin.cmd',
|
|
85
85
|
exists: true,
|
|
86
86
|
needs: [
|
|
87
87
|
{ id: 'launcher', label: '自动启动脚本', hint: '补一个 napcat.quick.auto.bat', ready: true, why: '', needAdmin: false },
|
|
@@ -179,7 +179,7 @@ check('显示上次生成 2 步', /上次生成:.*2 步/.test(setupOut.text));
|
|
|
179
179
|
check('显示上次执行 1/2 步成', /执行:1\/2 步成/.test(setupOut.text));
|
|
180
180
|
check('显示用了管理员', setupOut.text.includes('用了管理员'));
|
|
181
181
|
check('失败项有 ✗', setupOut.text.includes('✗ 放行防火墙:没权限'));
|
|
182
|
-
check('显示脚本目录', setupOut.text.includes('napcat-keeper
|
|
182
|
+
check('显示脚本目录', setupOut.text.includes('dsh-napcat-keeper'));
|
|
183
183
|
|
|
184
184
|
// 没生成过 / 还没跑过
|
|
185
185
|
const keepSetup = states[0].setup;
|