dsh-napcat-keeper 2.1.2 → 2.5.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/lib/client.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // DSH「napcat桥」· 网页半边
2
2
  //
3
- // 在 **设置 → napcat桥** 里:看状态、填配置、点一下就能探测/拉起/杀掉重拉。
3
+ // 在 **设置 → napcat桥** 里:看状态、填配置、点一下就能拉起/杀掉重拉。
4
4
  // 这里不碰会话、不碰进程,只是往宿主那几条 /dsh-napcat-keeper/* 路由上读写。
5
5
  //
6
- // 对第一次用的人:只要三个空 —— NapCat Shell 目录、QQ 号、OneBot 端口,
7
- // 保存完就能用;其它都能自动探测。
6
+ // 对第一次用的人:NapCat Shell 目录自己不用猜 —— 上面「分盘扫描」把本机每一块盘
7
+ // 都列成一个按钮,点哪个扫哪个,扫到就点「用这个」填进去;也能自己手填路径。
8
8
 
9
9
  window.__ModuleLoader__.load({
10
10
  id: 'dsh-napcat-keeper',
@@ -112,12 +112,19 @@ window.__ModuleLoader__.load({
112
112
  }
113
113
 
114
114
  const 字段 = [
115
+ {
116
+ group: '分盘扫描(选一块盘,让它自己找 Shell 目录)',
117
+ note: '',
118
+ custom: 'scan',
119
+ items: [],
120
+ },
115
121
  {
116
122
  group: '连接',
117
123
  note: '这三项填完就能跑:NapCat 装在哪、要拉哪个 QQ、OneBot 开在哪个端口。',
118
124
  items: [
119
- ['shellDir', 'NapCat Shell 目录', '例如 C:/Users/你/NapCat/OneKey/NapCat.<版本>.Shell —— 里面有 napcat*.bat', 'text', true],
125
+ ['shellDir', 'NapCat Shell 目录', '用上面的「分盘扫描」找出来,或者手填完整路径,例如 C:/Users/你/NapCat/OneKey/NapCat.<版本>.Shell —— 里面有 napcat*.bat', 'text', true],
120
126
  ['launcherScript', '启动脚本', '留空自动挑(quick 优先,其次 napcat*.bat)', 'text'],
127
+ ['nodePath', 'Node 运行时(node.exe)', '留空 = 自动:DSH 自带那份 → 系统 PATH → 常见安装目录 → NapCat 目录。只有「Shell 型」NapCat(目录顶层有 napcat.mjs)才真的用到它;OneKey 注入型跑在 QQ 自己的 Node 里,跟这里无关', 'text'],
121
128
  ['launchAccount', '要快登的 QQ 号', '填了它、脚本又找不到时,会自动生成一个直接调 NapCatWinBootMain.exe 的脚本', 'text'],
122
129
  ['onebotPort', 'OneBot HTTP 端口', 'NapCat 里 onebot11 的 httpServers 端口,默认 3000', 'number', true],
123
130
  ['accessToken', 'OneBot 访问令牌', '没设就留空;设了要跟 NapCat 里填的一致', 'password'],
@@ -142,6 +149,12 @@ window.__ModuleLoader__.load({
142
149
  custom: 'password',
143
150
  items: [],
144
151
  },
152
+ {
153
+ group: '需要你点一下的脚本(免管理员提权)',
154
+ note: '',
155
+ custom: 'setup',
156
+ items: [],
157
+ },
145
158
  {
146
159
  group: '高级',
147
160
  note: '一般不用动。',
@@ -175,16 +188,26 @@ window.__ModuleLoader__.load({
175
188
  h('div', { style: { ...S.kv, marginTop: 8 } },
176
189
  h('span', { style: S.dim }, '进程'),
177
190
  h('span', null,
178
- 'NapCatWinBootMain ' + String((runtime.procs && runtime.procs.boot) || 0) +
179
- ' | QQ ' + String((runtime.procs && runtime.procs.qq) || 0) +
180
- ' | 链 ' + String((runtime.procs && runtime.procs.chains) || 0) +
181
- (runtime.procs && runtime.procs.dup ? ' ⚠ 有重复链' : '')),
191
+ runtime.procs && runtime.procs.known === false
192
+ ? '(还没探测)'
193
+ : 'NapCatWinBootMain ' + String((runtime.procs && runtime.procs.boot) || 0) +
194
+ ' | QQ ' + String((runtime.procs && runtime.procs.qq) || 0) +
195
+ ' | 链 ' + String((runtime.procs && runtime.procs.chains) || 0) +
196
+ (runtime.procs && runtime.procs.dup ? ' ⚠ 有重复链' : '')),
182
197
  h('span', { style: S.dim }, '路径前缀'),
183
198
  h('span', { style: { wordBreak: 'break-all' } }, String(runtime.procRoot || '(空)')),
184
199
  h('span', { style: S.dim }, '启动脚本'),
185
200
  h('span', { style: { wordBreak: 'break-all' } }, String(runtime.launcherFound || '⚠ 没找到')),
201
+ h('span', { style: S.dim }, 'Node'),
202
+ h('span', { style: { wordBreak: 'break-all' } },
203
+ runtime.node && runtime.node.path
204
+ ? String(runtime.node.source || '') + ' ' + String(runtime.node.version || '') + ' · ' + String(runtime.node.path)
205
+ : '⚠ 没找到 node(Shell 型 NapCat 会拉不起来;注入型不受影响)'),
186
206
  h('span', { style: S.dim }, '计划任务'),
187
- h('span', null, String(runtime.taskName || '(不用)') + (runtime.taskReady ? ' ✓ 就绪' : '(还没建)')),
207
+ h('span', null, String(runtime.taskName || '(不用)') +
208
+ (runtime.taskReady === null || runtime.taskReady === undefined
209
+ ? '(读取中…)'
210
+ : runtime.taskReady ? ' ✓ 就绪' : '(还没建)')),
188
211
  h('span', { style: S.dim }, '二维码'),
189
212
  h('span', null, runtime.qr ? '最新一张 ' + String(runtime.qr.ageSec) + ' 秒前' : '还没有(没进扫码态)'),
190
213
  h('span', { style: S.dim }, '桥接心跳'),
@@ -201,6 +224,8 @@ window.__ModuleLoader__.load({
201
224
  const [说, set说] = React.useState('')
202
225
  const [开日志, set开日志] = React.useState(false)
203
226
  const [风险开, set风险开] = React.useState(false)
227
+ // null = 「还没动过,默认全勾能做的」;一旦用户点过 checkbox 就固定用它(哪怕空数组)
228
+ const [勾, set勾] = React.useState(null)
204
229
 
205
230
  const 装 = (data) => {
206
231
  set态(data)
@@ -214,9 +239,95 @@ window.__ModuleLoader__.load({
214
239
  }
215
240
 
216
241
  React.useEffect(() => {
217
- get('/state').then((d) => d && 装(d))
242
+ let alive = true
243
+ let tries = 0
244
+ const 拉 = async () => {
245
+ const d = await get('/state')
246
+ if (!alive) return
247
+ if (d && d.config) {
248
+ 装(d)
249
+ return
250
+ }
251
+ if (tries < 3) {
252
+ tries += 1
253
+ setTimeout(拉, 900)
254
+ return
255
+ }
256
+ set说('接口没响应,读不到状态(日志见 ~/.dsh/napcat-keeper.log)')
257
+ set态({ ok: false, config: null, runtime: {}, scan: {}, drives: [], log: [] })
258
+ }
259
+ 拉()
260
+ return () => {
261
+ alive = false
262
+ }
218
263
  }, [])
219
264
 
265
+ /* ⚠ 所有 hook 都必须排在下面任何一个 return 之前。
266
+ 之前扫描轮询的 useEffect 写在早退之后:第一次渲染 hook 少一个,
267
+ 一旦拿到状态触发 re-render,hook 数变多 → React #310
268
+ 「Rendered more hooks than during the previous render」→ 整个
269
+ settings.section 崩掉(面板空白,还连带别的插件一起挂)。 */
270
+ const 扫着 = Boolean(态 && 态.scan && 态.scan.running)
271
+ React.useEffect(() => {
272
+ if (!扫着) return undefined
273
+ let alive = true
274
+ const timer = setInterval(() => {
275
+ // 轮询走轻量档:不开 PowerShell,只拉扫描进度和运行态
276
+ get('/state?light=1').then((d) => {
277
+ if (!alive || !d) return
278
+ set态((prev) =>
279
+ prev && {
280
+ ...prev,
281
+ scan: d.scan || prev.scan,
282
+ drives: d.drives && d.drives.length ? d.drives : prev.drives,
283
+ runtime: d.runtime || prev.runtime,
284
+ log: d.log && d.log.length ? d.log : prev.log,
285
+ },
286
+ )
287
+ })
288
+ }, 1500)
289
+ return () => {
290
+ alive = false
291
+ clearInterval(timer)
292
+ }
293
+ }, [扫着])
294
+
295
+ /* ---------- 早退(都在 hook 之后) ---------- */
296
+ if (!态) {
297
+ return h('div', { style: S.wrap }, h('div', { style: S.dim }, 说 || '正在读取 napcat 看门狗状态 …'))
298
+ }
299
+ if (态.ok === false) {
300
+ return h(
301
+ 'div',
302
+ { style: S.wrap },
303
+ h('div', { style: S.h }, 'napcat 看门狗'),
304
+ h('div', { style: S.bad }, 说 || '读不到状态'),
305
+ h(
306
+ 'div',
307
+ { style: { ...S.dim, marginTop: 8 } },
308
+ '插件本体(看门狗、napcat_status / napcat_restart 工具)照常工作,只是这个面板没拿到数据。',
309
+ ),
310
+ h(
311
+ 'button',
312
+ {
313
+ style: { ...S.btn, marginTop: 10 },
314
+ onClick: () => {
315
+ set说('')
316
+ set态(null)
317
+ get('/state').then((d) => {
318
+ if (d && d.config) 装(d)
319
+ else {
320
+ set说('重试还是没响应')
321
+ set态({ ok: false, config: null, runtime: {}, scan: {}, drives: [], log: [] })
322
+ }
323
+ })
324
+ },
325
+ },
326
+ '重试',
327
+ ),
328
+ )
329
+ }
330
+
220
331
  const 脏 = Boolean(态 && 草 && 全部键.some((k) => String(取(草, k)) !== String(取(态.config, k))))
221
332
 
222
333
  const 存 = async () => {
@@ -238,22 +349,230 @@ window.__ModuleLoader__.load({
238
349
  set忙(false)
239
350
  }
240
351
 
241
- const 探测 = async () => {
242
- set忙(true)
243
- const r = await post('/action', { do: 'detect' })
244
- if (r) {
245
- 装(r)
246
- if (r.extra) set草((prev) => ({ ...(prev || {}), shellDir: r.extra }))
352
+ const 改 = (k, v) => set草((prev) => ({ ...(prev || {}), [k]: v }))
353
+
354
+ /* 提权交接:插件自己不去要管理员,只写脚本;「是」由人在 UAC 上按。 */
355
+ function 提权区() {
356
+ const 设 = 态 && 态.setup
357
+ if (!设) return h('div', { style: S.hint }, '读不到脚本状态(刷新一下面板试试)')
358
+ const needs = 设.needs || []
359
+ const 可做 = needs.filter((n) => n.ready).map((n) => n.id)
360
+ const 选中 = (勾 === null ? 可做 : 勾).filter((id) => 可做.indexOf(id) >= 0)
361
+ const 切 = (id) => {
362
+ set勾(选中.indexOf(id) >= 0 ? 选中.filter((x) => x !== id) : 选中.concat(id))
247
363
  }
248
- set说((r && r.said) || '探测完了')
249
- set忙(false)
364
+ const 跑 = async (doIt, extra) => {
365
+ if (忙) return
366
+ set忙(true)
367
+ set说('执行中:' + doIt + ' …')
368
+ const r = await post('/action', { do: doIt, ...(extra || {}) })
369
+ // 只换态、不动草稿:用户可能还有没保存的设置
370
+ if (r) set态(r)
371
+ set说((r && r.said) || '做完了')
372
+ set忙(false)
373
+ }
374
+ const 生成 = () => {
375
+ if (!选中.length) {
376
+ set说('先勾上至少一条要它做的事')
377
+ return
378
+ }
379
+ 跑('makeSetup', { needs: 选中, open: true })
380
+ }
381
+ const 计划 = 设.plan
382
+ const 上次 = 设.lastRun
383
+ const 结果们 = (上次 && 上次.results) || []
384
+ const 成数 = 结果们.filter((x) => x.ok).length
385
+ return h('div', null,
386
+ h('div', { style: S.h }, '需要你点一下的脚本(插件不去抢管理员)'),
387
+ h('div', { style: { ...S.dim, marginBottom: 8 } },
388
+ '要动「装系统的位置」时,插件不偷偷提权,而是把要做的事写成一份脚本放到下面的目录里:'
389
+ + '你双击 apply-admin.cmd,只有含管理员步骤的那一批才会弹 UAC —— 那个「是」就是你自己按的确认键;'
390
+ + '不含的批次双击直接跑。跑完回来点「读结果」,想全部撤回就双击 undo-admin.cmd。'),
391
+ h('div', { style: S.card },
392
+ needs.map((n) => h('label', {
393
+ key: n.id,
394
+ style: {
395
+ display: 'flex',
396
+ gap: 8,
397
+ alignItems: 'flex-start',
398
+ padding: '6px 0',
399
+ opacity: n.ready ? 1 : 0.55,
400
+ cursor: n.ready ? 'pointer' : 'not-allowed',
401
+ },
402
+ },
403
+ h('input', {
404
+ type: 'checkbox',
405
+ name: 'npk-need-' + String(n.id),
406
+ checked: 选中.indexOf(n.id) >= 0,
407
+ disabled: !n.ready || 忙,
408
+ onChange: () => 切(n.id),
409
+ style: { marginTop: 3, flex: '0 0 auto' },
410
+ }),
411
+ h('div', { style: { flex: '1 1 auto', minWidth: 0 } },
412
+ h('div', null, String(n.label || n.id) + (n.needAdmin ? '(要不要管理员看你的目录权限)' : '(不需要管理员)')),
413
+ h('div', { style: S.hint }, String(n.hint || '')),
414
+ !n.ready ? h('div', { style: { ...S.warn, fontSize: 11.5, marginTop: 2 } }, '先解决:' + String(n.why || '')) : null),
415
+ )),
416
+ h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 10 } },
417
+ h('button', { type: 'button', style: S.btnOn, disabled: 忙, onClick: 生成 }, '生成脚本并打开文件夹'),
418
+ h('button', { type: 'button', style: S.btn, disabled: 忙 || !设.exists, onClick: () => 跑('openSetup') }, '打开文件夹'),
419
+ h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 跑('setupResult') }, '读结果'),
420
+ ),
421
+ h('div', { style: { ...S.hint, marginTop: 6, wordBreak: 'break-all' } }, String(设.dir || '')),
422
+ 计划
423
+ ? h('div', { style: { ...S.dim, marginTop: 8 } },
424
+ '上次生成:' + String(计划.createdAt || '').replace('T', ' ').slice(0, 19)
425
+ + '|' + String((计划.steps || []).length) + ' 步'
426
+ + (计划.needAdmin ? '|含管理员步骤' : '|全程普通权限'),
427
+ h('div', { style: S.hint }, (计划.steps || []).map((s) => String(s.label || s.id)).join('、')))
428
+ : h('div', { style: { ...S.dim, marginTop: 8 } }, '还没生成过脚本'),
429
+ 上次
430
+ ? h('div', { style: { marginTop: 8 } },
431
+ h('div', { style: 上次.ok ? S.ok : S.bad },
432
+ (上次.mode === 'undo' ? '撤回' : '执行') + ':' + String(成数) + '/' + String(结果们.length) + ' 步成'
433
+ + (上次.elevated ? '|用了管理员' : '|没用管理员')),
434
+ 结果们.map((x) => h('div', {
435
+ key: String(x.id || '') + String(x.label || ''),
436
+ style: { ...S.hint, color: x.ok ? undefined : '#c0392b' },
437
+ }, (x.ok ? '✓ ' : x.skipped ? '· 跳过 ' : '✗ ')
438
+ + String(x.label || x.id || '') + ':' + String(x.message || ''))))
439
+ : null,
440
+ ),
441
+ )
250
442
  }
251
443
 
252
- if (!态) {
253
- return h('div', { style: S.wrap }, h('div', { style: S.dim }, '正在读取 napcat 看门狗状态 …'))
254
- }
444
+ function 扫描区() {
445
+ const 扫 = (态 && 态.scan) || { running: false, found: [], drives: [], visited: 0 }
446
+ const 盘们 = (态 && 态.drives) || []
447
+ const 结果 = 扫.found || []
448
+ const 发起 = async (drive) => {
449
+ if (忙) return
450
+ set忙(true)
451
+ set说(drive ? `开始扫 ${drive} …` : '开始把所有盘都扫一遍 …')
452
+ const r = await post('/action', { do: 'scan', drive })
453
+ if (r) set态(r)
454
+ set说((r && r.said) || '')
455
+ set忙(false)
456
+ }
457
+ const 用这个 = async (p) => {
458
+ if (忙) return
459
+ set忙(true)
460
+ const r = await post('/action', { do: 'useShell', shellDir: p })
461
+ if (r) 装(r)
462
+ set说((r && r.said) || '已切过去')
463
+ set忙(false)
464
+ }
465
+ // 只刷新盘符列表,不碰下面正在编辑的草稿(别用 装(),那会把没保存的改动冲掉)
466
+ const 重读盘 = async () => {
467
+ if (忙) return
468
+ set忙(true)
469
+ const r = await post('/action', { do: 'drives' })
470
+ if (r && Array.isArray(r.drives)) set态((prev) => ({ ...(prev || {}), drives: r.drives }))
471
+ set说((r && r.said) || '')
472
+ set忙(false)
473
+ }
474
+ return h('div', null,
475
+ h('div', { style: S.h }, '分盘扫描(选一块盘,让它自己找)'),
476
+ h('div', { style: { ...S.dim, marginBottom: 8 } },
477
+ '插件不再替你猜 NapCat 装在哪:本机有几块盘,这里就列几个按钮(系统说有哪几块就是哪几块,'
478
+ + '光驱、移动硬盘、上着锁的盘也会列出来),点哪块扫哪块。深度 7 层、跳过 Windows/回收站/node_modules,'
479
+ + '单盘最多 90 秒;扫到像 NapCat Shell 的目录就停在那条路径上(不再往下钻),'
480
+ + '结果在下面列出来,点「用这个」直接填进 Shell 目录。'),
255
481
 
256
- const 改 = (k, v) => set草((prev) => ({ ...(prev || {}), [k]: v }))
482
+ h('div', { style: S.card },
483
+ h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
484
+ ...盘们.map((d) => h('button', {
485
+ key: d,
486
+ type: 'button',
487
+ style: 扫.running ? { ...S.btn, opacity: 0.5 } : S.btn,
488
+ disabled: 忙 || 扫.running,
489
+ onClick: () => 发起(d),
490
+ }, '扫 ' + d)),
491
+ 盘们.length
492
+ ? h('button', {
493
+ type: 'button',
494
+ style: 扫.running ? { ...S.btn, opacity: 0.5 } : S.btnOn,
495
+ disabled: 忙 || 扫.running,
496
+ onClick: () => 发起('all'),
497
+ }, '全部盘(慢)')
498
+ : h('span', { style: S.bad }, '没读到盘 —— 点右边「重新读盘」再试一次'),
499
+ 扫.running
500
+ ? h('button', {
501
+ type: 'button',
502
+ style: S.btn,
503
+ disabled: 忙,
504
+ onClick: async () => {
505
+ const r = await post('/action', { do: 'scanStop' })
506
+ set说((r && r.said) || '已要求停止')
507
+ },
508
+ }, '停止')
509
+ : null,
510
+ h('button', { type: 'button', style: S.btn, disabled: 忙 || 扫.running, onClick: 重读盘 }, '重新读盘'),
511
+ ),
512
+ h('div', { style: { ...S.dim, marginTop: 6 } },
513
+ 盘们.length
514
+ ? `读到 ${盘们.length} 块盘:${盘们.join('、')}(插了移动盘就点「重新读盘」)`
515
+ : '一块盘都没读到'),
516
+
517
+ 扫.running
518
+ ? h('div', { style: { ...S.dim, marginTop: 8 } },
519
+ `正在扫 ${扫.drive || (扫.drives || []).join('、')} … 已看 ${扫.visited} 个目录,`
520
+ + `用时 ${Math.round((扫.elapsedMs || 0) / 1000)} 秒,命中 ${结果.length} 个`)
521
+ : null,
522
+ !扫.running && (扫.elapsedMs || 0) > 0
523
+ ? h('div', { style: { ...S.dim, marginTop: 8 } },
524
+ `上一轮:${(扫.drives || []).join('、') || '—'},看了 ${扫.visited} 个目录,`
525
+ + `用时 ${Math.round((扫.elapsedMs || 0) / 1000)} 秒,命中 ${结果.length} 个`
526
+ + (扫.limitHit ? '(到时间/数量上限就停了,结果可能不全)' : '')
527
+ + (扫.cancelled ? '(你中途停了)' : ''))
528
+ : null,
529
+ 扫.error ? h('div', { style: { ...S.bad, marginTop: 6 } }, '扫描出错:' + 扫.error) : null,
530
+
531
+ 结果.length
532
+ ? h('div', { style: { marginTop: 10 } },
533
+ ...结果.map((f) => h('div', {
534
+ key: f.path,
535
+ style: {
536
+ display: 'flex',
537
+ gap: 10,
538
+ alignItems: 'flex-start',
539
+ padding: '8px 0',
540
+ borderTop: '1px solid var(--dsw-alias-border-1, rgba(0,0,0,.08))',
541
+ },
542
+ },
543
+ h('div', { style: { flex: '1 1 auto', minWidth: 0 } },
544
+ h('div', {
545
+ style: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 12, wordBreak: 'break-all' },
546
+ }, f.path),
547
+ h('div', { style: S.hint },
548
+ (f.launcher ? '启动脚本:' + f.launcher : '没找到 .bat 启动脚本')
549
+ + (f.boot ? ' · 含 NapCatWinBootMain.exe' : '')
550
+ + (f.drive ? ' · ' + f.drive : '')),
551
+ f.shell === undefined
552
+ ? h('div', { style: { ...S.hint, color: '#9a7b2f' } },
553
+ '(服务端还是旧版,没给判定 —— 重启 DSH 后这里会标真假)')
554
+ : (f.shell
555
+ ? h('div', { style: { ...S.hint, color: '#2f8f5b' } },
556
+ '✓ 像真 Shell:' + (f.evidence && f.evidence.length ? f.evidence.join(' · ') : '有 QQ 本体'))
557
+ : h('div', { style: { ...S.warn, fontSize: 11.5, marginTop: 2 } },
558
+ '⚠ 没看到 versions/QQ.exe/napcat.mjs —— 多半是 OneKey 装完留下的壳模板(只有 bootmain + bat),'
559
+ + '选它大概率拉不起来;确定你这份是精简布局再选')),
560
+ ),
561
+ h('button', {
562
+ type: 'button',
563
+ style: f.shell === false ? S.btn : S.btnOn,
564
+ disabled: 忙,
565
+ onClick: () => 用这个(f.path),
566
+ }, f.shell === false ? '仍要用这个' : '用这个'),
567
+ )),
568
+ )
569
+ : (!扫.running && (扫.elapsedMs || 0) > 0
570
+ ? h('div', { style: { ...S.bad, marginTop: 8 } },
571
+ '这个范围里没找到。要么换个盘,要么把目录直接手填到下面「NapCat Shell 目录」里。')
572
+ : null),
573
+ ),
574
+ )
575
+ }
257
576
 
258
577
  /* ---------- 「记住密码」那一块:先讲风险,再让人自己决定 ---------- */
259
578
  const 风险条目 = [
@@ -369,8 +688,9 @@ window.__ModuleLoader__.load({
369
688
  hint ? h('div', { style: S.hint }, hint) : null),
370
689
  h('div', { style: S.ctl },
371
690
  ctl,
372
- key === 'shellDir' ? h('div', { style: { marginTop: 6 } },
373
- h('button', { type: 'button', style: S.btn, onClick: 探测, disabled: 忙 }, '自动探测')) : null),
691
+ key === 'shellDir'
692
+ ? h('div', { style: S.hint }, '不想手填就用上面「分盘扫描」:挑一块盘让它自己找,扫到点「用这个」。')
693
+ : null),
374
694
  )
375
695
  }
376
696
 
@@ -383,23 +703,28 @@ window.__ModuleLoader__.load({
383
703
  状态条(态.runtime),
384
704
 
385
705
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', margin: '10px 0' } },
386
- h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('probe') }, '重新探测'),
706
+ h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('probe') }, '重新探活'),
387
707
  h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('launch') }, '拉起 NapCat'),
388
708
  h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('restart') }, '杀掉重拉'),
389
709
  h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('dedupe') }, '去重复链'),
390
710
  h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('createTask') }, '建计划任务'),
