tt-help-cli-ycl 1.4.18 → 1.4.20
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/recheck.js +381 -0
- package/src/cli/tag.js +1 -1
- package/src/lib/args.js +64 -7
- package/src/lib/constants.js +11 -0
- package/src/main.js +3 -0
- package/src/watch/data-store.js +270 -9
- package/src/watch/db-tags.js +6 -6
- package/src/watch/public/app.js +247 -1
- package/src/watch/public/index.html +2 -0
- package/src/watch/public/style.css +11 -0
- package/src/watch/server.js +95 -0
package/src/watch/data-store.js
CHANGED
|
@@ -556,6 +556,64 @@ function getModifiedTargetUsersOutsideLocations(targetLocations = []) {
|
|
|
556
556
|
return { total: rows.length, countries };
|
|
557
557
|
}
|
|
558
558
|
|
|
559
|
+
/**
|
|
560
|
+
* 目标商家刷新进度统计:按国家统计最近 N 小时内刷新过的 tt_seller 用户数。
|
|
561
|
+
* 返回格式与 getRefreshStats(tags)一致,便于前端复用渲染逻辑。
|
|
562
|
+
*/
|
|
563
|
+
function getTargetRefreshStats(hours = 24) {
|
|
564
|
+
if (!getDb()) return { countries: [], total: 0, refreshed: 0 };
|
|
565
|
+
|
|
566
|
+
const targetLocations = DEFAULT_TARGET_LOCATIONS;
|
|
567
|
+
const placeholders = targetLocations.map(() => "?").join(", ");
|
|
568
|
+
|
|
569
|
+
// 按国家统计总数
|
|
570
|
+
const totalRows = getDb()
|
|
571
|
+
.prepare(
|
|
572
|
+
`
|
|
573
|
+
SELECT location_created as country, COUNT(*) as cnt
|
|
574
|
+
FROM jobs
|
|
575
|
+
WHERE tt_seller = 1
|
|
576
|
+
AND verified = 0
|
|
577
|
+
AND location_created IN (${placeholders})
|
|
578
|
+
GROUP BY location_created
|
|
579
|
+
ORDER BY cnt DESC
|
|
580
|
+
`,
|
|
581
|
+
)
|
|
582
|
+
.all(...targetLocations);
|
|
583
|
+
|
|
584
|
+
if (!totalRows.length) return { countries: [], total: 0, refreshed: 0 };
|
|
585
|
+
|
|
586
|
+
// 按国家统计最近 N 小时内刷新过的数量
|
|
587
|
+
const threshold = Date.now() - hours * 3600 * 1000;
|
|
588
|
+
const refreshedRows = getDb()
|
|
589
|
+
.prepare(
|
|
590
|
+
`
|
|
591
|
+
SELECT location_created as country, COUNT(*) as cnt
|
|
592
|
+
FROM jobs
|
|
593
|
+
WHERE tt_seller = 1
|
|
594
|
+
AND verified = 0
|
|
595
|
+
AND location_created IN (${placeholders})
|
|
596
|
+
AND refresh_time >= ?
|
|
597
|
+
GROUP BY location_created
|
|
598
|
+
`,
|
|
599
|
+
)
|
|
600
|
+
.all(...targetLocations, threshold);
|
|
601
|
+
const refreshedMap = new Map();
|
|
602
|
+
for (const r of refreshedRows) refreshedMap.set(r.country, r.cnt);
|
|
603
|
+
|
|
604
|
+
const results = [];
|
|
605
|
+
let totalAll = 0;
|
|
606
|
+
let refreshedAll = 0;
|
|
607
|
+
for (const r of totalRows) {
|
|
608
|
+
const refreshed = refreshedMap.get(r.country) || 0;
|
|
609
|
+
totalAll += r.cnt;
|
|
610
|
+
refreshedAll += refreshed;
|
|
611
|
+
results.push({ country: r.country, total: r.cnt, refreshed });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return { countries: results, total: totalAll, refreshed: refreshedAll };
|
|
615
|
+
}
|
|
616
|
+
|
|
559
617
|
export function createStore(filePath, options = {}) {
|
|
560
618
|
if (!filePath) {
|
|
561
619
|
throw new Error("createStore requires an explicit .db path");
|
|
@@ -1008,7 +1066,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1008
1066
|
|
|
1009
1067
|
if (normalizedLocations && normalizedLocations.length > 0) {
|
|
1010
1068
|
conditions.push(
|
|
1011
|
-
`UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")})`,
|
|
1069
|
+
`(UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")}) OR COALESCE(guessed_location, '') = '')`,
|
|
1012
1070
|
);
|
|
1013
1071
|
args.push(...normalizedLocations);
|
|
1014
1072
|
}
|
|
@@ -1164,7 +1222,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1164
1222
|
const llmCount =
|
|
1165
1223
|
getDb()
|
|
1166
1224
|
.prepare(
|
|
1167
|
-
`SELECT COUNT(*) as c FROM raw_jobs WHERE ${whereSql} AND guessed_location = ? AND (sources NOT LIKE '%tag%' OR sources IS NULL) AND (sources NOT LIKE '%comment%' OR sources IS NULL)`,
|
|
1225
|
+
`SELECT COUNT(*) as c FROM raw_jobs WHERE ${whereSql} AND (guessed_location = ? OR COALESCE(guessed_location, '') = '') AND (sources NOT LIKE '%tag%' OR sources IS NULL) AND (sources NOT LIKE '%comment%' OR sources IS NULL)`,
|
|
1168
1226
|
)
|
|
1169
1227
|
.get(...locationArgs)?.c || 0;
|
|
1170
1228
|
|
|
@@ -1190,7 +1248,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1190
1248
|
const samples = getDb()
|
|
1191
1249
|
.prepare(
|
|
1192
1250
|
`
|
|
1193
|
-
SELECT * FROM raw_jobs WHERE ${whereSql} AND guessed_location = ?
|
|
1251
|
+
SELECT * FROM raw_jobs WHERE ${whereSql} AND (guessed_location = ? OR COALESCE(guessed_location, '') = '')
|
|
1194
1252
|
AND (sources NOT LIKE '%tag%' OR sources IS NULL)
|
|
1195
1253
|
AND (sources NOT LIKE '%comment%' OR sources IS NULL)
|
|
1196
1254
|
ORDER BY COALESCE(video_count, 0) DESC, created_at DESC
|
|
@@ -1361,7 +1419,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1361
1419
|
const currentLimit = Math.max(0, Math.min(quota, safeLimit - moved));
|
|
1362
1420
|
if (!currentLimit) continue;
|
|
1363
1421
|
|
|
1364
|
-
const locationWhere = `${whereSql} AND UPPER(COALESCE(guessed_location, '')) =
|
|
1422
|
+
const locationWhere = `${whereSql} AND (UPPER(COALESCE(guessed_location, '')) = ? OR COALESCE(guessed_location, '') = '')`;
|
|
1365
1423
|
const locationArgs = [...args, location];
|
|
1366
1424
|
|
|
1367
1425
|
getDb()
|
|
@@ -1488,7 +1546,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1488
1546
|
if (!group) return;
|
|
1489
1547
|
if (group.type === "include") {
|
|
1490
1548
|
where.push(
|
|
1491
|
-
`UPPER(COALESCE(guessed_location, '')) IN (${group.values.map(() => "?").join(", ")})`,
|
|
1549
|
+
`(UPPER(COALESCE(guessed_location, '')) IN (${group.values.map(() => "?").join(", ")}) OR COALESCE(guessed_location, '') = '')`,
|
|
1492
1550
|
);
|
|
1493
1551
|
args.push(...group.values);
|
|
1494
1552
|
return;
|
|
@@ -1559,7 +1617,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1559
1617
|
}
|
|
1560
1618
|
if (normalizedLocations.length > 0) {
|
|
1561
1619
|
where.push(
|
|
1562
|
-
`UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")})`,
|
|
1620
|
+
`(UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")}) OR COALESCE(guessed_location, '') = '')`,
|
|
1563
1621
|
);
|
|
1564
1622
|
args.push(...normalizedLocations);
|
|
1565
1623
|
}
|
|
@@ -1957,7 +2015,7 @@ export function createStore(filePath, options = {}) {
|
|
|
1957
2015
|
if (!group) return;
|
|
1958
2016
|
if (group.type === "include") {
|
|
1959
2017
|
where.push(
|
|
1960
|
-
`UPPER(COALESCE(guessed_location, '')) IN (${group.values.map(() => "?").join(", ")})`,
|
|
2018
|
+
`(UPPER(COALESCE(guessed_location, '')) IN (${group.values.map(() => "?").join(", ")}) OR COALESCE(guessed_location, '') = '')`,
|
|
1961
2019
|
);
|
|
1962
2020
|
args.push(...group.values);
|
|
1963
2021
|
return;
|
|
@@ -2025,7 +2083,7 @@ export function createStore(filePath, options = {}) {
|
|
|
2025
2083
|
}
|
|
2026
2084
|
if (normalizedLocations.length > 0) {
|
|
2027
2085
|
where.push(
|
|
2028
|
-
`UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")})`,
|
|
2086
|
+
`(UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")}) OR COALESCE(guessed_location, '') = '')`,
|
|
2029
2087
|
);
|
|
2030
2088
|
args.push(...normalizedLocations);
|
|
2031
2089
|
}
|
|
@@ -2677,7 +2735,20 @@ export function createStore(filePath, options = {}) {
|
|
|
2677
2735
|
}
|
|
2678
2736
|
|
|
2679
2737
|
function getClientErrors() {
|
|
2680
|
-
|
|
2738
|
+
const now = Date.now();
|
|
2739
|
+
const expired = [];
|
|
2740
|
+
const result = [];
|
|
2741
|
+
for (const [userId, err] of clientErrors) {
|
|
2742
|
+
if (now - err.timestamp > 10 * 60 * 1000) {
|
|
2743
|
+
expired.push(userId);
|
|
2744
|
+
} else {
|
|
2745
|
+
result.push(err);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
for (const userId of expired) {
|
|
2749
|
+
clientErrors.delete(userId);
|
|
2750
|
+
}
|
|
2751
|
+
return result;
|
|
2681
2752
|
}
|
|
2682
2753
|
|
|
2683
2754
|
function getClientLoginStatus() {
|
|
@@ -2817,6 +2888,143 @@ export function createStore(filePath, options = {}) {
|
|
|
2817
2888
|
return pending;
|
|
2818
2889
|
}
|
|
2819
2890
|
|
|
2891
|
+
// ===== recheck-jobs:重检 jobs 表中 done 非商家用户 =====
|
|
2892
|
+
|
|
2893
|
+
function getRecheckJobs(limit, countries) {
|
|
2894
|
+
const targetCountries = countries
|
|
2895
|
+
? countries.map((c) => String(c).trim().toUpperCase())
|
|
2896
|
+
: [];
|
|
2897
|
+
const hasCountryFilter = targetCountries.length > 0;
|
|
2898
|
+
|
|
2899
|
+
if (getDb()) {
|
|
2900
|
+
const l = Math.max(1, parseInt(limit) || 5);
|
|
2901
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
2902
|
+
|
|
2903
|
+
let sql = `
|
|
2904
|
+
SELECT *
|
|
2905
|
+
FROM jobs
|
|
2906
|
+
WHERE status = 'done'
|
|
2907
|
+
AND (tt_seller IS NULL OR tt_seller != 1)
|
|
2908
|
+
AND (updated_at IS NULL OR updated_at < ?)
|
|
2909
|
+
`;
|
|
2910
|
+
const sqlParams = [sevenDaysAgo];
|
|
2911
|
+
|
|
2912
|
+
if (hasCountryFilter) {
|
|
2913
|
+
const placeholders = targetCountries.map(() => "?").join(", ");
|
|
2914
|
+
sql += ` AND UPPER(COALESCE(guessed_location, '')) IN (${placeholders})`;
|
|
2915
|
+
sqlParams.push(...targetCountries);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
sql += ` ORDER BY updated_at ASC LIMIT ?`;
|
|
2919
|
+
sqlParams.push(l);
|
|
2920
|
+
|
|
2921
|
+
const rows = getDb()
|
|
2922
|
+
.prepare(sql)
|
|
2923
|
+
.all(...sqlParams);
|
|
2924
|
+
if (rows.length === 0) return [];
|
|
2925
|
+
|
|
2926
|
+
const now = Date.now();
|
|
2927
|
+
const bumpStmt = getDb().prepare(
|
|
2928
|
+
`UPDATE jobs SET updated_at = ? WHERE unique_id = ?`,
|
|
2929
|
+
);
|
|
2930
|
+
const bumpTxn = getDb().transaction((items) => {
|
|
2931
|
+
for (const item of items) {
|
|
2932
|
+
bumpStmt.run(now, item.unique_id);
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
bumpTxn(rows);
|
|
2936
|
+
|
|
2937
|
+
return rows.map((row) => {
|
|
2938
|
+
const mapped = mapJobRow(row);
|
|
2939
|
+
mapped.updatedAt = now;
|
|
2940
|
+
return mapped;
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
const l = Math.max(1, parseInt(limit) || 5);
|
|
2945
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
2946
|
+
const pending = data
|
|
2947
|
+
.filter((u) => {
|
|
2948
|
+
if (u.status !== "done") return false;
|
|
2949
|
+
if (u.ttSeller === 1) return false;
|
|
2950
|
+
if (u.updatedAt && u.updatedAt >= sevenDaysAgo) return false;
|
|
2951
|
+
if (hasCountryFilter) {
|
|
2952
|
+
const loc = (u.guessedLocation || "").toUpperCase();
|
|
2953
|
+
return targetCountries.includes(loc);
|
|
2954
|
+
}
|
|
2955
|
+
return true;
|
|
2956
|
+
})
|
|
2957
|
+
.sort((a, b) => (a.updatedAt || 0) - (b.updatedAt || 0))
|
|
2958
|
+
.slice(0, l);
|
|
2959
|
+
pending.forEach((u) => {
|
|
2960
|
+
u.updatedAt = Date.now();
|
|
2961
|
+
});
|
|
2962
|
+
save();
|
|
2963
|
+
return pending;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
function batchUpdateRecheckJobs(updates) {
|
|
2967
|
+
if (getDb()) {
|
|
2968
|
+
const results = [];
|
|
2969
|
+
const now = Date.now();
|
|
2970
|
+
const bumpStmt = getDb().prepare(
|
|
2971
|
+
`UPDATE jobs SET updated_at = ? WHERE unique_id = ?`,
|
|
2972
|
+
);
|
|
2973
|
+
const txn = getDb().transaction((items) => {
|
|
2974
|
+
items.forEach((item) => {
|
|
2975
|
+
const uniqueId = item.uniqueId;
|
|
2976
|
+
const info = item.info;
|
|
2977
|
+
if (info && info.error && info.statusCode !== undefined) {
|
|
2978
|
+
// 无效账号:只更新 updated_at,不污染其他字段
|
|
2979
|
+
bumpStmt.run(now, uniqueId);
|
|
2980
|
+
results.push({ uniqueId, ok: true, skipped: true });
|
|
2981
|
+
} else {
|
|
2982
|
+
// 过滤 info 中的 null/undefined/空值,避免覆盖 jobs 表已有数据
|
|
2983
|
+
const cleanInfo = {};
|
|
2984
|
+
for (const [k, v] of Object.entries(info || {})) {
|
|
2985
|
+
if (v !== null && v !== undefined && v !== "") {
|
|
2986
|
+
cleanInfo[k] = v;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
const ret = updateJobInfo(uniqueId, cleanInfo, true);
|
|
2990
|
+
results.push(
|
|
2991
|
+
ret.error
|
|
2992
|
+
? { uniqueId, error: ret.error }
|
|
2993
|
+
: { uniqueId, ok: true },
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
});
|
|
2998
|
+
txn(updates);
|
|
2999
|
+
return { results };
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
const results = [];
|
|
3003
|
+
const now = Date.now();
|
|
3004
|
+
for (const item of updates) {
|
|
3005
|
+
const { uniqueId, info } = item;
|
|
3006
|
+
if (info && info.error && info.statusCode !== undefined) {
|
|
3007
|
+
// 无效账号:只更新 updated_at
|
|
3008
|
+
const existing = data.find((u) => u.uniqueId === uniqueId);
|
|
3009
|
+
if (existing) existing.updatedAt = now;
|
|
3010
|
+
results.push({ uniqueId, ok: true, skipped: true });
|
|
3011
|
+
} else {
|
|
3012
|
+
const cleanInfo = {};
|
|
3013
|
+
for (const [k, v] of Object.entries(info || {})) {
|
|
3014
|
+
if (v !== null && v !== undefined && v !== "") {
|
|
3015
|
+
cleanInfo[k] = v;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
const ret = updateJobInfo(uniqueId, cleanInfo, true);
|
|
3019
|
+
results.push(
|
|
3020
|
+
ret.error ? { uniqueId, error: ret.error } : { uniqueId, ok: true },
|
|
3021
|
+
);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
save();
|
|
3025
|
+
return { results };
|
|
3026
|
+
}
|
|
3027
|
+
|
|
2820
3028
|
function updateUserInfo(uniqueId, info) {
|
|
2821
3029
|
if (getDb()) {
|
|
2822
3030
|
return updateJobInfo(uniqueId, info, true);
|
|
@@ -3363,6 +3571,8 @@ export function createStore(filePath, options = {}) {
|
|
|
3363
3571
|
getNextRedoJob,
|
|
3364
3572
|
commitRedoJob,
|
|
3365
3573
|
getPendingUserUpdateTasks,
|
|
3574
|
+
getRecheckJobs,
|
|
3575
|
+
batchUpdateRecheckJobs,
|
|
3366
3576
|
updateUserInfo,
|
|
3367
3577
|
updateUserLocation,
|
|
3368
3578
|
setNonSeller,
|
|
@@ -3375,6 +3585,7 @@ export function createStore(filePath, options = {}) {
|
|
|
3375
3585
|
getActiveClients,
|
|
3376
3586
|
moveSellerJobsToBase, // 将 jobs/raw_jobs 中 tt_seller=1 且 video_count=0 的记录移动到 jobs_base
|
|
3377
3587
|
moveRestrictedJobsToBase, // 将 jobs 中 status='restricted' 的记录移动到 jobs_base
|
|
3588
|
+
moveNoCountryJobsToBase, // 将 jobs 中 status='done' 且无国家数据的记录移动到 jobs_base
|
|
3378
3589
|
moveSingleJobToBase, // 将单个 job 移动到 jobs_base 并重置(目标商家页面用)
|
|
3379
3590
|
registerVideos,
|
|
3380
3591
|
getVideo,
|
|
@@ -3404,6 +3615,7 @@ export function createStore(filePath, options = {}) {
|
|
|
3404
3615
|
claimRefreshTag,
|
|
3405
3616
|
reportRefreshResult,
|
|
3406
3617
|
getRefreshStats,
|
|
3618
|
+
getTargetRefreshStats,
|
|
3407
3619
|
data,
|
|
3408
3620
|
};
|
|
3409
3621
|
|
|
@@ -3572,6 +3784,55 @@ export function createStore(filePath, options = {}) {
|
|
|
3572
3784
|
return { ok: true, moved: deleted.changes || 0 };
|
|
3573
3785
|
}
|
|
3574
3786
|
|
|
3787
|
+
// 将 jobs 中 status='done' 且无国家数据的记录移动到 jobs_base
|
|
3788
|
+
function moveNoCountryJobsToBase() {
|
|
3789
|
+
const db = getDb();
|
|
3790
|
+
if (!db) return { ok: false, error: "db not ready" };
|
|
3791
|
+
|
|
3792
|
+
const now = Date.now();
|
|
3793
|
+
const condition = `status = 'done'
|
|
3794
|
+
AND (guessed_location IS NULL OR guessed_location = '')
|
|
3795
|
+
AND (location_created IS NULL OR location_created = '')
|
|
3796
|
+
AND (confirmed_location IS NULL OR confirmed_location = '')`;
|
|
3797
|
+
|
|
3798
|
+
const countRow = db
|
|
3799
|
+
.prepare(`SELECT COUNT(*) as c FROM jobs WHERE ${condition}`)
|
|
3800
|
+
.get();
|
|
3801
|
+
if (!countRow?.c) return { ok: true, moved: 0 };
|
|
3802
|
+
|
|
3803
|
+
db.prepare(
|
|
3804
|
+
`
|
|
3805
|
+
INSERT OR REPLACE INTO jobs_base (
|
|
3806
|
+
unique_id, nickname, sources, pinned, no_video, user_update_count,
|
|
3807
|
+
tt_seller, verified, video_count, comment_count, guessed_location,
|
|
3808
|
+
location_created, follower_count, following_count, heart_count,
|
|
3809
|
+
refresh_time, region, signature, sec_uid, status_code,
|
|
3810
|
+
latest_video_time, bio_link, status, restricted, error, claimed_by,
|
|
3811
|
+
claimed_at, processed, processed_at, created_at, updated_at
|
|
3812
|
+
)
|
|
3813
|
+
SELECT
|
|
3814
|
+
unique_id, nickname, sources, pinned, no_video, 0,
|
|
3815
|
+
CASE WHEN tt_seller = 0 THEN NULL ELSE tt_seller END,
|
|
3816
|
+
verified, video_count, comment_count, guessed_location,
|
|
3817
|
+
location_created, follower_count, following_count, heart_count,
|
|
3818
|
+
refresh_time, region, signature, sec_uid, status_code,
|
|
3819
|
+
latest_video_time, bio_link,
|
|
3820
|
+
'pending', 0, NULL, NULL, NULL, 0, NULL,
|
|
3821
|
+
COALESCE(created_at, ?), ?
|
|
3822
|
+
FROM jobs WHERE ${condition}
|
|
3823
|
+
`,
|
|
3824
|
+
).run(now, now);
|
|
3825
|
+
|
|
3826
|
+
const deleted = db.prepare(`DELETE FROM jobs WHERE ${condition}`).run();
|
|
3827
|
+
|
|
3828
|
+
markStatsDirty();
|
|
3829
|
+
return {
|
|
3830
|
+
ok: true,
|
|
3831
|
+
moved: deleted.changes || 0,
|
|
3832
|
+
total: countRow.c,
|
|
3833
|
+
};
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3575
3836
|
// 将单个 job 移动到 jobs_base(用于目标商家页面"移到base重走流程")
|
|
3576
3837
|
function moveSingleJobToBase(uniqueId) {
|
|
3577
3838
|
const db = getDb();
|
package/src/watch/db-tags.js
CHANGED
|
@@ -112,7 +112,7 @@ export function getRefreshStats(hours = 24) {
|
|
|
112
112
|
|
|
113
113
|
const rows = db
|
|
114
114
|
.prepare(
|
|
115
|
-
"SELECT countries FROM tags WHERE status = 'productive' AND score >
|
|
115
|
+
"SELECT countries FROM tags WHERE status = 'productive' AND score > 60",
|
|
116
116
|
)
|
|
117
117
|
.all();
|
|
118
118
|
const countrySet = new Set();
|
|
@@ -128,10 +128,10 @@ export function getRefreshStats(hours = 24) {
|
|
|
128
128
|
const countryList = [...countrySet].sort();
|
|
129
129
|
|
|
130
130
|
const stmtTotal = db.prepare(
|
|
131
|
-
"SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score >
|
|
131
|
+
"SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score > 60 AND countries LIKE ?",
|
|
132
132
|
);
|
|
133
133
|
const stmtRefresh = db.prepare(
|
|
134
|
-
`SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score >
|
|
134
|
+
`SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score > 60 AND countries LIKE ? AND refreshed_at >= datetime('now', ?)`,
|
|
135
135
|
);
|
|
136
136
|
|
|
137
137
|
for (const c of countryList) {
|
|
@@ -1338,7 +1338,7 @@ function getLang(country) {
|
|
|
1338
1338
|
// ===== Tag 刷新 CRUD(productive 标签轮询重新抓取) =====
|
|
1339
1339
|
|
|
1340
1340
|
/**
|
|
1341
|
-
* 获取一个待刷新的 productive 标签(score >
|
|
1341
|
+
* 获取一个待刷新的 productive 标签(score > 60 且未被刷新锁定)
|
|
1342
1342
|
* 按 refreshed_at ASC 排序(最久未刷新的优先),NULL 排最前
|
|
1343
1343
|
*/
|
|
1344
1344
|
export function getProductiveRefreshTask() {
|
|
@@ -1355,7 +1355,7 @@ export function getProductiveRefreshTask() {
|
|
|
1355
1355
|
const row = db
|
|
1356
1356
|
.prepare(
|
|
1357
1357
|
`SELECT * FROM tags
|
|
1358
|
-
WHERE status = 'productive' AND score >
|
|
1358
|
+
WHERE status = 'productive' AND score > 60 AND refreshing = 0
|
|
1359
1359
|
ORDER BY refreshed_at IS NULL DESC, refreshed_at ASC
|
|
1360
1360
|
LIMIT 1`,
|
|
1361
1361
|
)
|
|
@@ -1379,7 +1379,7 @@ export function claimRefreshTag(tag) {
|
|
|
1379
1379
|
const result = db
|
|
1380
1380
|
.prepare(
|
|
1381
1381
|
`UPDATE tags SET refreshing = 1, refreshed_at = datetime('now')
|
|
1382
|
-
WHERE (tag = ? OR tag = ?) AND status = 'productive' AND score >
|
|
1382
|
+
WHERE (tag = ? OR tag = ?) AND status = 'productive' AND score > 60 AND refreshing = 0`,
|
|
1383
1383
|
)
|
|
1384
1384
|
.run(normalized, `#${normalized}`);
|
|
1385
1385
|
if (result.changes === 0) {
|