dsh-qq-stack 0.1.1 → 0.1.3

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.
Files changed (4) hide show
  1. package/README.md +10 -0
  2. package/index.js +29 -9
  3. package/package.json +4 -3
  4. package/watch.mjs +22 -0
package/README.md CHANGED
@@ -54,3 +54,13 @@ dsh plugin --profile web add dsh-qq-stack
54
54
  ## 许可
55
55
 
56
56
  MIT
57
+
58
+ ## 卸载
59
+
60
+ 插件卸载后,后台会有一个清理器(20 秒内)自动停掉 QQ、删掉 `~/.dsh/qq-bot`(含 700MB+ 的客户端和登录数据)。
61
+
62
+ 如果想立刻清,也可以在卸载前手动执行:
63
+
64
+ ```bash
65
+ bash ~/.dsh/qq-bot/stop-qq.sh && rm -rf ~/.dsh/qq-bot
66
+ ```
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process'
2
- import { existsSync, readFileSync, writeFileSync, statSync, readdirSync, unlinkSync } from 'node:fs'
2
+ import { existsSync, readFileSync, writeFileSync, statSync, readdirSync, unlinkSync, mkdirSync } from 'node:fs'
3
3
  import { readFileSync as rf } from 'node:fs'
4
4
 
5
5
  export const name = 'dsh-qq-stack'
@@ -102,6 +102,16 @@ async function probeLogin(port, token, force) {
102
102
  return probeCache
103
103
  }
104
104
 