391
711
  h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('hideConsole') }, '收起黑框'),
712
+ h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('checkNode') }, '测一下 node'),
392
713
  h('button', { type: 'button', style: S.btn, disabled: 忙, onClick: () => 动('kill') }, '全部杀掉'),
393
714
  ),
394
715
  说 ? h('div', { style: { ...S.dim, marginBottom: 6 } }, 说) : null,
395
716
 
396
717
  字段.map((g) => g.custom === 'password'
397
718
  ? h('div', { key: g.group }, 密码区())
398
- : h('div', { key: g.group },
399
- h('div', { style: S.h }, g.group),
400
- g.note ? h('div', { style: { ...S.dim, marginBottom: 8 } }, g.note) : null,
401
- g.items.map(渲染项),
402
- )),
719
+ : g.custom === 'scan'
720
+ ? h('div', { key: g.group }, 扫描区())
721
+ : g.custom === 'setup'
722
+ ? h('div', { key: g.group }, 提权区())
723
+ : h('div', { key: g.group },
724
+ h('div', { style: S.h }, g.group),
725
+ g.note ? h('div', { style: { ...S.dim, marginBottom: 8 } }, g.note) : null,
726
+ g.items.map(渲染项),
727
+ )),
403
728
 
404
729
  h('div', { style: S.h }, '日志'),
405
730
  h('div', { style: { display: 'flex', gap: 8, marginBottom: 6 } },