dsh-napcat-keeper 2.7.3 → 2.8.1
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 +31 -0
- package/README.md +79 -6
- package/lib/client.js +106 -9
- package/lib/index.js +917 -21
- package/package.json +3 -1
- package/scripts/selftest-companion.mjs +488 -0
- package/scripts/selftest-probe.mjs +191 -0
- package/scripts/smoke-client.mjs +95 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.8.1 — 认得出两个共存插件(dsh-onebot-qq 跟它的端口,dsh-qq-bridge 替它对端口)
|
|
4
|
+
- 2.8.0 解决了「反向 WS 下不再误判离线、不再无限重启」,但端口还是得你自己抄:`dsh-onebot-qq` 默认监听 `8643`,你 `onebotPort` 里填的却是 `3000`,那就一直「没客户端连进来」。
|
|
5
|
+
- 现在会读 `dsh-onebot-qq` 自己的配置:扫 `~/.dsh/profiles/*` 的 `package.json`(`dependencies` + `dsh.profile.bundles`)和 `cordis.patch.yml` / `cordis.yml` 里它那一段,得出「装没装 / 挂没挂 / 反向还是正向 / 监听哪个端口 / 有没有配令牌」。不认识 `!!js` 这类表达式,认不出的宁可当没配。
|
|
6
|
+
- **跟随(`companionFollow`,默认开)**:你填的端口**不通**、而它确实挂着反向 WS 时,自动改看它那个端口 —— 状态条写「端口 N(跟随 dsh-onebot-qq)」,日志说明「你填的 X 上没有服务,已跟随 Y」。你自己填的端口一旦通了,它绝不插手。
|
|
7
|
+
- 三种情况**不跟**,并在提示里说明为什么:它没挂进 profile(端口根本不会开)、它是正向模式(它自己去连 NapCat,不监听端口)、你把「跟随」关了。
|
|
8
|
+
- 设置页多一行「共存插件」:`dsh-onebot-qq 0.9.6 · 已启用 · 反向 WS :8643 · 令牌一致`;没装就写「没检测到」;它配了令牌而你这边填得不一样,会标 ⚠ 不一致。
|
|
9
|
+
- **新:把 `dsh-qq-bridge` 也认进来了**。它是「客户端」(拿 HTTP 去连 NapCat,再把消息接进 QQ),跟 `dsh-onebot-qq`(提供接口)正好相反,最容易崩的就是端口对不上。现在会读它的配置 —— 注意它**不在 profile 里**,摊在 `~/.dsh/qq-bridge-config.json`(中文键名 `NapCat` / `总开关` / `试运行` / `令牌` / `绑定`)。
|
|
10
|
+
- 交叉检查五种结论写进设置页新的「桥接插件」行 + `napcat_status`:**装了但没挂进 profile**(只在 `dependencies` 里,桥接根本没跑)/ **连的是反向 WS 端口**(HTTP 客户端敲 WebSocket 端口,必然 426 或 401)/ **端口对不上**(桥接端口 ≠ 本插件看到的 OneBot 端口)/ **总开关关着**(在跑但一句不发)/ **试运行中**(只记账)。全对得上就什么都不说。
|
|
11
|
+
- 这层检查只看配置,不碰桥接的进程、不改它的文件,纯提示。
|
|
12
|
+
- 两个插件共用同一套 profile 扫描(`profileHits`):挂载 = 在 `dsh.profile.bundles` 里,或 patch 里出现 `id: <bundle id>`;只装在 `dependencies` 而没进 bundles 的,一律判「装了但没挂」,不会假装它在生效。
|
|
13
|
+
- `napcat_status` 的 `companion` 改成 `{ onebot: …, bridge: … }`(这个字段上一版才加、没发布,直接换结构,不留旧形状)。
|
|
14
|
+
- `selftest:companion` 从 39 项扩到 **66 项**:新增桥接那一段 —— 地址抠端口、读中文配置、挂没挂、五种交叉检查各一条、未挂载优先于端口问题、以及 `probe` 会把检查结果一起带出来。`smoke-client` 加了 8 项桥接渲染断言(没装 / 装了在发 / 连错端口 / 总开关关着),共 52 项。
|
|
15
|
+
- 探测仍然**只走 HTTP + 系统连接表,不做 WebSocket 握手**:它的反向 WS 是「后来者顶掉前人」的,拿真令牌握手会把 NapCat 自己那条挤掉。
|
|
16
|
+
- **改:「跟随 dsh-onebot-qq」这一行不再是个摆设开关**。以前它在任何机器上都画出来、还默认勾着,可多数时候对面压根没挂进 profile,跟了也是白跟。现在它跟着探测结果走 —— 只有检测到 `dsh-onebot-qq` 装了、并且真挂进 profile(= 已启用)时才出现,且默认开;没装或只躺在 `dependencies` 里没挂载,这一行**整行不画**,`resolveConfig` 里值也直接按关算(`companionReady=false`),跟 `probe()` 里「没挂载就不跟」那条前提对齐。
|
|
17
|
+
- 你显式关过一次就一直是关:`resolveConfig` 只在存储/cordis 里**没有**这个键时才套用动态默认值,不会因为你升级或重启又偷偷掰回开。
|
|
18
|
+
- 配套挡住一个隐形的坑:设置页「保存」原本是把整份草稿全量发过去,界面上没画出来的项会带着旧值一起写进 `config.json`。现在只发真有变化的键,`saveAndApply` 那边也加了一道 —— 对面没启用时 `patch` 里就算带着 `companionFollow` 也不落盘,免得把用户以前的选择盖掉。
|
|
19
|
+
- `selftest:companion` 66 → **83 项**(新增 17 项:`companionReady` 三种组合、没装 / 装了没挂 / 挂了 profile 三种场景下的 `companionReady` 与 `companionFollow`、可用时默认开、以及「存了 false / cordis 写 false 都保持关」;`companionPublic` 那份瘦身结果也认;再加 `uiState` 每轮校准的两组断言);`smoke-client` 52 → **56 项**(没装不画这一行、挂了 profile 才画、没启用时不画、共存那行说明开关不会出现)。
|
|
20
|
+
- 面板每轮都来问一次状态,`uiState` 顺手按**最新**探测结果校准这一项:你把 `dsh-onebot-qq` 中途挂进 profile(或者摘掉),那一行立刻跟着出现 / 消失,值也跟着变,不用重启 DSH 等 `apply` 重跑(探测本身有 20 秒缓存,最多慢这么久)。
|
|
21
|
+
- `companionReady` 两种入参都吃:`detectCompanion()` 的完整结果(带 `found`)和 `companionPublic()` 的瘦身版(没有 `found`,只带 `version` / `profile` / `mounted`)—— 面板拿到的正是后面这种,早先只认 `found` 的话会一直判「不可用」。
|
|
22
|
+
- 顺带清掉一处历史脏值:`config.json` 里那条由整包保存写进去的 `"companionFollow": "false"` 已删除(删前备份到 `legacy/config.before-cf-default-fix.json`),所以这台机器一旦启用 `dsh-onebot-qq`,这一行会按新默认值出现并勾上。
|
|
23
|
+
|
|
24
|
+
## 2.8.0 — 认得出反向 WebSocket(和 dsh-onebot-qq 共存)
|
|
25
|
+
- **修的什么**:装了 `dsh-onebot-qq` 这类插件、把 NapCat 配成**反向 WebSocket** 连过来的机器上,本插件永远判「离线」,然后无限重启。原因是探测只用 HTTP 打 `get_login_info`,而反向 WS 的端口根本不吃 HTTP(换来 401 / 426);老代码只看返回的 body、不看 HTTP 状态码,于是把「端口明明在回话」当成「端口不通」→ 卡死判定 → 杀掉重拉 → 循环。
|
|
26
|
+
- 现在 `postOneBot` 会把 HTTP 状态码带回来,`probe()` 据此分清四种情况:能问到 `login_info`(普通 HTTP)、只收 WebSocket(反向 WS)、端口要求鉴权(多半是令牌不对)、端口没开。
|
|
27
|
+
- 新增「在线探测方式」(`probeMode`):`auto`(默认,先按 HTTP 问,问不通再看是不是反向 WS)/ `http` / `reverse` / `process`(只看进程,不碰端口)。设置页「连接」那组里能直接选。
|
|
28
|
+
- 反向 WS 模式下「在线」的判定改成:端口上有**客户端连进来**(`Get-NetTCPConnection` 数客户端那一侧的连接)。关键的一条是**进程在 + 端口有服务就别杀** —— 端口回 401/426 说明服务活着,重拉解决不了任何问题,只会把正在连的那条链踹下去。
|
|
29
|
+
- 真等不来客户端时也不再每轮都杀:先按 `stuckSec` 等,超时才**温和重拉**一次,最多两次,还不成就进 `reverse-giveup`,在日志和状态条里说清楚该去 NapCat 里把反向 WS 地址填成 `ws://127.0.0.1:<端口>`。
|
|
30
|
+
- 顺带加了拉起退避:连续拉不起来时宽限期按 2 的幂放大(最多 8 倍),不会再 45 秒一次地反复拉。
|
|
31
|
+
- 状态条能显示「反向 WS 已连接 / 客户端 N」,端口回 401 时显示「需鉴权」,并把该改哪里写在提示里。
|
|
32
|
+
- 补了 `pnpm selftest:probe`(27 项):本地起假服务器分别模拟正常 OneBot / 426 无客户端 / 426 有客户端 / 401 / 端口没开,另加 `probeMode` 的强制值归一化。全程环回 + 临时端口,不碰真配置。
|
|
33
|
+
|
|
3
34
|
## 2.7.3 — 修「设置页保存不进去」+ README 补「装不上」的两条排障
|
|
4
35
|
- **修了个真 bug,2.7.0 起就有**:设置页点「保存」、「用这个」、或者「清除密码」,值当场弹回原样,什么都没存下来。起因是 2.7.0 把插件自己的文件收进 `~/.dsh/dsh-napcat-keeper/` 时,只把**读取**改成了新目录,**写入**还留在老地方 `~/.dsh/config.json` —— 于是存进一个没人读的文件,界面重新渲染时拿到的还是旧值。2.7.0 / 2.7.1 / 2.7.2 都中招。
|
|
5
36
|
- 现在读和写认同一个目录(`saveAndApply` 与「清除密码」都改走 `keeperDir()`)。
|
package/README.md
CHANGED
|
@@ -193,11 +193,74 @@ QQ 那条进程链挂在本 DSH 实例下面——DSH 开着它就在,DSH 关
|
|
|
193
193
|
|
|
194
194
|
> 顺带一提:`versions\*\resources\app\napcat\napcat.mjs` 那份**不会**被当成入口——它是等 QQ 注入用的,拿普通 Node 直接跑必崩。
|
|
195
195
|
|
|
196
|
+
## 反向 WebSocket:和 dsh-onebot-qq 共存
|
|
197
|
+
|
|
198
|
+
NapCat 连 OneBot 有两条相反的路:
|
|
199
|
+
|
|
200
|
+
- **正向(HTTP / 正向 WS)**:NapCat 自己开端口(`httpServers`),外面去问它 —— 本插件默认就是这么看它的;
|
|
201
|
+
- **反向 WebSocket**:端口由**别人**开着(比如 `dsh-onebot-qq` 插件起的反向 WS 服务,或者你自己写的服务),**NapCat 反过来连进去**。
|
|
202
|
+
|
|
203
|
+
第二条路上没有 HTTP 接口可问:拿 HTTP 去敲那个端口,换来的是 `401` / `426`。2.7.3 及更早的版本只看返回内容、不看 HTTP 状态码,于是把「端口明明在回话」当成「端口不通」→ 判卡死 → 杀掉重拉 → 无限循环。现在不会了:
|
|
204
|
+
|
|
205
|
+
| 探测到的情况 | 插件怎么判 | 会不会重启 |
|
|
206
|
+
| --- | --- | --- |
|
|
207
|
+
| 能问到 `get_login_info` | 在线(普通 HTTP) | 不会 |
|
|
208
|
+
| 端口回 `426`(只收 WebSocket) | **反向 WS 模式** | 不重启;等 NapCat 连进来 |
|
|
209
|
+
| 端口回 `401` | 需鉴权(多半是令牌不对) | 不重启,提示你去对 `accessToken` |
|
|
210
|
+
| 端口开着、也有客户端连进来 | 在线(反向 WS) | 不会 |
|
|
211
|
+
| 端口连不上、进程也不在 | 离线 | 拉起(带退避) |
|
|
212
|
+
|
|
213
|
+
配置上只要两步:
|
|
214
|
+
|
|
215
|
+
1. 把 `onebotPort` 填成**反向 WS 服务监听的端口**(不是 NapCat 里那个 `httpServers` 的端口——反向模式下 NapCat 根本不监听);
|
|
216
|
+
2. 「在线探测方式」留 `auto` 就行;想明确指定就选 `反向 WebSocket`。
|
|
217
|
+
|
|
218
|
+
然后在 NapCat 那边把反向 WS 地址填成 `ws://127.0.0.1:<上面那个端口>`,令牌对上 `accessToken`。
|
|
219
|
+
|
|
220
|
+
### 装了 dsh-onebot-qq 的,端口可以不用自己填
|
|
221
|
+
|
|
222
|
+
从 2.8.x 起,本插件会去读 `dsh-onebot-qq` 自己的配置(profile 的 `package.json`、`cordis.patch.yml`/`cordis.yml` 里它那一段),知道它监听哪个端口、是反向还是正向、令牌配没配。于是:
|
|
223
|
+
|
|
224
|
+
- 你填的 `onebotPort` **能通**,就永远用你的 —— 它不会插嘴;
|
|
225
|
+
- 你填的端口**没人听**,而 `dsh-onebot-qq` 正开着反向 WS,就自动改看它那个端口(默认 `8643`),状态条会写「端口 N(跟随 dsh-onebot-qq)」,日志里也会说明「你填的 X 上没有服务,已跟随 Y」;
|
|
226
|
+
- 它没挂进 profile(只在 `dependencies` 里、没进 `dsh.profile.bundles`、patch 里也没它)→ 端口根本不会开,这时**不跟**,因为跟了也没用;
|
|
227
|
+
- 它是**正向**模式(它自己去连 NapCat,不监听端口)→ 也不跟,照常按你的端口 + `auto` 探;
|
|
228
|
+
- 它配了 `accessToken` 但你这边填的不一样 → 设置页「共存插件」那行会标 ⚠ 令牌不一致。
|
|
229
|
+
|
|
230
|
+
对照开关就是设置页那个「跟随 dsh-onebot-qq」(配置项 `companionFollow`)。它现在**跟着探测结果走**:只有检测到 `dsh-onebot-qq` 装了、并且真挂进 profile(= 已启用)时,这一行才会出现在设置页,且默认开;没装、或者只躺在 `dependencies` 里没挂载(那种情况端口根本不会开),这一行干脆不画,值也按关算 —— 免得上面那句「不跟」全靠猜。你要是自己关过,那就一直按关处理,不会因为它升级/重启又偷偷掰回开。设置页每轮刷新都会重算一遍,所以你中途把它挂上或者摘掉,那一行立刻跟着出现/消失,不用重启 DSH。
|
|
231
|
+
|
|
232
|
+
> 跟着它看端口的前提是它真的在跑。只装在 `dependencies` 里的,设置页「共存插件」那行会写「已安装但没挂进 profile(「跟随」那个开关不会出现)」,先去 profile 里把它挂上再说。
|
|
233
|
+
|
|
234
|
+
> 探测全程只用 HTTP + 系统连接表,**不做 WebSocket 握手**:`dsh-onebot-qq` 的反向 WS 是「后来者顶掉前人」的,拿真令牌去握手会把 NapCat 自己那条连接挤掉。
|
|
235
|
+
|
|
236
|
+
真等不来客户端时,插件也不会每轮都杀进程:先按 `stuckSec`(默认 420 秒)等,超时才**温和重拉**一次,最多两次;还不行就进 `reverse-giveup`,把「该去 NapCat 里改反向 WS 地址」写进日志和状态条,不再折磨那条链。
|
|
237
|
+
|
|
238
|
+
> 同一个端口没法既当 HTTP 接口又当反向 WS 服务 —— 谁先占着就听谁的。两样都想要,就给它们分两个端口。
|
|
239
|
+
|
|
240
|
+
### 装了 dsh-qq-bridge 的,插件会替你把端口对一遍
|
|
241
|
+
|
|
242
|
+
`dsh-onebot-qq` 提供的是**接口**(端口由它开,NapCat 连进去);`dsh-qq-bridge` 是**客户端**(它反过来拿 HTTP/WS 去连 NapCat,再把消息接到 QQ 聊天里)。两者对端口的要求正好相反,最容易崩的地方就一句话:**桥接里填的 `NapCat` 地址,必须是 NapCat 自己开着的那个 OneBot 端口。**
|
|
243
|
+
|
|
244
|
+
本插件会顺手读桥接的配置(`~/.dsh/qq-bridge-config.json`,它的配置不在 profile 里、摊在 `~/.dsh` 根上),然后拿自己看到的实际情况跟它比一比,结论写在设置页「桥接插件」那行:
|
|
245
|
+
|
|
246
|
+
| 看到的情况 | 结论 |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| 只在 `dependencies` 里、没进 `dsh.profile.bundles` | **装了但没挂进 profile** —— 桥接根本没跑 |
|
|
249
|
+
| 桥接指向的就是那个**反向 WS** 端口 | **连的是反向 WS 端口** —— 那里只收 WebSocket 握手,HTTP 客户端敲过去必然 426/401 |
|
|
250
|
+
| 桥接端口 ≠ 本插件看到的 OneBot 端口 | **端口对不上** —— 消息发不出去 |
|
|
251
|
+
| 桥接「总开关」是关的 | 在跑,但一句都不发 |
|
|
252
|
+
| 桥接开着「试运行」 | 只记账,不真发到 QQ |
|
|
253
|
+
| 以上都对得上 | 不吭声(对得上就没什么可说的) |
|
|
254
|
+
|
|
255
|
+
这层检查只看**配置**,不碰桥接的进程、不改它的文件,纯提示。想让它真发消息,还是得去桥接自己的设置页把总开关打开、把地址填对。
|
|
256
|
+
|
|
257
|
+
> 典型的踩坑组合:`dsh-onebot-qq` 开着反向 WS 占着 `8643`,桥接的 `NapCat` 又填成 `http://127.0.0.1:8643` —— 看着「端口明明通」,桥接就是一直不动。给 NapCat 另开一个 `httpServers` 端口(比如 `3000`)让桥接连,两边就都活了。
|
|
258
|
+
|
|
196
259
|
## 给 agent 用的三个工具
|
|
197
260
|
|
|
198
261
|
| 工具 | 作用 |
|
|
199
262
|
| --- | --- |
|
|
200
|
-
| `napcat_status` |
|
|
263
|
+
| `napcat_status` | 端口/登录态、进程链、二维码副本路径、桥接心跳、日志尾部,以及两个共存插件的状态(`dsh-onebot-qq` 装了没/挂了没/端口模式/令牌一致不,`dsh-qq-bridge` 装了没/挂了没/桥接端口对不对) |
|
|
201
264
|
| `napcat_restart` | 干净地杀掉整条链并重拉,然后等它上线 |
|
|
202
265
|
| `napcat_dedupe` | 只在真有重复链时才杀(没重复就什么都不做) |
|
|
203
266
|
|
|
@@ -213,8 +276,10 @@ QQ 那条进程链挂在本 DSH 实例下面——DSH 开着它就在,DSH 关
|
|
|
213
276
|
| `nodePath` | 空(自动) | 指定 `node.exe`;留空按「DSH 自带 → 系统 PATH → 常见安装目录 → NapCat 目录」找,见上一节 |
|
|
214
277
|
| `rememberPassword` | `false` | 记住 QQ 密码、令牌失效时自动密码登录(**明文落盘,详见上节**) |
|
|
215
278
|
| `quickPassword` | 空 | QQ 密码;只在保存时写入,接口不回显、日志不打印 |
|
|
216
|
-
| `onebotPort` | `3000` | OneBot
|
|
217
|
-
| `accessToken` | 空 | OneBot access token |
|
|
279
|
+
| `onebotPort` | `3000` | OneBot 端口。普通部署填 NapCat 里 `httpServers` 的端口;反向 WebSocket 部署填**反向 WS 服务**监听的端口 |
|
|
280
|
+
| `accessToken` | 空 | OneBot access token(跟 NapCat 或反向 WS 服务里填的一致) |
|
|
281
|
+
| `probeMode` | `auto` | 在线探测方式:`auto` 先按 HTTP 问、问不通再看是不是反向 WS;`http` 只认 HTTP 接口;`reverse` 只认反向 WebSocket;`process` 只看进程不碰端口 |
|
|
282
|
+
| `companionFollow` | `true` | 你填的端口不通时,是否跟随 `dsh-onebot-qq` 配的反向 WS 端口(详见「反向 WebSocket」那节);你自己填的端口一通,它就绝不插手。**这一项只在检测到 `dsh-onebot-qq` 装了且挂进 profile 时才出现、才默认开**,否则设置页不显示、值按关算 |
|
|
218
283
|
| `taskName` | 空(= 用 cscript,QQ 跟着 DSH 生灭) | 想脱离 DSH 才填计划任务名 |
|
|
219
284
|
| `autoCreateTask` | `false` | 填了 `taskName` 且任务不存在时自动创建 |
|
|
220
285
|
| `hideConsole` | `true` | 拉起时不显示黑框(用 wscript 壳藏窗口) |
|
|
@@ -275,7 +340,7 @@ npm run check:privacy # 只查隐私(发版前必跑)
|
|
|
275
340
|
|
|
276
341
|
**隐私体检**是发版前那道闸:把包里所有文本文件过一遍,找 Windows / Unix 家目录路径、邮箱、`ghp_` / `npm_` / `sk-` / `AKIA` 这类凭证、`Bearer` 后面的真串、DSH 会话 id,以及自测里那种 `launchAccount: '1234567890'` 的真实 QQ 号。命中就按 `文件:行` 列出来并返回非 0;确实是文档里必须留的示例,在那行末尾写 `check-privacy:ignore` 放过。占位写法(`C:/Users/你/…`、`C:/Users/x/…`)本来就放行。
|
|
277
342
|
|
|
278
|
-
|
|
343
|
+
另外几个自测,都不依赖本机路径,clone 下来就能跑:`npm run smoke:client`(把 `lib/client.js` 塞进假 React 渲染一遍)、`npm run selftest:scan`(造假树验扫盘判定)、`npm run selftest:save`(保存真的落到插件目录、误写的那份被认领回来)、`npm run selftest:probe`(本地起假服务器,验普通 HTTP / 反向 WS / 需要鉴权 / 端口没开四种判定)、`npm run selftest:companion`(造假 profile 与桥接配置,验 `dsh-onebot-qq` 的端口跟随和 `dsh-qq-bridge` 的交叉结论)、`npm run selftest:setup`(临时目录里真跑一遍 apply / 重跑 / undo)。
|
|
279
344
|
|
|
280
345
|
> **改这个插件的文件的规矩**:别用 `Get-Content` 读、`Set-Content -Encoding utf8` 写 —— PowerShell 5.1 会按 ANSI 读、再按 UTF-8 写,一趟往返中文全变乱码,BOM 还照写。用编辑器另存为「UTF-8 无 BOM」;非要脚本写,就走
|
|
281
346
|
> `[IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))`。
|
|
@@ -295,6 +360,14 @@ npm run check:privacy # 只查隐私(发版前必跑)
|
|
|
295
360
|
**换端口了?**
|
|
296
361
|
改「OneBot HTTP 端口」保存即可;如果同时换了 Shell 目录,跨进程锁会自动清掉。
|
|
297
362
|
|
|
363
|
+
**用着 dsh-onebot-qq(反向 WebSocket),插件永远显示离线、进程反复重启?**
|
|
364
|
+
2.7.3 及更早的版本只会用 HTTP 去问 `get_login_info`,而反向 WS 的端口不吃 HTTP(回 `401` / `426`),
|
|
365
|
+
于是被当成「端口不通」→ 判卡死 → 杀掉重拉,一轮一轮地折腾。
|
|
366
|
+
现在的版本认这条路:端口回 426 就判成**反向 WS 模式**,改成看「端口上有没有客户端连进来」,
|
|
367
|
+
进程在 + 端口有服务时**不再杀**。设置上只要把「OneBot HTTP 端口」改成反向 WS 服务监听的端口,
|
|
368
|
+
「在线探测方式」留 `auto`(或直接选「反向 WebSocket」),细节见前面「反向 WebSocket:和 dsh-onebot-qq 共存」那节。
|
|
369
|
+
状态条里会显示 `探测:反向 WebSocket · 客户端 N`,卡住时给的提示也是「去 NapCat 里把反向 WS 地址填成 `ws://127.0.0.1:<端口>`」。
|
|
370
|
+
|
|
298
371
|
**老是掉线、每次都要扫码?**
|
|
299
372
|
令牌过期了。要么每次手机扫,要么开「记住密码」让它自己用密码登(先看上上节的风险)。
|
|
300
373
|
`napcat_status` 会直接告诉你现在是 `password` 还是 `token` 在撑、以及密码文件在不在。
|
|
@@ -330,13 +403,13 @@ DSH 自带的 pnpm 不捎带 git,Windows 上又默认没装,于是就卡在
|
|
|
330
403
|
| 填进去的 | 结果 |
|
|
331
404
|
| --- | --- |
|
|
332
405
|
| `dsh-napcat-keeper` | ✅ |
|
|
333
|
-
| `dsh-napcat-keeper@2.
|
|
406
|
+
| `dsh-napcat-keeper@2.8.1` | ✅(指定版本) |
|
|
334
407
|
| `dsh plugin --profile desktop add dsh-napcat-keeper` | ❌ 整条命令 |
|
|
335
408
|
| `add dsh-napcat-keeper` | ❌ 多了 `add` |
|
|
336
409
|
| `Dsh-napcat-keeper` | ❌ 大写 |
|
|
337
410
|
| `dsh-napcat-keeper。`、`dsh-napcat-keeper;` | ❌ 全角标点 |
|
|
338
411
|
|
|
339
|
-
改法就一句:**那一栏只填 `dsh-napcat-keeper`**(要指定版本就 `dsh-napcat-keeper@2.
|
|
412
|
+
改法就一句:**那一栏只填 `dsh-napcat-keeper`**(要指定版本就 `dsh-napcat-keeper@2.8.1`)。想跑命令行的话,得在终端里跑,
|
|
340
413
|
不是在输入框里跑。
|
|
341
414
|
|
|
342
415
|
## License
|
package/lib/client.js
CHANGED
|
@@ -126,8 +126,11 @@ window.__ModuleLoader__.load({
|
|
|
126
126
|
['launcherScript', '启动脚本', '留空自动挑(quick 优先,其次 napcat*.bat)', 'text'],
|
|
127
127
|
['nodePath', 'Node 运行时(node.exe)', '留空 = 自动:DSH 自带那份 → 系统 PATH → 常见安装目录 → NapCat 目录。只有「Shell 型」NapCat(目录顶层有 napcat.mjs)才真的用到它;OneKey 注入型跑在 QQ 自己的 Node 里,跟这里无关', 'text'],
|
|
128
128
|
['launchAccount', '要快登的 QQ 号', '填了它、脚本又找不到时,会自动生成一个直接调 NapCatWinBootMain.exe 的脚本', 'text'],
|
|
129
|
-
['onebotPort', 'OneBot HTTP 端口', 'NapCat 里 onebot11 的 httpServers 端口,默认 3000', 'number', true],
|
|
130
|
-
['accessToken', 'OneBot 访问令牌', '没设就留空;设了要跟 NapCat
|
|
129
|
+
['onebotPort', 'OneBot HTTP 端口', 'NapCat 里 onebot11 的 httpServers 端口,默认 3000。反向 WebSocket 模式下填反向 WS 服务占的那个端口(装了 dsh-onebot-qq 的话通常是它监听的那个)', 'number', true],
|
|
130
|
+
['accessToken', 'OneBot 访问令牌', '没设就留空;设了要跟 NapCat(或反向 WS 服务)里填的一致', 'password'],
|
|
131
|
+
['probeMode', '在线探测方式', '自动 = 先按 HTTP 接口问;端口只收 WebSocket 时会认成「反向 WS」。如果端口是 dsh-onebot-qq 这类反向 WS 服务在听(HTTP 换来 401/426、老被判离线),明确选「反向 WebSocket」最省事', 'select', false,
|
|
132
|
+
[['auto', '自动(推荐)'], ['http', '只认 HTTP 接口'], ['reverse', '反向 WebSocket'], ['process', '只看进程']]],
|
|
133
|
+
['companionFollow', '跟随 dsh-onebot-qq', '只在 dsh-onebot-qq 装了、并且真挂进 profile(= 已启用)时才出现这一行,可用时默认开。开着的话:你自己填的端口连不上时,自动改看它的反向 WS 端口(就是它监听的那个);填的端口一旦通了,还是用你的', 'bool'],
|
|
131
134
|
],
|
|
132
135
|
},
|
|
133
136
|
{
|
|
@@ -174,14 +177,78 @@ window.__ModuleLoader__.load({
|
|
|
174
177
|
return obj && Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : ''
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
/** 「共存插件」那行:dsh-onebot-qq 装没装、挂没挂、它监听哪个端口 */
|
|
181
|
+
function 共存描述(runtime) {
|
|
182
|
+
const c = runtime.companion
|
|
183
|
+
if (!c || !c.version) return '没检测到 dsh-onebot-qq(不装也不影响;装了它就会显示在这里)'
|
|
184
|
+
const 状态 = c.mounted ? '已启用' : '已安装但没挂进 profile(「跟随」那个开关不会出现)'
|
|
185
|
+
const 模式 = c.mode === 'reverse' ? '反向 WS :' + String(c.port) : '正向连 ' + String(c.url || '')
|
|
186
|
+
const 令牌 = runtime.companionTokenMatch === true
|
|
187
|
+
? ' · 令牌一致'
|
|
188
|
+
: runtime.companionTokenMatch === false
|
|
189
|
+
? ' · ⚠ 令牌不一致'
|
|
190
|
+
: ''
|
|
191
|
+
return 'dsh-onebot-qq ' + String(c.version) + ' · ' + 状态 + ' · ' + 模式 + 令牌
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 「桥接插件」那行:dsh-qq-bridge 装没装、挂没挂、它连的端口对不对 */
|
|
195
|
+
function 桥接描述(runtime) {
|
|
196
|
+
const b = runtime && runtime.bridgePlugin
|
|
197
|
+
if (!b || !b.version) return '没检测到 dsh-qq-bridge(不装也不影响;装了它就会显示在这里)'
|
|
198
|
+
const 状态 = b.mounted ? '已启用' : '已安装但没挂进 profile'
|
|
199
|
+
const 开关 = !b.configKnown ? '没读到配置' : !b.enabled ? '总开关关着' : b.dryRun ? '试运行中' : '在发'
|
|
200
|
+
const 问题 =
|
|
201
|
+
b.check === 'bridge-hits-reverse'
|
|
202
|
+
? ' · ⚠ 连的是反向 WS 端口'
|
|
203
|
+
: b.check === 'port-mismatch'
|
|
204
|
+
? ' · ⚠ 端口和 NapCat 对不上'
|
|
205
|
+
: ''
|
|
206
|
+
return 'dsh-qq-bridge ' + String(b.version) + ' · ' + 状态 + ' · ' + 开关 + ' · 连 ' + String(b.napcatUrl || '') + 问题
|
|
207
|
+
}
|
|
208
|
+
|
|
177
209
|
function 状态条(runtime) {
|
|
178
210
|
if (!runtime) return null
|
|
179
211
|
const 在 = runtime.online
|
|
212
|
+
// 反向 WS:端口是别人在听(dsh-onebot-qq 之类),NapCat 反过来连进去,
|
|
213
|
+
// 「在线」= 端口上有客户端连进来了,跟 HTTP 那套不是一回事,词得写清楚
|
|
214
|
+
const 反向 = runtime.mode === 'reverse'
|
|
215
|
+
const 模式名 = 反向
|
|
216
|
+
? '反向 WebSocket'
|
|
217
|
+
: runtime.mode === 'auth'
|
|
218
|
+
? '端口要求鉴权'
|
|
219
|
+
: runtime.mode === 'process'
|
|
220
|
+
? '只看进程'
|
|
221
|
+
: runtime.mode === 'http'
|
|
222
|
+
? 'HTTP 接口'
|
|
223
|
+
: '端口不通'
|
|
224
|
+
const 阶段名 = {
|
|
225
|
+
idle: '待机',
|
|
226
|
+
online: '在线',
|
|
227
|
+
launching: '正在拉起',
|
|
228
|
+
starting: '正在启动',
|
|
229
|
+
'need-scan': '等扫码',
|
|
230
|
+
'need-scan-giveup': '扫码放弃',
|
|
231
|
+
'reverse-wait': '等反向 WS 客户端连进来',
|
|
232
|
+
'reverse-giveup': '反向 WS 等不到客户端',
|
|
233
|
+
'need-token': '令牌不对',
|
|
234
|
+
'port-busy': '端口被别的程序占着',
|
|
235
|
+
'dedupe-blocked': '去重没杀干净',
|
|
236
|
+
offline: '离线',
|
|
237
|
+
}[String(runtime.phase || '')] || String(runtime.phase || '?')
|
|
180
238
|
return h('div', { style: S.card },
|
|
181
239
|
h('div', { style: { display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap' } },
|
|
182
|
-
h('span', { style: 在 ? S.ok : S.bad }, 在 ? '● 在线' : '○ 离线'),
|
|
183
|
-
在
|
|
184
|
-
|
|
240
|
+
h('span', { style: 在 ? S.ok : S.bad }, 在 ? (反向 ? '● 反向 WS 已连接' : '● 在线') : '○ 离线'),
|
|
241
|
+
在 && (runtime.nickname || runtime.userId)
|
|
242
|
+
? h('span', null, String(runtime.nickname || '') + '(' + String(runtime.userId || '') + ')')
|
|
243
|
+
: null,
|
|
244
|
+
h('span', { style: S.dim }, '探测:' + 模式名 + (runtime.portStatus ? ' · HTTP ' + String(runtime.portStatus) : '')),
|
|
245
|
+
runtime.portUsed
|
|
246
|
+
? h('span', { style: S.dim }, '端口 ' + String(runtime.portUsed) +
|
|
247
|
+
(runtime.followed ? '(跟随 dsh-onebot-qq)' : ''))
|
|
248
|
+
: null,
|
|
249
|
+
反向 ? h('span', { style: S.dim },
|
|
250
|
+
'客户端 ' + (runtime.clients === null || runtime.clients === undefined ? '查不到' : String(runtime.clients))) : null,
|
|
251
|
+
h('span', { style: S.dim }, '阶段:' + 阶段名),
|
|
185
252
|
h('span', { style: S.dim }, '启动 ' + String(runtime.launchCount || 0) + ' 次'),
|
|
186
253
|
runtime.dedupeCount ? h('span', { style: S.dim }, '去重 ' + String(runtime.dedupeCount) + ' 次') : null,
|
|
187
254
|
),
|
|
@@ -212,8 +279,16 @@ window.__ModuleLoader__.load({
|
|
|
212
279
|
h('span', null, runtime.qr ? '最新一张 ' + String(runtime.qr.ageSec) + ' 秒前' : '还没有(没进扫码态)'),
|
|
213
280
|
h('span', { style: S.dim }, '桥接心跳'),
|
|
214
281
|
h('span', null, runtime.bridge && runtime.bridge.ageSec !== null ? String(runtime.bridge.ageSec) + ' 秒前' : '没读到'),
|
|
282
|
+
h('span', { style: S.dim }, '共存插件'),
|
|
283
|
+
h('span', null, 共存描述(runtime)),
|
|
284
|
+
h('span', { style: S.dim }, '桥接插件'),
|
|
285
|
+
h('span', { style: { wordBreak: 'break-all' } }, 桥接描述(runtime)),
|
|
215
286
|
),
|
|
287
|
+
runtime.bridgePlugin && runtime.bridgePlugin.note
|
|
288
|
+
? h('div', { style: { ...S.dim, marginTop: 4 } }, '桥接提示:' + String(runtime.bridgePlugin.note))
|
|
289
|
+
: null,
|
|
216
290
|
runtime.error ? h('div', { style: { ...S.bad, marginTop: 6 } }, '最近错误:' + String(runtime.error)) : null,
|
|
291
|
+
runtime.hint ? h('div', { style: { ...S.dim, marginTop: 4 } }, '提示:' + String(runtime.hint)) : null,
|
|
217
292
|
)
|
|
218
293
|
}
|
|
219
294
|
|
|
@@ -328,12 +403,27 @@ window.__ModuleLoader__.load({
|
|
|
328
403
|
)
|
|
329
404
|
}
|
|
330
405
|
|
|
331
|
-
|
|
406
|
+
// 「跟随 dsh-onebot-qq」只有在那插件装了、且挂进 profile(= 真启用)时才有意义:
|
|
407
|
+
// 没得跟的时候这一行整行不画,它的值也不参与「有没有改动」和保存。
|
|
408
|
+
const 跟随可开 = Boolean(
|
|
409
|
+
态 && 态.runtime
|
|
410
|
+
&& (态.runtime.companionReady === true || Boolean(态.runtime.companion && 态.runtime.companion.mounted)),
|
|
411
|
+
)
|
|
412
|
+
const 可写键 = (k) => k !== 'companionFollow' || 跟随可开
|
|
413
|
+
const 草键 = 全部键.filter(可写键)
|
|
414
|
+
|
|
415
|
+
const 脏 = Boolean(态 && 草 && 草键.some((k) => String(取(草, k)) !== String(取(态.config, k))))
|
|
332
416
|
|
|
333
417
|
const 存 = async () => {
|
|
334
418
|
if (!草 || 忙) return
|
|
335
419
|
set忙(true)
|
|
336
|
-
|
|
420
|
+
// 只发真有变化的键:界面上没画出来的项(比如这会儿不可用的 companionFollow),
|
|
421
|
+
// 草稿里带的是旧值/假值,整包发过去会把用户以前的选择盖掉。
|
|
422
|
+
const patch = {}
|
|
423
|
+
草键.forEach((k) => {
|
|
424
|
+
if (String(取(草, k)) !== String(取(态 && 态.config, k))) patch[k] = 取(草, k)
|
|
425
|
+
})
|
|
426
|
+
const r = await post('/save', { patch })
|
|
337
427
|
if (r) 装(r)
|
|
338
428
|
set说(r && r.config ? '设置已保存并生效' : '保存失败,看看日志')
|
|
339
429
|
set忙(false)
|
|
@@ -661,7 +751,7 @@ window.__ModuleLoader__.load({
|
|
|
661
751
|
}
|
|
662
752
|
|
|
663
753
|
const 渲染项 = (item) => {
|
|
664
|
-
const [key, label, hint, type] = item
|
|
754
|
+
const [key, label, hint, type, , options] = item
|
|
665
755
|
const val = 取(草, key)
|
|
666
756
|
let ctl
|
|
667
757
|
if (type === 'bool') {
|
|
@@ -672,6 +762,13 @@ window.__ModuleLoader__.load({
|
|
|
672
762
|
onChange: (e) => 改(key, e.target.checked),
|
|
673
763
|
}),
|
|
674
764
|
h('span', null, val !== false && String(val) !== 'false' ? '开' : '关'))
|
|
765
|
+
} else if (type === 'select') {
|
|
766
|
+
ctl = h('select', {
|
|
767
|
+
style: { ...S.input, maxWidth: 260 },
|
|
768
|
+
value: val === null || val === undefined || val === '' ? 'auto' : String(val),
|
|
769
|
+
onChange: (e) => 改(key, e.target.value),
|
|
770
|
+
},
|
|
771
|
+
(options || []).map(([v, text]) => h('option', { key: v, value: v }, text)))
|
|
675
772
|
} else {
|
|
676
773
|
ctl = h('input', {
|
|
677
774
|
style: S.input,
|
|
@@ -723,7 +820,7 @@ window.__ModuleLoader__.load({
|
|
|
723
820
|
: h('div', { key: g.group },
|
|
724
821
|
h('div', { style: S.h }, g.group),
|
|
725
822
|
g.note ? h('div', { style: { ...S.dim, marginBottom: 8 } }, g.note) : null,
|
|
726
|
-
g.items.map(渲染项),
|
|
823
|
+
g.items.filter((it) => it[0] !== 'companionFollow' || 跟随可开).map(渲染项),
|
|
727
824
|
)),
|
|
728
825
|
|
|
729
826
|
h('div', { style: S.h }, '日志'),
|