tt-help-cli-ycl 1.4.13 → 1.4.15
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/tag.js +513 -13
- package/src/lib/api-interceptor.js +2 -0
- package/src/lib/args.js +27 -0
- package/src/lib/constants.js +16 -0
- package/src/main.js +3 -0
- package/src/scraper/explore-core.js +1 -0
- package/src/watch/data-store.js +62 -0
- package/src/watch/db-columns.js +2 -0
- package/src/watch/db-crud.js +28 -0
- package/src/watch/db-schema.js +3 -0
- package/src/watch/db-tags.js +95 -0
- package/src/watch/public/app.js +80 -13
- package/src/watch/public/index.html +7 -1
- package/src/watch/public/style.css +43 -0
- package/src/watch/server.js +90 -17
package/src/watch/server.js
CHANGED
|
@@ -93,6 +93,7 @@ function inferClientType(routePath) {
|
|
|
93
93
|
if (routePath.startsWith("/api/redo-job")) return "refresh";
|
|
94
94
|
if (routePath.startsWith("/api/user-update-tasks")) return "attach";
|
|
95
95
|
if (routePath.startsWith("/api/comment-task")) return "comments";
|
|
96
|
+
if (routePath.startsWith("/api/tags/refresh")) return "tag-refresh";
|
|
96
97
|
if (routePath.startsWith("/api/tags")) return "scoring";
|
|
97
98
|
if (
|
|
98
99
|
routePath.startsWith("/api/job") ||
|
|
@@ -142,9 +143,12 @@ export function startWatchServer(
|
|
|
142
143
|
let discoverRunning = false;
|
|
143
144
|
let lastDiscoverTime = 0;
|
|
144
145
|
|
|
145
|
-
//
|
|
146
|
-
const
|
|
147
|
-
|
|
146
|
+
// 每国标签不足阈值:某国 new 标签低于此值时触发 discover
|
|
147
|
+
const DISCOVER_PER_COUNTRY_THRESHOLD = Math.max(
|
|
148
|
+
3,
|
|
149
|
+
parseInt(process.env.TAG_DISCOVER_PER_COUNTRY_THRESHOLD || "10", 10) ||
|
|
150
|
+
10,
|
|
151
|
+
);
|
|
148
152
|
|
|
149
153
|
async function runAutoDiscover(trigger = "timer") {
|
|
150
154
|
if (discoverRunning) return;
|
|
@@ -156,23 +160,14 @@ export function startWatchServer(
|
|
|
156
160
|
);
|
|
157
161
|
return;
|
|
158
162
|
}
|
|
159
|
-
// 先看全局总量:标签池充足时直接跳过,不浪费 LLM token
|
|
160
|
-
const globalStats = store.getTagStats();
|
|
161
|
-
const totalNew = globalStats?.new || 0;
|
|
162
|
-
if (totalNew >= DISCOVER_SUFFICIENT_TOTAL) {
|
|
163
|
-
console.error(
|
|
164
|
-
`[discover] 标签池充足 (${totalNew} 个 new ≥ ${DISCOVER_SUFFICIENT_TOTAL}),跳过 [触发: ${trigger}]`,
|
|
165
|
-
);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
163
|
discoverRunning = true;
|
|
169
164
|
try {
|
|
170
|
-
//
|
|
165
|
+
// 按国家维度判断:只为 new 标签不足的国家发现
|
|
171
166
|
const countriesToDiscover = [];
|
|
172
167
|
const { getTagsByStatus } = await import("./db-tags.js");
|
|
173
168
|
for (const country of DEFAULT_TARGET_LOCATIONS) {
|
|
174
169
|
const newTags = getTagsByStatus("new", 100, 0, country);
|
|
175
|
-
if (newTags.length <
|
|
170
|
+
if (newTags.length < DISCOVER_PER_COUNTRY_THRESHOLD) {
|
|
176
171
|
countriesToDiscover.push(country);
|
|
177
172
|
}
|
|
178
173
|
}
|
|
@@ -860,12 +855,13 @@ export function startWatchServer(
|
|
|
860
855
|
offset: parseInt(params.offset || "0", 10),
|
|
861
856
|
},
|
|
862
857
|
);
|
|
863
|
-
// 确保每个用户对象包含 topVideoPlayCount 和
|
|
858
|
+
// 确保每个用户对象包含 topVideoPlayCount、topVideoHref 和 topVideoIsAd
|
|
864
859
|
if (result.users && Array.isArray(result.users)) {
|
|
865
860
|
result.users = result.users.map((u) => ({
|
|
866
861
|
...u,
|
|
867
862
|
topVideoPlayCount: u.topVideoPlayCount || null,
|
|
868
863
|
topVideoHref: u.topVideoHref || null,
|
|
864
|
+
topVideoIsAd: u.topVideoIsAd ?? null,
|
|
869
865
|
}));
|
|
870
866
|
}
|
|
871
867
|
sendJSON(res, 200, result);
|
|
@@ -874,7 +870,7 @@ export function startWatchServer(
|
|
|
874
870
|
|
|
875
871
|
// 默认:全量(兼容旧调用)
|
|
876
872
|
const result = store.getTargetUsersByCountry(DEFAULT_TARGET_LOCATIONS);
|
|
877
|
-
// 确保每个用户对象包含 topVideoPlayCount 和
|
|
873
|
+
// 确保每个用户对象包含 topVideoPlayCount、topVideoHref 和 topVideoIsAd
|
|
878
874
|
if (result.countries && Array.isArray(result.countries)) {
|
|
879
875
|
for (const country of result.countries) {
|
|
880
876
|
if (country.users && Array.isArray(country.users)) {
|
|
@@ -882,6 +878,7 @@ export function startWatchServer(
|
|
|
882
878
|
...u,
|
|
883
879
|
topVideoPlayCount: u.topVideoPlayCount || null,
|
|
884
880
|
topVideoHref: u.topVideoHref || null,
|
|
881
|
+
topVideoIsAd: u.topVideoIsAd ?? null,
|
|
885
882
|
}));
|
|
886
883
|
}
|
|
887
884
|
}
|
|
@@ -890,6 +887,17 @@ export function startWatchServer(
|
|
|
890
887
|
return;
|
|
891
888
|
}
|
|
892
889
|
|
|
890
|
+
if (
|
|
891
|
+
req.method === "GET" &&
|
|
892
|
+
routePath === "/api/target-users-other-countries"
|
|
893
|
+
) {
|
|
894
|
+
const result = store.getModifiedTargetUsersOutsideLocations(
|
|
895
|
+
DEFAULT_TARGET_LOCATIONS,
|
|
896
|
+
);
|
|
897
|
+
sendJSON(res, 200, result);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
893
901
|
if (req.method === "GET" && routePath === "/api/client-errors") {
|
|
894
902
|
sendJSON(res, 200, { clients: store.getClientErrors() });
|
|
895
903
|
return;
|
|
@@ -1327,6 +1335,71 @@ export function startWatchServer(
|
|
|
1327
1335
|
return;
|
|
1328
1336
|
}
|
|
1329
1337
|
|
|
1338
|
+
// POST /api/bulk-author-locations — 批量查询作者国家(用于 tag 客户端 enrich 前跳过已知数据)
|
|
1339
|
+
if (req.method === "POST" && routePath === "/api/bulk-author-locations") {
|
|
1340
|
+
try {
|
|
1341
|
+
const body = await readBody(req);
|
|
1342
|
+
const uniqueIds = Array.isArray(body.uniqueIds) ? body.uniqueIds : [];
|
|
1343
|
+
const locations = store.getBulkAuthorLocations(uniqueIds);
|
|
1344
|
+
sendJSON(res, 200, { locations });
|
|
1345
|
+
} catch (e) {
|
|
1346
|
+
sendJSON(res, 500, { error: e.message });
|
|
1347
|
+
}
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// GET /api/tags/refresh-task — 领取一个 productive 标签用于刷新(score>70,未被锁定)
|
|
1352
|
+
if (req.method === "GET" && routePath === "/api/tags/refresh-task") {
|
|
1353
|
+
const task = store.getProductiveRefreshTask();
|
|
1354
|
+
if (!task) {
|
|
1355
|
+
sendJSON(res, 200, {
|
|
1356
|
+
tag: null,
|
|
1357
|
+
message: "no productive tags available for refresh",
|
|
1358
|
+
});
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
sendJSON(res, 200, {
|
|
1362
|
+
tag: task.tag,
|
|
1363
|
+
score: task.score,
|
|
1364
|
+
refreshedAt: task.refreshed_at,
|
|
1365
|
+
});
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// POST /api/tags/refresh-claim { tag } — 锁定 productive 标签用于刷新(防并发冲突)
|
|
1370
|
+
if (req.method === "POST" && routePath === "/api/tags/refresh-claim") {
|
|
1371
|
+
try {
|
|
1372
|
+
const body = await readBody(req);
|
|
1373
|
+
const tag = (body.tag || "").trim().toLowerCase();
|
|
1374
|
+
if (!tag) {
|
|
1375
|
+
sendJSON(res, 400, { error: "tag 不能为空" });
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const result = store.claimRefreshTag(tag);
|
|
1379
|
+
sendJSON(res, result.ok ? 200 : 409, result);
|
|
1380
|
+
} catch (e) {
|
|
1381
|
+
sendJSON(res, 500, { error: e.message });
|
|
1382
|
+
}
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// POST /api/tags/refresh-result { tag, pushedUsers, error } — 报告刷新结果并解锁
|
|
1387
|
+
if (req.method === "POST" && routePath === "/api/tags/refresh-result") {
|
|
1388
|
+
try {
|
|
1389
|
+
const body = await readBody(req);
|
|
1390
|
+
const tag = (body.tag || "").trim().toLowerCase();
|
|
1391
|
+
if (!tag) {
|
|
1392
|
+
sendJSON(res, 400, { error: "tag 不能为空" });
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const result = store.reportRefreshResult(tag, body);
|
|
1396
|
+
sendJSON(res, result.ok ? 200 : 400, result);
|
|
1397
|
+
} catch (e) {
|
|
1398
|
+
sendJSON(res, 500, { error: e.message });
|
|
1399
|
+
}
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1330
1403
|
// GET /api/tags/recent-generated?country=ES&lookback=10 — 最近N轮生成的标签(去重)
|
|
1331
1404
|
if (req.method === "GET" && routePath === "/api/tags/recent-generated") {
|
|
1332
1405
|
try {
|
|
@@ -1915,7 +1988,7 @@ export function startWatchServer(
|
|
|
1915
1988
|
);
|
|
1916
1989
|
setTimeout(() => runAutoDiscover("startup"), 30000);
|
|
1917
1990
|
console.error(
|
|
1918
|
-
`[discover] 定时+按需发现已启动: 定时间隔 ${DISCOVER_INTERVAL_MS / 60000} 分钟, 每国 ${DISCOVER_COUNT_PER_COUNTRY} 个标签,
|
|
1991
|
+
`[discover] 定时+按需发现已启动: 定时间隔 ${DISCOVER_INTERVAL_MS / 60000} 分钟, 每国 ${DISCOVER_COUNT_PER_COUNTRY} 个标签, 每国不足阈值 ${DISCOVER_PER_COUNTRY_THRESHOLD}, 按需触发阈值 ${DISCOVER_LOW_THRESHOLD}, 冷却期 ${DISCOVER_COOLDOWN_MS / 1000}s`,
|
|
1919
1992
|
);
|
|
1920
1993
|
|
|
1921
1994
|
_resolve({ server, port });
|