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.
Files changed (4) hide show
  1. package/client.js +14 -2
  2. package/index.js +24 -3
  3. package/install.sh +76 -15
  4. 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
- const child = spawnSafe('bash', [installer], { cwd: cfg.root, detached: true, stdio: 'ignore', env })
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
- 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"
37
- done
38
- if [ -n "$missing" ]; then
39
- say "安装依赖:$missing"
40
- export DEBIAN_FRONTEND=noninteractive
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
- curl -fL --retry 3 -o /tmp/QQ.deb "$QQ_DEB_URL" || { say "LinuxQQ 下载失败"; exit 1; }
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
- curl -fL --retry 2 -o /tmp/NapCat.Shell.zip "$NAPCAT_ZIP" \
61
- || curl -fL --retry 2 -o /tmp/NapCat.Shell.zip "https://gh-proxy.com/$NAPCAT_ZIP" \
62
- || { say "NapCat 下载失败"; exit 1; }
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:-未设置账号} 的快速登录)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-stack",
3
- "version": "0.1.3",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "description": "DeepSeek Harness 的 QQ 协议端底座:LinuxQQ + NapCat 随引擎自启,扫码一次后自动快登。",