dsh-qq-stack 0.1.3 → 0.1.7
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 +14 -2
- package/index.js +24 -3
- package/install.sh +76 -15
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -48,7 +48,10 @@ 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}
|
|
53
|
+
.fs-bar{height:6px;border-radius:3px;background:rgba(127,127,127,.2);overflow:hidden;margin:8px 0 6px}
|
|
54
|
+
.fs-bar-in{height:100%;background:#12b7f5;border-radius:3px;transition:width .4s}`
|
|
52
55
|
|
|
53
56
|
function Panel() {
|
|
54
57
|
const [st, setSt] = react.useState(null)
|
|
@@ -78,11 +81,20 @@ window.__ModuleLoader__.load({
|
|
|
78
81
|
row('OneBot 端口', !!st?.qq, st?.qq ? '已监听' : '未监听'),
|
|
79
82
|
row('登录状态', logged, logged ? `已登录 ${st?.nickname || ''} ${st?.uin || ''}` : '未登录'),
|
|
80
83
|
),
|
|
81
|
-
!logged ? h('div', { className: 'fs-card fs-center' },
|
|
84
|
+
(st?.installed !== false && !st?.installing && !logged) ? h('div', { className: 'fs-card fs-center' },
|
|
82
85
|
st?.qrAt
|
|
83
86
|
? h('img', { className: 'fs-qr', src: qrUrl, alt: '登录二维码' })
|
|
84
87
|
: h('div', { className: 'fs-hint' }, '正在生成二维码…'),
|
|
85
88
|
) : null,
|
|
89
|
+
(st && (!st.installed || st.installing)) ? h('div', { className: 'fs-card' },
|
|
90
|
+
h('div', { className: 'fs-install' },
|
|
91
|
+
st.installing ? `正在安装 ${st.installLabel || 'QQ 运行环境'}…` : '运行环境未就绪'),
|
|
92
|
+
(st.installPct >= 0) ? h('div', { className: 'fs-bar' },
|
|
93
|
+
h('div', { className: 'fs-bar-in', style: { width: `${Math.max(2, Math.min(100, st.installPct))}%` } }),
|
|
94
|
+
) : null,
|
|
95
|
+
h('div', { className: 'fs-hint' },
|
|
96
|
+
st.installPct >= 0 ? `${st.installPct}%` : (st.installTail || '首次安装约 250MB,请稍候')),
|
|
97
|
+
) : null,
|
|
86
98
|
logged ? h('div', { className: 'fs-card fs-account' },
|
|
87
99
|
h('img', {
|
|
88
100
|
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, mkdirSync } 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'
|
|
@@ -154,7 +154,10 @@ function applyInner(ctx, pluginConfig = {}) {
|
|
|
154
154
|
if (!existsSync(installer)) { log('[qq-stack] 找不到 install.sh'); return }
|
|
155
155
|
log('[qq-stack] 首次安装开始(LinuxQQ + NapCat,约 250MB)')
|
|
156
156
|
try {
|
|
157
|
-
|
|
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 {} }
|
|
158
161
|
} catch (e) { log(`[qq-stack] 安装启动失败:${e?.message ?? e}`) }
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -223,6 +226,24 @@ function applyInner(ctx, pluginConfig = {}) {
|
|
|
223
226
|
const pidM = statSync(`${cfg.root}/qq.pid`).mtimeMs
|
|
224
227
|
if (qrM >= pidM) qrAt = Math.floor(qrM)
|
|
225
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
|
+
let installLabel = ''
|
|
234
|
+
let installPct = -1
|
|
235
|
+
try {
|
|
236
|
+
const raw = readFileSync(`${cfg.root}/logs/install.log`, 'utf8')
|
|
237
|
+
const lines = raw.split(/[\r\n]+/).map((x) => x.trim()).filter(Boolean)
|
|
238
|
+
const prog = [...lines].reverse().find((l) => l.startsWith('[progress]'))
|
|
239
|
+
if (prog) {
|
|
240
|
+
const m = prog.match(/\[progress\]\s+(.*?)\s+(\d+)%/)
|
|
241
|
+
if (m) { installLabel = m[1]; installPct = Number(m[2]) }
|
|
242
|
+
installTail = prog.slice(11).trim()
|
|
243
|
+
} else {
|
|
244
|
+
installTail = lines.slice(-1).join(' ').slice(-160)
|
|
245
|
+
}
|
|
246
|
+
} catch {}
|
|
226
247
|
const login = qq ? await probeLogin(Number(envFile.DSH_QQ_WS_PORT || cfg.wsPort || 3001), envFile.DSH_QQ_TOKEN || cfg.token) : { loggedIn: false, nickname: '' }
|
|
227
248
|
if (login.loggedIn) {
|
|
228
249
|
const qqUin = detectedUin || envFile.DSH_QQ_UIN || ''
|
|
@@ -245,7 +266,7 @@ function applyInner(ctx, pluginConfig = {}) {
|
|
|
245
266
|
writeFileSync(`${cfg.root}/config.env`, Object.entries(e2).map(([k, x]) => `${k}=${x}`).join('\n') + '\n')
|
|
246
267
|
} catch {}
|
|
247
268
|
}
|
|
248
|
-
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 })
|
|
269
|
+
return json({ ok: true, qq, loggedIn: !!login.loggedIn, nickname: login.nickname, uin: login.loggedIn ? login.uin : (envFile.DSH_QQ_UIN || detectedUin), installed, installing, installTail, installLabel, installPct, port: envFile.DSH_QQ_WS_PORT || '3001', token: envFile.DSH_QQ_TOKEN || '', qrAt })
|
|
249
270
|
}
|
|
250
271
|
if (req.method === 'GET' && p === '/qr.png') {
|
|
251
272
|
let detectedUin = ''
|
package/install.sh
CHANGED
|
@@ -19,6 +19,42 @@ DSH_QQ_TOKEN=$TOKEN
|
|
|
19
19
|
EOF
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
|
+
speed_of() {
|
|
23
|
+
curl -sL -r 0-262144 -o /dev/null --connect-timeout 6 --max-time 8 -w '%{speed_download}' "$1" 2>/dev/null || echo 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pick_mirror() {
|
|
27
|
+
local best="" bestv=0 u v
|
|
28
|
+
echo "[progress] 测速中 0%" >&2
|
|
29
|
+
for u in $NAPCAT_MIRRORS; do
|
|
30
|
+
v=$(speed_of "$u")
|
|
31
|
+
case "$v" in ''|*[!0-9.]*) v=0 ;; esac
|
|
32
|
+
if awk -v a="$v" -v b="$bestv" 'BEGIN{exit !(a>b)}'; then best="$u"; bestv="$v"; fi
|
|
33
|
+
done
|
|
34
|
+
echo "[progress] 选源 $(( ${bestv%.*} / 1024 ))KB/s" >&2
|
|
35
|
+
echo "$best"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dl() {
|
|
39
|
+
local url="$1" out="$2" label="$3" total cur pid
|
|
40
|
+
echo "[progress] $label 0%"
|
|
41
|
+
total=$(curl -sIL --connect-timeout 8 --max-time 15 "$url" 2>/dev/null | awk 'tolower($1)=="content-length:"{print $2}' | tail -1)
|
|
42
|
+
curl -fL --retry 3 --connect-timeout 15 -o "$out" "$url" >/dev/null 2>&1 &
|
|
43
|
+
pid=$!
|
|
44
|
+
while kill -0 "$pid" 2>/dev/null; do
|
|
45
|
+
cur=$(stat -c %s "$out" 2>/dev/null || echo 0)
|
|
46
|
+
if [ -n "${total:-}" ] && [ "$total" -gt 0 ] 2>/dev/null; then
|
|
47
|
+
echo "[progress] $label $(( cur * 100 / total ))% ($(( cur / 1048576 ))/$(( total / 1048576 ))MB)"
|
|
48
|
+
else
|
|
49
|
+
echo "[progress] $label $(( cur / 1048576 ))MB"
|
|
50
|
+
fi
|
|
51
|
+
sleep 2
|
|
52
|
+
done
|
|
53
|
+
wait "$pid" || return 1
|
|
54
|
+
echo "[progress] $label 100%"
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
|
|
22
58
|
say() { echo "[install $(date +%H:%M:%S)] $*"; }
|
|
23
59
|
|
|
24
60
|
ARCH="$(dpkg --print-architecture 2>/dev/null || uname -m)"
|
|
@@ -30,20 +66,18 @@ esac
|
|
|
30
66
|
QQ_VER="3.2.34-53644"
|
|
31
67
|
QQ_DEB_URL="https://qqdl.gtimg.cn/qqfile/QQNT/9.9.36/beta/9ee04bef/linuxqq_${QQ_VER}_${QQ_ARCH}.deb"
|
|
32
68
|
NAPCAT_ZIP="https://github.com/NapNeko/NapCatQQ/releases/latest/download/NapCat.Shell.zip"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
apt-get update -qq && apt-get install -y $missing || { say "依赖安装失败"; exit 1; }
|
|
42
|
-
fi
|
|
69
|
+
NAPCAT_MIRRORS="
|
|
70
|
+
https://github.com/NapNeko/NapCatQQ/releases/latest/download/NapCat.Shell.zip
|
|
71
|
+
https://gh-proxy.com/$NAPCAT_ZIP
|
|
72
|
+
https://ghfast.top/$NAPCAT_ZIP
|
|
73
|
+
https://ghproxy.net/$NAPCAT_ZIP
|
|
74
|
+
https://gh-proxy.org/$NAPCAT_ZIP
|
|
75
|
+
https://hub.gitmirror.com/$NAPCAT_ZIP
|
|
76
|
+
"
|
|
43
77
|
|
|
44
78
|
if [ ! -x "$ROOT/opt/opt/QQ/qq" ]; then
|
|
45
79
|
say "下载 LinuxQQ ${QQ_ARCH}(约 200MB)"
|
|
46
|
-
|
|
80
|
+
dl "$QQ_DEB_URL" /tmp/QQ.deb "LinuxQQ" || { say "LinuxQQ 下载失败"; exit 1; }
|
|
47
81
|
dpkg -x /tmp/QQ.deb "$ROOT/opt" || { say "解包失败"; exit 1; }
|
|
48
82
|
rm -f /tmp/QQ.deb
|
|
49
83
|
else
|
|
@@ -52,14 +86,42 @@ fi
|
|
|
52
86
|
|
|
53
87
|
QQ_APP="$ROOT/opt/opt/QQ/resources/app"
|
|
54
88
|
|
|
89
|
+
ensure_deps() {
|
|
90
|
+
need=0
|
|
91
|
+
command -v xvfb-run >/dev/null 2>&1 || need=1
|
|
92
|
+
command -v dbus-run-session >/dev/null 2>&1 || need=1
|
|
93
|
+
if [ -x "$ROOT/opt/opt/QQ/qq" ] && ldd "$ROOT/opt/opt/QQ/qq" 2>/dev/null | grep -q "not found"; then
|
|
94
|
+
need=1
|
|
95
|
+
fi
|
|
96
|
+
[ "$need" = 0 ] && return 0
|
|
97
|
+
say "补齐运行依赖"
|
|
98
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
99
|
+
apt-get update -qq || true
|
|
100
|
+
pkgs=""
|
|
101
|
+
for p in xvfb dbus-x11 libnss3 libgbm1 libasound2t64 libasound2 libgtk-3-0t64 libgtk-3-0 \
|
|
102
|
+
libxss1 libxtst6 libdrm2 libxkbcommon0 libcups2 libpango-1.0-0 libcairo2 \
|
|
103
|
+
libatk1.0-0 libatk-bridge2.0-0 libxdamage1 libxrandr2 libatspi2.0-0 libnspr4 \
|
|
104
|
+
libxcomposite1 libxfixes3 libxrender1; do
|
|
105
|
+
apt-cache show "$p" >/dev/null 2>&1 && pkgs="$pkgs $p"
|
|
106
|
+
done
|
|
107
|
+
apt-get install -y $pkgs || { say "依赖安装失败"; exit 1; }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ensure_deps
|
|
111
|
+
|
|
55
112
|
if [ ! -f "$QQ_APP/app_launcher/napcat/napcat.mjs" ]; then
|
|
56
113
|
say "下载 NapCat"
|
|
57
114
|
if [ -f /tmp/NapCat.Shell.zip ] && unzip -t /tmp/NapCat.Shell.zip >/dev/null 2>&1; then
|
|
58
115
|
say "复用已下载的 NapCat 包"
|
|
59
116
|
else
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
117
|
+
MIRROR=$(pick_mirror)
|
|
118
|
+
if [ -n "$MIRROR" ]; then
|
|
119
|
+
say "使用镜像:$MIRROR"
|
|
120
|
+
dl "$MIRROR" /tmp/NapCat.Shell.zip "NapCat" || { say "NapCat 下载失败"; exit 1; }
|
|
121
|
+
else
|
|
122
|
+
say "所有源都不可用"
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
63
125
|
fi
|
|
64
126
|
rm -rf /tmp/napcat-unzip && mkdir -p /tmp/napcat-unzip
|
|
65
127
|
unzip -q -o /tmp/NapCat.Shell.zip -d /tmp/napcat-unzip || { say "NapCat 解压失败"; exit 1; }
|
|
@@ -160,4 +222,3 @@ fi
|
|
|
160
222
|
chmod +x "$ROOT"/*.sh
|
|
161
223
|
say "安装完成,启动 QQ"
|
|
162
224
|
bash "$ROOT/start-qq.sh"
|
|
163
|
-
say "完成(首次需要扫码,之后启动会走 ${UIN:-未设置账号} 的快速登录)"
|