tt-help-cli-ycl 1.4.179 → 1.4.181
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 +1 -1
- package/scripts/test-login-pool-ui.mjs +1 -1
- package/scripts/test-protocol-bridge.mjs +39 -8
- package/scripts/verify-protocol-quote.mjs +35 -0
- package/src/cli/login-pool.js +78 -30
- package/src/cli/protocol-bridge.js +3 -3
- package/src/lib/args.js +7 -2
- package/src/lib/constants.js +2 -2
- package/src/watch/server.js +2 -2
package/package.json
CHANGED
|
@@ -157,7 +157,7 @@ check("状态徽章=使用中", row1.includes("使用中"));
|
|
|
157
157
|
check("使用者=机器:口", row1.includes("F-11:9222"));
|
|
158
158
|
const poolSrc = await page.content();
|
|
159
159
|
check("独立页无 cookie 明文", !poolSrc.includes("SECRET-SID-XYZ"));
|
|
160
|
-
check("
|
|
160
|
+
check("独立页检测按钮已点亮(重登录已改名)", await page.$$eval("#content button:not([disabled])", (bs) => bs.some((b) => b.textContent.trim() === "检测")));
|
|
161
161
|
check("独立页含新增账号/批量检测按钮", await page.$$eval(".bar button", (bs) => {
|
|
162
162
|
const t = bs.map((b) => b.textContent).join();
|
|
163
163
|
return t.includes("新增账号") && t.includes("批量检测");
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 协议桥单元测试 — parseProtocolUrl 白名单/参数校验 + 双平台模板生成
|
|
2
|
+
* 协议桥单元测试 — parseProtocolUrl 白名单/参数校验 + 双平台模板生成 + 单 token 别名路由
|
|
3
3
|
*
|
|
4
4
|
* 覆盖:
|
|
5
5
|
* [1] URL 解析:合法样例(大小写 normalize / origin 归一 / 尾斜杠 / note 清洗)
|
|
6
6
|
* [2] URL 拒绝:白名单外 action / 缺参 / 非 http server / username 非法 / scheme 不对
|
|
7
7
|
* [3] 模板生成:AppleScript(osacompile 源)与 Windows 包装 cmd 的结构断言
|
|
8
|
+
* [4] 别名路由:protocol-handle 单 token(OS 拉起链路兼容;parseArgs 无参签名读 process.argv)
|
|
8
9
|
*
|
|
9
10
|
* 运行: node scripts/test-protocol-bridge.mjs
|
|
10
11
|
*/
|
|
11
12
|
import { strict as assert } from "assert";
|
|
12
13
|
import { parseProtocolUrl, buildAppleScript, buildWindowsCmd } from "../src/cli/protocol-bridge.js";
|
|
14
|
+
import { parseArgs } from "../src/lib/args.js";
|
|
13
15
|
|
|
14
16
|
let pass = 0;
|
|
15
17
|
let fail = 0;
|
|
@@ -101,31 +103,60 @@ check("AppleScript: 生成源可通过 osacompile 语法结构(do shell script
|
|
|
101
103
|
const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
|
|
102
104
|
const m = src.match(/do shell script ("(?:[^"\\]|\\.)*")/);
|
|
103
105
|
assert.ok(m, "do shell script 后必须是合法 AppleScript 字符串字面量");
|
|
104
|
-
// 拼接表达式收尾
|
|
105
106
|
assert.ok(/& quoted form of theURL & "(?:[^"\\]|\\.)*"\s*\nend open location/.test(src), "拼接式必须完整闭合");
|
|
107
|
+
assert.ok(src.includes('protocol handle \\"%s\\"'), "URL 槽必须引号化(quoted form 引号会被生成侧 shell 消费)");
|
|
106
108
|
});
|
|
107
109
|
check("AppleScript: node 路径烧录且单引号包裹", () => {
|
|
108
110
|
const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
|
|
109
111
|
assert.ok(src.includes(`'${NODE}'`), "node 绝对路径必须烧录");
|
|
110
112
|
assert.ok(src.includes(`'${CLI}'`), "cli.js 绝对路径必须烧录");
|
|
111
113
|
});
|
|
112
|
-
check("AppleScript:
|
|
113
|
-
const src = buildAppleScript({ nodePath:
|
|
114
|
-
assert.ok(src.includes(
|
|
114
|
+
check("AppleScript: .command 用 protocol handle 两 token(对齐路由)", () => {
|
|
115
|
+
const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
|
|
116
|
+
assert.ok(src.includes('protocol handle \\"%s\\"'), "必须 protocol handle 且 URL 槽引号化");
|
|
117
|
+
assert.ok(!src.includes("protocol-handle"), "不得残留单 token 旧形态");
|
|
115
118
|
});
|
|
116
|
-
check("AppleScript: .command 退出码保持 + 按键防闪退", () => {
|
|
119
|
+
check("AppleScript: .command 退出码保持 + 按键防闪退 + 自清理", () => {
|
|
117
120
|
const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
|
|
118
121
|
assert.ok(src.includes("st=$?"), "必须捕获退出码");
|
|
119
122
|
assert.ok(src.includes("read -n 1"), "必须按键保持窗口");
|
|
123
|
+
assert.ok(src.includes("rm -f"), "必须自清理临时 .command");
|
|
120
124
|
});
|
|
121
|
-
check("Windows cmd: 绝对路径 + %1
|
|
125
|
+
check("Windows cmd: 绝对路径 + protocol handle %1 + pause", () => {
|
|
122
126
|
const cmd = buildWindowsCmd({ nodePath: "C:\\n\\node.exe", cliPath: "C:\\p\\cli.js" });
|
|
123
127
|
assert.ok(cmd.startsWith("@echo off"), "必须关回显");
|
|
124
|
-
assert.ok(cmd.includes('"C:\\n\\node.exe" "C:\\p\\cli.js" protocol
|
|
128
|
+
assert.ok(cmd.includes('"C:\\n\\node.exe" "C:\\p\\cli.js" protocol handle "%~1"'), "URL 槽必须 %~1 再引号化");
|
|
129
|
+
assert.ok(!cmd.includes("protocol-handle"), "不得残留单 token 旧形态");
|
|
125
130
|
assert.ok(cmd.includes("pause"), "必须 pause 防闪退");
|
|
126
131
|
assert.ok(cmd.includes("exit /b %RC%"), "必须透传退出码");
|
|
127
132
|
assert.ok(cmd.includes("\r\n"), "必须是 CRLF");
|
|
128
133
|
});
|
|
129
134
|
|
|
135
|
+
// ── [4] 单 token 别名路由 ──
|
|
136
|
+
console.log("[4] 别名路由(parseArgs 无参签名,读 process.argv)");
|
|
137
|
+
check("protocol-handle 单 token → protocol/handle(OS 拉起链路兼容)", () => {
|
|
138
|
+
const oldArgv = process.argv;
|
|
139
|
+
process.argv = ["node", "tt-help", "protocol-handle", "tt-help://login-pool/add?server=http://x.io"];
|
|
140
|
+
try {
|
|
141
|
+
const p = parseArgs();
|
|
142
|
+
assert.equal(p.subcommand, "protocol");
|
|
143
|
+
assert.equal(p.action, "handle");
|
|
144
|
+
assert.equal(p.url, "tt-help://login-pool/add?server=http://x.io");
|
|
145
|
+
} finally {
|
|
146
|
+
process.argv = oldArgv;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
check("protocol handle 两 token 仍正常", () => {
|
|
150
|
+
const oldArgv = process.argv;
|
|
151
|
+
process.argv = ["node", "tt-help", "protocol", "handle", "tt-help://login-pool/add?server=http://x.io"];
|
|
152
|
+
try {
|
|
153
|
+
const p = parseArgs();
|
|
154
|
+
assert.equal(p.subcommand, "protocol");
|
|
155
|
+
assert.equal(p.action, "handle");
|
|
156
|
+
} finally {
|
|
157
|
+
process.argv = oldArgv;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
130
161
|
console.log(`\n结果: ${pass} 通过, ${fail} 失败`);
|
|
131
162
|
process.exit(fail ? 1 : 0);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 验证模板输出的 .command 里 URL 是否带引号(模拟 printf 行为)
|
|
3
|
+
import { buildAppleScript, buildWindowsCmd } from "../src/cli/protocol-bridge.js";
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { strict as assert } from "assert";
|
|
7
|
+
|
|
8
|
+
const NODE = "/Users/x/.nvm/versions/node/v24.14.1/bin/node";
|
|
9
|
+
const CLI = "/Users/x/pkg/cli.js";
|
|
10
|
+
const URL = "tt-help://login-pool/relogin?server=http%3A%2F%2F117.71.53.99%3A17301&username=asset698";
|
|
11
|
+
|
|
12
|
+
const src = buildAppleScript({ nodePath: NODE, cliPath: CLI });
|
|
13
|
+
|
|
14
|
+
// 从 AppleScript 源抽取 sh 命令(模拟 AppleScript 字符串求值:\" → ",\\ → \)
|
|
15
|
+
const m = src.match(/do shell script "((?:[^"\\]|\\.)*)" & quoted form of theURL & "((?:[^"\\]|\\.)*)"\n/);
|
|
16
|
+
assert.ok(m, "do shell script 拼接式结构完整");
|
|
17
|
+
const unesc = (s) => s.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
18
|
+
const sh = unesc(m[1]) + "'" + URL + "'" + unesc(m[2]);
|
|
19
|
+
|
|
20
|
+
// 用 /bin/sh 真实执行 printf(macOS 上等价;这里在 Linux 模拟——printf POSIX 行为一致)
|
|
21
|
+
const out2 = execFileSync("/bin/sh", ["-c", sh.replace("open -a Terminal", "echo OPEN-TARGET:")], { encoding: "utf-8" });
|
|
22
|
+
const target = (out2.match(/OPEN-TARGET: (\S+)/) || [])[1];
|
|
23
|
+
assert.ok(target, "未取得 .command 路径");
|
|
24
|
+
const out = readFileSync(target, "utf-8");
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// 断言:URL 必须带引号出现在 .command 里
|
|
28
|
+
assert.ok(out.includes(`protocol handle "${URL}"`), "URL 必须被双引号包裹");
|
|
29
|
+
assert.ok(out.includes(`"${NODE}"`), "node 路径必须被双引号包裹");
|
|
30
|
+
assert.ok(out.includes(`"${CLI}"`), "cli 路径必须被双引号包裹");
|
|
31
|
+
console.log("✓ macOS:URL/路径均已引号化(& 不再截断命令)");
|
|
32
|
+
|
|
33
|
+
const w = buildWindowsCmd({ nodePath: "C:\\n\\node.exe", cliPath: "C:\\p\\cli.js" });
|
|
34
|
+
assert.ok(w.includes('protocol handle "%~1"'), "Windows 必须用 %~1 再引号化");
|
|
35
|
+
console.log("✓ Windows:protocol handle \"%~1\"(引号恒在)");
|
package/src/cli/login-pool.js
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* add: 起独立 Edge(临时目录+代理)→ 人工在窗口里登录 TikTok → 轮询 passport 检出
|
|
10
10
|
* → 导出 cookies → POST /api/login-pool/import(幂等,同号跳过)
|
|
11
|
-
* relogin: POST /prepare 取池内旧 cookies → 注入本机 Edge
|
|
12
|
-
* →
|
|
11
|
+
* relogin: 检测:POST /prepare 取池内旧 cookies → 注入本机 Edge 打开账号当前登录态
|
|
12
|
+
* → 人工核对/退出重登(机器不做任何自动判定)→ 回车提交 → 身份校验 → POST /relogin 覆盖刷新
|
|
13
13
|
* check: 服务端无浏览器 HTTP 探测各号登录态(默认空闲+疑似),死号自动标疑似
|
|
14
14
|
*
|
|
15
15
|
* 协议桥(页面一键拉起本命令)为独立后续项,本命令即其执行端。
|
|
16
16
|
*/
|
|
17
17
|
import { mkdtempSync, rmSync } from "fs";
|
|
18
|
+
import { createInterface } from "readline";
|
|
18
19
|
import { tmpdir } from "os";
|
|
19
20
|
import { join } from "path";
|
|
20
21
|
import { chromium } from "playwright";
|
|
@@ -23,11 +24,37 @@ import { proxy as configuredProxy } from "../lib/constants.js";
|
|
|
23
24
|
|
|
24
25
|
const POLL_INTERVAL_MS = 3000;
|
|
25
26
|
const CDP_FALLBACK_NOTE = "(关掉窗口或 Ctrl+C 可中止)";
|
|
27
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
26
28
|
|
|
27
29
|
function log(t) {
|
|
28
30
|
console.error(`[登录池] ${t}`);
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/** 等待回车提交;stdin 关闭或超时则中止(不更新池)。返回 {promise, abort}:失败路径须 abort() 关闭监听,否则进程不退 */
|
|
34
|
+
function waitEnter(timeoutMs) {
|
|
35
|
+
let finish = null;
|
|
36
|
+
const promise = new Promise((resolve, reject) => {
|
|
37
|
+
const rl = createInterface({ input: process.stdin });
|
|
38
|
+
let done = false;
|
|
39
|
+
finish = (err) => {
|
|
40
|
+
if (done) return;
|
|
41
|
+
done = true;
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
rl.close();
|
|
44
|
+
process.stdin.pause();
|
|
45
|
+
process.stdin.unref(); // 释放事件循环对 stdin 的引用:提交/中止后进程立即退出
|
|
46
|
+
err ? reject(err) : resolve();
|
|
47
|
+
};
|
|
48
|
+
const timer = setTimeout(
|
|
49
|
+
() => finish(new Error(`等待回车超时(${Math.round(timeoutMs / 1000)}s),已中止(不更新池)`)),
|
|
50
|
+
timeoutMs,
|
|
51
|
+
);
|
|
52
|
+
rl.once("line", () => finish(null));
|
|
53
|
+
rl.once("close", () => finish(new Error("stdin 已关闭,已中止(不更新池)")));
|
|
54
|
+
});
|
|
55
|
+
return { promise, abort: (msg) => finish && finish(new Error(msg || "已中止")) };
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
/** HTTP POST,默认 30s 超时(check 场景调用方自带更长超时) */
|
|
32
59
|
async function post(base, path, body, timeoutMs = 30 * 1000) {
|
|
33
60
|
const res = await fetch(`${String(base).replace(/\/+$/, "")}/api/login-pool/${path}`, {
|
|
@@ -106,45 +133,66 @@ async function cmdAdd(opts) {
|
|
|
106
133
|
}
|
|
107
134
|
}
|
|
108
135
|
|
|
109
|
-
/** relogin
|
|
136
|
+
/** relogin 子命令:检测——打开账号当前登录态,人工核对/退出重登后回车提交更新 */
|
|
110
137
|
async function cmdRelogin(opts) {
|
|
111
138
|
const { serverUrl, proxy, username, timeoutMs, dir } = opts;
|
|
112
139
|
if (!username) throw new Error("用法: tt-help login-pool relogin <username>");
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
name: c.name,
|
|
119
|
-
value: c.value,
|
|
120
|
-
domain: c.domain || ".tiktok.com",
|
|
121
|
-
path: c.path || "/",
|
|
122
|
-
secure: c.secure !== false,
|
|
123
|
-
httpOnly: c.httpOnly !== false,
|
|
124
|
-
...(typeof c.expires === "number" && c.expires > 0 ? { expires: c.expires } : {}),
|
|
125
|
-
}));
|
|
126
|
-
const session = await openEdgeSession({ proxy, dir });
|
|
140
|
+
// 尽早挂回车监听:fd0 在重活(Edge 启动/页面导航)期间可能被环境消费,迟挂会丢事件;
|
|
141
|
+
// 提前监听则回车随时可达,结果由下方 await 消费
|
|
142
|
+
const enter = waitEnter(timeoutMs);
|
|
143
|
+
enter.promise.catch(() => {}); // 防 unhandled rejection(真正消费在下方 await/abort)
|
|
144
|
+
let session = null;
|
|
127
145
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
const prep = await post(serverUrl, "prepare", { username });
|
|
147
|
+
const oldCookies = prep.account.cookies;
|
|
148
|
+
log(`目标 @${username}(池内 ${oldCookies.length} 条 cookies)。在窗口中打开当前登录态…`);
|
|
149
|
+
const toPlaywrightCookies = (cookies) =>
|
|
150
|
+
(cookies || []).map((c) => ({
|
|
151
|
+
name: c.name,
|
|
152
|
+
value: c.value,
|
|
153
|
+
domain: c.domain || ".tiktok.com",
|
|
154
|
+
path: c.path || "/",
|
|
155
|
+
secure: c.secure !== false,
|
|
156
|
+
httpOnly: c.httpOnly !== false,
|
|
157
|
+
...(typeof c.expires === "number" && c.expires > 0 ? { expires: c.expires } : {}),
|
|
158
|
+
}));
|
|
159
|
+
session = await openEdgeSession({ proxy, dir });
|
|
160
|
+
await session.context.addCookies(toPlaywrightCookies(oldCookies));
|
|
161
|
+
await gotoTiktok(session.page);
|
|
162
|
+
log(`已在窗口打开 @${username}。可保持不动 / 退出重登 / 直接处理异常(人工操作)`);
|
|
163
|
+
log(
|
|
164
|
+
`操作完成后回这里按回车,提交最新登录态(${Math.round(timeoutMs / 1000)}s 无操作自动中止;关窗口=中止)…`,
|
|
165
|
+
);
|
|
166
|
+
await enter.promise;
|
|
167
|
+
|
|
168
|
+
// 读提交时刻的登录态:页面可能在加载,未检出时短暂重试
|
|
169
|
+
let id = null;
|
|
170
|
+
for (let i = 0; i < 3; i++) {
|
|
171
|
+
id = await whoAmI(session.page);
|
|
172
|
+
if (id.hasAccount) break;
|
|
173
|
+
if (i < 2) {
|
|
174
|
+
log("未检测到登录态,3 秒后重试…");
|
|
175
|
+
await sleep(3000);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!id || !id.hasAccount) {
|
|
179
|
+
enter.abort();
|
|
180
|
+
throw new Error("窗口当前未登录(登出后未重登?),已中止(不更新池)");
|
|
137
181
|
}
|
|
138
182
|
if (id.username !== username) {
|
|
183
|
+
enter.abort();
|
|
139
184
|
throw new Error(`身份不符:窗口登录的是 @${id.username},目标是 @${username},已中止(不更新池)`);
|
|
140
185
|
}
|
|
141
186
|
const cookies = await session.context.cookies();
|
|
142
|
-
const r = await post(serverUrl, "relogin", { username, cookies, userId: "cli-login-pool", note: "
|
|
187
|
+
const r = await post(serverUrl, "relogin", { username, cookies, userId: "cli-login-pool", note: "命令行检测更新" });
|
|
143
188
|
log(
|
|
144
|
-
`✓ @${username}
|
|
189
|
+
`✓ @${username} 检测完成: cookies 已覆盖刷新(${r.account.cookieCount} 条)${r.revived ? ",已停用账号复活回池" : r.wasSuspect ? ",疑似已清回池" : ""}`,
|
|
145
190
|
);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
enter.abort(); // 失败路径关闭回车监听,否则 readline 持有事件循环、进程不退
|
|
193
|
+
throw e;
|
|
146
194
|
} finally {
|
|
147
|
-
await session.cleanup();
|
|
195
|
+
if (session) await session.cleanup();
|
|
148
196
|
}
|
|
149
197
|
}
|
|
150
198
|
|
|
@@ -192,7 +240,7 @@ export async function handleLoginPool(parsed) {
|
|
|
192
240
|
" tt-help login-pool check [--server URL] [--proxy URL] [--ids id1,id2] [--json]",
|
|
193
241
|
"",
|
|
194
242
|
" add 新登录收号:弹出 Edge 人工登录 TikTok,检出后自动入池(幂等)",
|
|
195
|
-
" relogin
|
|
243
|
+
" relogin 检测:窗口打开账号当前登录态,人工核对/退出重登后按回车提交,身份校验后覆盖刷新",
|
|
196
244
|
" check 巡检:服务端 HTTP 探测各号登录态,死号自动标疑似",
|
|
197
245
|
].join("\n"),
|
|
198
246
|
);
|
|
@@ -102,7 +102,7 @@ async function cmdHandle(rawUrl) {
|
|
|
102
102
|
noProxy: false,
|
|
103
103
|
note: p.note || null,
|
|
104
104
|
dir: null,
|
|
105
|
-
timeoutSec:
|
|
105
|
+
timeoutSec: 600,
|
|
106
106
|
ids: null,
|
|
107
107
|
json: false,
|
|
108
108
|
showHelp: false,
|
|
@@ -118,7 +118,7 @@ export function buildAppleScript({ nodePath, cliPath }) {
|
|
|
118
118
|
// .command 内容: #!/bin/bash + 跑 node(不 exec,退出码非 0 时窗口必留)+ 按键保持
|
|
119
119
|
const shellP1 =
|
|
120
120
|
"t=$(mktemp ${TMPDIR:-/tmp}/tt-help-bridge.XXXXXX); f=\"$t.command\"; mv \"$t\" \"$f\"; " + // mktemp 要求 X 结尾,先随机后 mv 补 .command 后缀(实测:直接带后缀会退化成固定名,并发互踩)
|
|
121
|
-
"printf '#!/bin/bash\\n%s %s protocol
|
|
121
|
+
"printf '#!/bin/bash\\n\"%s\" \"%s\" protocol handle \"%s\"\\nst=$?\\necho\\nrm -f \"$0\"\\nread -n 1 -s -r -p \"完成(退出码 $st)——按任意键关闭…\"\\nexit $st\\n' " +
|
|
122
122
|
`${shQ(nodePath)} ${shQ(cliPath)} `;
|
|
123
123
|
const shellP2 = ' > "$f"; chmod +x "$f"; /usr/bin/open -a Terminal "$f"';
|
|
124
124
|
return (
|
|
@@ -204,7 +204,7 @@ export function buildWindowsCmd({ nodePath, cliPath }) {
|
|
|
204
204
|
return [
|
|
205
205
|
"@echo off",
|
|
206
206
|
"title tt-help 登录池",
|
|
207
|
-
`"${nodePath}" "${cliPath}" protocol
|
|
207
|
+
`"${nodePath}" "${cliPath}" protocol handle "%~1"`,
|
|
208
208
|
"set RC=%ERRORLEVEL%",
|
|
209
209
|
"echo.",
|
|
210
210
|
`pause`,
|
package/src/lib/args.js
CHANGED
|
@@ -1175,7 +1175,7 @@ function parseLoginPoolArgs(args) {
|
|
|
1175
1175
|
let noProxy = false;
|
|
1176
1176
|
let note = null;
|
|
1177
1177
|
let dir = null;
|
|
1178
|
-
let timeoutSec =
|
|
1178
|
+
let timeoutSec = 600;
|
|
1179
1179
|
let ids = null;
|
|
1180
1180
|
let json = false;
|
|
1181
1181
|
let showHelp = false;
|
|
@@ -1193,7 +1193,7 @@ function parseLoginPoolArgs(args) {
|
|
|
1193
1193
|
} else if (arg === "--dir" && i + 1 < args.length) {
|
|
1194
1194
|
dir = args[++i];
|
|
1195
1195
|
} else if (arg === "--timeout" && i + 1 < args.length) {
|
|
1196
|
-
timeoutSec = parseInt(args[++i], 10) ||
|
|
1196
|
+
timeoutSec = parseInt(args[++i], 10) || 600;
|
|
1197
1197
|
} else if (arg === "--ids" && i + 1 < args.length) {
|
|
1198
1198
|
ids = args[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
1199
1199
|
} else if (arg === "--json") {
|
|
@@ -1806,6 +1806,11 @@ export function parseArgs() {
|
|
|
1806
1806
|
return parseProtocolBridgeArgs(args.slice(1));
|
|
1807
1807
|
}
|
|
1808
1808
|
|
|
1809
|
+
// protocol-handle 单 token 别名:协议处理器拉起链路的历史形态,兼容已注册机器
|
|
1810
|
+
if (args.length > 0 && args[0] === "protocol-handle") {
|
|
1811
|
+
return parseProtocolBridgeArgs(["handle", ...args.slice(1)]);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1809
1814
|
if (args.length > 0 && args[0] === "comments") {
|
|
1810
1815
|
return parseCommentsArgs(args.slice(1));
|
|
1811
1816
|
}
|
package/src/lib/constants.js
CHANGED
|
@@ -294,13 +294,13 @@ const HELP_TEXT = [
|
|
|
294
294
|
" tt-help login-pool <add|relogin|check> [选项]",
|
|
295
295
|
" 账号中心:新登录收号 / 重登录 / 巡检(与网页 /login-pool 同一套账号库)",
|
|
296
296
|
" add 弹出独立 Edge,人工登录 TikTok 后自动导出登录态入池(幂等)",
|
|
297
|
-
" relogin <username>
|
|
297
|
+
" relogin <username> 检测:窗口打开账号当前登录态,人工核对/退出重登后按回车提交,覆盖刷新池内 cookies",
|
|
298
298
|
" 选项:",
|
|
299
299
|
" --server <URL> 账号中心地址(watch 服务端)",
|
|
300
300
|
" --proxy <地址> Edge/探测代理,默认配置文件 proxy;--no-proxy 直连",
|
|
301
301
|
" --note <备注> add 备注信息",
|
|
302
302
|
" --dir <路径> 自定义 Edge 用户目录(默认临时目录用后即清)",
|
|
303
|
-
" --timeout <秒>
|
|
303
|
+
" --timeout <秒> 等待人工操作上限,默认 600",
|
|
304
304
|
" --ids <id1,id2> check 只探测指定账号",
|
|
305
305
|
" --json check 输出 JSON",
|
|
306
306
|
" 示例: tt-help login-pool add --server http://172.18.154.201:3011",
|
package/src/watch/server.js
CHANGED
|
@@ -758,7 +758,7 @@ async function load() {
|
|
|
758
758
|
(a.status === 'retired' ? '<button onclick="enable(\\'' + a.id + '\\',\\'' + esc(a.username) + '\\',false)">启用</button> ' : '') +
|
|
759
759
|
(a.status !== 'retired' ? '<button onclick="disable(\\'' + a.id + '\\',\\'' + esc(a.username) + '\\',' + (a.status === 'leased') + ')">停用</button> ' : '') +
|
|
760
760
|
(a.status === 'retired' ? '<button onclick="del(\\'' + a.id + '\\')">删除</button> ' : '') +
|
|
761
|
-
'<button onclick="reloginDlg(\\'' + esc(a.username) + '\\')" title="一键拉起本机 tt-help
|
|
761
|
+
'<button onclick="reloginDlg(\\'' + esc(a.username) + '\\')" title="一键拉起本机 tt-help 检测:窗口打开账号当前登录态,可退出重登或保持,回终端按回车提交更新池内 cookies">检测</button>' +
|
|
762
762
|
'</td>' +
|
|
763
763
|
'<td><details data-acc="' + esc(a.id) + '" ' + (openHist.has(a.id) ? 'open' : '') + ' ontoggle="onHistToggle(this)"><summary class="muted">' + (a.history ? a.history.length : 0) + ' 条</summary>' +
|
|
764
764
|
(a.history || []).slice(0, 8).map(h => '<div class="muted">' + fmt(h.at) + ' ' + esc(h.event) + ' ' + esc(h.detail) + '</div>').join('') + '</details></td>' +
|
|
@@ -809,7 +809,7 @@ function addCmdDlg() {
|
|
|
809
809
|
function reloginDlg(name) {
|
|
810
810
|
launchProto(protoHref('login-pool/relogin', { server: location.origin, username: name }),
|
|
811
811
|
'tt-help login-pool relogin ' + name + ' --server ' + location.origin,
|
|
812
|
-
'已拉起 tt-help
|
|
812
|
+
'已拉起 tt-help 检测 @' + name + ':窗口中核对/退出重登,完成后回终端按回车提交,然后回来点刷新');
|
|
813
813
|
}
|
|
814
814
|
function setProgress(t) {
|
|
815
815
|
const el = document.getElementById('msg');
|