tt-help-cli-ycl 1.0.7 → 1.1.0

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.
@@ -1,145 +1,153 @@
1
- import http from 'http';
2
- import { readFileSync, existsSync, writeFileSync } from 'fs';
3
- import { join, dirname, resolve } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { spawn } from 'child_process';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = dirname(__filename);
9
- const publicDir = join(__dirname, 'public');
10
-
11
- function analyzeData(users) {
12
- if (!Array.isArray(users)) users = [];
13
-
14
- const totalUsers = users.length;
15
- const processedUsers = users.filter(u => u.followerCount !== undefined).length;
16
- const pendingUsers = users.filter(u => u.followerCount === undefined && !u.error && !u.restricted).length;
17
- const restrictedUsers = users.filter(u => u.restricted).length;
18
- const errorUsers = users.filter(u => u.error && !u.followerCount).length;
19
-
20
- const countryMap = {};
21
- for (const u of users) {
22
- if (u.followerCount === undefined) continue;
23
- const loc = u.locationCreated || '未知';
24
- countryMap[loc] = (countryMap[loc] || 0) + 1;
25
- }
26
- const countryStats = Object.entries(countryMap)
27
- .map(([country, count]) => ({ country, count }))
28
- .sort((a, b) => b.count - a.count);
29
-
30
- const sourceCounts = { seed: 0, video: 0, comment: 0, processed: 0, restricted: 0, error: 0 };
31
- for (const u of users) {
32
- if (u.restricted) {
33
- sourceCounts.restricted++;
34
- continue;
35
- }
36
- if (u.error && !u.followerCount) {
37
- sourceCounts.error++;
38
- continue;
39
- }
40
- const sources = u.sources || [];
41
- if (sources.includes('processed')) sourceCounts.processed++;
42
- if (sources.includes('video') && !sources.includes('processed')) sourceCounts.video++;
43
- if (sources.includes('comment') && !sources.includes('processed')) sourceCounts.comment++;
44
- if (!sources.includes('video') && !sources.includes('comment') && !sources.includes('processed')) sourceCounts.seed++;
45
- }
46
-
47
- return {
48
- totalUsers,
49
- processedUsers,
50
- pendingUsers,
51
- restrictedUsers,
52
- errorUsers,
53
- countryStats,
54
- sourceStats: sourceCounts,
55
- users,
56
- };
57
- }
58
-
59
- function readDataFile(filePath) {
60
- const resolved = resolve(filePath);
61
- if (!existsSync(resolved)) {
62
- return [];
63
- }
64
- try {
65
- const raw = readFileSync(resolved, 'utf-8');
66
- const data = JSON.parse(raw);
67
- return Array.isArray(data) ? data : [];
68
- } catch (e) {
69
- return [];
70
- }
71
- }
72
-
73
- export function startWatchServer(outputFile, port = 3000) {
74
- return new Promise((resolve, reject) => {
75
- const server = http.createServer((req, res) => {
76
- if (req.url === '/' || req.url === '/index.html') {
77
- const html = readFileSync(join(publicDir, 'index.html'), 'utf-8');
78
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
79
- res.end(html);
80
- } else if (req.url === '/api/data') {
81
- const users = readDataFile(outputFile);
82
- const data = analyzeData(users);
83
- res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
84
- res.end(JSON.stringify(data));
85
- } else if (req.url === '/api/users' && req.method === 'POST') {
86
- let body = '';
87
- req.on('data', chunk => body += chunk);
88
- req.on('end', () => {
89
- try {
90
- const { usernames } = JSON.parse(body);
91
- if (!Array.isArray(usernames) || usernames.length === 0) {
92
- res.writeHead(400, { 'Content-Type': 'application/json' });
93
- res.end(JSON.stringify({ error: 'usernames 数组不能为空' }));
94
- return;
95
- }
96
- const resolved = resolve(outputFile);
97
- const existing = existsSync(resolved) ? readDataFile(outputFile) : [];
98
- const existingIds = new Set(existing.map(u => u.uniqueId));
99
- const newUsers = usernames
100
- .map(u => u.replace(/^@/, '').trim())
101
- .filter(u => u && !existingIds.has(u))
102
- .map(u => ({ uniqueId: u, sources: ['seed'] }));
103
- const updated = [...newUsers, ...existing];
104
- writeFileSync(resolved, JSON.stringify(updated, null, 2));
105
- res.writeHead(200, { 'Content-Type': 'application/json' });
106
- res.end(JSON.stringify({ added: newUsers.length, skipped: usernames.length - newUsers.length }));
107
- } catch (e) {
108
- res.writeHead(400, { 'Content-Type': 'application/json' });
109
- res.end(JSON.stringify({ error: e.message }));
110
- }
111
- });
112
- } else {
113
- res.writeHead(404);
114
- res.end('Not Found');
115
- }
116
- });
117
-
118
- server.on('error', (err) => {
119
- if (err.code === 'EADDRINUSE') {
120
- console.error(`端口 ${port} 已被占用,请更换端口后重试`);
121
- reject(err);
122
- } else {
123
- reject(err);
124
- }
125
- return;
126
- });
127
-
128
- server.listen(port, '127.0.0.1', () => {
129
- console.error(`Watch 监控服务已启动: http://127.0.0.1:${port}`);
130
- resolve({ server, port });
131
- });
132
- });
133
- }
134
-
135
- export function startWatchServerStandalone(outputFile, port = 3000) {
136
- return new Promise((resolve, reject) => {
137
- const openProcess = spawn('open', [`http://127.0.0.1:${port}`]);
138
- openProcess.on('error', () => {});
139
- startWatchServer(outputFile, port).then(resolve).catch(reject);
140
- });
141
- }
142
-
143
- export function openBrowser(port) {
144
- spawn('open', [`http://127.0.0.1:${port}`]).on('error', () => {});
145
- }
1
+ import http from 'http';
2
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
3
+ import { join, dirname, resolve } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { spawn } from 'child_process';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const publicDir = join(__dirname, 'public');
10
+
11
+ function analyzeData(users) {
12
+ if (!Array.isArray(users)) users = [];
13
+
14
+ const totalUsers = users.length;
15
+ const processedUsers = users.filter(u => u.processed).length;
16
+ const pendingUsers = users.filter(u => !u.processed && !u.error && !u.restricted).length;
17
+ const restrictedUsers = users.filter(u => u.restricted).length;
18
+ const errorUsers = users.filter(u => u.error && !u.processed).length;
19
+ const noVideoUsers = users.filter(u => u.noVideo).length;
20
+ const keepFollowUsers = users.filter(u => u.keepFollow).length;
21
+
22
+ const countryMap = {};
23
+ for (const u of users) {
24
+ if (!u.processed) continue;
25
+ const loc = u.locationCreated || '未知';
26
+ countryMap[loc] = (countryMap[loc] || 0) + 1;
27
+ }
28
+ const countryStats = Object.entries(countryMap)
29
+ .map(([country, count]) => ({ country, count }))
30
+ .sort((a, b) => b.count - a.count);
31
+
32
+ const sourceCounts = { seed: 0, video: 0, comment: 0, guess: 0, following: 0, follower: 0, processed: 0, restricted: 0, error: 0, noVideo: 0 };
33
+ for (const u of users) {
34
+ if (u.restricted) {
35
+ sourceCounts.restricted++;
36
+ continue;
37
+ }
38
+ if (u.error && !u.processed) {
39
+ sourceCounts.error++;
40
+ continue;
41
+ }
42
+ if (u.noVideo) sourceCounts.noVideo++;
43
+ const sources = u.sources || [];
44
+ if (u.processed) sourceCounts.processed++;
45
+ if (sources.includes('video') && !u.processed) sourceCounts.video++;
46
+ if (sources.includes('comment') && !u.processed) sourceCounts.comment++;
47
+ if (sources.includes('guess') && !u.processed) sourceCounts.guess++;
48
+ if (sources.includes('following') && !u.processed) sourceCounts.following++;
49
+ if (sources.includes('follower') && !u.processed) sourceCounts.follower++;
50
+ if (!sources.includes('video') && !sources.includes('comment') && !sources.includes('guess') && !sources.includes('following') && !sources.includes('follower') && !u.processed) sourceCounts.seed++;
51
+ }
52
+
53
+ return {
54
+ totalUsers,
55
+ processedUsers,
56
+ pendingUsers,
57
+ restrictedUsers,
58
+ errorUsers,
59
+ noVideoUsers,
60
+ keepFollowUsers,
61
+ countryStats,
62
+ sourceStats: sourceCounts,
63
+ users,
64
+ };
65
+ }
66
+
67
+ function readDataFile(filePath) {
68
+ const resolved = resolve(filePath);
69
+ if (!existsSync(resolved)) {
70
+ return [];
71
+ }
72
+ try {
73
+ const raw = readFileSync(resolved, 'utf-8');
74
+ const data = JSON.parse(raw);
75
+ return Array.isArray(data) ? data : [];
76
+ } catch (e) {
77
+ return [];
78
+ }
79
+ }
80
+
81
+ export function startWatchServer(outputFile, port = 3000) {
82
+ return new Promise((resolve, reject) => {
83
+ const server = http.createServer((req, res) => {
84
+ if (req.url === '/' || req.url === '/index.html') {
85
+ const html = readFileSync(join(publicDir, 'index.html'), 'utf-8');
86
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
87
+ res.end(html);
88
+ } else if (req.url === '/api/data') {
89
+ const users = readDataFile(outputFile);
90
+ const data = analyzeData(users);
91
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
92
+ res.end(JSON.stringify(data));
93
+ } else if (req.url === '/api/users' && req.method === 'POST') {
94
+ let body = '';
95
+ req.on('data', chunk => body += chunk);
96
+ req.on('end', () => {
97
+ try {
98
+ const { usernames } = JSON.parse(body);
99
+ if (!Array.isArray(usernames) || usernames.length === 0) {
100
+ res.writeHead(400, { 'Content-Type': 'application/json' });
101
+ res.end(JSON.stringify({ error: 'usernames 数组不能为空' }));
102
+ return;
103
+ }
104
+ const resolved = resolve(outputFile);
105
+ const existing = existsSync(resolved) ? readDataFile(outputFile) : [];
106
+ const existingIds = new Set(existing.map(u => u.uniqueId));
107
+ const newUsers = usernames
108
+ .map(u => u.replace(/^@/, '').trim())
109
+ .filter(u => u && !existingIds.has(u))
110
+ .map(u => ({ uniqueId: u, sources: ['seed'] }));
111
+ const updated = [...newUsers, ...existing];
112
+ writeFileSync(resolved, JSON.stringify(updated, null, 2));
113
+ res.writeHead(200, { 'Content-Type': 'application/json' });
114
+ res.end(JSON.stringify({ added: newUsers.length, skipped: usernames.length - newUsers.length }));
115
+ } catch (e) {
116
+ res.writeHead(400, { 'Content-Type': 'application/json' });
117
+ res.end(JSON.stringify({ error: e.message }));
118
+ }
119
+ });
120
+ } else {
121
+ res.writeHead(404);
122
+ res.end('Not Found');
123
+ }
124
+ });
125
+
126
+ server.on('error', (err) => {
127
+ if (err.code === 'EADDRINUSE') {
128
+ console.error(`端口 ${port} 已被占用,请更换端口后重试`);
129
+ reject(err);
130
+ } else {
131
+ reject(err);
132
+ }
133
+ return;
134
+ });
135
+
136
+ server.listen(port, '127.0.0.1', () => {
137
+ console.error(`Watch 监控服务已启动: http://127.0.0.1:${port}`);
138
+ resolve({ server, port });
139
+ });
140
+ });
141
+ }
142
+
143
+ export function startWatchServerStandalone(outputFile, port = 3000) {
144
+ return new Promise((resolve, reject) => {
145
+ const openProcess = spawn('open', [`http://127.0.0.1:${port}`]);
146
+ openProcess.on('error', () => {});
147
+ startWatchServer(outputFile, port).then(resolve).catch(reject);
148
+ });
149
+ }
150
+
151
+ export function openBrowser(port) {
152
+ spawn('open', [`http://127.0.0.1:${port}`]).on('error', () => {});
153
+ }
@@ -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
- }