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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.3.60",
3
+ "version": "1.3.62",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 无法判断(超时/元素未找到)→ 信任 Cookie,判定已登录
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 sessionCookie = cookies.find((c) => c.name === "sessionid");
80
-
81
- if (!sessionCookie) {
82
- console.error("[登录检测] sessionid Cookie,判定未登录");
83
- return false;
84
- }
85
-
86
- // Cookie 过期检查:TikTok sessionid 可能是 session cookie(expires=-1)
87
- // 只有当 expires > 0 且已过期时才判定无效
88
- if (
89
- sessionCookie.expires > 0 &&
90
- sessionCookie.expires < Math.floor(Date.now() / 1000)
91
- ) {
92
- console.error("[登录检测] sessionid Cookie 已过期,判定未登录");
93
- return false;
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
- // null: DOM 无法判断,信任 Cookie
107
- console.error("[登录检测] DOM 验真超时,信任 Cookie 判定已登录");
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 loginOrLoggedInSelector = [
118
- '[class*="DivProfileContainer"]',
119
- '[class*="DivUserContainer"]',
120
- '[class*="UserMenu"]',
121
- '[class*="CurrentUserInfo"]',
122
- 'button:has-text("登录")',
123
- 'button:has-text("Log in")',
124
- 'button:has-text("Sign in")',
125
- ].join(", ");
126
-
127
- // 分阶段等待:先 5 秒,超时再重试 10
128
- const timeouts = [5000, 10000];
129
- let selectorFound = false;
130
-
131
- for (let i = 0; i < timeouts.length; i++) {
132
- const found = await page
133
- .waitForSelector(loginOrLoggedInSelector, { timeout: timeouts[i] })
134
- .then(() => true)
135
- .catch(() => false);
136
-
137
- if (found) {
138
- selectorFound = true;
139
- break;
140
- }
141
- // 非最后一次,输出重试日志
142
- if (i < timeouts.length - 1) {
143
- console.error(
144
- `[登录检测] DOM 元素等待超时 (${timeouts[i]}ms),重试中...`,
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
- if (!selectorFound) {
150
- // 所有阶段都超时,DOM 无法判断
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
  }