tt-help-cli-ycl 1.3.41 → 1.3.43
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/scripts/run-explore.bat +3 -1
- package/scripts/run-explore.ps1 +3 -1
- package/scripts/run-explore.sh +3 -1
- package/src/cli/explore.js +5 -4
- package/src/lib/args.js +1 -1
- package/src/lib/constants.js +1 -1
- package/src/watch/data-store.js +456 -0
- package/src/watch/public/index.html +878 -100
- package/src/watch/server.js +83 -0
package/package.json
CHANGED
package/scripts/run-explore.bat
CHANGED
|
@@ -127,6 +127,8 @@ CALL ECHO User ID: %%USER_ID%%
|
|
|
127
127
|
CALL ECHO Base port: %%BASE_PORT%%
|
|
128
128
|
CALL ECHO Port count: %%PORT_COUNT%%
|
|
129
129
|
IF NOT "%JOB_LOCATIONS%"=="" ECHO Job locations: %JOB_LOCATIONS%
|
|
130
|
+
ECHO Max following: 5
|
|
131
|
+
ECHO Max followers: 5
|
|
130
132
|
ECHO Speed: stealth (slowest)
|
|
131
133
|
ECHO ========================================
|
|
132
|
-
CALL tt-help explore stealth --user-id %%USER_ID%% --base-port %%BASE_PORT%% --port-count %%PORT_COUNT%% %%JOB_LOC_ARGS%%
|
|
134
|
+
CALL tt-help explore stealth --user-id %%USER_ID%% --base-port %%BASE_PORT%% --port-count %%PORT_COUNT%% --max-following 5 --max-followers 5 %%JOB_LOC_ARGS%%
|
package/scripts/run-explore.ps1
CHANGED
|
@@ -152,6 +152,8 @@ Write-Host " 用户编号: $userId"
|
|
|
152
152
|
Write-Host " 起始端口: $basePort"
|
|
153
153
|
Write-Host " 端口数量: $portCount"
|
|
154
154
|
if ($inputJobLocations) { Write-Host " 任务国家: $inputJobLocations" }
|
|
155
|
+
Write-Host " 最大关注: 5"
|
|
156
|
+
Write-Host " 最大粉丝: 5"
|
|
155
157
|
Write-Host " 速度: stealth (最慢)"
|
|
156
158
|
Write-Host "========================================"
|
|
157
|
-
tt-help explore stealth --user-id $userId --base-port $basePort --port-count $portCount $jobLocationsArg
|
|
159
|
+
tt-help explore stealth --user-id $userId --base-port $basePort --port-count $portCount --max-following 5 --max-followers 5 $jobLocationsArg
|
package/scripts/run-explore.sh
CHANGED
|
@@ -112,8 +112,10 @@ echo " 用户编号: $USER_ID"
|
|
|
112
112
|
echo " 起始端口: $BASE_PORT"
|
|
113
113
|
echo " 端口数量: $PORT_COUNT"
|
|
114
114
|
if [ -n "$INPUT_JOB_LOCATIONS" ]; then echo " 任务国家: $INPUT_JOB_LOCATIONS"; fi
|
|
115
|
+
echo " 最大关注: 5"
|
|
116
|
+
echo " 最大粉丝: 5"
|
|
115
117
|
echo " 速度: stealth (最慢)"
|
|
116
118
|
echo "========================================"
|
|
117
119
|
JOB_LOC_ARGS=""
|
|
118
120
|
if [ -n "$INPUT_JOB_LOCATIONS" ]; then JOB_LOC_ARGS="--job-locations $INPUT_JOB_LOCATIONS"; fi
|
|
119
|
-
tt-help explore stealth --user-id "$USER_ID" --base-port "$BASE_PORT" --port-count "$PORT_COUNT" $JOB_LOC_ARGS
|
|
121
|
+
tt-help explore stealth --user-id "$USER_ID" --base-port "$BASE_PORT" --port-count "$PORT_COUNT" --max-following 5 --max-followers 5 $JOB_LOC_ARGS
|
package/src/cli/explore.js
CHANGED
|
@@ -24,6 +24,7 @@ import path from "path";
|
|
|
24
24
|
import os from "os";
|
|
25
25
|
|
|
26
26
|
const MAX_RETRY_WAIT = 5 * 60 * 1000;
|
|
27
|
+
const STARTUP_TIKTOK_URL = "https://www.tiktok.com/@ycl5007";
|
|
27
28
|
|
|
28
29
|
async function withRetry(label, fn) {
|
|
29
30
|
let backoff = 1000;
|
|
@@ -164,8 +165,8 @@ export async function handleExplore(options) {
|
|
|
164
165
|
|
|
165
166
|
const page = await getOrCreatePage(browser);
|
|
166
167
|
|
|
167
|
-
// 先导航到 TikTok
|
|
168
|
-
await page.goto(
|
|
168
|
+
// 先导航到 TikTok 页面,再检测登录状态
|
|
169
|
+
await page.goto(STARTUP_TIKTOK_URL, {
|
|
169
170
|
waitUntil: "domcontentloaded",
|
|
170
171
|
});
|
|
171
172
|
|
|
@@ -214,8 +215,8 @@ export async function handleExplore(options) {
|
|
|
214
215
|
userDataDir: nextAccount.userDataDir,
|
|
215
216
|
});
|
|
216
217
|
console.error(`[健康检查] 已切换到端口 ${nextAccount.port}`);
|
|
217
|
-
// 切换账户后先导航到 TikTok
|
|
218
|
-
await page.goto(
|
|
218
|
+
// 切换账户后先导航到 TikTok 页面,再重新检测登录状态
|
|
219
|
+
await page.goto(STARTUP_TIKTOK_URL, {
|
|
219
220
|
waitUntil: "domcontentloaded",
|
|
220
221
|
});
|
|
221
222
|
loggedIn = await isLoggedIn(page);
|
package/src/lib/args.js
CHANGED
|
@@ -203,7 +203,7 @@ function parseExploreArgs(args) {
|
|
|
203
203
|
exploreEnableFollow = false;
|
|
204
204
|
} else if (arg === "--max-following") {
|
|
205
205
|
exploreMaxFollowing = parseInt(args[++i]) || 50;
|
|
206
|
-
|
|
206
|
+
} else if (arg === "--max-followers") {
|
|
207
207
|
exploreMaxFollowers = parseInt(args[++i]) || 50;
|
|
208
208
|
} else if (arg === "--max-users") {
|
|
209
209
|
exploreMaxUsers = parseInt(args[++i]) || 0;
|
package/src/lib/constants.js
CHANGED
|
@@ -128,7 +128,7 @@ const HELP_TEXT = [
|
|
|
128
128
|
" --max-videos <数量> 每用户最大视频数,默认 16",
|
|
129
129
|
" --enable-follow 启用关注/粉丝提取(默认启用)",
|
|
130
130
|
" --disable-follow 禁用关注/粉丝提取",
|
|
131
|
-
" --max-following <数量> 最大获取关注数,默认 50
|
|
131
|
+
" --max-following <数量> 最大获取关注数,默认 50",
|
|
132
132
|
" --max-followers <数量> 最大获取粉丝数,默认 50",
|
|
133
133
|
" --max-users <数量> 最大处理用户数,默认无限制",
|
|
134
134
|
" -i, --interval <秒数> 无任务时轮询间隔,默认 10 秒",
|
package/src/watch/data-store.js
CHANGED
|
@@ -116,6 +116,38 @@ function initUserDb(filePath) {
|
|
|
116
116
|
sec_uid TEXT
|
|
117
117
|
)
|
|
118
118
|
`);
|
|
119
|
+
db.exec(`
|
|
120
|
+
CREATE TABLE IF NOT EXISTS raw_jobs (
|
|
121
|
+
unique_id TEXT PRIMARY KEY,
|
|
122
|
+
nickname TEXT,
|
|
123
|
+
status TEXT DEFAULT 'pending',
|
|
124
|
+
sources TEXT,
|
|
125
|
+
claimed_by TEXT,
|
|
126
|
+
claimed_at INTEGER,
|
|
127
|
+
error TEXT,
|
|
128
|
+
pinned INTEGER DEFAULT 0,
|
|
129
|
+
no_video INTEGER DEFAULT 0,
|
|
130
|
+
restricted INTEGER DEFAULT 0,
|
|
131
|
+
user_update_count INTEGER DEFAULT 0,
|
|
132
|
+
tt_seller INTEGER,
|
|
133
|
+
verified INTEGER,
|
|
134
|
+
video_count INTEGER DEFAULT 0,
|
|
135
|
+
comment_count INTEGER DEFAULT 0,
|
|
136
|
+
guessed_location TEXT,
|
|
137
|
+
location_created TEXT,
|
|
138
|
+
follower_count INTEGER DEFAULT 0,
|
|
139
|
+
following_count INTEGER DEFAULT 0,
|
|
140
|
+
heart_count INTEGER DEFAULT 0,
|
|
141
|
+
refresh_time INTEGER,
|
|
142
|
+
processed INTEGER DEFAULT 0,
|
|
143
|
+
processed_at INTEGER,
|
|
144
|
+
created_at INTEGER,
|
|
145
|
+
updated_at INTEGER,
|
|
146
|
+
region TEXT,
|
|
147
|
+
signature TEXT,
|
|
148
|
+
sec_uid TEXT
|
|
149
|
+
)
|
|
150
|
+
`);
|
|
119
151
|
db.exec(`
|
|
120
152
|
CREATE TABLE IF NOT EXISTS videos (
|
|
121
153
|
id TEXT PRIMARY KEY,
|
|
@@ -454,6 +486,11 @@ function getPendingJobsUserUpdateCount() {
|
|
|
454
486
|
.get().c;
|
|
455
487
|
}
|
|
456
488
|
|
|
489
|
+
function getRawJobsCount() {
|
|
490
|
+
if (!db) return 0;
|
|
491
|
+
return db.prepare("SELECT COUNT(*) as c FROM raw_jobs").get().c;
|
|
492
|
+
}
|
|
493
|
+
|
|
457
494
|
function getDashboardStatsFromDb(targetLocations = []) {
|
|
458
495
|
if (!db) return null;
|
|
459
496
|
|
|
@@ -560,6 +597,7 @@ function getDashboardStatsFromDb(targetLocations = []) {
|
|
|
560
597
|
|
|
561
598
|
return {
|
|
562
599
|
totalUsers: total,
|
|
600
|
+
rawJobs: getRawJobsCount(),
|
|
563
601
|
dbTotalUsers: getUserDbCount(),
|
|
564
602
|
jobsTotal: total,
|
|
565
603
|
jobsPending: getPendingJobsCount(),
|
|
@@ -587,6 +625,415 @@ function getDashboardStatsFromDb(targetLocations = []) {
|
|
|
587
625
|
};
|
|
588
626
|
}
|
|
589
627
|
|
|
628
|
+
function getPendingByCountryFromDb() {
|
|
629
|
+
if (!db) return [];
|
|
630
|
+
|
|
631
|
+
// 按 guessed_location 分组统计待处理任务
|
|
632
|
+
const rows = db
|
|
633
|
+
.prepare(
|
|
634
|
+
`
|
|
635
|
+
SELECT
|
|
636
|
+
COALESCE(guessed_location, '未知') as country,
|
|
637
|
+
COUNT(*) as count
|
|
638
|
+
FROM jobs
|
|
639
|
+
WHERE status = 'pending'
|
|
640
|
+
GROUP BY COALESCE(guessed_location, '未知')
|
|
641
|
+
ORDER BY count DESC
|
|
642
|
+
`,
|
|
643
|
+
)
|
|
644
|
+
.all();
|
|
645
|
+
|
|
646
|
+
return rows;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function getUserUpdateByCountryFromDb() {
|
|
650
|
+
if (!db) return [];
|
|
651
|
+
|
|
652
|
+
// 按 guessed_location 分组统计待补资料任务
|
|
653
|
+
const rows = db
|
|
654
|
+
.prepare(
|
|
655
|
+
`
|
|
656
|
+
SELECT
|
|
657
|
+
COALESCE(guessed_location, '未知') as country,
|
|
658
|
+
COUNT(*) as count
|
|
659
|
+
FROM jobs
|
|
660
|
+
WHERE COALESCE(tt_seller, '') = ''
|
|
661
|
+
AND COALESCE(user_update_count, 0) <= 0
|
|
662
|
+
GROUP BY COALESCE(guessed_location, '未知')
|
|
663
|
+
ORDER BY count DESC
|
|
664
|
+
`,
|
|
665
|
+
)
|
|
666
|
+
.all();
|
|
667
|
+
|
|
668
|
+
return rows;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function getAttachStuckByCountryFromDb() {
|
|
672
|
+
if (!db) return [];
|
|
673
|
+
|
|
674
|
+
return db
|
|
675
|
+
.prepare(
|
|
676
|
+
`
|
|
677
|
+
SELECT
|
|
678
|
+
COALESCE(guessed_location, '未知') as country,
|
|
679
|
+
COUNT(*) as count
|
|
680
|
+
FROM jobs
|
|
681
|
+
WHERE COALESCE(tt_seller, '') = ''
|
|
682
|
+
AND COALESCE(user_update_count, 0) = 1
|
|
683
|
+
GROUP BY COALESCE(guessed_location, '未知')
|
|
684
|
+
ORDER BY count DESC
|
|
685
|
+
`,
|
|
686
|
+
)
|
|
687
|
+
.all();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function restoreAttachStuckByCountry(country) {
|
|
691
|
+
if (!db) {
|
|
692
|
+
return { restored: 0, country, error: "db not ready" };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const normalizedCountry = String(country == null ? "未知" : country).trim();
|
|
696
|
+
if (!normalizedCountry) {
|
|
697
|
+
return {
|
|
698
|
+
restored: 0,
|
|
699
|
+
country: normalizedCountry,
|
|
700
|
+
error: "country is required",
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const whereSql = `
|
|
705
|
+
COALESCE(tt_seller, '') = ''
|
|
706
|
+
AND COALESCE(user_update_count, 0) = 1
|
|
707
|
+
AND COALESCE(guessed_location, '未知') = ?
|
|
708
|
+
`;
|
|
709
|
+
const count =
|
|
710
|
+
db
|
|
711
|
+
.prepare(
|
|
712
|
+
`
|
|
713
|
+
SELECT COUNT(*) as c
|
|
714
|
+
FROM jobs
|
|
715
|
+
WHERE ${whereSql}
|
|
716
|
+
`,
|
|
717
|
+
)
|
|
718
|
+
.get(normalizedCountry)?.c || 0;
|
|
719
|
+
|
|
720
|
+
if (!count) {
|
|
721
|
+
return { restored: 0, country: normalizedCountry };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
db.prepare(
|
|
725
|
+
`
|
|
726
|
+
UPDATE jobs
|
|
727
|
+
SET user_update_count = 0,
|
|
728
|
+
updated_at = ?,
|
|
729
|
+
claimed_by = NULL,
|
|
730
|
+
claimed_at = NULL
|
|
731
|
+
WHERE ${whereSql}
|
|
732
|
+
`,
|
|
733
|
+
).run(Date.now(), normalizedCountry);
|
|
734
|
+
|
|
735
|
+
return { restored: count, country: normalizedCountry };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function getRawByCountryFromDb() {
|
|
739
|
+
if (!db) return [];
|
|
740
|
+
|
|
741
|
+
return db
|
|
742
|
+
.prepare(
|
|
743
|
+
`
|
|
744
|
+
SELECT
|
|
745
|
+
COALESCE(guessed_location, '未知') as country,
|
|
746
|
+
COUNT(*) as count
|
|
747
|
+
FROM raw_jobs
|
|
748
|
+
GROUP BY COALESCE(guessed_location, '未知')
|
|
749
|
+
ORDER BY count DESC
|
|
750
|
+
`,
|
|
751
|
+
)
|
|
752
|
+
.all();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function moveJobsToRawByCountry(scope, country) {
|
|
756
|
+
if (!db) {
|
|
757
|
+
return { moved: 0, scope, country, error: "db not ready" };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const normalizedScope = String(scope || "").trim();
|
|
761
|
+
const normalizedCountry = String(country == null ? "未知" : country).trim();
|
|
762
|
+
if (!normalizedCountry) {
|
|
763
|
+
return {
|
|
764
|
+
moved: 0,
|
|
765
|
+
scope: normalizedScope,
|
|
766
|
+
country: normalizedCountry,
|
|
767
|
+
error: "country is required",
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
let scopeWhere = "";
|
|
772
|
+
if (normalizedScope === "pending") {
|
|
773
|
+
scopeWhere = `status = 'pending'`;
|
|
774
|
+
} else if (normalizedScope === "userUpdate") {
|
|
775
|
+
scopeWhere = `COALESCE(tt_seller, '') = '' AND COALESCE(user_update_count, 0) <= 0`;
|
|
776
|
+
} else {
|
|
777
|
+
return {
|
|
778
|
+
moved: 0,
|
|
779
|
+
scope: normalizedScope,
|
|
780
|
+
country: normalizedCountry,
|
|
781
|
+
error: "unsupported scope",
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const whereSql = `
|
|
786
|
+
${scopeWhere}
|
|
787
|
+
AND COALESCE(guessed_location, '未知') = ?
|
|
788
|
+
`;
|
|
789
|
+
const count =
|
|
790
|
+
db
|
|
791
|
+
.prepare(
|
|
792
|
+
`
|
|
793
|
+
SELECT COUNT(*) as c
|
|
794
|
+
FROM jobs
|
|
795
|
+
WHERE ${whereSql}
|
|
796
|
+
`,
|
|
797
|
+
)
|
|
798
|
+
.get(normalizedCountry)?.c || 0;
|
|
799
|
+
|
|
800
|
+
if (!count) {
|
|
801
|
+
return { moved: 0, scope: normalizedScope, country: normalizedCountry };
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const moveTxn = db.transaction((targetCountry) => {
|
|
805
|
+
db.prepare(
|
|
806
|
+
`
|
|
807
|
+
INSERT OR REPLACE INTO raw_jobs (
|
|
808
|
+
unique_id,
|
|
809
|
+
nickname,
|
|
810
|
+
status,
|
|
811
|
+
sources,
|
|
812
|
+
claimed_by,
|
|
813
|
+
claimed_at,
|
|
814
|
+
error,
|
|
815
|
+
pinned,
|
|
816
|
+
no_video,
|
|
817
|
+
restricted,
|
|
818
|
+
user_update_count,
|
|
819
|
+
tt_seller,
|
|
820
|
+
verified,
|
|
821
|
+
video_count,
|
|
822
|
+
comment_count,
|
|
823
|
+
guessed_location,
|
|
824
|
+
location_created,
|
|
825
|
+
follower_count,
|
|
826
|
+
following_count,
|
|
827
|
+
heart_count,
|
|
828
|
+
refresh_time,
|
|
829
|
+
processed,
|
|
830
|
+
processed_at,
|
|
831
|
+
created_at,
|
|
832
|
+
updated_at,
|
|
833
|
+
region,
|
|
834
|
+
signature,
|
|
835
|
+
sec_uid
|
|
836
|
+
)
|
|
837
|
+
SELECT
|
|
838
|
+
unique_id,
|
|
839
|
+
nickname,
|
|
840
|
+
status,
|
|
841
|
+
sources,
|
|
842
|
+
claimed_by,
|
|
843
|
+
claimed_at,
|
|
844
|
+
error,
|
|
845
|
+
pinned,
|
|
846
|
+
no_video,
|
|
847
|
+
restricted,
|
|
848
|
+
user_update_count,
|
|
849
|
+
tt_seller,
|
|
850
|
+
verified,
|
|
851
|
+
video_count,
|
|
852
|
+
comment_count,
|
|
853
|
+
guessed_location,
|
|
854
|
+
location_created,
|
|
855
|
+
follower_count,
|
|
856
|
+
following_count,
|
|
857
|
+
heart_count,
|
|
858
|
+
refresh_time,
|
|
859
|
+
processed,
|
|
860
|
+
processed_at,
|
|
861
|
+
created_at,
|
|
862
|
+
updated_at,
|
|
863
|
+
region,
|
|
864
|
+
signature,
|
|
865
|
+
sec_uid
|
|
866
|
+
FROM jobs
|
|
867
|
+
WHERE ${whereSql}
|
|
868
|
+
`,
|
|
869
|
+
).run(targetCountry);
|
|
870
|
+
|
|
871
|
+
db.prepare(
|
|
872
|
+
`
|
|
873
|
+
DELETE FROM jobs
|
|
874
|
+
WHERE ${whereSql}
|
|
875
|
+
`,
|
|
876
|
+
).run(targetCountry);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
moveTxn(normalizedCountry);
|
|
880
|
+
return { moved: count, scope: normalizedScope, country: normalizedCountry };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function restoreRawJobsByCountry(country) {
|
|
884
|
+
if (!db) {
|
|
885
|
+
return { restored: 0, country, error: "db not ready" };
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const normalizedCountry = String(country == null ? "未知" : country).trim();
|
|
889
|
+
if (!normalizedCountry) {
|
|
890
|
+
return {
|
|
891
|
+
restored: 0,
|
|
892
|
+
country: normalizedCountry,
|
|
893
|
+
error: "country is required",
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const whereSql = `COALESCE(guessed_location, '未知') = ?`;
|
|
898
|
+
const count =
|
|
899
|
+
db
|
|
900
|
+
.prepare(
|
|
901
|
+
`
|
|
902
|
+
SELECT COUNT(*) as c
|
|
903
|
+
FROM raw_jobs
|
|
904
|
+
WHERE ${whereSql}
|
|
905
|
+
`,
|
|
906
|
+
)
|
|
907
|
+
.get(normalizedCountry)?.c || 0;
|
|
908
|
+
|
|
909
|
+
if (!count) {
|
|
910
|
+
return { restored: 0, country: normalizedCountry };
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const restoreTxn = db.transaction((targetCountry) => {
|
|
914
|
+
db.prepare(
|
|
915
|
+
`
|
|
916
|
+
INSERT OR REPLACE INTO jobs (
|
|
917
|
+
unique_id,
|
|
918
|
+
nickname,
|
|
919
|
+
status,
|
|
920
|
+
sources,
|
|
921
|
+
claimed_by,
|
|
922
|
+
claimed_at,
|
|
923
|
+
error,
|
|
924
|
+
pinned,
|
|
925
|
+
no_video,
|
|
926
|
+
restricted,
|
|
927
|
+
user_update_count,
|
|
928
|
+
tt_seller,
|
|
929
|
+
verified,
|
|
930
|
+
video_count,
|
|
931
|
+
comment_count,
|
|
932
|
+
guessed_location,
|
|
933
|
+
location_created,
|
|
934
|
+
follower_count,
|
|
935
|
+
following_count,
|
|
936
|
+
heart_count,
|
|
937
|
+
refresh_time,
|
|
938
|
+
processed,
|
|
939
|
+
processed_at,
|
|
940
|
+
created_at,
|
|
941
|
+
updated_at,
|
|
942
|
+
region,
|
|
943
|
+
signature,
|
|
944
|
+
sec_uid
|
|
945
|
+
)
|
|
946
|
+
SELECT
|
|
947
|
+
unique_id,
|
|
948
|
+
nickname,
|
|
949
|
+
status,
|
|
950
|
+
sources,
|
|
951
|
+
claimed_by,
|
|
952
|
+
claimed_at,
|
|
953
|
+
error,
|
|
954
|
+
pinned,
|
|
955
|
+
no_video,
|
|
956
|
+
restricted,
|
|
957
|
+
user_update_count,
|
|
958
|
+
tt_seller,
|
|
959
|
+
verified,
|
|
960
|
+
video_count,
|
|
961
|
+
comment_count,
|
|
962
|
+
guessed_location,
|
|
963
|
+
location_created,
|
|
964
|
+
follower_count,
|
|
965
|
+
following_count,
|
|
966
|
+
heart_count,
|
|
967
|
+
refresh_time,
|
|
968
|
+
processed,
|
|
969
|
+
processed_at,
|
|
970
|
+
created_at,
|
|
971
|
+
updated_at,
|
|
972
|
+
region,
|
|
973
|
+
signature,
|
|
974
|
+
sec_uid
|
|
975
|
+
FROM raw_jobs
|
|
976
|
+
WHERE ${whereSql}
|
|
977
|
+
`,
|
|
978
|
+
).run(targetCountry);
|
|
979
|
+
|
|
980
|
+
db.prepare(
|
|
981
|
+
`
|
|
982
|
+
DELETE FROM raw_jobs
|
|
983
|
+
WHERE ${whereSql}
|
|
984
|
+
`,
|
|
985
|
+
).run(targetCountry);
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
restoreTxn(normalizedCountry);
|
|
989
|
+
return { restored: count, country: normalizedCountry };
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function getRawJobsPageFromDb({ search, location, limit, offset }) {
|
|
993
|
+
if (!db) return null;
|
|
994
|
+
|
|
995
|
+
const safeLimit = Math.max(1, Math.min(200, parseInt(limit) || 50));
|
|
996
|
+
const safeOffset = Math.max(0, parseInt(offset) || 0);
|
|
997
|
+
const where = [];
|
|
998
|
+
const args = [];
|
|
999
|
+
|
|
1000
|
+
if (search) {
|
|
1001
|
+
where.push(
|
|
1002
|
+
"(LOWER(unique_id) LIKE ? OR LOWER(COALESCE(nickname, '')) LIKE ?)",
|
|
1003
|
+
);
|
|
1004
|
+
const pattern = `%${String(search).toLowerCase()}%`;
|
|
1005
|
+
args.push(pattern, pattern);
|
|
1006
|
+
}
|
|
1007
|
+
if (location) {
|
|
1008
|
+
where.push("COALESCE(guessed_location, '未知') = ?");
|
|
1009
|
+
args.push(location);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
1013
|
+
const total = db
|
|
1014
|
+
.prepare(`SELECT COUNT(*) as c FROM raw_jobs ${whereSql}`)
|
|
1015
|
+
.get(...args).c;
|
|
1016
|
+
|
|
1017
|
+
const rows = db
|
|
1018
|
+
.prepare(
|
|
1019
|
+
`
|
|
1020
|
+
SELECT *
|
|
1021
|
+
FROM raw_jobs
|
|
1022
|
+
${whereSql}
|
|
1023
|
+
ORDER BY created_at DESC, unique_id ASC
|
|
1024
|
+
LIMIT ? OFFSET ?
|
|
1025
|
+
`,
|
|
1026
|
+
)
|
|
1027
|
+
.all(...args, safeLimit, safeOffset);
|
|
1028
|
+
|
|
1029
|
+
return {
|
|
1030
|
+
total,
|
|
1031
|
+
limit: safeLimit,
|
|
1032
|
+
offset: safeOffset,
|
|
1033
|
+
users: rows.map(mapJobRow),
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
590
1037
|
function getUsersPageFromDb({
|
|
591
1038
|
status,
|
|
592
1039
|
search,
|
|
@@ -2356,10 +2803,19 @@ export function createStore(filePath) {
|
|
|
2356
2803
|
getAllUsers,
|
|
2357
2804
|
getUserDbCount,
|
|
2358
2805
|
getJobsCount,
|
|
2806
|
+
getRawJobsCount,
|
|
2359
2807
|
getPendingJobsCount,
|
|
2360
2808
|
getPendingJobsUserUpdateCount,
|
|
2361
2809
|
getDashboardStats: getDashboardStatsFromDb,
|
|
2810
|
+
getPendingByCountry: getPendingByCountryFromDb,
|
|
2811
|
+
getUserUpdateByCountry: getUserUpdateByCountryFromDb,
|
|
2812
|
+
getAttachStuckByCountry: getAttachStuckByCountryFromDb,
|
|
2813
|
+
getRawByCountry: getRawByCountryFromDb,
|
|
2814
|
+
moveJobsToRawByCountry,
|
|
2815
|
+
restoreAttachStuckByCountry,
|
|
2816
|
+
restoreRawJobsByCountry,
|
|
2362
2817
|
getUsersPage: getUsersPageFromDb,
|
|
2818
|
+
getRawJobsPage: getRawJobsPageFromDb,
|
|
2363
2819
|
getTargetUsers: getTargetUsersFromDb,
|
|
2364
2820
|
getStats,
|
|
2365
2821
|
getStatusGroups,
|