tt-help-cli-ycl 1.3.5 → 1.3.7

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