tt-help-cli-ycl 1.3.65 → 1.3.73
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 +1 -1
- package/src/cli/attach.js +8 -4
- package/src/cli/refresh.js +16 -4
- package/src/lib/parse-ssr.mjs +1 -0
- package/src/scraper/explore-core.js +36 -19
- package/src/watch/data-store.js +390 -155
- package/src/watch/public/app.js +325 -62
- package/src/watch/public/index.html +10 -6
- package/src/watch/public/style.css +88 -1
- package/src/watch/server.js +73 -3
|
@@ -273,6 +273,18 @@ body {
|
|
|
273
273
|
border-color: #fe2c55;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
#targetReprocessBtn {
|
|
277
|
+
background: #dc2626;
|
|
278
|
+
border-color: #dc2626;
|
|
279
|
+
color: #fff;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#targetReprocessBtn:hover {
|
|
283
|
+
background: #ef4444;
|
|
284
|
+
border-color: #ef4444;
|
|
285
|
+
color: #fff;
|
|
286
|
+
}
|
|
287
|
+
|
|
276
288
|
.add-users {
|
|
277
289
|
display: flex;
|
|
278
290
|
gap: 8px;
|
|
@@ -392,6 +404,58 @@ body {
|
|
|
392
404
|
background: #e61944;
|
|
393
405
|
}
|
|
394
406
|
|
|
407
|
+
.loc-grid {
|
|
408
|
+
display: grid;
|
|
409
|
+
grid-template-columns: repeat(4, 1fr);
|
|
410
|
+
gap: 8px;
|
|
411
|
+
margin-top: 12px;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.loc-option {
|
|
415
|
+
padding: 10px 8px;
|
|
416
|
+
border: 1px solid #333;
|
|
417
|
+
border-radius: 6px;
|
|
418
|
+
background: #2a2a3a;
|
|
419
|
+
color: #ccc;
|
|
420
|
+
font-size: 13px;
|
|
421
|
+
font-weight: 600;
|
|
422
|
+
cursor: pointer;
|
|
423
|
+
transition: all 0.15s;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.loc-option:hover {
|
|
427
|
+
border-color: #fe2c55;
|
|
428
|
+
background: rgba(254, 44, 85, 0.1);
|
|
429
|
+
color: #fe2c55;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.loc-option.active {
|
|
433
|
+
border-color: #fe2c55;
|
|
434
|
+
background: rgba(254, 44, 85, 0.15);
|
|
435
|
+
color: #fe2c55;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.location-cell {
|
|
439
|
+
cursor: pointer;
|
|
440
|
+
transition: all 0.15s;
|
|
441
|
+
position: relative;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.location-cell:hover {
|
|
445
|
+
background: rgba(254, 44, 85, 0.08);
|
|
446
|
+
color: #fe2c55;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.location-cell.modified {
|
|
450
|
+
color: #f59e0b !important;
|
|
451
|
+
font-weight: 600 !important;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.location-cell.modified:hover {
|
|
455
|
+
color: #fbbf24 !important;
|
|
456
|
+
background: rgba(245, 158, 11, 0.1) !important;
|
|
457
|
+
}
|
|
458
|
+
|
|
395
459
|
.toast {
|
|
396
460
|
position: fixed;
|
|
397
461
|
top: 16px;
|
|
@@ -507,8 +571,9 @@ tr.row-flash {
|
|
|
507
571
|
}
|
|
508
572
|
|
|
509
573
|
.table-scroll {
|
|
510
|
-
max-height:
|
|
574
|
+
max-height: none;
|
|
511
575
|
overflow-y: auto;
|
|
576
|
+
height: 100%;
|
|
512
577
|
}
|
|
513
578
|
|
|
514
579
|
table {
|
|
@@ -788,6 +853,25 @@ td.user-id:hover {
|
|
|
788
853
|
color: #a78bfa;
|
|
789
854
|
}
|
|
790
855
|
|
|
856
|
+
.pending-country-item.selected {
|
|
857
|
+
background: #7c3aed;
|
|
858
|
+
border-color: #a78bfa;
|
|
859
|
+
box-shadow: 0 0 16px rgba(124, 58, 237, 0.6);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.pending-country-item.selected .country-name {
|
|
863
|
+
color: #fff;
|
|
864
|
+
font-weight: 700;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.pending-country-item.selected .country-count {
|
|
868
|
+
color: #fff;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.pending-country-item.selected .country-label {
|
|
872
|
+
color: #ddd6fe;
|
|
873
|
+
}
|
|
874
|
+
|
|
791
875
|
.back-btn {
|
|
792
876
|
padding: 6px 14px;
|
|
793
877
|
border: 1px solid #333;
|
|
@@ -983,7 +1067,10 @@ td.user-id:hover {
|
|
|
983
1067
|
.target-page-layout {
|
|
984
1068
|
display: grid;
|
|
985
1069
|
grid-template-columns: 320px 1fr;
|
|
1070
|
+
grid-template-rows: 1fr;
|
|
986
1071
|
gap: 16px;
|
|
1072
|
+
min-height: calc(100vh - 280px);
|
|
1073
|
+
align-items: stretch;
|
|
987
1074
|
}
|
|
988
1075
|
|
|
989
1076
|
.target-side-card {
|
package/src/watch/server.js
CHANGED
|
@@ -24,8 +24,24 @@ function getLocalIP() {
|
|
|
24
24
|
const __dirname = dirname(__filename);
|
|
25
25
|
const publicDir = join(__dirname, "public");
|
|
26
26
|
|
|
27
|
+
// Stats 缓存:避免每次请求都扫描 100 万+ 条 jobs 数据
|
|
28
|
+
let statsCache = null;
|
|
29
|
+
let statsCacheTime = 0;
|
|
30
|
+
const STATS_CACHE_TTL = 10000; // 10 秒缓存
|
|
31
|
+
|
|
32
|
+
function invalidateStatsCache() {
|
|
33
|
+
statsCache = null;
|
|
34
|
+
statsCacheTime = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
function computeStatsIncremental(st) {
|
|
28
|
-
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
if (statsCache && now - statsCacheTime < STATS_CACHE_TTL) {
|
|
40
|
+
return statsCache;
|
|
41
|
+
}
|
|
42
|
+
statsCache = st.getDashboardStats(DEFAULT_TARGET_LOCATIONS);
|
|
43
|
+
statsCacheTime = now;
|
|
44
|
+
return statsCache;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
function readBody(req) {
|
|
@@ -439,6 +455,29 @@ export function startWatchServer(dataAnchor, port = 3000, existingStore) {
|
|
|
439
455
|
return;
|
|
440
456
|
}
|
|
441
457
|
|
|
458
|
+
const userLocationMatch = routePath.match(
|
|
459
|
+
/^\/api\/user-location\/([^/]+)$/,
|
|
460
|
+
);
|
|
461
|
+
if (req.method === "PUT" && userLocationMatch) {
|
|
462
|
+
const uniqueId = userLocationMatch[1];
|
|
463
|
+
try {
|
|
464
|
+
const body = await readBody(req);
|
|
465
|
+
const ret = store.updateUserLocation(uniqueId, body.location);
|
|
466
|
+
if (ret.error) {
|
|
467
|
+
sendJSON(res, 404, { error: ret.error });
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
471
|
+
console.error(
|
|
472
|
+
`[JOB ${ts}] USER-LOCATION: ${uniqueId} → ${body.location} (modifiedAt=${ret.modifiedAt})`,
|
|
473
|
+
);
|
|
474
|
+
sendJSON(res, 200, ret);
|
|
475
|
+
} catch (e) {
|
|
476
|
+
sendJSON(res, 400, { error: e.message });
|
|
477
|
+
}
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
442
481
|
if (req.method === "GET" && routePath === "/api/comment-tasks") {
|
|
443
482
|
const limit = parseInt(params.limit) || 1;
|
|
444
483
|
const tasks = store.getPendingCommentTasks(limit);
|
|
@@ -530,8 +569,21 @@ export function startWatchServer(dataAnchor, port = 3000, existingStore) {
|
|
|
530
569
|
req.method === "GET" &&
|
|
531
570
|
routePath === "/api/target-users-by-country"
|
|
532
571
|
) {
|
|
533
|
-
|
|
572
|
+
// 摘要模式:只返回各国统计数
|
|
573
|
+
if (params.summary === "1") {
|
|
574
|
+
const result = store.getTargetUsersByCountry(
|
|
575
|
+
DEFAULT_TARGET_LOCATIONS,
|
|
576
|
+
{ summaryOnly: true },
|
|
577
|
+
);
|
|
578
|
+
sendJSON(res, 200, result);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// CSV 导出:全量数据
|
|
534
583
|
if (req.headers["accept"]?.includes("text/csv")) {
|
|
584
|
+
const result = store.getTargetUsersByCountry(
|
|
585
|
+
DEFAULT_TARGET_LOCATIONS,
|
|
586
|
+
);
|
|
535
587
|
const columns = [
|
|
536
588
|
"uniqueId",
|
|
537
589
|
"nickname",
|
|
@@ -580,9 +632,27 @@ export function startWatchServer(dataAnchor, port = 3000, existingStore) {
|
|
|
580
632
|
columns.map((c) => csvEscape(r[c])).join(","),
|
|
581
633
|
);
|
|
582
634
|
res.end(BOM + [header, ...lines].join("\r\n"));
|
|
583
|
-
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// 分页模式:按国家+搜索+分页查询用户
|
|
639
|
+
if (params.limit) {
|
|
640
|
+
const result = store.getTargetUsersByCountry(
|
|
641
|
+
DEFAULT_TARGET_LOCATIONS,
|
|
642
|
+
{
|
|
643
|
+
country: params.country || undefined,
|
|
644
|
+
search: params.search || undefined,
|
|
645
|
+
limit: parseInt(params.limit, 10),
|
|
646
|
+
offset: parseInt(params.offset || "0", 10),
|
|
647
|
+
},
|
|
648
|
+
);
|
|
584
649
|
sendJSON(res, 200, result);
|
|
650
|
+
return;
|
|
585
651
|
}
|
|
652
|
+
|
|
653
|
+
// 默认:全量(兼容旧调用)
|
|
654
|
+
const result = store.getTargetUsersByCountry(DEFAULT_TARGET_LOCATIONS);
|
|
655
|
+
sendJSON(res, 200, result);
|
|
586
656
|
return;
|
|
587
657
|
}
|
|
588
658
|
|