105
+
106
+ function spawnSafe(cmd, args, opts) {
107
+ try {
108
+ const child = spawn(cmd, args, opts)
109
+ child.on('error', () => {})
110
+ child.unref()
111
+ return child
112
+ } catch { return null }
113
+ }
114
+
105
115
  function alive(pid) {
106
116
  if (!pid) return false
107
117
  try { process.kill(pid, 0); return true } catch { return false }
@@ -113,6 +123,7 @@ export function apply(ctx, pluginConfig = {}) {
113
123
 
114
124
  function applyInner(ctx, pluginConfig = {}) {
115
125
  const cfg = resolveConfig(pluginConfig)
126
+ try { mkdirSync(cfg.root, { recursive: true }) } catch {}
116
127
  const log = (msg) => { try { ctx.logger?.info?.(msg); if (process.env.DSH_QQ_DEBUG) console.log(msg) } catch {} }
117
128
  log(`[qq-stack] 激活 root=${cfg.root} uin=${cfg.uin || '(空)'} autoInstall=${cfg.autoInstall}`)
118
129
 
@@ -131,8 +142,7 @@ function applyInner(ctx, pluginConfig = {}) {
131
142
  if (alive(pid)) return
132
143
  const script = `${cfg.root}/start-qq.sh`
133
144
  if (!existsSync(script)) return
134
- const child = spawn('bash', [script], { cwd: cfg.root, detached: true, stdio: 'ignore', env })
135
- child.unref()
145
+ const child = spawnSafe('bash', [script], { cwd: cfg.root, detached: true, stdio: 'ignore', env })
136
146
  log('[qq-stack] 拉起 QQ')
137
147
  } catch (e) { log(`[qq-stack] 启动失败:${e?.message ?? e}`) }
138
148
  }
@@ -144,11 +154,21 @@ function applyInner(ctx, pluginConfig = {}) {
144
154
  if (!existsSync(installer)) { log('[qq-stack] 找不到 install.sh'); return }
145
155
  log('[qq-stack] 首次安装开始(LinuxQQ + NapCat,约 250MB)')
146
156
  try {
147
- const child = spawn('bash', [installer], { cwd: cfg.root, detached: true, stdio: 'ignore', env })
148
- child.unref()
157
+ const child = spawnSafe('bash', [installer], { cwd: cfg.root, detached: true, stdio: 'ignore', env })
149
158
  } catch (e) { log(`[qq-stack] 安装启动失败:${e?.message ?? e}`) }
150
159
  }
151
160
 
161
+ ctx.effect(() => {
162
+ try {
163
+ const watchPid = `${cfg.root}/watch.pid`
164
+ let pid = 0
165
+ try { pid = Number(readFileSync(watchPid, 'utf8').trim()) || 0 } catch {}
166
+ if (alive(pid)) return
167
+ const child = spawnSafe(process.execPath, [`${SELF}watch.mjs`, SELF, cfg.root], { detached: true, stdio: 'ignore' })
168
+ try { writeFileSync(watchPid, String(child.pid)) } catch {}
169
+ } catch (e) { log(`[qq-stack] 卸载清理器启动失败:${e?.message ?? e}`) }
170
+ }, 'dsh-qq-stack: uninstall-watch')
171
+
152
172
  ctx.effect(() => {
153
173
  const timer = setInterval(async () => {
154
174
  try {
@@ -165,7 +185,7 @@ function applyInner(ctx, pluginConfig = {}) {
165
185
  if (healed && Date.now() - lastHealAt > 90000) {
166
186
  lastHealAt = Date.now()
167
187
  log('[qq-stack] 补齐 OneBot 配置,重启 QQ')
168
- try { spawn('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
188
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
169
189
  }
170
190
  } catch (e) { log(`[qq-stack] 巡检失败:${e?.message ?? e}`) }
171
191
  }, 60000)
@@ -216,7 +236,7 @@ function applyInner(ctx, pluginConfig = {}) {
216
236
  const healed = ensureOnebot(cfg.root, portNum, tokenVal)
217
237
  if (healed && !login.loggedIn && Date.now() - lastHealAt > 90000) {
218
238
  lastHealAt = Date.now()
219
- try { spawn('bash', ['-c', `bash stop-qq.sh; bash start-qq.sh`], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
239
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
220
240
  }
221
241
  const probeFresh = Date.now() - (probeCache.at || 0) < 6000
222
242
  if (login.loggedIn && login.uin && probeFresh && envFile.DSH_QQ_UIN !== login.uin) {
@@ -271,11 +291,11 @@ function applyInner(ctx, pluginConfig = {}) {
271
291
  if (existsSync(auth)) unlinkSync(auth)
272
292
  } catch {}
273
293
  probeCache = { at: 0, loggedIn: false, nickname: '', uin: '' }
274
- try { spawn('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
294
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
275
295
  return json({ ok: true })
276
296
  }
277
297
  if (req.method === 'POST' && p === '/restart') {
278
- try { spawn('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
298
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
279
299
  return json({ ok: true })
280
300
  }
281
301
  json({ ok: false }, 404)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-stack",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "description": "DeepSeek Harness 的 QQ 协议端底座:LinuxQQ + NapCat 随引擎自启,扫码一次后自动快登。",
@@ -22,7 +22,8 @@
22
22
  "install.sh",
23
23
  "cordis.patch.yml",
24
24
  "README.md",
25
- "LICENSE"
25
+ "LICENSE",
26
+ "watch.mjs"
26
27
  ],
27
28
  "exports": {
28
29
  ".": "./index.js",
@@ -44,4 +45,4 @@
44
45
  "scripts": {
45
46
  "test": "node --input-type=module -e \"await import('./index.js')\""
46
47
  }
47
- }
48
+ }
package/watch.mjs ADDED
@@ -0,0 +1,22 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { spawnSync, spawn } from 'node:child_process'
3
+
4
+ const [pkgDir, root] = process.argv.slice(2)
5
+ if (!pkgDir || !root) process.exit(0)
6
+
7
+ const alive = (pid) => {
8
+ try { process.kill(Number(pid), 0); return true } catch { return false }
9
+ }
10
+
11
+ function cleanup() {
12
+ try { spawnSync('bash', [`${root}/stop-qq.sh`], { timeout: 30000 }) } catch {}
13
+ try { spawnSync('bash', ['-c', `rm -rf "${root}"`], { timeout: 120000 }) } catch {}
14
+ try {
15
+ const pids = spawnSync('bash', ['-c', "ps -eo pid=,comm= | awk '$2==\"Xvfb\"{print $1}'"], { encoding: 'utf8' }).stdout
16
+ for (const p of String(pids).split(/\s+/).filter(Boolean)) { try { process.kill(Number(p), 'SIGKILL') } catch {} }
17
+ } catch {}
18
+ }
19
+
20
+ setInterval(() => {
21
+ if (!existsSync(`${pkgDir}/package.json`)) { cleanup(); process.exit(0) }
22
+ }, 20000)