tt-help-cli-ycl 1.4.120 → 1.4.122

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.120",
3
+ "version": "1.4.122",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/attach.js CHANGED
@@ -163,8 +163,16 @@ export async function handleAttach(options) {
163
163
  ) {
164
164
  throw new CDNBlockedError(data.error);
165
165
  }
166
- // 其余失败(瞬时 503/5xx)返回 null,由服务端后续重新排队
167
- return null;
166
+ // 其余失败(504 超时 / 503 / 502 / 500 等):抛出带状态码与错误详情的异常,
167
+ // 让外层日志显示真实原因(如 page.goto 超时)而非笼统"未获取到用户信息"。
168
+ // 行为不变:failCount++、不标记 needRefresh、服务端后续重新排队重试。
169
+ const detail =
170
+ (data && typeof data.error === "string" && data.error) ||
171
+ res.statusText ||
172
+ "unknown";
173
+ const err = new Error(`webserver ${res.status}: ${detail}`);
174
+ err.statusCode = res.status;
175
+ throw err;
168
176
  }
169
177
  // 业务结果:TikTok 明确 statusCode(209002 验证码等)→ 返回上层走"已记录"分支
170
178
  if (data && data.error && data.tiktokStatusCode) {
@@ -177,8 +177,16 @@ export async function handlePrecheck(options) {
177
177
  ) {
178
178
  throw new CDNBlockedError(data.error);
179
179
  }
180
- // 其余失败(瞬时 503/5xx)返回 null,由服务端后续重新排队
181
- return null;
180
+ // 其余失败(504 超时 / 503 / 502 / 500 等):抛出带状态码与错误详情的异常,
181
+ // 让外层日志显示真实原因(如 page.goto 超时)而非笼统"未获取到用户信息"。
182
+ // 行为不变:failCount++、不标记 needRefresh、服务端后续重新排队重试。
183
+ const detail =
184
+ (data && typeof data.error === "string" && data.error) ||
185
+ res.statusText ||
186
+ "unknown";
187
+ const err = new Error(`webserver ${res.status}: ${detail}`);
188
+ err.statusCode = res.status;
189
+ throw err;
182
190
  }
183
191
  // 业务结果:TikTok 明确 statusCode(209002 验证码等)→ 返回上层走"已记录"分支
