tt-help-cli-ycl 1.3.25 → 1.3.26

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.
Files changed (59) hide show
  1. package/README.md +17 -17
  2. package/cli.js +9 -9
  3. package/package.json +47 -47
  4. package/scripts/run-explore copy.bat +68 -68
  5. package/scripts/run-explore.bat +68 -68
  6. package/scripts/run-explore.ps1 +81 -81
  7. package/scripts/run-explore.sh +73 -73
  8. package/scripts/test-captcha-lib.mjs +68 -0
  9. package/scripts/test-captcha.mjs +81 -0
  10. package/scripts/test-incognito-lib.mjs +36 -0
  11. package/scripts/test-login-state.mjs +128 -0
  12. package/scripts/test-safe-click.mjs +45 -0
  13. package/src/cli/attach.js +180 -180
  14. package/src/cli/auto.js +240 -186
  15. package/src/cli/config.js +152 -152
  16. package/src/cli/explore.js +439 -296
  17. package/src/cli/info.js +88 -88
  18. package/src/cli/progress.js +111 -111
  19. package/src/cli/refresh.js +216 -216
  20. package/src/cli/scrape.js +47 -47
  21. package/src/cli/utils.js +18 -18
  22. package/src/cli/videos.js +41 -41
  23. package/src/cli/watch.js +31 -31
  24. package/src/lib/api-interceptor.js +191 -191
  25. package/src/lib/args.js +551 -551
  26. package/src/lib/browser/anti-detect.js +23 -23
  27. package/src/lib/browser/cdp.js +236 -236
  28. package/src/lib/browser/health-checker.js +43 -4
  29. package/src/lib/browser/launch.js +43 -43
  30. package/src/lib/browser/page.js +156 -156
  31. package/src/lib/constants.js +206 -206
  32. package/src/lib/delay.js +54 -54
  33. package/src/lib/explore-fetch.js +118 -118
  34. package/src/lib/fetcher.js +45 -45
  35. package/src/lib/filter.js +66 -66
  36. package/src/lib/io.js +54 -54
  37. package/src/lib/output.js +80 -80
  38. package/src/lib/page-error-detector.js +71 -71
  39. package/src/lib/parse-ssr.mjs +69 -69
  40. package/src/lib/parser.js +47 -47
  41. package/src/lib/retry.js +45 -45
  42. package/src/lib/scrape.js +89 -89
  43. package/src/lib/tiktok-scraper.mjs +194 -194
  44. package/src/lib/url.js +52 -52
  45. package/src/main.js +44 -44
  46. package/src/results/user-videos-bar.lar.lar.moeta.json +37 -0
  47. package/src/scraper/auto-core.js +203 -203
  48. package/src/scraper/core.js +211 -211
  49. package/src/scraper/explore-core.js +201 -198
  50. package/src/scraper/modules/captcha-handler.js +114 -114
  51. package/src/scraper/modules/comment-extractor.js +74 -74
  52. package/src/scraper/modules/follow-extractor.js +118 -118
  53. package/src/scraper/modules/guess-extractor.js +51 -51
  54. package/src/scraper/modules/page-helpers.js +48 -48
  55. package/src/scraper/refresh-core.js +179 -179
  56. package/src/videos/core.js +101 -101
  57. package/src/watch/data-store.js +828 -828
  58. package/src/watch/public/index.html +753 -753
  59. package/src/watch/server.js +705 -705
