tt-help-cli-ycl 1.4.171 → 1.4.172

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.4.171",
3
+ "version": "1.4.172",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4192,6 +4192,49 @@ function esc(s) {
4192
4192
 
4193
4193
  var _wdLastRendered = "";
4194
4194
 
4195
+ // ── 看门狗客户端卡片折叠(默认只显示头部,点击展开全部;内存态,刷新重置) ──
4196
+ var _wdCardExpandAll = false; // 全局默认态(顶栏「全部展开/收起」切换)
4197
+ var _wdCardExpanded = {}; // 单卡覆盖:userId → true/false(点过单卡按钮才有)
4198
+
4199
+ function wdCardIsExpanded(userId) {
4200
+ return _wdCardExpanded[userId] !== undefined
4201
+ ? _wdCardExpanded[userId]
4202
+ : _wdCardExpandAll;
4203
+ }
4204
+
4205
+ // 单卡展开/收起:直接切 DOM 不重渲染(数据变化重建时按此状态生成)
4206
+ function wdToggleCardBody(userId) {
4207
+ var body = document.getElementById("wdCardBody-" + wdEscId(userId));
4208
+ if (!body) return;
4209
+ var expand = body.style.display === "none";
4210
+ body.style.display = expand ? "" : "none";
4211
+ _wdCardExpanded[userId] = expand;
4212
+ var btn = document.getElementById("wdCardToggle-" + wdEscId(userId));
4213
+ if (btn) {
4214
+ btn.textContent = expand ? "▲ 收起" : "▼ 展开";
4215
+ btn.title = expand ? "收起卡片" : "展开卡片";
4216
+ }
4217
+ }
4218
+
4219
+ // 顶栏总开关:重置单卡覆盖,全部统一切换
4220
+ function wdToggleAllCards() {
4221
+ _wdCardExpandAll = !_wdCardExpandAll;
4222
+ _wdCardExpanded = {};
4223
+ var bodies = document.querySelectorAll("[id^='wdCardBody-']");
4224
+ for (var i = 0; i < bodies.length; i++) {
4225
+ bodies[i].style.display = _wdCardExpandAll ? "" : "none";
4226
+ var btn = document.getElementById(
4227
+ bodies[i].id.replace("wdCardBody-", "wdCardToggle-"),
4228
+ );
4229
+ if (btn) {
4230
+ btn.textContent = _wdCardExpandAll ? "▲ 收起" : "▼ 展开";
4231
+ btn.title = _wdCardExpandAll ? "收起卡片" : "展开卡片";
4232
+ }
4233
+ }
4234
+ var label = document.getElementById("wdToggleAllLabel");
4235
+ if (label) label.textContent = _wdCardExpandAll ? "⊟ 全部收起" : "⊞ 全部展开";
4236
+ }
4237
+
4195
4238
  function renderWatchdogDashboard(
4196
4239
  clients,
4197
4240
  clientCommands,
@@ -4242,9 +4285,9 @@ function renderWatchdogDashboard(
4242
4285
  b.hostname ||
4243
4286
  ""
4244
4287
  ).toLowerCase();
4245
- // 在线优先
4288
+ // 在线优先;名称用自然数字序(F-2 排在 F-10 前)
4246
4289
  if (a.online !== b.online) return a.online ? -1 : 1;
4247
- return nameA.localeCompare(nameB);
4290
+ return nameA.localeCompare(nameB, undefined, { numeric: true });
4248
4291
  });
4249
4292
 
4250
4293
  // 构建 userId → 在线客户端 映射
@@ -4281,7 +4324,9 @@ function renderWatchdogDashboard(
4281
4324
  }
4282
4325
  }
4283
4326
  offlineUids.sort(function (a, b) {
4284
- return a.toLowerCase().localeCompare(b.toLowerCase());
4327
+ return a
4328
+ .toLowerCase()
4329
+ .localeCompare(b.toLowerCase(), undefined, { numeric: true });
4285
4330
  });