184
192
  if (data && data.error && data.tiktokStatusCode) {
@@ -3989,86 +3989,182 @@ async function wdSetCountries() {
3989
3989
 
3990
3990
  // ── 登录检测端口配置区域(看门狗面板中每个客户端卡片) ──
3991
3991
 
3992
+ // userId -> 详情展开状态(跨刷新保留)
3993
+ var _wdLoginExpanded = {};
3994
+
3995
+ function wdToggleLoginDetail(userId) {
3996
+ _wdLoginExpanded[userId] = !_wdLoginExpanded[userId];
3997
+ var el = document.getElementById("wdLoginDetail-" + wdEscId(userId));
3998
+ if (el) {
3999
+ el.style.display = _wdLoginExpanded[userId] ? "grid" : "none";
4000
+ }
4001
+ }
4002
+
4003
+ var _wdLoginStatusLabel = {
4004
+ logged_in: ["已登录", "#4ade80"],
4005
+ expired: ["已过期", "#fbbf24"],
4006
+ error: ["失败", "#f87171"],
4007
+ logged_out: ["未登录", "#f87171"],
4008
+ };
4009
+
4010
+ function wdLoginStatusLabel(s) {
4011
+ return _wdLoginStatusLabel[s] || ["未登录", "#888"];
4012
+ }
4013
+
3992
4014
  function renderLoginSection(c, userId, loginPorts) {
3993
4015
  if (!userId) return "";
3994
4016
  var cfgEntry = (loginPorts && loginPorts[userId]) || {};
3995
4017
  var configured =
3996
4018
  cfgEntry.ports && cfgEntry.ports.length ? cfgEntry.ports : [];
3997
4019
  var report = c.loginReport || null;
3998
- var reportHtml;
4020
+
4021
+ // 配置按钮:只显示端口数量,完整列表放 title
4022
+ var cfgBtn = tag(
4023
+ "button",
4024
+ {
4025
+ onclick: "wdShowLoginPortsModal('" + esc(userId) + "')",
4026
+ style:
4027
+ "padding:2px 8px;font-size:11px;background:#333;color:#4ade80;" +
4028
+ "border:1px solid #444;border-radius:3px;cursor:pointer",
4029
+ title:
4030
+ "配置要定期检测登录状态的 CDP 端口(逗号分隔,留空清空)\n当前: " +
4031
+ (configured.length ? configured.join(",") : "无"),
4032
+ },
4033
+ (configured.length ? configured.length + "端口" : "未配置") + " ⚙️",
4034
+ );
4035
+
4036
+ var head =
4037
+ styled("span", "font-size:14px", "🔑") +
4038
+ styled("span", "color:#888", "登录检测:") +
4039
+ cfgBtn;
4040
+
4041
+ var wrapStyle =
4042
+ "margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
4043
+ "display:flex;align-items:center;gap:8px;font-size:12px;flex-wrap:wrap";
4044
+
3999
4045
  if (!report) {
4000
- reportHtml = styled(
4001
- "span",
4002
- "color:#555;margin-left:auto",
4003
- configured.length ? "等待客户端上报..." : "",
4046
+ return styled(
4047
+ "div",
4048
+ wrapStyle,
4049
+ head +
4050
+ styled(
4051
+ "span",
4052
+ "color:#555;margin-left:auto",
4053
+ configured.length ? "等待客户端上报..." : "",
4054
+ ),
4004
4055
  );
4005
- } else if (report.error) {
4006
- var et = report.checkedAt
4007
- ? new Date(report.checkedAt).toLocaleTimeString("zh-CN")
4008
- : "?";
4009
- reportHtml =
4010
- styled("span", "color:#888;margin-left:auto", "最近检测(" + et + "): ") +
4011
- styled("span", "color:#f87171", "失败");
4012
- } else if (report.ports && report.ports.length) {
4013
- var et2 = report.checkedAt
4014
- ? new Date(report.checkedAt).toLocaleTimeString("zh-CN")
4015
- : "?";
4016
- var badges = report.ports
4017
- .map(function (r) {
4018
- var color =
4019
- r.status === "logged_in"
4020
- ? "#4ade80"
4021
- : r.status === "expired"
4022
- ? "#fbbf24"
4023
- : r.status === "error"
4024
- ? "#f87171"
4025
- : "#888";
4026
- var label =
4027
- r.status === "logged_in"
4028
- ? "已登录"
4029
- : r.status === "expired"
4030
- ? "已过期"
4031
- : r.status === "error"
4032
- ? "失败"
4033
- : "未登录";
4034
- var txt =
4035
- r.port +
4036
- " " +
4037
- label +
4038
- (r.status === "logged_in" && r.account ? " @" + r.account : "");
4039
- return styled(
4056
+ }
4057
+
4058
+ var et = report.checkedAt
4059
+ ? new Date(report.checkedAt).toLocaleTimeString("zh-CN", {
4060
+ hour: "2-digit",
4061
+ minute: "2-digit",
4062
+ })
4063
+ : "?";
4064
+
4065
+ if (report.error) {
4066
+ return styled(
4067
+ "div",
4068
+ wrapStyle,
4069
+ head +
4070
+ styled(
4040
4071
  "span",
4041
- "color:" +
4042
- color +
4043
- ";background:#0d1117;border:1px solid #333;padding:1px 6px;border-radius:4px",
4044
- txt,
4045
- );
4072
+ "color:#f87171;margin-left:auto",
4073
+ "最近检测(" + et + ") 失败",
4074
+ ),
4075
+ );
4076
+ }
4077
+
4078
+ var ports = Array.isArray(report.ports) ? report.ports : [];
4079
+ if (!ports.length) {
4080
+ return styled(
4081
+ "div",
4082
+ wrapStyle,
4083
+ head + styled("span", "color:#555;margin-left:auto", "无结果"),
4084
+ );
4085
+ }
4086
+
4087
+ // 汇总统计 + 异常端口内联提示
4088
+ var ok = 0;
4089
+ var abnormal = [];
4090
+ var hasError = false;
4091
+ for (var i = 0; i < ports.length; i++) {
4092
+ var r = ports[i];
4093
+ if (r.status === "logged_in") {
4094
+ ok++;
4095
+ } else {
4096
+ var lb = wdLoginStatusLabel(r.status);
4097
+ abnormal.push({ port: r.port, label: lb[0] });
4098
+ if (r.status === "error" || r.status === "logged_out") hasError = true;
4099
+ }
4100
+ }
4101
+ var sumColor = abnormal.length
4102
+ ? hasError
4103
+ ? "#f87171"
4104
+ : "#fbbf24"
4105
+ : "#4ade80";
4106
+ var summaryTxt =
4107
+ ok + "/" + ports.length + " 已登录" + (abnormal.length ? "" : " ✓");
4108
+ if (abnormal.length) {
4109
+ var shown = abnormal
4110
+ .slice(0, 3)
4111
+ .map(function (a) {
4112
+ return a.port + " " + a.label;
4046
4113
  })
4047
- .join(" ");
4048
- reportHtml =
4049
- styled("span", "color:#888;margin-left:auto", "最近检测(" + et2 + "): ") +
4050
- badges;
4051
- } else {
4052
- reportHtml = styled("span", "color:#555;margin-left:auto", "无结果");
4114
+ .join(", ");
4115
+ summaryTxt +=
4116
+ " ⚠ " + shown + (abnormal.length > 3 ? " +" + (abnormal.length - 3) : "");
4053
4117
  }
4118
+
4119
+ // 明细(默认折叠,点汇总展开/收起)
4120
+ var detailHtml = "";
4121
+ for (var j = 0; j < ports.length; j++) {
4122
+ var r2 = ports[j];
4123
+ var lb2 = wdLoginStatusLabel(r2.status);
4124
+ detailHtml += styled(
4125
+ "div",
4126
+ "display:flex;gap:6px;align-items:center;font-size:11px",
4127
+ styled("span", "color:#888;min-width:42px", r2.port) +
4128
+ styled("span", "color:" + lb2[1], lb2[0]) +
4129
+ (r2.status === "logged_in" && r2.account
4130
+ ? styled("span", "color:#4ade80", "@" + r2.account)
4131
+ : ""),
4132
+ );
4133
+ }
4134
+
4054
4135
  return styled(
4055
4136
  "div",
4056
- "margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;" +
4057
- "display:flex;align-items:center;gap:8px;font-size:12px;flex-wrap:wrap",
4058
- styled("span", "font-size:14px", "🔑") +
4059
- styled("span", "color:#888", "登录检测:") +
4137
+ "margin-top:6px;padding:6px 8px;background:#111;border-radius:6px;font-size:12px",
4138
+ styled(
4139
+ "div",
4140
+ "display:flex;align-items:center;gap:8px;flex-wrap:wrap",
4141
+ head +
4142
+ tag(
4143
+ "span",
4144
+ {
4145
+ style:
4146
+ "color:" +
4147
+ sumColor +
4148
+ ";margin-left:auto;cursor:pointer;text-decoration:underline dotted",
4149
+ onclick: "wdToggleLoginDetail('" + esc(userId) + "')",
4150
+ title: "点击展开/收起各端口明细",
4151
+ },
4152
+ summaryTxt,
4153
+ ) +
4154
+ styled("span", "color:#555", "最近 " + et),
4155
+ ) +
4060
4156
  tag(
4061
- "button",
4157
+ "div",
4062
4158
  {
4063
- onclick: "wdShowLoginPortsModal('" + esc(userId) + "')",
4159
+ id: "wdLoginDetail-" + wdEscId(userId),
4064
4160
  style:
4065
- "padding:2px 8px;font-size:11px;background:#333;color:#4ade80;" +
4066
- "border:1px solid #444;border-radius:3px;cursor:pointer",
4067
- title: "配置要定期检测登录状态的 CDP 端口(逗号分隔,留空清空)",
4161
+ "display:" +
4162
+ (_wdLoginExpanded[userId] ? "grid" : "none") +
4163
+ ";grid-template-columns:repeat(auto-fill,minmax(170px,1fr));" +
4164
+ "gap:3px 16px;margin-top:6px;padding-top:6px;border-top:1px dashed #2a2a3a",
4068
4165
  },
4069
- (configured.length ? configured.join(",") : "未配置") + " ⚙️",
4070
- ) +
4071
- reportHtml,
4166
+ detailHtml,
4167
+ ),
4072
4168
  );
4073
4169
  }
4074
4170
 
@@ -686,7 +686,7 @@
686
686
  </div>
687
687
  </div>
688
688
 
689
- <script src="app.js?v=20260816b"></script>
689
+ <script src="app.js?v=20260817"></script>
690
690
  </body>
691
691
 
692
692
  </html>
@@ -30,6 +30,7 @@ import { fileURLToPath } from "url";
30
30
  const __dirname = dirname(fileURLToPath(import.meta.url));
31
31
  const homeDir = homedir() || process.env.HOME || process.env.USERPROFILE || "";
32
32
  const TT_HELP_CONFIG = join(homeDir, ".tt-help.json");
33
+ const IS_WIN = process.platform === "win32";
33
34
 
34
35
  // ── 业务日志错误统计 → 触发节点切换 ──
35
36
  // 识别采集程序日志里已有的错误分类标签(explore 等已打标),连续错误达标则切换出口节点
@@ -612,9 +613,17 @@ export class WatchdogAgent {
612
613
  if (cfg.proxy) args.push("--proxy", cfg.proxy);
613
614
 
614
615
  const { stdout, stderr, code } = await new Promise((resolve) => {
615
- const child = spawn(cmd, args, {
616
- stdio: ["ignore", "pipe", "pipe"],
617
- });
616
+ // Windows 下 tt-help 是 .cmd 包装,必须 shell:true 才能被 cmd.exe 解析
617
+ // (与 process-manager.startCommand 的 Windows 处理保持一致)
618
+ const child = IS_WIN
619
+ ? spawn([cmd, ...args].join(" "), {
620
+ stdio: ["ignore", "pipe", "pipe"],
621
+ shell: true,
622
+ windowsHide: true,
623
+ })
624
+ : spawn(cmd, args, {
625
+ stdio: ["ignore", "pipe", "pipe"],
626
+ });
618
627
  let out = "",
619
628
  err = "";
620
629
  child.stdout.on("data", (d) => (out += d));