tt-help-cli-ycl 1.3.12 → 1.3.14
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 +47 -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/attach.js +160 -0
- package/src/cli/auto.js +186 -157
- package/src/cli/config.js +39 -3
- package/src/cli/explore.js +234 -193
- package/src/cli/info.js +88 -0
- 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 +517 -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 +199 -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/parse-ssr.mjs +69 -0
- package/src/lib/parser.js +47 -47
- package/src/lib/retry.js +45 -45
- package/src/lib/scrape.js +89 -40
- package/src/lib/tiktok-scraper.mjs +176 -0
- package/src/lib/url.js +52 -52
- package/src/main.js +12 -16
- 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 +162 -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 +536 -302
- package/src/watch/public/index.html +721 -701
- package/src/watch/server.js +527 -359
package/src/cli/auto.js
CHANGED
|
@@ -1,157 +1,186 @@
|
|
|
1
|
-
import { getOrCreatePage } from '../lib/browser/page.js';
|
|
2
|
-
import { userId as configuredUserId, saveUserId } from '../lib/constants.js';
|
|
3
|
-
import { getMacOrUuid } from '../lib/mac-or-uuid.js';
|
|
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
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
1
|
+
import { getOrCreatePage, isBrowserClosedError, relaunchBrowser } from '../lib/browser/page.js';
|
|
2
|
+
import { userId as configuredUserId, saveUserId } from '../lib/constants.js';
|
|
3
|
+
import { getMacOrUuid } from '../lib/mac-or-uuid.js';
|
|
4
|
+
import { ensureBrowserReady as ensureBrowserReadyCDP } from '../lib/browser/cdp.js';
|
|
5
|
+
|
|
6
|
+
const MAX_RETRY_WAIT = 5 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
async function withRetry(label, fn) {
|
|
9
|
+
let backoff = 1000;
|
|
10
|
+
while (true) {
|
|
11
|
+
try {
|
|
12
|
+
return await fn();
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error(`[连接] ${label} 失败: ${err.message},${backoff / 1000}秒后重试...`);
|
|
15
|
+
await new Promise(r => setTimeout(r, backoff));
|
|
16
|
+
if (backoff < MAX_RETRY_WAIT) backoff *= 2;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function apiPost(url, body) {
|
|
22
|
+
return withRetry(`POST ${url}`, async () => {
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify(body),
|
|
27
|
+
});
|
|
28
|
+
return res.json();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function apiGet(url) {
|
|
33
|
+
return withRetry(`GET ${url}`, async () => {
|
|
34
|
+
const res = await fetch(url);
|
|
35
|
+
return res.json();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function handleAuto(options) {
|
|
40
|
+
const { autoUsernames, autoCollectMax, autoScrapeDepth, autoMaxComments, autoMaxGuess,
|
|
41
|
+
autoPreset, autoSwitchDelay, autoCommentDelay, serverUrl,
|
|
42
|
+
autoEnableFollow, autoMaxFollowing, autoMaxFollowers } = options;
|
|
43
|
+
|
|
44
|
+
let userId = configuredUserId;
|
|
45
|
+
if (!userId) {
|
|
46
|
+
userId = await getMacOrUuid();
|
|
47
|
+
saveUserId(userId);
|
|
48
|
+
console.error(`[初始化] 未检测到本地用户编号,已生成并使用: ${userId}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const runOptions = {
|
|
52
|
+
collectMax: autoCollectMax,
|
|
53
|
+
scrapeDepth: autoScrapeDepth,
|
|
54
|
+
maxComments: autoMaxComments,
|
|
55
|
+
maxGuess: autoMaxGuess,
|
|
56
|
+
preset: autoPreset,
|
|
57
|
+
switchMax: autoSwitchDelay,
|
|
58
|
+
commentMax: autoCommentDelay,
|
|
59
|
+
enableFollow: autoEnableFollow,
|
|
60
|
+
maxFollowing: autoMaxFollowing,
|
|
61
|
+
maxFollowers: autoMaxFollowers,
|
|
62
|
+
userId,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await apiGet(`${serverUrl}/api/stats`);
|
|
66
|
+
|
|
67
|
+
if (autoUsernames.length > 0) {
|
|
68
|
+
const { added, skipped } = await apiPost(`${serverUrl}/api/users`, { usernames: autoUsernames });
|
|
69
|
+
console.error(`种子用户: ${added} 个新增, ${skipped} 个已存在`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.error(`服务器: ${serverUrl}(断开会自动重连)`);
|
|
73
|
+
|
|
74
|
+
const { ensureBrowserReady, processUser } = await import('../scraper/auto-core.js');
|
|
75
|
+
let browser = await ensureBrowserReady();
|
|
76
|
+
|
|
77
|
+
const page = await getOrCreatePage(browser);
|
|
78
|
+
|
|
79
|
+
let processedCount = 0;
|
|
80
|
+
let errorCount = 0;
|
|
81
|
+
let consecutiveNetworkErrors = 0;
|
|
82
|
+
|
|
83
|
+
while (true) {
|
|
84
|
+
const job = await apiGet(`${serverUrl}/api/job?userId=${encodeURIComponent(userId)}`);
|
|
85
|
+
if (!job.hasJob) break;
|
|
86
|
+
|
|
87
|
+
const username = job.user.uniqueId;
|
|
88
|
+
processedCount++;
|
|
89
|
+
|
|
90
|
+
if (consecutiveNetworkErrors > 0) {
|
|
91
|
+
const waitTime = consecutiveNetworkErrors <= 2
|
|
92
|
+
? 0
|
|
93
|
+
: consecutiveNetworkErrors <= 5
|
|
94
|
+
? 30000
|
|
95
|
+
: 300000;
|
|
96
|
+
if (waitTime > 0) {
|
|
97
|
+
console.error(` [网络] 连续 ${consecutiveNetworkErrors} 次网络异常,等待 ${waitTime / 1000}s 后重试...`);
|
|
98
|
+
await new Promise(r => setTimeout(r, waitTime));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.error(`\n[${processedCount}] 处理 @${username}...`);
|
|
103
|
+
|
|
104
|
+
const result = await processUser(page, username, { ...runOptions, browser }, console.error);
|
|
105
|
+
|
|
106
|
+
if (result.restricted) {
|
|
107
|
+
consecutiveNetworkErrors = 0;
|
|
108
|
+
await apiPost(`${serverUrl}/api/job/${username}`, result);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (result.error) {
|
|
113
|
+
// 浏览器关闭检测
|
|
114
|
+
if (isBrowserClosedError(new Error(result.error))) {
|
|
115
|
+
const newBrowser = await relaunchBrowser({}, 9222);
|
|
116
|
+
browser = newBrowser;
|
|
117
|
+
const newPage = await getOrCreatePage(browser);
|
|
118
|
+
Object.assign(page, newPage);
|
|
119
|
+
// 重试当前用户
|
|
120
|
+
const retryResult = await processUser(page, username, { ...runOptions, browser }, console.error);
|
|
121
|
+
Object.assign(result, retryResult);
|
|
122
|
+
// 继续下方逻辑
|
|
123
|
+
} else {
|
|
124
|
+
consecutiveNetworkErrors++;
|
|
125
|
+
errorCount++;
|
|
126
|
+
await apiPost(`${serverUrl}/api/job/${username}`, result);
|
|
127
|
+
const errorType = consecutiveNetworkErrors > 1 ? 'network' : 'other';
|
|
128
|
+
await withRetry('report error', () =>
|
|
129
|
+
apiPost(`${serverUrl}/api/error-report`, {
|
|
130
|
+
userId,
|
|
131
|
+
username,
|
|
132
|
+
errorType,
|
|
133
|
+
errorMessage: result.error,
|
|
134
|
+
stage: 'process',
|
|
135
|
+
errorStack: result.errorStack || '',
|
|
136
|
+
})
|
|
137
|
+
).catch(() => {});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (result.captchaDetected) {
|
|
143
|
+
await withRetry('report captcha', () =>
|
|
144
|
+
apiPost(`${serverUrl}/api/error-report`, {
|
|
145
|
+
userId,
|
|
146
|
+
username,
|
|
147
|
+
errorType: 'captcha',
|
|
148
|
+
errorMessage: result.captchaMessage || '页面出现验证码',
|
|
149
|
+
stage: result.captchaStage || 'video-page',
|
|
150
|
+
errorStack: '',
|
|
151
|
+
})
|
|
152
|
+
).catch(() => {});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
consecutiveNetworkErrors = 0;
|
|
156
|
+
|
|
157
|
+
const guessedLocation = result.locationCreated || null;
|
|
158
|
+
|
|
159
|
+
const payload = {
|
|
160
|
+
userInfo: result.userInfo || {},
|
|
161
|
+
discoveredVideoAuthors: (result.discoveredVideoAuthors || []).map(item =>
|
|
162
|
+
typeof item === 'object' ? { ...item, guessedLocation } : item
|
|
163
|
+
),
|
|
164
|
+
discoveredCommentAuthors: (result.discoveredCommentAuthors || []).map(author => ({ author, guessedLocation })),
|
|
165
|
+
discoveredGuessAuthors: (result.discoveredGuessAuthors || []).map(author => ({ author, guessedLocation })),
|
|
166
|
+
discoveredFollowing: (result.discoveredFollowing || []).map(f => ({
|
|
167
|
+
handle: Array.isArray(f) ? f[0] : f,
|
|
168
|
+
displayName: Array.isArray(f) ? f[1] : null,
|
|
169
|
+
guessedLocation,
|
|
170
|
+
})),
|
|
171
|
+
discoveredFollowers: (result.discoveredFollowers || []).map(f => ({
|
|
172
|
+
handle: Array.isArray(f) ? f[0] : f,
|
|
173
|
+
displayName: Array.isArray(f) ? f[1] : null,
|
|
174
|
+
guessedLocation,
|
|
175
|
+
})),
|
|
176
|
+
};
|
|
177
|
+
await apiPost(`${serverUrl}/api/job/${username}`, payload);
|
|
178
|
+
console.error(' 已提交');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const stats = await apiGet(`${serverUrl}/api/stats`);
|
|
182
|
+
console.error(`\n完成: ${processedCount} 个用户处理, ${errorCount} 个出错`);
|
|
183
|
+
console.error(` 总用户: ${stats.totalUsers}, 已完成: ${stats.processedUsers}, 待处理: ${stats.pendingUsers}, 错误: ${stats.errorUsers}`);
|
|
184
|
+
|
|
185
|
+
await browser.close().catch(() => {});
|
|
186
|
+
}
|
package/src/cli/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HELP_TEXT, configPath, saveBrowser, saveUserId, getConfigText } from '../lib/constants.js';
|
|
1
|
+
import { HELP_TEXT, configPath, saveBrowser, saveUserId, saveMaxFollowing, saveMaxFollowers, saveMaxVideos, saveMaxComments, getConfigText } from '../lib/constants.js';
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
@@ -34,7 +34,7 @@ function handleConfig(action, key, value) {
|
|
|
34
34
|
case 'set': {
|
|
35
35
|
if (!key) {
|
|
36
36
|
console.error('用法: tt-help config set <key> <value>');
|
|
37
|
-
console.error(' 可用 key: proxy, server, browser, userId');
|
|
37
|
+
console.error(' 可用 key: proxy, server, browser, userId, maxFollowing, maxFollowers, maxVideos, maxComments');
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -78,9 +78,45 @@ function handleConfig(action, key, value) {
|
|
|
78
78
|
console.error(`用户号已更新: ${value}`);
|
|
79
79
|
break;
|
|
80
80
|
|
|
81
|
+
case 'maxFollowing':
|
|
82
|
+
if (!value) {
|
|
83
|
+
console.error('请提供 maxFollowing 的值');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
saveMaxFollowing(value);
|
|
87
|
+
console.error(`商家关注采集数已更新: ${value}`);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'maxFollowers':
|
|
91
|
+
if (!value) {
|
|
92
|
+
console.error('请提供 maxFollowers 的值');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
saveMaxFollowers(value);
|
|
96
|
+
console.error(`粉丝采集数已更新: ${value}`);
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case 'maxVideos':
|
|
100
|
+
if (!value) {
|
|
101
|
+
console.error('请提供 maxVideos 的值');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
saveMaxVideos(value);
|
|
105
|
+
console.error(`视频采集数已更新: ${value}`);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case 'maxComments':
|
|
109
|
+
if (!value) {
|
|
110
|
+
console.error('请提供 maxComments 的值');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
saveMaxComments(value);
|
|
114
|
+
console.error(`评论采集数已更新: ${value}`);
|
|
115
|
+
break;
|
|
116
|
+
|
|
81
117
|
default:
|
|
82
118
|
console.error(`未知配置项: ${key}`);
|
|
83
|
-
console.error(' 可用 key: proxy, server, browser, userId');
|
|
119
|
+
console.error(' 可用 key: proxy, server, browser, userId, maxFollowing, maxFollowers, maxVideos, maxComments');
|
|
84
120
|
}
|
|
85
121
|
break;
|
|
86
122
|
}
|