dsh-qq-stack 0.1.2 → 0.1.4

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/client.js CHANGED
@@ -48,7 +48,8 @@ window.__ModuleLoader__.load({
48
48
  .fs-login{display:flex;flex-direction:column;gap:10px}
49
49
  .fs-field{width:100%;box-sizing:border-box;border:0;border-bottom:1px solid rgba(127,127,127,.28);background:transparent;color:inherit;padding:10px 2px;font-size:15px;outline:none}
50
50
  .fs-wide{padding:11px}
51
- .fs-sep{text-align:center;font-size:12px;opacity:.5;margin-top:6px}`
51
+ .fs-sep{text-align:center;font-size:12px;opacity:.5;margin-top:6px}
52
+ .fs-install{font-size:14px;font-weight:600;margin-bottom:6px}`
52
53
 
53
54
  function Panel() {
54
55
  const [st, setSt] = react.useState(null)
@@ -78,11 +79,15 @@ window.__ModuleLoader__.load({
78
79
  row('OneBot 端口', !!st?.qq, st?.qq ? '已监听' : '未监听'),
79
80
  row('登录状态', logged, logged ? `已登录 ${st?.nickname || ''} ${st?.uin || ''}` : '未登录'),
80
81
  ),
81
- !logged ? h('div', { className: 'fs-card fs-center' },
82
+ (st?.installed !== false && !st?.installing && !logged) ? h('div', { className: 'fs-card fs-center' },
82
83
  st?.qrAt
83
84
  ? h('img', { className: 'fs-qr', src: qrUrl, alt: '登录二维码' })
84
85
  : h('div', { className: 'fs-hint' }, '正在生成二维码…'),
85
86
  ) : null,
87
+ (st && (!st.installed || st.installing)) ? h('div', { className: 'fs-card' },
88
+ h('div', { className: 'fs-install' }, st.installing ? '正在安装 QQ 运行环境…' : '运行环境未就绪'),
89
+ h('div', { className: 'fs-hint' }, st.installTail || '首次安装约 250MB,请稍候'),
90
+ ) : null,
86
91
  logged ? h('div', { className: 'fs-card fs-account' },
87
92
  h('img', {
88
93
  className: 'fs-avatar',
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, openSync } 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,8 +154,10 @@ 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
+ mkdirSync(`${cfg.root}/logs`, { recursive: true })
158
+ const fd = openSync(`${cfg.root}/logs/install.log`, 'a')
159
+ const child = spawnSafe('bash', [installer], { cwd: cfg.root, detached: true, stdio: ['ignore', fd, fd], env })
160
+ if (child?.pid) { try { writeFileSync(`${cfg.root}/install.pid`, String(child.pid)) } catch {} }
149
161
  } catch (e) { log(`[qq-stack] 安装启动失败:${e?.message ?? e}`) }
150
162
  }
151
163
 
@@ -155,8 +167,7 @@ function applyInner(ctx, pluginConfig = {}) {
155
167
  let pid = 0
156
168
  try { pid = Number(readFileSync(watchPid, 'utf8').trim()) || 0 } catch {}
157
169
  if (alive(pid)) return
158
- const child = spawn(process.execPath, [`${SELF}watch.mjs`, SELF, cfg.root], { detached: true, stdio: 'ignore' })
159
- child.unref()
170
+ const child = spawnSafe(process.execPath, [`${SELF}watch.mjs`, SELF, cfg.root], { detached: true, stdio: 'ignore' })
160
171
  try { writeFileSync(watchPid, String(child.pid)) } catch {}
161
172
  } catch (e) { log(`[qq-stack] 卸载清理器启动失败:${e?.message ?? e}`) }
162
173
  }, 'dsh-qq-stack: uninstall-watch')
@@ -177,7 +188,7 @@ function applyInner(ctx, pluginConfig = {}) {
177
188
  if (healed && Date.now() - lastHealAt > 90000) {
178
189
  lastHealAt = Date.now()
179
190
  log('[qq-stack] 补齐 OneBot 配置,重启 QQ')
180
- try { spawn('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
191
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
181
192
  }
182
193
  } catch (e) { log(`[qq-stack] 巡检失败:${e?.message ?? e}`) }
183
194
  }, 60000)
@@ -215,6 +226,15 @@ function applyInner(ctx, pluginConfig = {}) {
215
226
  const pidM = statSync(`${cfg.root}/qq.pid`).mtimeMs
216
227
  if (qrM >= pidM) qrAt = Math.floor(qrM)
217
228
  } catch {}
229
+ let installing = false
230
+ try { installing = alive(Number(readFileSync(`${cfg.root}/install.pid`, 'utf8').trim())) } catch {}
231
+ let installed = existsSync(`${cfg.root}/opt/opt/QQ/qq`)
232
+ let installTail = ''
233
+ try {
234
+ const raw = readFileSync(`${cfg.root}/logs/install.log`, 'utf8')
235
+ const lines = raw.split(/[\r\n]+/).map((x) => x.trim()).filter(Boolean)
236
+ installTail = lines.slice(-2).join(' ').slice(-160)
237
+ } catch {}
218
238
  const login = qq ? await probeLogin(Number(envFile.DSH_QQ_WS_PORT || cfg.wsPort || 3001), envFile.DSH_QQ_TOKEN || cfg.token) : { loggedIn: false, nickname: '' }
219
239
  if (login.loggedIn) {
220
240
  const qqUin = detectedUin || envFile.DSH_QQ_UIN || ''
@@ -228,7 +248,7 @@ function applyInner(ctx, pluginConfig = {}) {
228
248
  const healed = ensureOnebot(cfg.root, portNum, tokenVal)
229
249
  if (healed && !login.loggedIn && Date.now() - lastHealAt > 90000) {
230
250
  lastHealAt = Date.now()
231
- try { spawn('bash', ['-c', `bash stop-qq.sh; bash start-qq.sh`], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
251
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
232
252
  }
233
253
  const probeFresh = Date.now() - (probeCache.at || 0) < 6000
234
254
  if (login.loggedIn && login.uin && probeFresh && envFile.DSH_QQ_UIN !== login.uin) {
@@ -237,7 +257,7 @@ function applyInner(ctx, pluginConfig = {}) {
237
257
  writeFileSync(`${cfg.root}/config.env`, Object.entries(e2).map(([k, x]) => `${k}=${x}`).join('\n') + '\n')
238
258
  } catch {}
239
259
  }
240
- return json({ ok: true, qq, loggedIn: !!login.loggedIn, nickname: login.nickname, uin: login.loggedIn ? login.uin : (envFile.DSH_QQ_UIN || detectedUin), port: envFile.DSH_QQ_WS_PORT || '3001', token: envFile.DSH_QQ_TOKEN || '', qrAt })
260
+ return json({ ok: true, qq, loggedIn: !!login.loggedIn, nickname: login.nickname, uin: login.loggedIn ? login.uin : (envFile.DSH_QQ_UIN || detectedUin), installed, installing, installTail, port: envFile.DSH_QQ_WS_PORT || '3001', token: envFile.DSH_QQ_TOKEN || '', qrAt })
241
261
  }
242
262
  if (req.method === 'GET' && p === '/qr.png') {
243
263
  let detectedUin = ''
@@ -283,11 +303,11 @@ function applyInner(ctx, pluginConfig = {}) {
283
303
  if (existsSync(auth)) unlinkSync(auth)
284
304
  } catch {}
285
305
  probeCache = { at: 0, loggedIn: false, nickname: '', uin: '' }
286
- try { spawn('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
306
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
287
307
  return json({ ok: true })
288
308
  }
289
309
  if (req.method === 'POST' && p === '/restart') {
290
- try { spawn('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' }).unref() } catch {}
310
+ spawnSafe('bash', ['-c', 'bash stop-qq.sh; bash start-qq.sh'], { cwd: cfg.root, detached: true, stdio: 'ignore' })
291
311
  return json({ ok: true })
292
312
  }
293
313
  json({ ok: false }, 404)
package/install.sh CHANGED
@@ -32,9 +32,20 @@ QQ_DEB_URL="https://qqdl.gtimg.cn/qqfile/QQNT/9.9.36/beta/9ee04bef/linuxqq_${QQ_
32
32
  NAPCAT_ZIP="https://github.com/NapNeko/NapCatQQ/releases/latest/download/NapCat.Shell.zip"
33
33
 
34
34
  missing=""
35
- for p in xvfb libnss3 libgbm1 libasound2t64 libgtk-3-0 dbus-x11 unzip curl; do
36
- dpkg -s "$p" >/dev/null 2>&1 || missing="$missing $p"
35
+ for p in xvfb libnss3 libgbm1 libasound2t64 libasound2 libgtk-3-0 libgtk-3-0t64 dbus-x11 unzip curl; do
36
+ dpkg -s "$p" >/dev/null 2>&1 || continue
37
+ case "$p" in
38
+ libasound2) miss="$miss libasound2" ;;
39
+ libasound2t64) [ -n "${miss##*libasound2 }" ] && miss="$miss libasound2t64" ;;
40
+ libgtk-3-0) miss="$miss libgtk-3-0" ;;
41
+ libgtk-3-0t64) miss="$miss libgtk-3-0t64" ;;
42
+ *) miss="$miss $p" ;;
43
+ esac
37
44
  done
45
+ # 成对包只留一个(t64 与旧名并存,供不同发行版)
46
+ case " $miss " in *" libasound2 "*) miss=$(echo "$miss" | sed 's/ libasound2t64 / /g') ;; *) miss=$(echo "$miss" | sed 's/ libasound2 / /g') ;; esac
47
+ case " $miss " in *" libgtk-3-0t64 "*) miss=$(echo "$miss" | sed 's/ libgtk-3-0 / /g') ;; *) miss=$(echo "$miss" | sed 's/ libgtk-3-0t64 / /g') ;; esac
48
+ missing="$miss"
38
49
  if [ -n "$missing" ]; then