4286
4331
  offlineUids.forEach(function (uid) {
4287
4332
  allCards.push(
@@ -4348,6 +4393,18 @@ function renderOfflineCard(
4348
4393
  autoUpgrades,
4349
4394
  ) {
4350
4395
  var cmdCount = cmds ? cmds.length : 0;
4396
+ var expanded = wdCardIsExpanded(userId);
4397
+ var toggleBtn = tag(
4398
+ "button",
4399
+ {
4400
+ id: "wdCardToggle-" + wdEscId(userId),
4401
+ onclick: "wdToggleCardBody('" + esc(userId) + "')",
4402
+ style:
4403
+ "padding:2px 8px;font-size:11px;background:#333;color:#888;border:1px solid #444;border-radius:3px;cursor:pointer",
4404
+ title: expanded ? "收起卡片" : "展开卡片",
4405
+ },
4406
+ expanded ? "▲ 收起" : "▼ 展开",
4407
+ );
4351
4408
  return tag(
4352
4409
  "div",
4353
4410
  {
@@ -4411,20 +4468,28 @@ function renderOfflineCard(
4411
4468
  title: "删除此客户端配置",
4412
4469
  },
4413
4470
  "删除",
4414
- ),
4471
+ ) +
4472
+ toggleBtn,
4415
4473
  ),
4416
4474
  ) +
4417
- styled(
4475
+ tag(
4418
4476
  "div",
4419
- "color:#555;font-size:12px",
4420
- "该机器尚未连接看门狗,配置了 " + cmdCount + " 条命令",
4421
- ) +
4422
- renderCountrySection({}, userId, clashCountries) +
4423
- renderErrorTriggerSection({}, userId, errorTriggers) +
4424
- renderSwitchRestartSection({}, userId, switchRestarts) +
4425
- renderAutoUpgradeSection({}, userId, autoUpgrades) +
4426
- renderLoginSection({}, userId, loginPorts) +
4427
- renderCmdRows(cmds || [], userId, [], false),
4477
+ {
4478
+ id: "wdCardBody-" + wdEscId(userId),
4479
+ style: expanded ? "" : "display:none",
4480
+ },
4481
+ styled(
4482
+ "div",
4483
+ "color:#555;font-size:12px",
4484
+ "该机器尚未连接看门狗,配置了 " + cmdCount + " 条命令",
4485
+ ) +
4486
+ renderCountrySection({}, userId, clashCountries) +
4487
+ renderErrorTriggerSection({}, userId, errorTriggers) +
4488
+ renderSwitchRestartSection({}, userId, switchRestarts) +
4489
+ renderAutoUpgradeSection({}, userId, autoUpgrades) +
4490
+ renderLoginSection({}, userId, loginPorts) +
4491
+ renderCmdRows(cmds || [], userId, [], false),
4492
+ ),
4428
4493
  );
4429
4494
  }
4430
4495
 
@@ -4474,6 +4539,20 @@ function renderClientCard(
4474
4539
  var myCmds = userId && clientCommands[userId] ? clientCommands[userId] : [];
4475
4540
  var online = c.online;
4476
4541
 
4542
+ // 折叠状态:默认只显示头部,点展开按钮看全部(需在 header 组装前定义)
4543
+ var expanded = wdCardIsExpanded(userId);
4544
+ var toggleBtn = tag(
4545
+ "button",
4546
+ {
4547
+ id: "wdCardToggle-" + wdEscId(userId),
4548
+ onclick: "wdToggleCardBody('" + esc(userId) + "')",
4549
+ style:
4550
+ "padding:2px 8px;font-size:11px;background:#333;color:#888;border:1px solid #444;border-radius:3px;cursor:pointer",
4551
+ title: expanded ? "收起卡片" : "展开卡片",
4552
+ },
4553
+ expanded ? "▲ 收起" : "▼ 展开",
4554
+ );
4555
+
4477
4556
  var osStr = [
4478
4557
  os.platform,
4479
4558
  os.release,
@@ -4578,6 +4657,11 @@ function renderClientCard(
4578
4657
  : "color:#fca5a5;font-size:12px",
4579
4658
  "最后心跳 " + lastSeen,
4580
4659
  ) +
4660
+ styled(
4661
+ "span",
4662
+ "color:#888;font-size:13px",
4663
+ "进程 " + procs.length,
4664
+ ) +
4581
4665
  (!online && userId
4582
4666
  ? tag(
4583
4667
  "button",
@@ -4589,7 +4673,8 @@ function renderClientCard(
4589
4673
  },
4590
4674
  "删除",
4591
4675
  )
4592
- : ""),
4676
+ : "") +
4677
+ toggleBtn,
4593
4678
  ),
