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 +7 -2
- package/index.js +32 -12
- package/install.sh +36 -3
- package/package.json +1 -1
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 =
|
|
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
|
-
|
|
148
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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:-未设置账号} 的快速登录)"
|