39
50
  say "安装依赖:$missing"
40
51
  export DEBIAN_FRONTEND=noninteractive
@@ -52,6 +63,29 @@ fi
52
63
 
53
64
  QQ_APP="$ROOT/opt/opt/QQ/resources/app"
54
65
 
66
+ ensure_deps() {
67
+ need=0
68
+ command -v xvfb-run >/dev/null 2>&1 || need=1
69
+ command -v dbus-run-session >/dev/null 2>&1 || need=1
70
+ if [ -x "$ROOT/opt/opt/QQ/qq" ] && ldd "$ROOT/opt/opt/QQ/qq" 2>/dev/null | grep -q "not found"; then
71
+ need=1
72
+ fi
73
+ [ "$need" = 0 ] && return 0
74
+ say "补齐运行依赖"
75
+ export DEBIAN_FRONTEND=noninteractive
76
+ apt-get update -qq || true
77
+ pkgs=""
78
+ for p in xvfb dbus-x11 libnss3 libgbm1 libasound2t64 libasound2 libgtk-3-0t64 libgtk-3-0 \
79
+ libxss1 libxtst6 libdrm2 libxkbcommon0 libcups2 libpango-1.0-0 libcairo2 \
80
+ libatk1.0-0 libatk-bridge2.0-0 libxdamage1 libxrandr2 libatspi2.0-0 libnspr4 \
81
+ libxcomposite1 libxfixes3 libxrender1; do
82
+ apt-cache show "$p" >/dev/null 2>&1 && pkgs="$pkgs $p"
83
+ done
84
+ apt-get install -y $pkgs || { say "依赖安装失败"; exit 1; }
85
+ }
86
+
87
+ ensure_deps
88
+
55
89
  if [ ! -f "$QQ_APP/app_launcher/napcat/napcat.mjs" ]; then
56
90
  say "下载 NapCat"
57
91
  if [ -f /tmp/NapCat.Shell.zip ] && unzip -t /tmp/NapCat.Shell.zip >/dev/null 2>&1; then
@@ -160,4 +194,3 @@ fi
160
194
  chmod +x "$ROOT"/*.sh
161
195
  say "安装完成,启动 QQ"
162
196
  bash "$ROOT/start-qq.sh"
163
- say "完成(首次需要扫码,之后启动会走 ${UIN:-未设置账号} 的快速登录)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-stack",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "description": "DeepSeek Harness 的 QQ 协议端底座:LinuxQQ + NapCat 随引擎自启,扫码一次后自动快登。",