4594
4679
  );
4595
4680
 
@@ -4603,15 +4688,33 @@ function renderClientCard(
4603
4688
  ";border-radius:12px;padding:16px",
4604
4689
  },
4605
4690
  header +
4606
- styled("div", "color:#888;font-size:12px", osStr) +
4607
- styled("div", "color:#888;font-size:12px;margin-bottom:6px", infoLine) +
4608
- renderCountrySection(c, userId, clashCountries) +
4609
- renderErrorTriggerSection(c, userId, errorTriggers) +
4610
- renderSwitchRestartSection(c, userId, switchRestarts) +
4611
- renderAutoUpgradeSection(c, userId, autoUpgrades) +
4612
- renderLoginSection(c, userId, loginPorts) +
4613
- renderProxySection(c, userId) +
4614
- renderCmdRows(myCmds, userId, procs, online, c.llmAnalysis, c.clientId),
4691
+ tag(
4692
+ "div",
4693
+ {
4694
+ id: "wdCardBody-" + wdEscId(userId),
4695
+ style: expanded ? "" : "display:none",
4696
+ },
4697
+ styled("div", "color:#888;font-size:12px", osStr) +
4698
+ styled(
4699
+ "div",
4700
+ "color:#888;font-size:12px;margin-bottom:6px",
4701
+ infoLine,
4702
+ ) +
4703
+ renderCountrySection(c, userId, clashCountries) +
4704
+ renderErrorTriggerSection(c, userId, errorTriggers) +
4705
+ renderSwitchRestartSection(c, userId, switchRestarts) +
4706
+ renderAutoUpgradeSection(c, userId, autoUpgrades) +
4707
+ renderLoginSection(c, userId, loginPorts) +
4708
+ renderProxySection(c, userId) +
4709
+ renderCmdRows(
4710
+ myCmds,
4711
+ userId,
4712
+ procs,
4713
+ online,
4714
+ c.llmAnalysis,
4715
+ c.clientId,
4716
+ ),
4717
+ ),
4615
4718
  );
4616
4719
  }
4617
4720
 
@@ -5395,7 +5498,7 @@ function renderLoginSection(c, userId, loginPorts) {
5395
5498
  style:
5396
5499
  "padding:2px 8px;font-size:11px;background:#333;color:#4ade80;" +
5397
5500
  "border:1px solid #444;border-radius:3px;cursor:pointer",
5398
- title: "立即重检一次登录状态(绕过 2h 周期,结果随心跳自动刷新)",
5501
+ title: "立即重检上次未通过的端口(全部已登录则跳过;结果随心跳自动刷新)",
5399
5502
  },
5400
5503
  "重检",
5401
5504
  );
@@ -644,6 +644,10 @@
644
644
  <div class="label">命令池</div>
645
645
  <div class="value slate" id="wdCmdCount">0</div>
646
646
  </div>
647
+ <div class="stat-card clickable" onclick="wdToggleAllCards()" style="background:rgba(96,165,250,0.05)">
648
+ <div class="label" id="wdToggleAllLabel" style="font-size:18px">⊞</div>
649
+ <div class="value" style="font-size:11px;color:#60a5fa">全部展开</div>
650
+ </div>
647
651
  <div class="stat-card clickable" onclick="fetchWatchdogData()" style="background:rgba(96,165,250,0.05)">
648
652
  <div class="label" style="font-size:18px">🔄</div>
649
653
  <div class="value" style="font-size:11px;color:#60a5fa">刷新</div>
@@ -284,6 +284,7 @@ export class WatchdogAgent {
284
284
  this.loginReport = null; // 最近一次登录检测结果(随心跳上报)
285
285
  this._loginChecking = false; // 防重入
286
286
  this._lastLoginCheckAt = 0; // 上次检测时间戳
287
+ this._loginRecheckPending = false; // 服务端下发的手动重检待执行标记
287
288
  this.loginCheckTimer = null;
288
289
  this.running = false;
289
290
  this.heartbeatTimer = null;
@@ -915,10 +916,14 @@ export class WatchdogAgent {
915
916
  );
916
917
  }
