tt-help-cli-ycl 1.3.0 → 1.3.2

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 (58) hide show
  1. package/README.md +17 -17
  2. package/cli.js +9 -9
  3. package/package.json +44 -44
  4. package/src/cli/auto.js +94 -0
  5. package/src/cli/explore.js +117 -0
  6. package/src/cli/progress.js +111 -0
  7. package/src/cli/scrape.js +47 -0
  8. package/src/cli/utils.js +18 -0
  9. package/src/cli/videos.js +41 -0
  10. package/src/cli/watch.js +28 -0
  11. package/src/lib/args.js +386 -397
  12. package/src/lib/browser/anti-detect.js +23 -0
  13. package/src/lib/browser/cdp.js +142 -0
  14. package/src/lib/browser/launch.js +43 -0
  15. package/src/lib/browser/page.js +80 -0
  16. package/src/lib/constants.js +85 -168
  17. package/src/lib/delay.js +54 -0
  18. package/src/lib/explore-fetch.js +118 -0
  19. package/src/lib/fetcher.js +45 -60
  20. package/src/lib/filter.js +66 -66
  21. package/src/lib/io.js +54 -76
  22. package/src/lib/output.js +80 -80
  23. package/src/lib/parser.js +47 -47
  24. package/src/lib/retry.js +44 -0
  25. package/src/lib/scrape.js +40 -39
  26. package/src/lib/url.js +52 -0
  27. package/src/main.mjs +199 -962
  28. package/src/results/user-videos-bar.lar.lar.moeta.json +37 -0
  29. package/src/scraper/auto-core.mjs +183 -0
  30. package/src/scraper/{core.cjs → core.mjs} +188 -214
  31. package/src/{explore-core.cjs → scraper/explore-core.mjs} +44 -42
  32. package/src/scraper/modules/captcha-handler.mjs +114 -0
  33. package/src/scraper/modules/comment-extractor.mjs +69 -0
  34. package/src/scraper/modules/follow-extractor.mjs +121 -0
  35. package/src/scraper/modules/{guess-extractor.cjs → guess-extractor.mjs} +51 -53
  36. package/src/scraper/modules/page-error-detector.mjs +70 -0
  37. package/src/scraper/modules/page-helpers.mjs +46 -0
  38. package/src/scraper/modules/scroll-collector.mjs +189 -0
  39. package/src/{get-user-videos-core.cjs → videos/core.mjs} +126 -143
  40. package/src/watch/data-store.mjs +239 -0
  41. package/src/watch/public/index.html +446 -271
  42. package/src/watch/server.mjs +257 -153
  43. package/src/auto-core.cjs +0 -367
  44. package/src/data-store.cjs +0 -69
  45. package/src/get-user-videos.cjs +0 -59
  46. package/src/lib/auto-browser.mjs +0 -13
  47. package/src/lib/explore.js +0 -225
  48. package/src/lib/get-user-videos-browser.mjs +0 -6
  49. package/src/lib/scrape-browser.mjs +0 -6
  50. package/src/scraper/index.cjs +0 -97
  51. package/src/scraper/modules/comment-extractor.cjs +0 -49
  52. package/src/scraper/modules/follow-extractor.cjs +0 -112
  53. package/src/scraper/modules/page-helpers.cjs +0 -422
  54. package/src/scraper/modules/scroll-collector.cjs +0 -173
  55. package/src/scraper/modules/video-scanner.cjs +0 -43
  56. package/src/test-auto-follow.cjs +0 -109
  57. package/src/test-extractors.cjs +0 -75
  58. package/src/test-follow.cjs +0 -41
