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/CHANGELOG.md +89 -0
- package/README.md +117 -15
- package/lib/client.js +355 -30
- package/lib/index.js +1572 -67
- package/package.json +11 -1
- package/scripts/check-manifest.mjs +400 -0
- package/scripts/check-privacy.mjs +176 -0
- package/scripts/selftest-scan.mjs +69 -0
- package/scripts/selftest-setup.mjs +210 -0
- package/scripts/smoke-client.mjs +197 -0
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
|
-
//
|
|
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 目录', '
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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 || '(不用)') +
|
|
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
|
-
|
|
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
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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'
|
|
373
|
-
h('
|
|
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
|
-
:
|
|
399
|
-
h('div', {
|
|
400
|
-
g.
|
|
401
|
-
|
|
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 } },
|