tt-help-cli-ycl 1.3.65 → 1.3.73

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.65",
3
+ "version": "1.3.73",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/attach.js CHANGED
@@ -134,7 +134,9 @@ export async function handleAttach(options) {
134
134
  `[Attach] 并行数: ${attachParallel}, 空闲间隔: ${attachInterval}秒, 服务端: ${serverUrl}${countryStr}`,
135
135
  );
136
136
 
137
- const scraper = new TikTokScraper(effectiveProxy || null);
137
+ const scraper = new TikTokScraper({
138
+ proxyServer: effectiveProxy || null,
139
+ });
138
140
  const shutdown = async (signal) => {
139
141
  if (shuttingDown) return;
140
142
  shuttingDown = true;
@@ -287,12 +289,14 @@ export async function handleAttach(options) {
287
289
  const task = successTasks.find(
288
290
  (t) => t.uniqueId === r.uniqueId,
289
291
  );
290
- if (task && task.info && task.info.error) {
292
+ const info = task?.info;
293
+ const sellerTag = info?.ttSeller ? " [商家ttSeller=true]" : "";
294
+ if (info && info.error) {
291
295
  attachLog(
292
- ` @${r.uniqueId} 已记录 (statusCode=${task.info.statusCode})`,
296
+ ` ⚠${sellerTag} @${r.uniqueId} 已记录 (statusCode=${info.statusCode})`,
293
297
  );
294
298
  } else {
295
- attachLog(` @${r.uniqueId} 已提交更新`);
299
+ attachLog(` ✓${sellerTag} @${r.uniqueId} 已提交更新`);
296
300
  }
297
301
  } else {
298
302
  failCount++;
@@ -380,6 +380,7 @@ export async function handleRefresh(options) {
380
380
  maxFollowing: exploreMaxFollowing || 100,
381
381
  maxFollowers: exploreMaxFollowers || 100,
382
382
  location: exploreLocation,
383
+ locationMode: "refresh",
383
384
  browser,
384
385
  proxyServer: cdpOptions.proxyServer || null,
385
386
  },
@@ -405,6 +406,7 @@ export async function handleRefresh(options) {
405
406
  maxFollowing: exploreMaxFollowing || 100,
406
407
  maxFollowers: exploreMaxFollowers || 100,
407
408
  location: exploreLocation,
409
+ locationMode: "refresh",
408
410
  browser,
409
411
  proxyServer: cdpOptions.proxyServer || null,
410
412
  },
@@ -443,6 +445,7 @@ export async function handleRefresh(options) {
443
445
  maxFollowing: exploreMaxFollowing || 100,
444
446
  maxFollowers: exploreMaxFollowers || 100,
445
447
  location: exploreLocation,
448
+ locationMode: "refresh",
446
449
  browser,
447
450
  proxyServer: cdpOptions.proxyServer || null,
448
451
  },
@@ -525,10 +528,20 @@ export async function handleRefresh(options) {
525
528
 
526
529
  processedCount++;
527
530
 
528
- const guessedLocation = result.locationCreated || null;
531
+ // refresh 模式:confirmedLocation 是二次确认的国家,写入 confirmed_location
532
+ // locationCreated 保持原始值不变
533
+ const refreshLocation =
534
+ result.confirmedLocation || result.locationCreated;
535
+ const guessedLocation = refreshLocation || null;
536
+
537
+ // 把 confirmedLocation 合并到 userInfo 中(通过 commitRedoJob 写入 DB)
538
+ const refreshUserInfo = { ...(result.userInfo || {}) };
539
+ if (result.confirmedLocation) {
540
+ refreshUserInfo.confirmedLocation = result.confirmedLocation;
541
+ }
529
542
 
530
543
  const payload = {
531
- userInfo: result.userInfo || {},
544
+ userInfo: refreshUserInfo,
532
545
  discoveredFollowing: (result.discoveredFollowing || []).map((f) => ({
533
546
  handle: Array.isArray(f) ? f[0] : f,
534
547
  displayName: Array.isArray(f) ? f[1] : null,
@@ -542,7 +555,6 @@ export async function handleRefresh(options) {
542
555
  processed: result.processed,
543
556
  hasFollowData: result.hasFollowData,
544
557
  keepFollow: result.keepFollow,
545
- locationCreated: result.locationCreated,
546
558
  noVideo: result.noVideo,
547
559
  collectedVideos: result.collectedVideos,
548
560
  };
@@ -555,7 +567,7 @@ export async function handleRefresh(options) {
555
567
  {
556
568
  sourceUser: username,
557
569
  videoList: result.videoList,
558
- locationCreated: result.locationCreated,
570
+ locationCreated: refreshLocation,
559
571
  ttSeller: result.userInfo?.ttSeller || false,
560
572
  },
561
573
  );
@@ -70,6 +70,7 @@ export function parseUserInfo(rawHtml) {
70
70
  privateAccount: u.privateAccount,
71
71
  language: u.language,
72
72
  bio: u.signature || "",
73
+ bioLink: u.bioLink?.link || u.bioLink?.url || u.bioLink || null,
73
74
  avatar: u.avatarLarger || u.avatarMedium || u.avatarThumb || "",
74
75
  followerCount: s.followerCount,
75
76
  followingCount: s.followingCount,
@@ -23,6 +23,7 @@ async function processExplore(page, username, options, log) {
23
23
  maxFollowing = 50,
24
24
  maxFollowers = 50,
25
25
  location = DEFAULT_TARGET_LOCATIONS_CSV,
26
+ locationMode = "explore", // "explore" | "refresh"
26
27
  proxyServer = null,
27
28
  } = options;
28
29
 
@@ -91,10 +92,12 @@ async function processExplore(page, username, options, log) {
91
92
  return result;
92
93
  }
93
94
 
94
- // 从最多 5 个视频并发获取 locationCreated。
95
- // 新规则:采样列表里只要有任一国家命中目标列表,就直接采用该国家;否则回退到众数。
96
- const SAMPLE_SIZE = 5;
95
+ // 国家采样判断
96
+ // explore 模式:采样 5 个,优先命中目标国家,不匹配则回退众数 → 写 locationCreated
97
+ // refresh 模式:采样 7 个,纯众数逻辑 → 写 confirmedLocation(二次确认)
98
+ const SAMPLE_SIZE = locationMode === "refresh" ? 7 : 5;
97
99
  let locationCreated = null;
100
+ let confirmedLocation = null;
98
101
  let locationDecision = null;
99
102
  const sampleVideos = videoArray.slice(0, SAMPLE_SIZE);
100
103
  if (sampleVideos.length > 0) {
@@ -108,34 +111,48 @@ async function processExplore(page, username, options, log) {
108
111
  ` 国家采样(${locations.length}个): [${locations.filter(Boolean).join(", ") || "无数据"}]`,
109
112
  );
110
113
  const normalizedLocations = normalizeLocationList(locations, []);
111
- const matchedTargetLocation = findFirstMatchingLocation(
112
- normalizedLocations,
113
- locationList,
114
- );
114
+
115
+ // 统计频率
115
116
  const freq = {};
116
117
  for (const key of normalizedLocations) {
117
118
  freq[key] = (freq[key] || 0) + 1;
118
119
  }
119
120
  const entries = Object.entries(freq).sort((a, b) => b[1] - a[1]);
120
- if (matchedTargetLocation) {
121
- locationCreated = matchedTargetLocation;
122
- locationDecision = "命中目标国家";
123
- } else if (entries.length > 0) {
124
- locationCreated = entries[0][0];
125
- locationDecision = "回退众数";
121
+
122
+ if (locationMode === "refresh") {
123
+ // refresh 模式:纯众数逻辑 → 写 confirmedLocation
124
+ if (entries.length > 0) {
125
+ confirmedLocation = entries[0][0];
126
+ locationDecision = `众数 (${entries[0][1]}次)`;
127
+ }
128
+ } else {
129
+ // explore 模式:优先命中目标国家,不匹配则回退众数
130
+ const matchedTargetLocation = findFirstMatchingLocation(
131
+ normalizedLocations,
132
+ locationList,
133
+ );
134
+ if (matchedTargetLocation) {
135
+ locationCreated = matchedTargetLocation;
136
+ locationDecision = "命中目标国家";
137
+ } else if (entries.length > 0) {
138
+ locationCreated = entries[0][0];
139
+ locationDecision = "回退众数";
140
+ }
126
141
  }
127
142
  }
128
143
 
129
144
  result.locationCreated = locationCreated || null;
145
+ result.confirmedLocation = confirmedLocation || null;
130
146
  log(
131
- ` 国家: ${result.locationCreated || "未知"}${locationDecision ? ` (${locationDecision})` : ""}`,
147
+ ` 国家: ${result.confirmedLocation || result.locationCreated || "未知"}${locationDecision ? ` (${locationDecision})` : ""}`,
132
148
  );
133
149
 
134
- // 国家筛选
135
- const isTargetLocation = isLocationInList(
136
- result.locationCreated,
137
- locationList,
138
- );
150
+ // 国家筛选:refresh 模式用 confirmedLocation,explore 模式用 locationCreated
151
+ const effectiveLocation =
152
+ locationMode === "refresh"
153
+ ? result.confirmedLocation
154
+ : result.locationCreated;
155
+ const isTargetLocation = isLocationInList(effectiveLocation, locationList);
139
156
 
140
157
  if (isTargetLocation) {
141
158
  result.keepFollow = true;