tt-help-cli-ycl 1.3.3 → 1.3.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  .header h1 { font-size: 18px; color: #fe2c55; }
12
12
  .header .meta { font-size: 12px; color: #888; }
13
13
  .header .status { font-size: 12px; color: #4ade80; }
14
- .stats { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; margin-bottom: 16px; }
14
+ .stats { display: grid; grid-template-columns: repeat(7, 1fr); gap: 12px; margin-bottom: 16px; }
15
15
  .stat-card { background: #1a1a24; border-radius: 8px; padding: 16px; text-align: center; }
16
16
  .stat-card .label { font-size: 12px; color: #888; margin-bottom: 8px; }
17
17
  .stat-card .value { font-size: 28px; font-weight: 700; }
@@ -96,7 +96,8 @@
96
96
  <div class="stat-card"><div class="label">处理中</div><div class="value total" id="statProcessing">0</div></div>
97
97
  <div class="stat-card"><div class="label">已完成</div><div class="value done" id="statDone">0</div></div>
98
98
  <div class="stat-card"><div class="label">待处理</div><div class="value pending" id="statPending">0</div></div>
99
- <div class="stat-card"><div class="label">错误/受限</div><div class="value error" id="statError">0</div></div>
99
+ <div class="stat-card"><div class="label">错误</div><div class="value error" id="statError">0</div></div>
100
+ <div class="stat-card"><div class="label">受限</div><div class="value error" id="statRestricted">0</div></div>
100
101
  <div class="stat-card clickable" id="statTargetCard"><div class="label">目标用户(ES商家)</div><div class="value target" id="statTarget">0</div></div>
101
102
  </div>
102
103
  <div class="charts">
@@ -125,6 +126,7 @@
125
126
  <button data-filter="done" onclick="setFilter('done')">已处理</button>
126
127
  <button data-filter="error" onclick="setFilter('error')">错误</button>
127
128
  <button data-filter="restricted" onclick="setFilter('restricted')">受限</button>
129
+ <button data-filter="target" onclick="setFilter('target')" style="background:#7c3aed;color:#fff">目标用户</button>
128
130
  </div>
129
131
  <div class="table-scroll">
130
132
  <table>
@@ -156,7 +158,11 @@ async function fetchStats() {
156
158
  async function fetchUsers() {
157
159
  try {
158
160
  const params = new URLSearchParams();
159
- if (currentFilter !== 'all') params.set('status', currentFilter);
161
+ if (currentFilter === 'target') {
162
+ params.set('target', '1');
163
+ } else if (currentFilter !== 'all') {
164
+ params.set('status', currentFilter);
165
+ }
160
166
  const search = document.getElementById('searchInput').value.trim();
161
167
  if (search) params.set('search', search);
162
168
  params.set('limit', '200');
@@ -187,7 +193,8 @@ function renderStats() {
187
193
  flashEl('statProcessing', d.processingUsers || 0);
188
194
  flashEl('statDone', d.processedUsers);
189
195
  flashEl('statPending', d.pendingUsers);
190
- flashEl('statError', d.restrictedUsers + d.errorUsers);
196
+ flashEl('statError', d.errorUsers);
197
+ flashEl('statRestricted', d.restrictedUsers);
191
198
  flashEl('statTarget', d.targetUsers);
192
199
  document.getElementById('lastUpdate').textContent = '\u66f4\u65b0\u4e8e ' + new Date().toLocaleTimeString();
193
200
  document.getElementById('fileMeta').textContent = (d.processingUsers || 0) + ' \u5904\u7406\u4e2d, ' + d.totalUsers + ' \u4e2a\u7528\u6237';
@@ -431,8 +438,20 @@ document.getElementById('statTargetCard').addEventListener('click', async () =>
431
438
  const data = await res.json();
432
439
  if (!data.users.length) { showToast('暂无目标用户', true); return; }
433
440
  const text = data.users.map(u => '@' + u.uniqueId).join(', ');
434
- await navigator.clipboard.writeText(text);
435
- showToast(data.users.length + ' 个目标用户 ID 已复制到剪贴板');
441
+ if (navigator.clipboard && navigator.clipboard.writeText) {
442
+ await navigator.clipboard.writeText(text);
443
+ showToast(data.users.length + ' 个目标用户 ID 已复制到剪贴板');
444
+ } else {
445
+ const ta = document.createElement('textarea');
446
+ ta.value = text;
447
+ ta.style.position = 'fixed';
448
+ ta.style.left = '-9999px';
449
+ document.body.appendChild(ta);
450
+ ta.select();
451
+ document.execCommand('copy');
452
+ document.body.removeChild(ta);
453
+ showToast(data.users.length + ' 个目标用户 ID 已复制到剪贴板');
454
+ }
436
455
  } catch (e) {
437
456
  showToast('获取失败: ' + e.message, true);
438
457
  }
@@ -1,4 +1,6 @@
1
1
  import http from 'http';
2
+ import os from 'os';
3
+
2
4
  import { readFileSync } from 'fs';
3
5
  import { join, dirname } from 'path';
4
6
  import { fileURLToPath } from 'url';
@@ -6,6 +8,18 @@ import { spawn } from 'child_process';
6
8
  import { createStore } from './data-store.mjs';
7
9
 
8
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
+ }
9
23
  const __dirname = dirname(__filename);
10
24
  const publicDir = join(__dirname, 'public');
11
25
 
@@ -197,6 +211,11 @@ export function startWatchServer(outputFile, port = 3000) {
197
211
  if (params.status && params.status !== 'all') {
198
212
  filtered = filtered.filter(u => u.status === params.status);
199
213
  }
214
+ if (params.target === '1') {
215
+ filtered = filtered.filter(u =>
216
+ u.ttSeller && u.verified === false && u.locationCreated === 'ES'
217
+ );
218
+ }
200
219
  if (params.search) {
201
220
  const s = params.search.toLowerCase();
202
221
  filtered = filtered.filter(u =>
@@ -245,8 +264,11 @@ export function startWatchServer(outputFile, port = 3000) {
245
264
  }
246
265
  });
247
266
 
248
- server.listen(port, '127.0.0.1', () => {
249
- console.error(`Watch \u76d1\u63a7\u670d\u52a1\u5df2\u542f\u52a8: http://127.0.0.1:${port}`);
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}`);
250
272
  _resolve({ server, port });
251
273
  });
252
274
  });
@@ -255,3 +277,5 @@ export function startWatchServer(outputFile, port = 3000) {
255
277
  export function openBrowser(port) {
256
278
  spawn('open', [`http://127.0.0.1:${port}`]).on('error', () => {});
257
279
  }
280
+
281
+ export { getLocalIP };