@@ -0,0 +1,239 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ function inferStatus(u) {
5
+ if (u.restricted) return 'restricted';
6
+ if (u.error) return 'error';
7
+ if (u.processed) return 'done';
8
+ return 'pending';
9
+ }
10
+
11
+ export function createStore(filePath) {
12
+ let data = [];
13
+
14
+ if (filePath) {
15
+ const resolved = path.resolve(filePath);
16
+ if (fs.existsSync(resolved)) {
17
+ try {
18
+ const raw = fs.readFileSync(resolved, 'utf-8');
19
+ data = JSON.parse(raw);
20
+ if (!Array.isArray(data)) data = [];
21
+ } catch (e) {
22
+ console.error(`[data-store] 读取文件失败: ${e.message}`);
23
+ data = [];
24
+ }
25
+ }
26
+ }
27
+
28
+ for (const u of data) {
29
+ if (!u.status) u.status = inferStatus(u);
30
+ }
31
+
32
+ function save() {
33
+ if (!filePath) return;
34
+ const resolved = path.resolve(filePath);
35
+ try {
36
+ if (fs.existsSync(resolved)) {
37
+ const raw = fs.readFileSync(resolved, 'utf-8');
38
+ const diskData = JSON.parse(raw);
39
+ if (Array.isArray(diskData)) {
40
+ const memIds = new Set(data.map(u => u.uniqueId));
41
+ for (const diskUser of diskData) {
42
+ if (!memIds.has(diskUser.uniqueId)) {
43
+ if (!diskUser.status) diskUser.status = inferStatus(diskUser);
44
+ data.push(diskUser);
45
+ }
46
+ }
47
+ }
48
+ }
49
+ } catch (e) { console.error(`[data-store] 合并磁盘数据失败: ${e.message}`); }
50
+ const json = JSON.stringify(data, null, 2);
51
+ fs.writeFileSync(resolved, json, 'utf-8');
52
+ }
53
+
54
+ function getUser(uid) {
55
+ return data.find(u => u.uniqueId === uid);
56
+ }
57
+
58
+ function hasUser(uid) {
59
+ return getUser(uid) !== undefined;
60
+ }
61
+
62
+ function addUser(user) {
63
+ const existing = getUser(user.uniqueId);
64
+ if (existing) {
65
+ for (const key of Object.keys(user)) {
66
+ if (key === 'uniqueId' || key === 'sources') continue;
67
+ if (user[key] !== undefined && user[key] !== null && user[key] !== '') {
68
+ existing[key] = user[key];
69
+ }
70
+ }
71
+ if (user.status && existing.status !== 'done') {
72
+ existing.status = user.status;
73
+ }
74
+ if (user.processed && !existing.processedAt) {
75
+ existing.processedAt = Date.now();
76
+ }
77
+ if (user.sources && Array.isArray(user.sources)) {
78
+ existing.sources = [...new Set([...(existing.sources || []), ...user.sources])];
79
+ }
80
+ } else {
81
+ if (!user.status) user.status = inferStatus(user);
82
+ if (user.processed) user.processedAt = user.processedAt || Date.now();
83
+ data.unshift(user);
84
+ }
85
+ }
86
+
87
+ function getPendingUsers() {
88
+ return data.filter(u => u.status === 'pending');
89
+ }
90
+
91
+ function getProcessedUsers() {
92
+ return data.filter(u => u.status === 'done');
93
+ }
94
+
95
+ function getAllUsers() {
96
+ return data;
97
+ }
98
+
99
+ function claimNextJob(expireMs = 5 * 60 * 1000) {
100
+ let next = data.find(u => u.status === 'pending' && u.pinned);
101
+
102
+ if (!next) {
103
+ next = data.find(u => u.status === 'pending');
104
+ }
105
+
106
+ if (!next) {
107
+ const now = Date.now();
108
+ const expired = data.find(u =>
109
+ u.status === 'processing' && u.claimedAt && (now - u.claimedAt) > expireMs
110
+ );
111
+ if (expired) {
112
+ expired.status = 'pending';
113
+ delete expired.claimedAt;
114
+ next = expired;
115
+ }
116
+ }
117
+
118
+ if (next) {
119
+ next.status = 'processing';
120
+ next.claimedAt = Date.now();
121
+ return { uniqueId: next.uniqueId, nickname: next.nickname, claimedAt: next.claimedAt };
122
+ }
123
+ return null;
124
+ }
125
+
126
+ function commitJob(uniqueId, result) {
127
+ const user = getUser(uniqueId);
128
+ if (!user) return { saved: false, error: 'user not found' };
129
+
130
+ if (result.restricted) {
131
+ user.status = 'restricted';
132
+ if (result.userInfo) {
133
+ const info = result.userInfo;
134
+ for (const key of Object.keys(info)) {
135
+ if (key === 'uniqueId' || key === 'sources') continue;
136
+ if (info[key] !== undefined && info[key] !== null && info[key] !== '') {
137
+ user[key] = info[key];
138
+ }
139
+ }
140
+ }
141
+ user.processed = true;
142
+ user.processedAt = Date.now();
143
+ user.noVideo = true;
144
+ user.sources = [...new Set([...(user.sources || []), 'restricted'])];
145
+ } else if (result.error) {
146
+ user.status = 'error';
147
+ user.error = result.error;
148
+ user.sources = [...new Set([...(user.sources || []), 'error'])];
149
+ } else {
150
+ user.status = 'done';
151
+ user.processed = true;
152
+ user.processedAt = Date.now();
153
+ user.followerCount = result.userInfo?.followerCount ?? user.followerCount;
154
+ user.videoCount = result.userInfo?.videoCount ?? user.videoCount;
155
+ user.nickname = result.userInfo?.nickname || user.nickname;
156
+ user.locationCreated = result.userInfo?.locationCreated || user.locationCreated;
157
+ user.ttSeller = result.userInfo?.ttSeller ?? user.ttSeller;
158
+ user.verified = result.userInfo?.verified ?? user.verified;
159
+ user.region = result.userInfo?.region || user.region;
160
+ user.signature = result.userInfo?.signature ?? user.signature;
161
+ user.followingCount = result.userInfo?.followingCount ?? user.followingCount;
162
+ user.heartCount = result.userInfo?.heartCount ?? user.heartCount;
163
+ if (result.userInfo?.secUid) user.secUid = result.userInfo.secUid;
164
+ const extraFields = ['restricted', 'error', 'userInfo', 'discoveredVideoAuthors',
165
+ 'discoveredCommentAuthors', 'discoveredGuessAuthors', 'discoveredFollowing',
166
+ 'discoveredFollowers', 'uniqueId', 'sources'];
167
+ for (const key of Object.keys(result)) {
168
+ if (extraFields.includes(key)) continue;
169
+ if (result[key] !== undefined && result[key] !== null && result[key] !== '') {
170
+ user[key] = result[key];
171
+ }
172
+ }
173
+ user.sources = [...new Set([...(user.sources || []), 'processed'])];
174
+
175
+ const discovered = [
176
+ ...(result.discoveredVideoAuthors || []).map(v => ({
177
+ uniqueId: v.uniqueId, nickname: v.nickname, locationCreated: v.locationCreated,
178
+ sources: ['video']
179
+ })),
180
+ ...(result.discoveredCommentAuthors || []).map(c => {
181
+ const id = typeof c === 'string' ? c.replace(/^@/, '') : c.uniqueId;
182
+ return typeof c === 'string'
183
+ ? { uniqueId: id, sources: ['comment'] }
184
+ : { uniqueId: id, ...c, sources: [...new Set([...(c.sources || []), 'comment'])] };
185
+ }),
186
+ ...(result.discoveredGuessAuthors || []).map(g => {
187
+ const id = typeof g === 'string' ? g.replace(/^@/, '') : g.uniqueId;
188
+ return typeof g === 'string'
189
+ ? { uniqueId: id, sources: ['guess'] }
190
+ : { uniqueId: id, ...g, sources: [...new Set([...(g.sources || []), 'guess'])] };
191
+ }),
192
+ ...(result.discoveredFollowing || []).map(([handle, name]) => ({
193
+ uniqueId: handle.replace(/^@/, ''), nickname: name, sources: ['following']
194
+ })),
195
+ ...(result.discoveredFollowers || []).map(([handle, name]) => ({
196
+ uniqueId: handle.replace(/^@/, ''), nickname: name, sources: ['follower']
197
+ })),
198
+ ];
199
+
200
+ for (const du of discovered) {
201
+ if (!du.uniqueId) continue;
202
+ addUser(du);
203
+ }
204
+ }
205
+
206
+ delete user.claimedAt;
207
+ save();
208
+ return { saved: true };
209
+ }
210
+
211
+ function resetJob(uniqueId) {
212
+ const user = getUser(uniqueId);
213
+ if (!user) return { saved: false, error: 'user not found' };
214
+ user.status = 'pending';
215
+ delete user.claimedAt;
216
+ delete user.processedAt;
217
+ delete user.processed;
218
+ delete user.error;
219
+ delete user.restricted;
220
+ delete user.noVideo;
221
+ save();
222
+ return { saved: true };
223
+ }
224
+
225
+ function togglePin(uniqueId) {
226
+ const user = getUser(uniqueId);
227
+ if (!user) return { saved: false, error: 'user not found' };
228
+ user.pinned = !user.pinned;
229
+ save();
230
+ return { saved: true, pinned: user.pinned };
231
+ }
232
+
233
+ return {
234
+ save, getUser, hasUser, addUser,
235
+ getPendingUsers, getProcessedUsers, getAllUsers,
236
+ claimNextJob, commitJob, resetJob, togglePin,
237
+ data,
238
+ };
239
+ }