package/src/cli/info.js CHANGED
@@ -1,88 +1,88 @@
1
- import { TikTokScraper } from '../lib/tiktok-scraper.mjs';
2
- import { isProfileUrl, isVideoUrl, extractUniqueId, normalizeUsername } from '../lib/url.js';
3
-
4
- async function handleInfo(options) {
5
- const { infoUrls, infoOnlyVideo } = options;
6
-
7
- if (!infoUrls || infoUrls.length === 0) {
8
- console.error('用法: tt-help info <URL> [URL2 URL3...] [--onlyvideo]');
9
- console.error('');
10
- console.error('参数:');
11
- console.error(' <URL> TikTok 主页或视频 URL,支持多个 URL 同时查询');
12
- console.error(' --onlyvideo 只返回视频信息(不返回用户信息)');
13
- console.error('');
14
- console.error('默认行为:');
15
- console.error(' 主页 URL → 返回用户信息(bio、region、粉丝数等)');
16
- console.error(' 视频 URL → 返回用户信息 + 视频信息');
17
- console.error(' 视频 URL + --onlyvideo → 只返回视频信息');
18
- console.error('');
19
- console.error('示例:');
20
- console.error(' tt-help info https://www.tiktok.com/@nike');
21
- console.error(' tt-help info https://www.tiktok.com/@nike/video/7234567890');
22
- console.error(' tt-help info https://www.tiktok.com/@nike https://www.tiktok.com/@apple');
23
- process.exit(1);
24
- }
25
-
26
- const scraper = new TikTokScraper({ poolSize: 1 });
27
-
28
- try {
29
- await scraper.init();
30
- const result = {};
31
-
32
- for (const url of infoUrls) {
33
- if (isProfileUrl(url)) {
34
- const uniqueId = extractUniqueId(url);
35
- const normalized = normalizeUsername(uniqueId);
36
- const user = await scraper.getUserInfo(normalized);
37
- if (!user) {
38
- console.error(`无法获取用户 @${uniqueId} 的信息`);
39
- continue;
40
- }
41
- result[normalized] = { user };
42
- console.error(`用户: @${user.uniqueId} (${user.nickname})`);
43
- } else if (isVideoUrl(url)) {
44
- const uniqueId = extractUniqueId(url);
45
- const normalized = normalizeUsername(uniqueId);
46
-
47
- if (infoOnlyVideo) {
48
- const video = await scraper.getVideoInfo(url);
49
- if (!video) {
50
- console.error(`无法获取视频信息: ${url}`);
51
- continue;
52
- }
53
- const key = normalized + '/video/' + video.id;
54
- result[key] = { video };
55
- console.error(`视频: ${video.id}`);
56
- } else {
57
- const [user, video] = await Promise.all([
58
- scraper.getUserInfo(normalized),
59
- scraper.getVideoInfo(url),
60
- ]);
61
- const entry = {};
62
- if (user) {
63
- entry.user = user;
64
- console.error(`用户: @${user.uniqueId} (${user.nickname})`);
65
- }
66
- if (video) {
67
- entry.video = video;
68
- console.error(`视频: ${video.id}`);
69
- }
70
- if (!user && !video) {
71
- console.error(`无法获取信息: ${url}`);
72
- continue;
73
- }
74
- const key = normalized + '/video/' + (video ? video.id : 'unknown');
75
- result[key] = entry;
76
- }
77
- } else {
78
- console.error(`无法识别 URL: ${url}`);
79
- }
80
- }
81
-
82
- console.log(JSON.stringify(result, null, 2));
83
- } finally {
84
- await scraper.close();
85
- }
86
- }
87
-
88
- export { handleInfo };
1
+ import { TikTokScraper } from '../lib/tiktok-scraper.mjs';
2
+ import { isProfileUrl, isVideoUrl, extractUniqueId, normalizeUsername } from '../lib/url.js';
3
+
4
+ async function handleInfo(options) {
5
+ const { infoUrls, infoOnlyVideo } = options;
6
+
7
+ if (!infoUrls || infoUrls.length === 0) {
8
+ console.error('用法: tt-help info <URL> [URL2 URL3...] [--onlyvideo]');
9
+ console.error('');
10
+ console.error('参数:');
11
+ console.error(' <URL> TikTok 主页或视频 URL,支持多个 URL 同时查询');
12
+ console.error(' --onlyvideo 只返回视频信息(不返回用户信息)');
13
+ console.error('');
14
+ console.error('默认行为:');
15
+ console.error(' 主页 URL → 返回用户信息(bio、region、粉丝数等)');
16
+ console.error(' 视频 URL → 返回用户信息 + 视频信息');
17
+ console.error(' 视频 URL + --onlyvideo → 只返回视频信息');
18
+ console.error('');
19
+ console.error('示例:');
20
+ console.error(' tt-help info https://www.tiktok.com/@nike');
21
+ console.error(' tt-help info https://www.tiktok.com/@nike/video/7234567890');
22
+ console.error(' tt-help info https://www.tiktok.com/@nike https://www.tiktok.com/@apple');
23
+ process.exit(1);
24
+ }
25
+
26
+ const scraper = new TikTokScraper({ poolSize: 1 });
27
+
28
+ try {
29
+ await scraper.init();
30
+ const result = {};
31
+
32
+ for (const url of infoUrls) {
33
+ if (isProfileUrl(url)) {
34
+ const uniqueId = extractUniqueId(url);
35
+ const normalized = normalizeUsername(uniqueId);
36
+ const user = await scraper.getUserInfo(normalized);
37
+ if (!user) {
38
+ console.error(`无法获取用户 @${uniqueId} 的信息`);
39
+ continue;
40
+ }
41
+ result[normalized] = { user };
42
+ console.error(`用户: @${user.uniqueId} (${user.nickname})`);
43
+ } else if (isVideoUrl(url)) {
44
+ const uniqueId = extractUniqueId(url);
45
+ const normalized = normalizeUsername(uniqueId);
46
+
47
+ if (infoOnlyVideo) {
48
+ const video = await scraper.getVideoInfo(url);
49
+ if (!video) {
50
+ console.error(`无法获取视频信息: ${url}`);
51
+ continue;
52
+ }
53
+ const key = normalized + '/video/' + video.id;
54
+ result[key] = { video };
55
+ console.error(`视频: ${video.id}`);
56
+ } else {
57
+ const [user, video] = await Promise.all([
58
+ scraper.getUserInfo(normalized),
59
+ scraper.getVideoInfo(url),
60
+ ]);
61
+ const entry = {};
62
+ if (user) {
63
+ entry.user = user;
64
+ console.error(`用户: @${user.uniqueId} (${user.nickname})`);
65
+ }
66
+ if (video) {
67
+ entry.video = video;
68
+ console.error(`视频: ${video.id}`);
69
+ }
70
+ if (!user && !video) {
71
+ console.error(`无法获取信息: ${url}`);
72
+ continue;
73
+ }
74
+ const key = normalized + '/video/' + (video ? video.id : 'unknown');
75
+ result[key] = entry;
76
+ }
77
+ } else {
78
+ console.error(`无法识别 URL: ${url}`);
79
+ }
80
+ }
81
+
82
+ console.log(JSON.stringify(result, null, 2));
83
+ } finally {
84
+ await scraper.close();
85
+ }
86
+ }
87
+
88
+ export { handleInfo };
@@ -1,111 +1,111 @@
1
- import { writeFileSync } from 'fs';
2
- import { formatOutput } from '../lib/output.js';
3
- import { deduplicate } from '../lib/output.js';
4
- import { applyFilter, formatFilterDescription } from '../lib/filter.js';
5
- import { calculateConcurrency, createMultiProgressBars, renderMultiProgressBars, clearProgressBars } from '../lib/io.js';
6
- import { randomDelay } from '../lib/delay.js';
7
-
8
- export async function processUrlsWithProgress({
9
- urls,
10
- proxyUrl,
11
- outputFile,
12
- outputFormat,
13
- filter,
14
- processFn,
15
- label = '数据',
16
- }) {
17
- const allResults = [];
18
- const errors = [];
19
-
20
- if (urls.length === 0) {
21
- console.error('\n未获取到数据');
22
- if (outputFile) writeFileSync(outputFile, '[]', 'utf-8');
23
- return;
24
- }
25
-
26
- const concurrency = calculateConcurrency(urls.length);
27
- const bars = createMultiProgressBars(concurrency);
28
-
29
- const slots = Array.from({ length: concurrency }, () => []);
30
- urls.forEach((url, i) => slots[i % concurrency].push(url));
31
-
32
- bars.forEach((bar, i) => {
33
- bar.total = slots[i].length;
34
- bar.status = slots[i].length > 0 ? 'running' : 'done';
35
- });
36
-
37
- renderMultiProgressBars(bars);
38
-
39
- const workers = slots.map(async (slotUrls, slotIndex) => {
40
- for (const url of slotUrls) {
41
- bars[slotIndex].url = url;
42
- renderMultiProgressBars(bars);
43
-
44
- await randomDelay();
45
-
46
- try {
47
- const results = await processFn(url, proxyUrl);
48
- allResults.push(...results);
49
- bars[slotIndex].current++;
50
- bars[slotIndex].status = 'running';
51
- } catch (err) {
52
- errors.push({ url, message: err.message });
53
- bars[slotIndex].current++;
54
- bars[slotIndex].status = 'error';
55
- }
56
-
57
- renderMultiProgressBars(bars);
58
- }
59
- bars[slotIndex].status = bars[slotIndex].current === bars[slotIndex].total ? 'done' : 'error';
60
- renderMultiProgressBars(bars);
61
- });
62
-
63
- await Promise.all(workers);
64
- clearProgressBars();
65
-
66
- const uniqueResults = deduplicate(allResults);
67
- const filteredResults = applyFilter(uniqueResults, filter);
68
-
69
- if (errors.length > 0) {
70
- const firstMsg = errors[0].message;
71
- const isProxyError = ['不可用', '连接被拒绝', '连接中断', '超时', '无法解析']
72
- .some(kw => firstMsg.includes(kw));
73
-
74
- if (filteredResults.length === 0) {
75
- if (isProxyError) {
76
- console.error(` 所有请求失败,请检查代理: ${proxyUrl}\n`);
77
- } else {
78
- const show = errors.slice(0, 5);
79
- for (const e of show) console.error(` ✗ ${e.url}: ${e.message}\n`);
80
- if (errors.length > 5) console.error(` ... 还有 ${errors.length - 5} 个失败\n`);
81
- }
82
- console.error('未获取到数据');
83
- if (outputFile) writeFileSync(outputFile, '[]', 'utf-8');
84
- return;
85
- } else {
86
- if (isProxyError) {
87
- console.error(` ${errors.length} 个请求失败,请检查代理: ${proxyUrl}\n`);
88
- } else {
89
- console.error(` ${errors.length} 个失败:`);
90
- const show = errors.slice(0, 5);
91
- for (const e of show) console.error(` ✗ ${e.url}: ${e.message}`);
92
- if (errors.length > 5) console.error(` ... 还有 ${errors.length - 5} 个`);
93
- }
94
- }
95
- }
96
-
97
- const output = formatOutput(filteredResults, outputFormat);
98
-
99
- if (outputFile) {
100
- writeFileSync(outputFile, output, 'utf-8');
101
- console.log(`\n结果已写入: ${outputFile}`);
102
- } else {
103
- process.stdout.write(output + '\n');
104
- }
105
-
106
- if (filter) {
107
- console.log(`\n共 ${uniqueResults.length} 个${label},过滤后 ${filteredResults.length} 个(过滤条件: ${formatFilterDescription(filter)})`);
108
- } else {
109
- console.log(`\n共 ${filteredResults.length} 个${label}`);
110
- }
111
- }
1
+ import { writeFileSync } from 'fs';
2
+ import { formatOutput } from '../lib/output.js';
3
+ import { deduplicate } from '../lib/output.js';
4
+ import { applyFilter, formatFilterDescription } from '../lib/filter.js';
5
+ import { calculateConcurrency, createMultiProgressBars, renderMultiProgressBars, clearProgressBars } from '../lib/io.js';
6
+ import { randomDelay } from '../lib/delay.js';
7
+
8
+ export async function processUrlsWithProgress({
9
+ urls,
10
+ proxyUrl,
11
+ outputFile,
12
+ outputFormat,
13
+ filter,
14
+ processFn,
15
+ label = '数据',
16
+ }) {
17
+ const allResults = [];
18
+ const errors = [];
19
+
20
+ if (urls.length === 0) {
21
+ console.error('\n未获取到数据');
22
+ if (outputFile) writeFileSync(outputFile, '[]', 'utf-8');
23
+ return;
24
+ }
25
+
26
+ const concurrency = calculateConcurrency(urls.length);
27
+ const bars = createMultiProgressBars(concurrency);
28
+
29
+ const slots = Array.from({ length: concurrency }, () => []);
30
+ urls.forEach((url, i) => slots[i % concurrency].push(url));
31
+
32
+ bars.forEach((bar, i) => {
33
+ bar.total = slots[i].length;
34
+ bar.status = slots[i].length > 0 ? 'running' : 'done';
35
+ });
36
+
37
+ renderMultiProgressBars(bars);
38
+
39
+ const workers = slots.map(async (slotUrls, slotIndex) => {
40
+ for (const url of slotUrls) {
41
+ bars[slotIndex].url = url;
42
+ renderMultiProgressBars(bars);
43
+
44
+ await randomDelay();
45
+
46
+ try {
47
+ const results = await processFn(url, proxyUrl);
48
+ allResults.push(...results);
49
+ bars[slotIndex].current++;
50
+ bars[slotIndex].status = 'running';
51
+ } catch (err) {
52
+ errors.push({ url, message: err.message });
53
+ bars[slotIndex].current++;
54
+ bars[slotIndex].status = 'error';
55
+ }
56
+
57
+ renderMultiProgressBars(bars);
58
+ }
59
+ bars[slotIndex].status = bars[slotIndex].current === bars[slotIndex].total ? 'done' : 'error';
60
+ renderMultiProgressBars(bars);
61
+ });
62
+
63
+ await Promise.all(workers);
64
+ clearProgressBars();
65
+
66
+ const uniqueResults = deduplicate(allResults);
67
+ const filteredResults = applyFilter(uniqueResults, filter);
68
+
69
+ if (errors.length > 0) {
70
+ const firstMsg = errors[0].message;
71
+ const isProxyError = ['不可用', '连接被拒绝', '连接中断', '超时', '无法解析']
72
+ .some(kw => firstMsg.includes(kw));
73
+
74
+ if (filteredResults.length === 0) {
75
+ if (isProxyError) {
76
+ console.error(` 所有请求失败,请检查代理: ${proxyUrl}\n`);
77
+ } else {
78
+ const show = errors.slice(0, 5);
79
+ for (const e of show) console.error(` ✗ ${e.url}: ${e.message}\n`);
80
+ if (errors.length > 5) console.error(` ... 还有 ${errors.length - 5} 个失败\n`);
81
+ }
82
+ console.error('未获取到数据');
83
+ if (outputFile) writeFileSync(outputFile, '[]', 'utf-8');
84
+ return;
85
+ } else {
86
+ if (isProxyError) {
87
+ console.error(` ${errors.length} 个请求失败,请检查代理: ${proxyUrl}\n`);
88
+ } else {
89
+ console.error(` ${errors.length} 个失败:`);
90
+ const show = errors.slice(0, 5);
91
+ for (const e of show) console.error(` ✗ ${e.url}: ${e.message}`);
92
+ if (errors.length > 5) console.error(` ... 还有 ${errors.length - 5} 个`);
93
+ }
94
+ }
95
+ }
96
+
97
+ const output = formatOutput(filteredResults, outputFormat);
98
+
99
+ if (outputFile) {
100
+ writeFileSync(outputFile, output, 'utf-8');
101
+ console.log(`\n结果已写入: ${outputFile}`);
102
+ } else {
103
+ process.stdout.write(output + '\n');
104
+ }
105
+
106
+ if (filter) {
107
+ console.log(`\n共 ${uniqueResults.length} 个${label},过滤后 ${filteredResults.length} 个(过滤条件: ${formatFilterDescription(filter)})`);
108
+ } else {
109
+ console.log(`\n共 ${filteredResults.length} 个${label}`);
110
+ }
111
+ }