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,281 +1,291 @@
1
- import http from 'http';
2
- import os from 'os';
3
-
4
- import { readFileSync } from 'fs';
5
- import { join, dirname } from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { spawn } from 'child_process';
8
- import { createStore } from './data-store.mjs';
9
-
10
- const __filename = fileURLToPath(import.meta.url);
11
-
12
- function getLocalIP() {
13
- const ifaces = os.networkInterfaces();
14
- for (const name of Object.keys(ifaces)) {
15
- for (const iface of ifaces[name]) {
16
- if (iface.family === 'IPv4' && !iface.internal) {
17
- return iface.address;
18
- }
19
- }
20
- }
21
- return '0.0.0.0';
22
- }
23
- const __dirname = dirname(__filename);
24
- const publicDir = join(__dirname, 'public');
25
-
26
- function parseQuery(url) {
27
- const idx = url.indexOf('?');
28
- if (idx === -1) return { path: url, params: {} };
29
- const params = {};
30
- for (const kv of url.slice(idx + 1).split('&')) {
31
- const [k, v] = kv.split('=');
32
- params[decodeURIComponent(k)] = decodeURIComponent(v || '');
33
- }
34
- return { path: url.slice(0, idx), params };
35
- }
36
-
37
- function computeStats(users) {
38
- const total = users.length;
39
- const statusCounts = { pending: 0, processing: 0, done: 0, error: 0, restricted: 0 };
40
- for (const u of users) {
41
- statusCounts[u.status] = (statusCounts[u.status] || 0) + 1;
42
- }
43
-
44
- const targetUsers = users.filter(u =>
45
- u.ttSeller && u.verified === false && u.locationCreated === 'ES'
46
- ).length;
47
-
48
- const countryMap = {};
49
- for (const u of users) {
50
- if (u.status !== 'done') continue;
51
- const loc = u.locationCreated || '\u672a\u77e5';
52
- countryMap[loc] = (countryMap[loc] || 0) + 1;
53
- }
54
- const countryStats = Object.entries(countryMap)
55
- .map(([country, count]) => ({ country, count }))
56
- .sort((a, b) => b.count - a.count);
57
-
58
- const sourceCounts = { seed: 0, video: 0, comment: 0, guess: 0, following: 0, follower: 0, processed: 0, restricted: 0, error: 0, noVideo: 0 };
59
- for (const u of users) {
60
- if (u.status === 'restricted') { sourceCounts.restricted++; continue; }
61
- if (u.status === 'error') { sourceCounts.error++; continue; }
62
- if (u.noVideo) sourceCounts.noVideo++;
63
- const sources = u.sources || [];
64
- if (u.status === 'done') sourceCounts.processed++;
65
- if (sources.includes('video') && u.status !== 'done') sourceCounts.video++;
66
- if (sources.includes('comment') && u.status !== 'done') sourceCounts.comment++;
67
- if (sources.includes('guess') && u.status !== 'done') sourceCounts.guess++;
68
- if (sources.includes('following') && u.status !== 'done') sourceCounts.following++;
69
- if (sources.includes('follower') && u.status !== 'done') sourceCounts.follower++;
70
- if (!sources.includes('video') && !sources.includes('comment') && !sources.includes('guess') &&
71
- !sources.includes('following') && !sources.includes('follower') && u.status !== 'done') sourceCounts.seed++;
72
- }
73
-
74
- return {
75
- totalUsers: total,
76
- processedUsers: statusCounts.done,
77
- pendingUsers: statusCounts.pending,
78
- processingUsers: statusCounts.processing,
79
- restrictedUsers: statusCounts.restricted,
80
- errorUsers: statusCounts.error,
81
- targetUsers,
82
- countryStats,
83
- sourceStats: sourceCounts,
84
- };
85
- }
86
-
87
- function readBody(req) {
88
- return new Promise((resolve, reject) => {
89
- let body = '';
90
- req.on('data', chunk => body += chunk);
91
- req.on('end', () => {
92
- try {
93
- resolve(body ? JSON.parse(body) : {});
94
- } catch (e) {
95
- reject(e);
96
- }
97
- });
98
- req.on('error', reject);
99
- });
100
- }
101
-
102
- function sendJSON(res, code, data) {
103
- res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8' });
104
- res.end(JSON.stringify(data));
105
- }
106
-
107
- export function startWatchServer(outputFile, port = 3000) {
108
- return new Promise((_resolve, reject) => {
109
- const store = createStore(outputFile);
110
-
111
- const server = http.createServer(async (req, res) => {
112
- const { path: routePath, params } = parseQuery(req.url);
113
-
114
- if (req.method === 'POST' && routePath === '/api/users') {
115
- try {
116
- const { usernames } = await readBody(req);
117
- if (!Array.isArray(usernames) || usernames.length === 0) {
118
- sendJSON(res, 400, { error: 'usernames \u6570\u7ec4\u4e0d\u80fd\u4e3a\u7a7a' });
119
- return;
120
- }
121
- const existingIds = new Set(store.getAllUsers().map(u => u.uniqueId));
122
- const newUsers = usernames
123
- .map(u => u.replace(/^@/, '').trim())
124
- .filter(u => u && !existingIds.has(u));
125
- for (const nu of newUsers) {
126
- store.addUser({ uniqueId: nu, sources: ['seed'], status: 'pending' });
127
- }
128
- store.save();
129
- sendJSON(res, 200, {
130
- added: newUsers.length,
131
- skipped: usernames.length - newUsers.length,
132
- message: `\u5df2\u63d2\u5165 ${newUsers.length} \u4e2a\u7528\u6237`
133
- });
134
- } catch (e) {
135
- sendJSON(res, 400, { error: e.message });
136
- }
137
- return;
138
- }
139
-
140
- if (req.method === 'GET' && routePath === '/api/job') {
141
- const job = store.claimNextJob();
142
- if (job) {
143
- store.save();
144
- sendJSON(res, 200, { hasJob: true, user: job });
145
- } else {
146
- sendJSON(res, 200, { hasJob: false });
147
- }
148
- return;
149
- }
150
-
151
- const jobCommitMatch = routePath.match(/^\/api\/job\/([^/]+)$/);
152
- if (req.method === 'POST' && jobCommitMatch) {
153
- const uniqueId = jobCommitMatch[1];
154
- try {
155
- const result = await readBody(req);
156
- const ret = store.commitJob(uniqueId, result);
157
- if (ret.saved) {
158
- sendJSON(res, 200, ret);
159
- } else {
160
- sendJSON(res, 404, ret);
161
- }
162
- } catch (e) {
163
- sendJSON(res, 400, { error: e.message });
164
- }
165
- return;
166
- }
167
-
168
- const jobResetMatch = routePath.match(/^\/api\/job\/([^/]+)\/reset$/);
169
- if (req.method === 'POST' && jobResetMatch) {
170
- const uniqueId = jobResetMatch[1];
171
- const ret = store.resetJob(uniqueId);
172
- if (ret.saved) {
173
- sendJSON(res, 200, ret);
174
- } else {
175
- sendJSON(res, 404, ret);
176
- }
177
- return;
178
- }
179
-
180
- const jobPinMatch = routePath.match(/^\/api\/job\/([^/]+)\/pin$/);
181
- if (req.method === 'POST' && jobPinMatch) {
182
- const uniqueId = jobPinMatch[1];
183
- const ret = store.togglePin(uniqueId);
184
- sendJSON(res, ret.saved ? 200 : 404, ret);
185
- return;
186
- }
187
-
188
- if (req.method === 'GET' && routePath === '/api/stats') {
189
- const stats = computeStats(store.getAllUsers());
190
- sendJSON(res, 200, stats);
191
- return;
192
- }
193
-
194
- if (req.method === 'GET' && routePath === '/api/target-users') {
195
- const all = store.getAllUsers();
196
- const targets = all.filter(u =>
197
- u.ttSeller && u.verified === false && u.locationCreated === 'ES'
198
- ).map(u => ({
199
- uniqueId: u.uniqueId,
200
- nickname: u.nickname || '',
201
- followerCount: u.followerCount || 0,
202
- }));
203
- sendJSON(res, 200, { total: targets.length, users: targets });
204
- return;
205
- }
206
-
207
- if (req.method === 'GET' && routePath === '/api/users') {
208
- const all = store.getAllUsers();
209
- let filtered = [...all];
210
-
211
- if (params.status && params.status !== 'all') {
212
- filtered = filtered.filter(u => u.status === params.status);
213
- }
214
- if (params.target === '1') {
215
- filtered = filtered.filter(u =>
216
- u.ttSeller && u.verified === false && u.locationCreated === 'ES'
217
- );
218
- }
219
- if (params.search) {
220
- const s = params.search.toLowerCase();
221
- filtered = filtered.filter(u =>
222
- u.uniqueId.toLowerCase().includes(s) ||
223
- (u.nickname || '').toLowerCase().includes(s)
224
- );
225
- }
226
-
227
- const statusOrder = { processing: 0, pending: 1, done: 2, error: 3, restricted: 4 };
228
- filtered.sort((a, b) => {
229
- // 置顶优先
230
- if (a.pinned && !b.pinned) return -1;
231
- if (!a.pinned && b.pinned) return 1;
232
- const sa = statusOrder[a.status] ?? 9;
233
- const sb = statusOrder[b.status] ?? 9;
234
- if (sa !== sb) return sa - sb;
235
- if (a.status === 'done' && b.status === 'done') return (b.processedAt || 0) - (a.processedAt || 0);
236
- return (b.followerCount || 0) - (a.followerCount || 0);
237
- });
238
-
239
- const limit = parseInt(params.limit) || 50;
240
- const offset = parseInt(params.offset) || 0;
241
- const paged = filtered.slice(offset, offset + limit);
242
-
243
- sendJSON(res, 200, { total: filtered.length, users: paged });
244
- return;
245
- }
246
-
247
- if ((req.method === 'GET' && (routePath === '/' || routePath === '/index.html'))) {
248
- const html = readFileSync(join(publicDir, 'index.html'), 'utf-8');
249
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
250
- res.end(html);
251
- return;
252
- }
253
-
254
- res.writeHead(404);
255
- res.end('Not Found');
256
- });
257
-
258
- server.on('error', (err) => {
259
- if (err.code === 'EADDRINUSE') {
260
- console.error(`\u7aef\u53e3 ${port} \u5df2\u88ab\u5360\u7528\uff0c\u8bf7\u66f4\u6362\u7aef\u53e3\u540e\u91cd\u8bd5`);
261
- reject(err);
262
- } else {
263
- reject(err);
264
- }
265
- });
266
-
267
- const localIP = getLocalIP();
268
- server.listen(port, '0.0.0.0', () => {
269
- console.error(`Watch 监控服务已启动:`);
270
- console.error(` 本地访问: http://127.0.0.1:${port}`);
271
- console.error(` 局域网访问: http://${localIP}:${port}`);
272
- _resolve({ server, port });
273
- });
274
- });
275
- }
276
-
277
- export function openBrowser(port) {
278
- spawn('open', [`http://127.0.0.1:${port}`]).on('error', () => {});
279
- }
280
-
281
- export { getLocalIP };
1
+ import http from 'http';
2
+ import os from 'os';
3
+
4
+ import { readFileSync } from 'fs';
5
+ import { join, dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawn } from 'child_process';
8
+ import { createStore } from './data-store.mjs';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+
12
+ function getLocalIP() {
13
+ const ifaces = os.networkInterfaces();
14
+ for (const name of Object.keys(ifaces)) {
15
+ for (const iface of ifaces[name]) {
16
+ if (iface.family === 'IPv4' && !iface.internal) {
17
+ return iface.address;
18
+ }
19
+ }
20
+ }
21
+ return '0.0.0.0';
22
+ }
23
+ const __dirname = dirname(__filename);
24
+ const publicDir = join(__dirname, 'public');
25
+
26
+ function parseQuery(url) {
27
+ const idx = url.indexOf('?');
28
+ if (idx === -1) return { path: url, params: {} };
29
+ const params = {};
30
+ for (const kv of url.slice(idx + 1).split('&')) {
31
+ const [k, v] = kv.split('=');
32
+ params[decodeURIComponent(k)] = decodeURIComponent(v || '');
33
+ }
34
+ return { path: url.slice(0, idx), params };
35
+ }
36
+
37
+ function computeStats(users) {
38
+ const total = users.length;
39
+ const statusCounts = { pending: 0, processing: 0, done: 0, error: 0, restricted: 0 };
40
+ for (const u of users) {
41
+ statusCounts[u.status] = (statusCounts[u.status] || 0) + 1;
42
+ }
43
+
44
+ const targetUsers = users.filter(u =>
45
+ u.ttSeller && u.verified === false && u.locationCreated === 'ES'
46
+ ).length;
47
+
48
+ const countryMap = {};
49
+ for (const u of users) {
50
+ if (u.status !== 'done') continue;
51
+ const loc = u.locationCreated || '\u672a\u77e5';
52
+ countryMap[loc] = (countryMap[loc] || 0) + 1;
53
+ }
54
+ const countryStats = Object.entries(countryMap)
55
+ .map(([country, count]) => ({ country, count }))
56
+ .sort((a, b) => b.count - a.count);
57
+
58
+ const sourceCounts = { seed: 0, video: 0, comment: 0, guess: 0, following: 0, follower: 0, processed: 0, restricted: 0, error: 0, noVideo: 0 };
59
+ for (const u of users) {
60
+ if (u.status === 'restricted') { sourceCounts.restricted++; continue; }
61
+ if (u.status === 'error') { sourceCounts.error++; continue; }
62
+ if (u.noVideo) sourceCounts.noVideo++;
63
+ const sources = u.sources || [];
64
+ if (u.status === 'done') sourceCounts.processed++;
65
+ if (sources.includes('video') && u.status !== 'done') sourceCounts.video++;
66
+ if (sources.includes('comment') && u.status !== 'done') sourceCounts.comment++;
67
+ if (sources.includes('guess') && u.status !== 'done') sourceCounts.guess++;
68
+ if (sources.includes('following') && u.status !== 'done') sourceCounts.following++;
69
+ if (sources.includes('follower') && u.status !== 'done') sourceCounts.follower++;
70
+ if (!sources.includes('video') && !sources.includes('comment') && !sources.includes('guess') &&
71
+ !sources.includes('following') && !sources.includes('follower') && u.status !== 'done') sourceCounts.seed++;
72
+ }
73
+
74
+ return {
75
+ totalUsers: total,
76
+ processedUsers: statusCounts.done,
77
+ pendingUsers: statusCounts.pending,
78
+ processingUsers: statusCounts.processing,
79
+ restrictedUsers: statusCounts.restricted,
80
+ errorUsers: statusCounts.error,
81
+ targetUsers,
82
+ countryStats,
83
+ sourceStats: sourceCounts,
84
+ };
85
+ }
86
+
87
+ function readBody(req) {
88
+ return new Promise((resolve, reject) => {
89
+ let body = '';
90
+ req.on('data', chunk => body += chunk);
91
+ req.on('end', () => {
92
+ try {
93
+ resolve(body ? JSON.parse(body) : {});
94
+ } catch (e) {
95
+ reject(e);
96
+ }
97
+ });
98
+ req.on('error', reject);
99
+ });
100
+ }
101
+
102
+ function sendJSON(res, code, data) {
103
+ res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8' });
104
+ res.end(JSON.stringify(data));
105
+ }
106
+
107
+ export function startWatchServer(outputFile, port = 3000, existingStore) {
108
+ return new Promise((_resolve, reject) => {
109
+ const store = existingStore || createStore(outputFile);
110
+
111
+ function logJob(action, detail) {
112
+ const ts = new Date().toLocaleTimeString('zh-CN', { hour12: false });
113
+ const d = detail ? ' ' + Object.entries(detail).map(([k, v]) => `${k}=${v}`).join(' ') : '';
114
+ console.error(`[JOB ${ts}] ${action}${d}`);
115
+ }
116
+
117
+ const server = http.createServer(async (req, res) => {
118
+ const { path: routePath, params } = parseQuery(req.url);
119
+
120
+ if (req.method === 'POST' && routePath === '/api/users') {
121
+ try {
122
+ const { usernames } = await readBody(req);
123
+ if (!Array.isArray(usernames) || usernames.length === 0) {
124
+ sendJSON(res, 400, { error: 'usernames \u6570\u7ec4\u4e0d\u80fd\u4e3a\u7a7a' });
125
+ return;
126
+ }
127
+ const existingIds = new Set(store.getAllUsers().map(u => u.uniqueId));
128
+ const newUsers = usernames
129
+ .map(u => u.replace(/^@/, '').trim())
130
+ .filter(u => u && !existingIds.has(u));
131
+ for (const nu of newUsers) {
132
+ store.addUser({ uniqueId: nu, sources: ['seed'], status: 'pending' });
133
+ }
134
+ store.save();
135
+ sendJSON(res, 200, {
136
+ added: newUsers.length,
137
+ skipped: usernames.length - newUsers.length,
138
+ message: `\u5df2\u63d2\u5165 ${newUsers.length} \u4e2a\u7528\u6237`
139
+ });
140
+ } catch (e) {
141
+ sendJSON(res, 400, { error: e.message });
142
+ }
143
+ return;
144
+ }
145
+
146
+ if (req.method === 'GET' && routePath === '/api/job') {
147
+ const userId = params.userId || '';
148
+ const job = store.claimNextJob(userId);
149
+ if (job) {
150
+ store.save();
151
+ logJob('CLAIM', { user: job.uniqueId, clientId: userId });
152
+ sendJSON(res, 200, { hasJob: true, user: job });
153
+ } else {
154
+ logJob('CLAIM', { result: 'no-job', clientId: userId });
155
+ sendJSON(res, 200, { hasJob: false });
156
+ }
157
+ return;
158
+ }
159
+
160
+ const jobCommitMatch = routePath.match(/^\/api\/job\/([^/]+)$/);
161
+ if (req.method === 'POST' && jobCommitMatch) {
162
+ const uniqueId = jobCommitMatch[1];
163
+ try {
164
+ const result = await readBody(req);
165
+ const ret = store.commitJob(uniqueId, result);
166
+ logJob('COMMIT', { user: uniqueId, status: ret.status, newUsers: ret.newUsers?.length || 0 });
167
+ if (ret.saved) {
168
+ sendJSON(res, 200, ret);
169
+ } else {
170
+ sendJSON(res, 404, ret);
171
+ }
172
+ } catch (e) {
173
+ sendJSON(res, 400, { error: e.message });
174
+ }
175
+ return;
176
+ }
177
+
178
+ const jobResetMatch = routePath.match(/^\/api\/job\/([^/]+)\/reset$/);
179
+ if (req.method === 'POST' && jobResetMatch) {
180
+ const uniqueId = jobResetMatch[1];
181
+ const ret = store.resetJob(uniqueId);
182
+ if (ret.saved) {
183
+ sendJSON(res, 200, ret);
184
+ } else {
185
+ sendJSON(res, 404, ret);
186
+ }
187
+ return;
188
+ }
189
+
190
+ const jobPinMatch = routePath.match(/^\/api\/job\/([^/]+)\/pin$/);
191
+ if (req.method === 'POST' && jobPinMatch) {
192
+ const uniqueId = jobPinMatch[1];
193
+ const ret = store.togglePin(uniqueId);
194
+ sendJSON(res, ret.saved ? 200 : 404, ret);
195
+ return;
196
+ }
197
+
198
+ if (req.method === 'GET' && routePath === '/api/stats') {
199
+ const stats = computeStats(store.getAllUsers());
200
+ sendJSON(res, 200, stats);
201
+ return;
202
+ }
203
+
204
+ if (req.method === 'GET' && routePath === '/api/target-users') {
205
+ const all = store.getAllUsers();
206
+ const targets = all.filter(u =>
207
+ u.ttSeller && u.verified === false && u.locationCreated === 'ES'
208
+ ).map(u => ({
209
+ uniqueId: u.uniqueId,
210
+ nickname: u.nickname || '',
211
+ followerCount: u.followerCount || 0,
212
+ }));
213
+ sendJSON(res, 200, { total: targets.length, users: targets });
214
+ return;
215
+ }
216
+
217
+ if (req.method === 'GET' && routePath === '/api/users') {
218
+ const all = store.getAllUsers();
219
+ let filtered = [...all];
220
+
221
+ if (params.status && params.status !== 'all') {
222
+ filtered = filtered.filter(u => u.status === params.status);
223
+ }
224
+ if (params.target === '1') {
225
+ filtered = filtered.filter(u =>
226
+ u.ttSeller && u.verified === false && u.locationCreated === 'ES'
227
+ );
228
+ }
229
+ if (params.search) {
230
+ const s = params.search.toLowerCase();
231
+ filtered = filtered.filter(u =>
232
+ u.uniqueId.toLowerCase().includes(s) ||
233
+ (u.nickname || '').toLowerCase().includes(s)
234
+ );
235
+ }
236
+
237
+ const statusOrder = { processing: 0, pending: 1, done: 2, error: 3, restricted: 4 };
238
+ filtered.sort((a, b) => {
239
+ // 置顶优先
240
+ if (a.pinned && !b.pinned) return -1;
241
+ if (!a.pinned && b.pinned) return 1;
242
+ const sa = statusOrder[a.status] ?? 9;
243
+ const sb = statusOrder[b.status] ?? 9;
244
+ if (sa !== sb) return sa - sb;
245
+ if (a.status === 'done' && b.status === 'done') return (b.processedAt || 0) - (a.processedAt || 0);
246
+ return (b.followerCount || 0) - (a.followerCount || 0);
247
+ });
248
+
249
+ const limit = parseInt(params.limit) || 50;
250
+ const offset = parseInt(params.offset) || 0;
251
+ const paged = filtered.slice(offset, offset + limit);
252
+
253
+ sendJSON(res, 200, { total: filtered.length, users: paged });
254
+ return;
255
+ }
256
+
257
+ if ((req.method === 'GET' && (routePath === '/' || routePath === '/index.html'))) {
258
+ const html = readFileSync(join(publicDir, 'index.html'), 'utf-8');
259
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
260
+ res.end(html);
261
+ return;
262
+ }
263
+
264
+ res.writeHead(404);
265
+ res.end('Not Found');
266
+ });
267
+
268
+ server.on('error', (err) => {
269
+ if (err.code === 'EADDRINUSE') {
270
+ console.error(`\u7aef\u53e3 ${port} \u5df2\u88ab\u5360\u7528\uff0c\u8bf7\u66f4\u6362\u7aef\u53e3\u540e\u91cd\u8bd5`);
271
+ reject(err);
272
+ } else {
273
+ reject(err);
274
+ }
275
+ });
276
+
277
+ const localIP = getLocalIP();
278
+ server.listen(port, '0.0.0.0', () => {
279
+ console.error(`Watch 监控服务已启动:`);
280
+ console.error(` 本地访问: http://127.0.0.1:${port}`);
281
+ console.error(` 局域网访问: http://${localIP}:${port}`);
282
+ _resolve({ server, port });
283
+ });
284
+ });
285
+ }
286
+
287
+ export function openBrowser(port) {
288
+ spawn('open', [`http://127.0.0.1:${port}`]).on('error', () => {});
289
+ }
290
+
291
+ export { getLocalIP };
@@ -1,37 +0,0 @@
1
- {
2
- "user": {
3
- "uniqueId": "bar.lar.lar.moeta",
4
- "secUid": "MS4wLjABAAAA3cgKTWvKfga0JAWeakAzx3zQ-aFAC8RuQvxD4HQFraKKsc_TbOIyMo3_ofVlXofV",
5
- "nickname": "Bar Lar Lar Moetain",
6
- "ttSeller": false,
7
- "verified": false,
8
- "followerCount": 24000,
9
- "videoCount": 749,
10
- "followingCount": 4293,
11
- "heartCount": 254300,
12
- "signature": ""
13
- },
14
- "totalVideos": 5,
15
- "videos": [
16
- {
17
- "id": "7638231799084158228",
18
- "url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638231799084158228"
19
- },
20
- {
21
- "id": "7638162444698914068",
22
- "url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638162444698914068"
23
- },
24
- {
25
- "id": "7638116251767819541",
26
- "url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638116251767819541"
27
- },
28
- {
29
- "id": "7638069637321690388",
30
- "url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638069637321690388"
31
- },
32
- {
33
- "id": "7637927171025112341",
34
- "url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7637927171025112341"
35
- }
36
- ]
37
- }