tt-help-cli-ycl 1.3.12 → 1.3.13
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/README.md +17 -17
- package/cli.js +9 -9
- package/package.json +45 -45
- package/scripts/run-explore.bat +68 -68
- package/scripts/run-explore.ps1 +81 -81
- package/scripts/run-explore.sh +73 -73
- package/scripts/test-captcha-lib.mjs +68 -0
- package/scripts/test-captcha.mjs +81 -0
- package/scripts/test-incognito-lib.mjs +36 -0
- package/scripts/test-login-state.mjs +128 -0
- package/scripts/test-safe-click.mjs +45 -0
- package/src/cli/auto.js +186 -157
- package/src/cli/explore.js +227 -193
- package/src/cli/progress.js +111 -111
- package/src/cli/refresh.js +216 -0
- package/src/cli/scrape.js +47 -47
- package/src/cli/utils.js +18 -18
- package/src/cli/videos.js +41 -41
- package/src/cli/watch.js +31 -31
- package/src/lib/args.js +456 -402
- package/src/lib/browser/anti-detect.js +23 -23
- package/src/lib/browser/cdp.js +52 -10
- package/src/lib/browser/launch.js +43 -43
- package/src/lib/browser/page.js +146 -87
- package/src/lib/constants.js +119 -115
- package/src/lib/delay.js +54 -54
- package/src/lib/explore-fetch.js +118 -118
- package/src/lib/fetcher.js +45 -45
- package/src/lib/filter.js +66 -66
- package/src/lib/io.js +54 -54
- package/src/lib/output.js +80 -80
- package/src/lib/parser.js +47 -47
- package/src/lib/retry.js +45 -45
- package/src/lib/scrape.js +40 -40
- package/src/lib/url.js +52 -52
- package/src/main.js +2 -0
- package/src/results/user-videos-bar.lar.lar.moeta.json +37 -0
- package/src/scraper/auto-core.js +203 -194
- package/src/scraper/core.js +211 -190
- package/src/scraper/explore-core.js +180 -171
- package/src/scraper/modules/captcha-handler.js +114 -114
- package/src/scraper/modules/comment-extractor.js +74 -69
- package/src/scraper/modules/follow-extractor.js +121 -121
- package/src/scraper/modules/guess-extractor.js +51 -51
- package/src/scraper/modules/page-helpers.js +48 -48
- package/src/scraper/refresh-core.js +179 -0
- package/src/videos/core.js +126 -126
- package/src/watch/data-store.js +431 -302
- package/src/watch/public/index.html +721 -701
- package/src/watch/server.js +483 -359
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
export function getAntiDetectScript() {
|
|
2
|
-
return () => {
|
|
3
|
-
Object.defineProperty(navigator, 'webdriver', { get: () => false });
|
|
4
|
-
|
|
5
|
-
if (!window.chrome) {
|
|
6
|
-
window.chrome = { runtime: {} };
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const originalQuery = window.navigator.permissions.query;
|
|
10
|
-
window.navigator.permissions.query = (params) =>
|
|
11
|
-
params.name === 'notifications'
|
|
12
|
-
? Promise.resolve({ state: Notification.permission })
|
|
13
|
-
: originalQuery(params);
|
|
14
|
-
|
|
15
|
-
Object.defineProperty(navigator, 'languages', {
|
|
16
|
-
get: () => ['en-US', 'en'],
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
20
|
-
get: () => [1, 2, 3, 4, 5],
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
}
|
|
1
|
+
export function getAntiDetectScript() {
|
|
2
|
+
return () => {
|
|
3
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => false });
|
|
4
|
+
|
|
5
|
+
if (!window.chrome) {
|
|
6
|
+
window.chrome = { runtime: {} };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const originalQuery = window.navigator.permissions.query;
|
|
10
|
+
window.navigator.permissions.query = (params) =>
|
|
11
|
+
params.name === 'notifications'
|
|
12
|
+
? Promise.resolve({ state: Notification.permission })
|
|
13
|
+
: originalQuery(params);
|
|
14
|
+
|
|
15
|
+
Object.defineProperty(navigator, 'languages', {
|
|
16
|
+
get: () => ['en-US', 'en'],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
20
|
+
get: () => [1, 2, 3, 4, 5],
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
package/src/lib/browser/cdp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
2
3
|
import http from 'http';
|
|
3
4
|
import os from 'os';
|
|
4
5
|
import path from 'path';
|
|
@@ -55,16 +56,40 @@ function checkEdgeArgs() {
|
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function killEdgeProcesses() {
|
|
59
|
+
function killEdgeProcesses(targetDir) {
|
|
59
60
|
return new Promise(resolve => {
|
|
60
61
|
const platform = os.platform();
|
|
61
62
|
let command;
|
|
62
63
|
if (platform === 'darwin') {
|
|
63
|
-
|
|
64
|
+
if (targetDir) {
|
|
65
|
+
let pids = '';
|
|
66
|
+
try {
|
|
67
|
+
// ps aux 输出中 --user-data-dir= 后面没有引号
|
|
68
|
+
// 用路径精确匹配,结尾加空格或行尾避免子串误杀
|
|
69
|
+
const escapedDir = targetDir.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
70
|
+
pids = execSync(
|
|
71
|
+
`ps aux | grep "[M]icrosoft Edge" | grep -v "Helper\\|crashpad" | grep -E -- '--user-data-dir=${escapedDir}($|[^A-Za-z0-9_])' | awk '{print $2}'`
|
|
72
|
+
).toString().trim();
|
|
73
|
+
} catch (e) {
|
|
74
|
+
pids = '';
|
|
75
|
+
}
|
|
76
|
+
if (pids) {
|
|
77
|
+
command = `kill -9 ${pids} 2>/dev/null; rm -f ~/Library/Caches/Microsoft\\ Edge/Singleton*; true`;
|
|
78
|
+
} else {
|
|
79
|
+
command = 'true';
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
command = 'killall -9 "Microsoft Edge" 2>/dev/null; rm -f ~/Library/Caches/Microsoft\\ Edge/Singleton*; true';
|
|
83
|
+
}
|
|
64
84
|
} else if (platform === 'win32') {
|
|
65
85
|
command = 'taskkill /F /IM msedge.exe 2>nul || exit 0';
|
|
66
86
|
} else {
|
|
67
|
-
|
|
87
|
+
if (targetDir) {
|
|
88
|
+
const escapedDir = targetDir.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
89
|
+
command = `ps aux | grep msedge | grep -v Helper | grep -E -- '--user-data-dir=${escapedDir}($|[^A-Za-z0-9_])' | awk '{print $2}' | xargs -r kill -9 2>/dev/null; true`;
|
|
90
|
+
} else {
|
|
91
|
+
command = 'pkill -9 -f msedge 2>/dev/null; true';
|
|
92
|
+
}
|
|
68
93
|
}
|
|
69
94
|
exec(command, () => resolve());
|
|
70
95
|
});
|
|
@@ -76,12 +101,27 @@ function launchEdgeWithCDP(port, userDataDir) {
|
|
|
76
101
|
const edgePath = getEdgePath();
|
|
77
102
|
let command;
|
|
78
103
|
|
|
104
|
+
const extraArgs = [
|
|
105
|
+
`--remote-debugging-port=${port}`,
|
|
106
|
+
`--user-data-dir="${userDataDir}"`,
|
|
107
|
+
'--disable-blink-features=AutomationControlled',
|
|
108
|
+
'--no-first-run',
|
|
109
|
+
'--no-default-browser-check',
|
|
110
|
+
'--password-store=basic',
|
|
111
|
+
'--disable-background-mode',
|
|
112
|
+
'--disable-component-update',
|
|
113
|
+
'--disable-crash-reporter',
|
|
114
|
+
'--disable-breakpad',
|
|
115
|
+
'--disable-background-networking',
|
|
116
|
+
'--disable-sync',
|
|
117
|
+
].join(' ');
|
|
118
|
+
|
|
79
119
|
if (platform === 'darwin') {
|
|
80
|
-
command = `open -a ${edgePath} --
|
|
120
|
+
command = `open -a ${edgePath} --new --args ${extraArgs}`;
|
|
81
121
|
} else if (platform === 'win32') {
|
|
82
|
-
command = `start msedge
|
|
122
|
+
command = `start msedge ${extraArgs}`;
|
|
83
123
|
} else {
|
|
84
|
-
command = `msedge
|
|
124
|
+
command = `msedge ${extraArgs} &`;
|
|
85
125
|
}
|
|
86
126
|
|
|
87
127
|
exec(command, (err) => {
|
|
@@ -101,6 +141,8 @@ async function waitForCDP(port, timeout = 30000, interval = 1000) {
|
|
|
101
141
|
return false;
|
|
102
142
|
}
|
|
103
143
|
|
|
144
|
+
export { killEdgeProcesses };
|
|
145
|
+
|
|
104
146
|
export async function ensureBrowserReady(options = {}) {
|
|
105
147
|
const port = options.port || DEFAULT_CDP_PORT;
|
|
106
148
|
const userDataDir = options.userDataDir || DEFAULT_USER_DATA_DIR;
|
|
@@ -113,8 +155,8 @@ export async function ensureBrowserReady(options = {}) {
|
|
|
113
155
|
if (!isCustom) {
|
|
114
156
|
const edgeArgsValid = await checkEdgeArgs();
|
|
115
157
|
if (!edgeArgsValid) {
|
|
116
|
-
console.error(
|
|
117
|
-
await killEdgeProcesses();
|
|
158
|
+
console.error(`Edge 已运行但启动参数不完整,正在重启端口 ${port}...`);
|
|
159
|
+
await killEdgeProcesses(userDataDir);
|
|
118
160
|
await new Promise(r => setTimeout(r, 3000));
|
|
119
161
|
needLaunch = true;
|
|
120
162
|
}
|
|
@@ -128,7 +170,7 @@ export async function ensureBrowserReady(options = {}) {
|
|
|
128
170
|
const edgeRunning = await isEdgeRunning();
|
|
129
171
|
if (edgeRunning) {
|
|
130
172
|
console.error(`Edge 已运行但 CDP 端口 ${port} 未启用,正在重启...`);
|
|
131
|
-
await killEdgeProcesses();
|
|
173
|
+
await killEdgeProcesses(userDataDir);
|
|
132
174
|
await new Promise(r => setTimeout(r, 3000));
|
|
133
175
|
} else {
|
|
134
176
|
console.error(`CDP 端口 ${port} 未就绪,正在启动 Edge 浏览器...`);
|
|
@@ -141,7 +183,7 @@ export async function ensureBrowserReady(options = {}) {
|
|
|
141
183
|
if (!launched) {
|
|
142
184
|
throw new Error(
|
|
143
185
|
`等待 CDP 端口 ${port} 超时。请确认 Edge 浏览器已安装,\n` +
|
|
144
|
-
`或手动启动: Microsoft Edge --remote-debugging-port=${port}`
|
|
186
|
+
`或手动启动: Microsoft Edge --remote-debugging-port=${port} [参见 cdp.js extraArgs]`
|
|
145
187
|
);
|
|
146
188
|
}
|
|
147
189
|
console.error('浏览器启动成功');
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import { accessSync } from 'fs';
|
|
2
|
-
|
|
3
|
-
export function detectBrowser() {
|
|
4
|
-
const isMac = process.platform === 'darwin';
|
|
5
|
-
const isWin = process.platform === 'win32';
|
|
6
|
-
const isLinux = process.platform === 'linux';
|
|
7
|
-
|
|
8
|
-
const paths = [];
|
|
9
|
-
|
|
10
|
-
if (isMac) {
|
|
11
|
-
paths.push(
|
|
12
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
13
|
-
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
14
|
-
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
15
|
-
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
16
|
-
);
|
|
17
|
-
} else if (isWin) {
|
|
18
|
-
const localAppData = process.env.LOCALAPPDATA || '';
|
|
19
|
-
const programFiles = process.env.PROGRAMFILES || '';
|
|
20
|
-
const programFilesX86 = process.env['PROGRAMFILES(X86)'] || '';
|
|
21
|
-
paths.push(
|
|
22
|
-
`${programFiles}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
23
|
-
`${programFilesX86}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
24
|
-
`${localAppData}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
25
|
-
`${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
26
|
-
`${programFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
27
|
-
);
|
|
28
|
-
} else if (isLinux) {
|
|
29
|
-
paths.push(
|
|
30
|
-
'/usr/bin/google-chrome',
|
|
31
|
-
'/usr/bin/google-chrome-stable',
|
|
32
|
-
'/usr/bin/chromium-browser',
|
|
33
|
-
'/usr/bin/chromium',
|
|
34
|
-
'/snap/bin/chromium',
|
|
35
|
-
'/usr/bin/microsoft-edge',
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
for (const p of paths) {
|
|
40
|
-
try { accessSync(p); return p; } catch { /* not found */ }
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
1
|
+
import { accessSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
export function detectBrowser() {
|
|
4
|
+
const isMac = process.platform === 'darwin';
|
|
5
|
+
const isWin = process.platform === 'win32';
|
|
6
|
+
const isLinux = process.platform === 'linux';
|
|
7
|
+
|
|
8
|
+
const paths = [];
|
|
9
|
+
|
|
10
|
+
if (isMac) {
|
|
11
|
+
paths.push(
|
|
12
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
13
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
14
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
15
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
16
|
+
);
|
|
17
|
+
} else if (isWin) {
|
|
18
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
19
|
+
const programFiles = process.env.PROGRAMFILES || '';
|
|
20
|
+
const programFilesX86 = process.env['PROGRAMFILES(X86)'] || '';
|
|
21
|
+
paths.push(
|
|
22
|
+
`${programFiles}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
23
|
+
`${programFilesX86}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
24
|
+
`${localAppData}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
25
|
+
`${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
26
|
+
`${programFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
27
|
+
);
|
|
28
|
+
} else if (isLinux) {
|
|
29
|
+
paths.push(
|
|
30
|
+
'/usr/bin/google-chrome',
|
|
31
|
+
'/usr/bin/google-chrome-stable',
|
|
32
|
+
'/usr/bin/chromium-browser',
|
|
33
|
+
'/usr/bin/chromium',
|
|
34
|
+
'/snap/bin/chromium',
|
|
35
|
+
'/usr/bin/microsoft-edge',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const p of paths) {
|
|
40
|
+
try { accessSync(p); return p; } catch { /* not found */ }
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
package/src/lib/browser/page.js
CHANGED
|
@@ -1,87 +1,146 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { delay } from '../delay.js';
|
|
4
|
+
import { retryWithBackoff } from '../retry.js';
|
|
5
|
+
import { getDelayConfig } from '../delay.js';
|
|
6
|
+
import { ensureBrowserReady, killEdgeProcesses } from './cdp.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_USER_DATA_DIR = path.join(os.homedir(), 'Library', 'Application Support', 'Microsoft Edge For Testing');
|
|
9
|
+
|
|
10
|
+
const BROWSER_CLOSED_PATTERNS = [
|
|
11
|
+
'Target page, context or browser has been closed',
|
|
12
|
+
'Target closed',
|
|
13
|
+
'Browser has been closed',
|
|
14
|
+
'Protocol error',
|
|
15
|
+
'Connection closed',
|
|
16
|
+
'net::ERR_CONNECTION',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export function isBrowserClosedError(error) {
|
|
20
|
+
if (!error) return false;
|
|
21
|
+
const msg = error.message || error.toString() || '';
|
|
22
|
+
return BROWSER_CLOSED_PATTERNS.some(p => msg.includes(p));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function relaunchBrowser(cdpOptions, port) {
|
|
26
|
+
console.error(` [浏览器] 浏览器已关闭,正在重启 (端口 ${port})...`);
|
|
27
|
+
const targetDir = cdpOptions.userDataDir || DEFAULT_USER_DATA_DIR;
|
|
28
|
+
// kill 并清理 Singleton 锁文件,确保 Edge 能启动新实例
|
|
29
|
+
await killEdgeProcesses(targetDir);
|
|
30
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
31
|
+
// 确保端口已释放后再启动
|
|
32
|
+
let retries = 0;
|
|
33
|
+
while (retries < 5) {
|
|
34
|
+
try {
|
|
35
|
+
return await ensureBrowserReady(cdpOptions);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e.message && e.message.includes('setDownloadBehavior')) {
|
|
38
|
+
retries++;
|
|
39
|
+
console.error(` [浏览器] CDP 连接异常,重试 ${retries}/5...`);
|
|
40
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
41
|
+
await killEdgeProcesses(targetDir);
|
|
42
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error('浏览器重启失败,CDP 连接异常');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function withBrowserRecovery(fn, browser, page, cdpOptions, port) {
|
|
52
|
+
try {
|
|
53
|
+
return await fn();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
if (isBrowserClosedError(e)) {
|
|
56
|
+
const newBrowser = await relaunchBrowser(cdpOptions, port);
|
|
57
|
+
const newPage = await getOrCreatePage(newBrowser);
|
|
58
|
+
return await fn(newBrowser, newPage);
|
|
59
|
+
}
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function isLoggedIn(page) {
|
|
65
|
+
return page.evaluate(() => {
|
|
66
|
+
// 已登录时会出现在个人主页区域的元素
|
|
67
|
+
const hasProfileContainer = !!document.querySelector(
|
|
68
|
+
'[class*="DivProfileContainer"], [class*="DivUserContainer"]'
|
|
69
|
+
);
|
|
70
|
+
// 已登录时顶部导航栏有用户相关菜单
|
|
71
|
+
const hasUserMenu = !!document.querySelector(
|
|
72
|
+
'[class*="UserMenu"], [class*="user-menu"], [class*="CurrentUserInfo"]'
|
|
73
|
+
);
|
|
74
|
+
// 有登录按钮说明未登录
|
|
75
|
+
const hasLoginButton = Array.from(document.querySelectorAll('button, [role="button"]'))
|
|
76
|
+
.some(el => /^(登录|Log in|Sign in)$/i.test(el.textContent.trim()));
|
|
77
|
+
|
|
78
|
+
return (hasProfileContainer || hasUserMenu) && !hasLoginButton;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function closeCommentPanel(page) {
|
|
83
|
+
await page.evaluate(() => {
|
|
84
|
+
const rightPanel = document.querySelector('[class*="RightPanelContainer"]');
|
|
85
|
+
if (rightPanel) {
|
|
86
|
+
const tabContainer = rightPanel.querySelector('[class*="TabContainer"]');
|
|
87
|
+
if (tabContainer) {
|
|
88
|
+
const closeOverlay = tabContainer.querySelector('div:last-child');
|
|
89
|
+
if (closeOverlay) closeOverlay.click();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function ensureTikTokPage(browser, url) {
|
|
96
|
+
const contexts = browser.contexts();
|
|
97
|
+
let page = null;
|
|
98
|
+
|
|
99
|
+
for (const ctx of contexts) {
|
|
100
|
+
for (const p of ctx.pages()) {
|
|
101
|
+
if (p.url().includes('tiktok.com')) {
|
|
102
|
+
page = p;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (page) break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!page) {
|
|
110
|
+
console.error('未找到 TikTok 页面,正在打开...');
|
|
111
|
+
const defaultCtx = browser.contexts()[0];
|
|
112
|
+
page = await defaultCtx.newPage();
|
|
113
|
+
await retryWithBackoff(() => page.goto(url, { waitUntil: 'load', timeout: 30000 }));
|
|
114
|
+
const config = getDelayConfig();
|
|
115
|
+
await delay(Math.round(config.switchMax * 0.5), config.switchMax);
|
|
116
|
+
console.error('TikTok 页面已打开');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return page;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function findTikTokPage(browser) {
|
|
123
|
+
const contexts = browser.contexts();
|
|
124
|
+
for (const ctx of contexts) {
|
|
125
|
+
for (const p of ctx.pages()) {
|
|
126
|
+
if (p.url().includes('tiktok.com')) return p;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function getOrCreatePage(browser) {
|
|
133
|
+
let page = await findTikTokPage(browser);
|
|
134
|
+
if (!page) {
|
|
135
|
+
const defaultCtx = browser.contexts()[0] || await browser.newContext();
|
|
136
|
+
page = await defaultCtx.newPage();
|
|
137
|
+
}
|
|
138
|
+
return page;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function assertPageUrl(page, expectedPath) {
|
|
142
|
+
const actual = page.url();
|
|
143
|
+
if (!actual.includes(expectedPath)) {
|
|
144
|
+
throw new Error(`[代理错误] 预期访问 ${expectedPath},实际跳转到了 ${actual}`);
|
|
145
|
+
}
|
|
146
|
+
}
|