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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.3.41",
3
+ "version": "1.3.43",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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%%
@@ -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
@@ -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
@@ -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("https://www.tiktok.com", {
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("https://www.tiktok.com", {
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;
@@ -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 秒",
@@ -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,