tt-help-cli-ycl 1.3.41 → 1.3.44

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.
@@ -1,6 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import Database from "better-sqlite3";
4
+ import { isLocationInList } from "../lib/target-locations.js";
4
5
 
5
6
  // SQLite 用户表(用于判重)
6
7
  let db = null;
@@ -116,6 +117,38 @@ function initUserDb(filePath) {
116
117
  sec_uid TEXT
117
118
  )
118
119
  `);
120
+ db.exec(`
121
+ CREATE TABLE IF NOT EXISTS raw_jobs (
122
+ unique_id TEXT PRIMARY KEY,
123
+ nickname TEXT,
124
+ status TEXT DEFAULT 'pending',
125
+ sources TEXT,
126
+ claimed_by TEXT,
127
+ claimed_at INTEGER,
128
+ error TEXT,
129
+ pinned INTEGER DEFAULT 0,
130
+ no_video INTEGER DEFAULT 0,
131
+ restricted INTEGER DEFAULT 0,
132
+ user_update_count INTEGER DEFAULT 0,
133
+ tt_seller INTEGER,
134
+ verified INTEGER,
135
+ video_count INTEGER DEFAULT 0,
136
+ comment_count INTEGER DEFAULT 0,
137
+ guessed_location TEXT,
138
+ location_created TEXT,
139
+ follower_count INTEGER DEFAULT 0,
140
+ following_count INTEGER DEFAULT 0,
141
+ heart_count INTEGER DEFAULT 0,
142
+ refresh_time INTEGER,
143
+ processed INTEGER DEFAULT 0,
144
+ processed_at INTEGER,
145
+ created_at INTEGER,
146
+ updated_at INTEGER,
147
+ region TEXT,
148
+ signature TEXT,
149
+ sec_uid TEXT
150
+ )
151
+ `);
119
152
  db.exec(`
