tt-help-cli-ycl 1.3.60 → 1.3.62
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/src/lib/browser/page.js +67 -67
package/package.json
CHANGED
package/src/lib/browser/page.js
CHANGED
|
@@ -66,92 +66,92 @@ export async function withBrowserRecovery(fn, browser, page, cdpOptions, port) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
const DOM_CHECK_TIMEOUT = 20000; // 单次 DOM 检测超时 20 秒
|
|
70
|
+
const DOM_CHECK_RETRIES = 3; // DOM 检测最大重试次数
|
|
71
|
+
|
|
69
72
|
/**
|
|
70
73
|
* 判断登录状态:Cookie 为主,DOM 验真为辅。
|
|
71
74
|
* - 无 sessionid Cookie → 未登录
|
|
72
|
-
* - sessionid Cookie 已过期 → 未登录
|
|
73
75
|
* - 有 Cookie + DOM 确认已登录 → 已登录
|
|
74
76
|
* - 有 Cookie + DOM 确认未登录(出现登录按钮)→ 未登录
|
|
75
|
-
* - 有 Cookie + DOM
|
|
77
|
+
* - 有 Cookie + DOM 无法判断(超时/元素未找到),重试后仍无法判断 → 信任 Cookie,判定已登录
|
|
76
78
|
*/
|
|
77
79
|
export async function isLoggedIn(page) {
|
|
78
80
|
const cookies = await page.context().cookies("https://www.tiktok.com");
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const domResult = await isLoggedInByDom(page);
|
|
97
|
-
// domResult: true=已登录, false=明确未登录, null=无法判断
|
|
98
|
-
if (domResult === true) {
|
|
99
|
-
console.error("[登录检测] DOM 验真确认已登录");
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
if (domResult === false) {
|
|
103
|
-
console.error("[登录检测] DOM 验真发现登录按钮,判定未登录(Cookie 可能已失效)");
|
|
104
|
-
return false;
|
|
81
|
+
const hasSessionId = cookies.some((c) => c.name === "sessionid");
|
|
82
|
+
if (!hasSessionId) return false;
|
|
83
|
+
|
|
84
|
+
// 重试 DOM 检测,直到得到明确结果或耗尽重试次数
|
|
85
|
+
for (let attempt = 1; attempt <= DOM_CHECK_RETRIES; attempt++) {
|
|
86
|
+
const domResult = await isLoggedInByDom(page);
|
|
87
|
+
// domResult: true=已登录, false=明确未登录, null=无法判断
|
|
88
|
+
if (domResult === true) return true;
|
|
89
|
+
if (domResult === false) return false;
|
|
90
|
+
// null: DOM 无法判断,刷新页面后重试
|
|
91
|
+
if (attempt < DOM_CHECK_RETRIES) {
|
|
92
|
+
console.error(
|
|
93
|
+
` [登录检测] DOM 无法判断,刷新页面后重试 (${attempt}/${DOM_CHECK_RETRIES})...`,
|
|
94
|
+
);
|
|
95
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
96
|
+
}
|
|
105
97
|
}
|
|
106
|
-
//
|
|
107
|
-
console.error(
|
|
98
|
+
// 重试后仍无法判断,信任 Cookie
|
|
99
|
+
console.error(
|
|
100
|
+
` [登录检测] DOM 检测 ${DOM_CHECK_RETRIES} 次均未明确结果,信任 Cookie 判定为已登录`,
|
|
101
|
+
);
|
|
108
102
|
return true;
|
|
109
103
|
}
|
|
110
104
|
|
|
111
105
|
/**
|
|
112
106
|
* 通过 DOM 元素判断登录状态(验真方案)
|
|
107
|
+
* 使用 locator API + state: 'attached' 来避免 CDP 连接下 waitForSelector 的可见性问题
|
|
113
108
|
* @returns {boolean|null} true=已登录, false=明确未登录, null=无法判断
|
|
114
109
|
*/
|
|
115
110
|
export async function isLoggedInByDom(page) {
|
|
116
|
-
//
|
|
117
|
-
const
|
|
118
|
-
'[class*="DivProfileContainer"]',
|
|
119
|
-
'[class*="DivUserContainer"]',
|
|
120
|
-
'[class*="UserMenu"]',
|
|
121
|
-
'[class*="CurrentUserInfo"]',
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
111
|
+
// 使用 Promise.race 等待:已登录元素 或 登录按钮,哪个先出现就停止
|
|
112
|
+
const loggedInLocators = [
|
|
113
|
+
page.locator('[class*="DivProfileContainer"]'),
|
|
114
|
+
page.locator('[class*="DivUserContainer"]'),
|
|
115
|
+
page.locator('[class*="UserMenu"]'),
|
|
116
|
+
page.locator('[class*="CurrentUserInfo"]'),
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const loginButtonLocators = [
|
|
120
|
+
page.getByText("登录", { exact: true }),
|
|
121
|
+
page.getByText("Log in", { exact: true }),
|
|
122
|
+
page.getByText("Sign in", { exact: true }),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// 并发等待:已登录元素 vs 登录按钮
|
|
126
|
+
const waitForLoggedIn = Promise.any(
|
|
127
|
+
loggedInLocators.map((loc) =>
|
|
128
|
+
loc.first().waitFor({ state: "attached", timeout: DOM_CHECK_TIMEOUT }),
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
.then(() => "loggedIn")
|
|
132
|
+
.catch(() => null);
|
|
133
|
+
|
|
134
|
+
const waitForLoginButton = Promise.any(
|
|
135
|
+
loginButtonLocators.map((loc) =>
|
|
136
|
+
loc.first().waitFor({ state: "attached", timeout: DOM_CHECK_TIMEOUT }),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
.then(() => "loginButton")
|
|
140
|
+
.catch(() => null);
|
|
141
|
+
|
|
142
|
+
// 哪个先完成就返回哪个结果
|
|
143
|
+
const result = await Promise.race([waitForLoggedIn, waitForLoginButton]);
|
|
144
|
+
|
|
145
|
+
if (result === "loginButton") {
|
|
146
|
+
// 明确看到登录按钮 → 未登录
|
|
147
|
+
return false;
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
console.error(`[登录检测] DOM 验真超时 (${timeouts[timeouts.length - 1]}ms),无法判断`);
|
|
152
|
-
return null;
|
|
149
|
+
if (result === "loggedIn") {
|
|
150
|
+
// 看到已登录元素 → 已登录
|
|
151
|
+
return true;
|
|
153
152
|
}
|
|
154
153
|
|
|
154
|
+
// 都超时了,回退到 evaluate 做最终判断
|
|
155
155
|
return page.evaluate(() => {
|
|
156
156
|
const hasProfileContainer = !!document.querySelector(
|
|
157
157
|
'[class*="DivProfileContainer"], [class*="DivUserContainer"]',
|
|
@@ -167,7 +167,7 @@ export async function isLoggedInByDom(page) {
|
|
|
167
167
|
if (hasLoginButton) return false;
|
|
168
168
|
// 看到已登录元素 → 已登录
|
|
169
169
|
if (hasProfileContainer || hasUserMenu) return true;
|
|
170
|
-
//
|
|
170
|
+
// 都无法判断
|
|
171
171
|
return null;
|
|
172
172
|
});
|
|
173
173
|
}
|