917
918
  }
918
- // 服务端下发「立即重检」:清零上次检测时间,60s 调度器下一跳即执行
919
+ // 服务端下发「立即重检」:标记待重检 + 清零上次检测时间,60s 调度器下一跳执行
920
+ // (重检只检上次未通过的端口,范围在调度器消费时按最新 loginReport 计算)
919
921
  if (data.loginRecheck) {
922
+ this._loginRecheckPending = true;
920
923
  this._lastLoginCheckAt = 0;
921
- console.error("[watchdog] 服务端下发登录重检,下一跳立即执行");
924
+ console.error(
925
+ "[watchdog] 服务端下发登录重检,下一跳立即执行(仅上次未通过的端口)",
926
+ );
922
927
  }
923
928
 
924
929
  // 心跳日志:仅状态变化或每 10 次打印一次
@@ -980,18 +985,102 @@ export class WatchdogAgent {
980
985
  if (!this.running || this._loginChecking) return;
981
986
  if (!Array.isArray(this.loginPorts) || this.loginPorts.length === 0) return;
982
987
  if (Date.now() - this._lastLoginCheckAt < this.loginCheckIntervalMs) return;
988
+
989
+ // 手动重检:只检上次未通过的端口;无有效历史结果退化为全量,全部已登录则跳过
990
+ let ports = this.loginPorts;
991
+ if (this._loginRecheckPending) {
992
+ this._loginRecheckPending = false;
993
+ const resolved = this._resolveRecheckPorts();
994
+ if (resolved) {
995
+ if (resolved.length === 0) {
996
+ console.error("[watchdog] 重检跳过: 上次全部已登录,无需重检");
997
+ return; // 不占位,2h 周期照常
998
+ }
999
+ ports = resolved;
1000
+ console.error(
1001
+ `[watchdog] 手动重检: 仅检测上次未通过的端口 (${ports.join(",")})`,
1002
+ );
1003
+ }
1004
+ // resolved === null → 无有效历史结果,保持全量
1005
+ }
1006
+
983
1007
  this._lastLoginCheckAt = Date.now(); // 先占位,防 60s tick 重入
984
- this._runLoginCheck().catch((e) => {
1008
+ this._runLoginCheck(ports).catch((e) => {
985
1009
  console.error(`[watchdog] 登录检测执行失败: ${e.message}`);
986
1010
  });
987
1011
  }
988
1012
 
1013
+ /**
1014
+ * 计算手动重检端口范围:上次检测未通过(status !== "logged_in")的端口,
1015
+ * 含配置了但上次没检到的端口(同样视为未通过)。
1016
+ * @returns {number[]|null} 端口子集;null = 无有效历史结果(调用方退化为全量)
1017
+ */
1018
+ _resolveRecheckPorts() {
1019
+ const report = this.loginReport;
1020
+ if (
1021
+ !report ||
1022
+ report.error ||
1023
+ !Array.isArray(report.ports) ||
1024
+ report.ports.length === 0
1025
+ ) {
1026
+ return null;
1027
+ }
1028
+ const last = new Map(report.ports.map((r) => [Number(r.port), r.status]));
1029
+ return this.loginPorts.filter((p) => last.get(Number(p)) !== "logged_in");
1030
+ }
1031
+
1032
+ /**
1033
+ * 合并登录检测结果:本次检测过的端口用新结果,未检端口保留上次结果。
1034
+ * 全量检测时未检端口为空,行为等同整体替换(与合并前一致)。
1035
+ * @param {Array|null} results 本次端口级结果(null = 无结果,仅保留旧数据)
1036
+ * @param {number[]} checkedPorts 本次实际检测的端口
1037
+ * @param {string} [error] 本次检测的整级错误(可选)
1038
+ */
1039
+ _mergeLoginReport(results, checkedPorts, error) {
1040
+ const prev =
1041
+ this.loginReport && Array.isArray(this.loginReport.ports)
1042
+ ? this.loginReport.ports
1043
+ : [];
1044
+ const checked = new Set(checkedPorts.map(Number));
1045
+ const order = this.loginPorts.map(Number);
1046
+ const byPort = new Map();
1047
+ // 未被本次覆盖的配置端口保留旧结果(配置外残留丢弃,全量检测时行为等同整体替换)
1048
+ for (const r of prev) {
1049
+ const key = Number(r.port);
1050
+ if (!checked.has(key) && order.includes(key)) byPort.set(key, r);
1051
+ }
1052
+ if (Array.isArray(results)) {
1053
+ for (const r of results) byPort.set(Number(r.port), r);
1054
+ }
1055
+ const ports = [...byPort.values()].sort((a, b) => {
1056
+ const ia = order.indexOf(Number(a.port));
1057
+ const ib = order.indexOf(Number(b.port));
1058
+ return (ia < 0 ? order.length : ia) - (ib < 0 ? order.length : ib);
1059
+ });
1060
+ const report = { ports, checkedAt: Date.now() };
1061
+ if (error) report.error = error;
1062
+ return report;
1063
+ }
1064
+
1065
+ /**
1066
+ * 整级失败时的 loginReport:子集重检失败保留未检端口历史结果,全量检测失败整体记错(原行为)
1067
+ */
1068
+ _failedLoginReport(checkedPorts, error) {
1069
+ if (checkedPorts.length < this.loginPorts.length) {
1070
+ return this._mergeLoginReport(null, checkedPorts, error);
1071
+ }
1072
+ return { error, checkedAt: Date.now() };
1073
+ }
1074
+
989
1075
  /**
990
1076
  * 执行一次登录检测:spawn 子进程跑 tt-help login-check(一次性短任务,不进 ProcessManager)
991
1077
  */
992
- async _runLoginCheck() {
1078
+ async _runLoginCheck(portsOverride) {
993
1079
  this._loginChecking = true;
994
- const ports = this.loginPorts;
1080
+ const ports =
1081
+ Array.isArray(portsOverride) && portsOverride.length > 0
1082
+ ? portsOverride
1083
+ : this.loginPorts;
995
1084
  console.error(`[watchdog] 开始登录检测 (端口: ${ports.join(",")})...`);
996
1085
  try {
997
1086
  // 全局安装用 tt-help 命令;源码模式用 node cli.js
@@ -1050,7 +1139,7 @@ export class WatchdogAgent {
1050
1139
  // stdout 不是 JSON → 走错误分支
1051
1140
  }
1052
1141
  if (partial) {
1053
- this.loginReport = { ports: partial, checkedAt: Date.now() };
1142
+ this.loginReport = this._mergeLoginReport(partial, ports);
1054
1143
  console.error(
1055
1144
  `[watchdog] 登录检测完成(部分失败): ${
1056
1145
  String(stderr)
@@ -1063,16 +1152,16 @@ export class WatchdogAgent {
1063
1152
  );
1064
1153
  return;
1065
1154
  }
1066
- this.loginReport = {
1067
- error: `exit ${code}: ${String(stderr).slice(-500)}`,
1068
- checkedAt: Date.now(),
1069
- };
1155
+ this.loginReport = this._failedLoginReport(
1156
+ ports,
1157
+ `exit ${code}: ${String(stderr).slice(-500)}`,
1158
+ );
1070
1159
  console.error(`[watchdog] 登录检测失败: ${this.loginReport.error}`);
1071
1160
  return;
1072
1161
  }
1073
1162
  const parsed = JSON.parse(String(stdout).trim());
1074
1163
  if (!Array.isArray(parsed)) throw new Error("结果不是数组");
1075
- this.loginReport = { ports: parsed, checkedAt: Date.now() };
1164
+ this.loginReport = this._mergeLoginReport(parsed, ports);
1076
1165
  const summary = parsed
1077
1166
  .map(
1078
1167
  (r) =>
@@ -1081,7 +1170,7 @@ export class WatchdogAgent {
1081
1170
  .join(" ");
1082
1171
  console.error(`[watchdog] 登录检测完成: ${summary}`);
1083
1172
  } catch (e) {
1084
- this.loginReport = { error: e.message, checkedAt: Date.now() };
1173
+ this.loginReport = this._failedLoginReport(ports, e.message);
1085
1174
  console.error(`[watchdog] 登录检测异常: ${e.message}`);
1086
1175
  } finally {
1087
1176
  this._loginChecking = false;