120
153
  CREATE TABLE IF NOT EXISTS videos (
121
154
  id TEXT PRIMARY KEY,
@@ -454,6 +487,11 @@ function getPendingJobsUserUpdateCount() {
454
487
  .get().c;
455
488
  }
456
489
 
490
+ function getRawJobsCount() {
491
+ if (!db) return 0;
492
+ return db.prepare("SELECT COUNT(*) as c FROM raw_jobs").get().c;
493
+ }
494
+
457
495
  function getDashboardStatsFromDb(targetLocations = []) {
458
496
  if (!db) return null;
459
497
 
@@ -560,6 +598,7 @@ function getDashboardStatsFromDb(targetLocations = []) {
560
598
 
561
599
  return {
562
600
  totalUsers: total,
601
+ rawJobs: getRawJobsCount(),
563
602
  dbTotalUsers: getUserDbCount(),
564
603
  jobsTotal: total,
565
604
  jobsPending: getPendingJobsCount(),
@@ -587,6 +626,415 @@ function getDashboardStatsFromDb(targetLocations = []) {
587
626
  };
588
627
  }
589
628
 
629
+ function getPendingByCountryFromDb() {
630
+ if (!db) return [];
631
+
632
+ // 按 guessed_location 分组统计待处理任务
633
+ const rows = db
634
+ .prepare(
635
+ `
636
+ SELECT
637
+ COALESCE(guessed_location, '未知') as country,
638
+ COUNT(*) as count
639
+ FROM jobs
640
+ WHERE status = 'pending'
641
+ GROUP BY COALESCE(guessed_location, '未知')
642
+ ORDER BY count DESC
643
+ `,
644
+ )
645
+ .all();
646
+
647
+ return rows;
648
+ }
649
+
650
+ function getUserUpdateByCountryFromDb() {
651
+ if (!db) return [];
652
+
653
+ // 按 guessed_location 分组统计待补资料任务
654
+ const rows = db
655
+ .prepare(
656
+ `
657
+ SELECT
658
+ COALESCE(guessed_location, '未知') as country,
659
+ COUNT(*) as count
660
+ FROM jobs
661
+ WHERE COALESCE(tt_seller, '') = ''
662
+ AND COALESCE(user_update_count, 0) <= 0
663
+ GROUP BY COALESCE(guessed_location, '未知')
664
+ ORDER BY count DESC
665
+ `,
666
+ )
667
+ .all();
668
+
669
+ return rows;
670
+ }
671
+
672
+ function getAttachStuckByCountryFromDb() {
673
+ if (!db) return [];
674
+
675
+ return db
676
+ .prepare(
677
+ `
678
+ SELECT
679
+ COALESCE(guessed_location, '未知') as country,
680
+ COUNT(*) as count
681
+ FROM jobs
682
+ WHERE COALESCE(tt_seller, '') = ''
683
+ AND COALESCE(user_update_count, 0) = 1
684
+ GROUP BY COALESCE(guessed_location, '未知')
685
+ ORDER BY count DESC
686
+ `,
687
+ )
688
+ .all();
689
+ }
690
+
691
+ function restoreAttachStuckByCountry(country) {
692
+ if (!db) {
693
+ return { restored: 0, country, error: "db not ready" };
694
+ }
695
+
696
+ const normalizedCountry = String(country == null ? "未知" : country).trim();
697
+ if (!normalizedCountry) {
698
+ return {
699
+ restored: 0,
700
+ country: normalizedCountry,
701
+ error: "country is required",
702
+ };
703
+ }
704
+
705
+ const whereSql = `
706
+ COALESCE(tt_seller, '') = ''
707
+ AND COALESCE(user_update_count, 0) = 1
708
+ AND COALESCE(guessed_location, '未知') = ?
709
+ `;
710
+ const count =
711
+ db
712
+ .prepare(
713
+ `
714
+ SELECT COUNT(*) as c
715
+ FROM jobs
716
+ WHERE ${whereSql}
717
+ `,
718
+ )
719
+ .get(normalizedCountry)?.c || 0;
720
+
721
+ if (!count) {
722
+ return { restored: 0, country: normalizedCountry };
723
+ }
724
+
725
+ db.prepare(
726
+ `
727
+ UPDATE jobs
728
+ SET user_update_count = 0,
729
+ updated_at = ?,
730
+ claimed_by = NULL,
731
+ claimed_at = NULL
732
+ WHERE ${whereSql}
733
+ `,
734
+ ).run(Date.now(), normalizedCountry);
735
+
736
+ return { restored: count, country: normalizedCountry };
737
+ }
738
+
739
+ function getRawByCountryFromDb() {
740
+ if (!db) return [];
741
+
742
+ return db
743
+ .prepare(
744
+ `
745
+ SELECT
746
+ COALESCE(guessed_location, '未知') as country,
747
+ COUNT(*) as count
748
+ FROM raw_jobs
749
+ GROUP BY COALESCE(guessed_location, '未知')
750
+ ORDER BY count DESC
751
+ `,
752
+ )
753
+ .all();
754
+ }
755
+
756
+ function moveJobsToRawByCountry(scope, country) {
757
+ if (!db) {
758
+ return { moved: 0, scope, country, error: "db not ready" };
759
+ }
760
+
761
+ const normalizedScope = String(scope || "").trim();
762
+ const normalizedCountry = String(country == null ? "未知" : country).trim();
763
+ if (!normalizedCountry) {
764
+ return {
765
+ moved: 0,
766
+ scope: normalizedScope,
767
+ country: normalizedCountry,
768
+ error: "country is required",
769
+ };
770
+ }
771
+
772
+ let scopeWhere = "";
773
+ if (normalizedScope === "pending") {
774
+ scopeWhere = `status = 'pending'`;
775
+ } else if (normalizedScope === "userUpdate") {
776
+ scopeWhere = `COALESCE(tt_seller, '') = '' AND COALESCE(user_update_count, 0) <= 0`;
777
+ } else {
778
+ return {
779
+ moved: 0,
780
+ scope: normalizedScope,
781
+ country: normalizedCountry,
782
+ error: "unsupported scope",
783
+ };
784
+ }
785
+
786
+ const whereSql = `
787
+ ${scopeWhere}
788
+ AND COALESCE(guessed_location, '未知') = ?
789
+ `;
790
+ const count =
791
+ db
792
+ .prepare(
793
+ `
794
+ SELECT COUNT(*) as c
795
+ FROM jobs
796
+ WHERE ${whereSql}
797
+ `,
798
+ )
799
+ .get(normalizedCountry)?.c || 0;
800
+
801
+ if (!count) {
802
+ return { moved: 0, scope: normalizedScope, country: normalizedCountry };
803
+ }
804
+
805
+ const moveTxn = db.transaction((targetCountry) => {
806
+ db.prepare(
807
+ `
808
+ INSERT OR REPLACE INTO raw_jobs (
809
+ unique_id,
810
+ nickname,
811
+ status,
812
+ sources,
813
+ claimed_by,
814
+ claimed_at,
815
+ error,
816
+ pinned,
817
+ no_video,
818
+ restricted,
819
+ user_update_count,
820
+ tt_seller,
821
+ verified,
822
+ video_count,
823
+ comment_count,
824
+ guessed_location,
825
+ location_created,
826
+ follower_count,
827
+ following_count,
828
+ heart_count,
829
+ refresh_time,
830
+ processed,
831
+ processed_at,
832
+ created_at,
833
+ updated_at,
834
+ region,
835
+ signature,
836
+ sec_uid
837
+ )
838
+ SELECT
839
+ unique_id,
840
+ nickname,
841
+ status,
842
+ sources,
843
+ claimed_by,
844
+ claimed_at,
845
+ error,
846
+ pinned,
847
+ no_video,
848
+ restricted,
849
+ user_update_count,
850
+ tt_seller,
851
+ verified,
852
+ video_count,
853
+ comment_count,
854
+ guessed_location,
855
+ location_created,
856
+ follower_count,
857
+ following_count,
858
+ heart_count,
859
+ refresh_time,
860
+ processed,
861
+ processed_at,
862
+ created_at,
863
+ updated_at,
864
+ region,
865
+ signature,
866
+ sec_uid
867
+ FROM jobs
868
+ WHERE ${whereSql}
869
+ `,
870
+ ).run(targetCountry);
871
+
872
+ db.prepare(
873
+ `
874
+ DELETE FROM jobs
875
+ WHERE ${whereSql}
876
+ `,
877
+ ).run(targetCountry);
878
+ });
879
+
880
+ moveTxn(normalizedCountry);
881
+ return { moved: count, scope: normalizedScope, country: normalizedCountry };
882
+ }
883
+
884
+ function restoreRawJobsByCountry(country) {
885
+ if (!db) {
886
+ return { restored: 0, country, error: "db not ready" };
887
+ }
888
+
889
+ const normalizedCountry = String(country == null ? "未知" : country).trim();
890
+ if (!normalizedCountry) {
891
+ return {
892
+ restored: 0,
893
+ country: normalizedCountry,
894
+ error: "country is required",
895
+ };
896
+ }
897
+
898
+ const whereSql = `COALESCE(guessed_location, '未知') = ?`;
899
+ const count =
900
+ db
901
+ .prepare(
902
+ `
903
+ SELECT COUNT(*) as c
904
+ FROM raw_jobs
905
+ WHERE ${whereSql}
906
+ `,
907
+ )
908
+ .get(normalizedCountry)?.c || 0;
909
+
910
+ if (!count) {
911
+ return { restored: 0, country: normalizedCountry };
912
+ }
913
+
914
+ const restoreTxn = db.transaction((targetCountry) => {
915
+ db.prepare(
916
+ `
917
+ INSERT OR REPLACE INTO jobs (
918
+ unique_id,
919
+ nickname,
920
+ status,
921
+ sources,
922
+ claimed_by,
923
+ claimed_at,
924
+ error,
925
+ pinned,
926
+ no_video,
927
+ restricted,
928
+ user_update_count,
929
+ tt_seller,
930
+ verified,
931
+ video_count,
932
+ comment_count,
933
+ guessed_location,
934
+ location_created,
935
+ follower_count,
936
+ following_count,
937
+ heart_count,
938
+ refresh_time,
939
+ processed,
940
+ processed_at,
941
+ created_at,
942
+ updated_at,
943
+ region,
944
+ signature,
945
+ sec_uid
946
+ )
947
+ SELECT
948
+ unique_id,
949
+ nickname,
950
+ status,
951
+ sources,
952
+ claimed_by,
953
+ claimed_at,
954
+ error,
955
+ pinned,
956
+ no_video,
957
+ restricted,
958
+ user_update_count,
959
+ tt_seller,
960
+ verified,
961
+ video_count,
962
+ comment_count,
963
+ guessed_location,
964
+ location_created,
965
+ follower_count,
966
+ following_count,
967
+ heart_count,
968
+ refresh_time,
969
+ processed,
970
+ processed_at,
971
+ created_at,
972
+ updated_at,
973
+ region,
974
+ signature,
975
+ sec_uid
976
+ FROM raw_jobs
977
+ WHERE ${whereSql}
978
+ `,
979
+ ).run(targetCountry);
980
+
981
+ db.prepare(
982
+ `
983
+ DELETE FROM raw_jobs
984
+ WHERE ${whereSql}
985
+ `,
986
+ ).run(targetCountry);
987
+ });
988
+
989
+ restoreTxn(normalizedCountry);
990
+ return { restored: count, country: normalizedCountry };
991
+ }
992
+
993
+ function getRawJobsPageFromDb({ search, location, limit, offset }) {
994
+ if (!db) return null;
995
+
996
+ const safeLimit = Math.max(1, Math.min(200, parseInt(limit) || 50));
997
+ const safeOffset = Math.max(0, parseInt(offset) || 0);
998
+ const where = [];
999
+ const args = [];
1000
+
1001
+ if (search) {
1002
+ where.push(
1003
+ "(LOWER(unique_id) LIKE ? OR LOWER(COALESCE(nickname, '')) LIKE ?)",
1004
+ );
1005
+ const pattern = `%${String(search).toLowerCase()}%`;
1006
+ args.push(pattern, pattern);
1007
+ }
1008
+ if (location) {
1009
+ where.push("COALESCE(guessed_location, '未知') = ?");
1010
+ args.push(location);
1011
+ }
1012
+
1013
+ const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
1014
+ const total = db
1015
+ .prepare(`SELECT COUNT(*) as c FROM raw_jobs ${whereSql}`)
1016
+ .get(...args).c;
1017
+
1018
+ const rows = db
1019
+ .prepare(
1020
+ `
1021
+ SELECT *
1022
+ FROM raw_jobs
1023
+ ${whereSql}
1024
+ ORDER BY created_at DESC, unique_id ASC
1025
+ LIMIT ? OFFSET ?
1026
+ `,
1027
+ )
1028
+ .all(...args, safeLimit, safeOffset);
1029
+
1030
+ return {
1031
+ total,
1032
+ limit: safeLimit,
1033
+ offset: safeOffset,
1034
+ users: rows.map(mapJobRow),
1035
+ };
1036
+ }
1037
+
590
1038
  function getUsersPageFromDb({
591
1039
  status,
592
1040
  search,
@@ -1464,8 +1912,7 @@ export function createStore(filePath) {
1464
1912
  // 国家过滤:如果指定了 locations,只保留 guessedLocation 在列表中的用户
1465
1913
  function locationFilter(u) {
1466
1914
  if (!locations || locations.length === 0) return true;
1467
- const loc = (u.guessedLocation || "").toUpperCase();
1468
- return locations.includes(loc);
1915
+ return isLocationInList(u.guessedLocation, locations);
1469
1916
  }
1470
1917
 
1471
1918
  // 从候选列表中按优先级取第一个:pinned > 超时回收 > seed > ttSeller(仅登录) > follow > other
@@ -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,