tt-help-cli-ycl 1.3.52 → 1.3.57

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.
@@ -114,9 +114,21 @@ function initUserDb(filePath) {
114
114
  updated_at INTEGER,
115
115
  region TEXT,
116
116
  signature TEXT,
117
- sec_uid TEXT
117
+ sec_uid TEXT,
118
+ status_code INTEGER
118
119
  )
119
120
  `);
121
+
122
+ // 迁移:为已存在的 jobs 表添加 status_code 列
123
+ const existingJobColumns = new Set(
124
+ db
125
+ .prepare("PRAGMA table_info(jobs)")
126
+ .all()
127
+ .map((c) => c.name),
128
+ );
129
+ if (!existingJobColumns.has("status_code")) {
130
+ db.exec(`ALTER TABLE jobs ADD COLUMN status_code INTEGER`);
131
+ }
120
132
  db.exec(`
121
133
  CREATE TABLE IF NOT EXISTS raw_jobs (
122
134
  unique_id TEXT PRIMARY KEY,
@@ -146,9 +158,21 @@ function initUserDb(filePath) {
146
158
  updated_at INTEGER,
147
159
  region TEXT,
148
160
  signature TEXT,
149
- sec_uid TEXT
161
+ sec_uid TEXT,
162
+ status_code INTEGER
150
163
  )
151
164
  `);
165
+
166
+ // 迁移:为已存在的 raw_jobs 表添加 status_code 列
167
+ const existingRawJobColumns = new Set(
168
+ db
169
+ .prepare("PRAGMA table_info(raw_jobs)")
170
+ .all()
171
+ .map((c) => c.name),
172
+ );
173
+ if (!existingRawJobColumns.has("status_code")) {
174
+ db.exec(`ALTER TABLE raw_jobs ADD COLUMN status_code INTEGER`);
175
+ }
152
176
  db.exec(`
153
177
  CREATE TABLE IF NOT EXISTS videos (
154
178
  id TEXT PRIMARY KEY,
@@ -1065,9 +1089,7 @@ function restoreRawJobsByFilter({ search, location }) {
1065
1089
 
1066
1090
  const count =
1067
1091
  db
1068
- .prepare(
1069
- `SELECT COUNT(*) as c FROM raw_jobs WHERE ${whereSql}`,
1070
- )
1092
+ .prepare(`SELECT COUNT(*) as c FROM raw_jobs WHERE ${whereSql}`)
1071
1093
  .get(...args)?.c || 0;
1072
1094
 
1073
1095
  if (!count) {
@@ -1146,6 +1168,17 @@ function getRawJobsPageFromDb({ search, location, limit, offset }) {
1146
1168
  };
1147
1169
  }
1148
1170
 
1171
+ // 调试接口:直接执行 SQL 查询,返回原始数据
1172
+ function rawQuery(sql, params = []) {
1173
+ if (!db) return { error: "db not ready" };
1174
+ try {
1175
+ const rows = db.prepare(sql).all(...params);
1176
+ return { rows };
1177
+ } catch (e) {
1178
+ return { error: e.message };
1179
+ }
1180
+ }
1181
+
1149
1182
  function getUsersPageFromDb({
1150
1183
  status,
1151
1184
  search,
@@ -1293,6 +1326,7 @@ const jobBooleanColumns = new Set([
1293
1326
  "processed",
1294
1327
  "tt_seller",
1295
1328
  "verified",
1329
+ "error",
1296
1330
  ]);
1297
1331
 
1298
1332
  const videoBooleanColumns = new Set(["tt_seller"]);
@@ -1324,17 +1358,20 @@ const writableJobColumns = new Set([
1324
1358
  "region",
1325
1359
  "signature",
1326
1360
  "sec_uid",
1361
+ "status_code",
1327
1362
  ]);
1328
1363
 
1329
1364
  function normalizeJobValue(column, value) {
1365
+ if (value === undefined || value === null) return null;
1330
1366
  if (column === "sources") {
1331
1367
  if (!Array.isArray(value)) return JSON.stringify([]);
1332
1368
  return JSON.stringify([...new Set(value)]);
1333
1369
  }
1334
1370
  if (jobBooleanColumns.has(column)) {
1335
- if (value === undefined || value === null || value === "") return null;
1336
1371
  return value ? 1 : 0;
1337
1372
  }
1373
+ // 防御:如果值是对象或数组,转为 JSON 字符串
1374
+ if (typeof value === "object") return JSON.stringify(value);
1338
1375
  return value;
1339
1376
  }
1340
1377
 
@@ -1812,7 +1849,15 @@ export function createStore(filePath) {
1812
1849
  const args = [];
1813
1850
  if (!loggedIn) {
1814
1851
  where.push("COALESCE(tt_seller, 0) != 1");
1852
+ // 未登录:只能领取 status_code 为空或 0 的任务
1853
+ where.push("(status_code IS NULL OR status_code = 0)");
1854
+ } else {
1855
+ // 登录:可以领取 status_code 为空、0、或 209002 的任务
1856
+ where.push(
1857
+ "(status_code IS NULL OR status_code = 0 OR status_code = 209002)",
1858
+ );
1815
1859
  }
1860
+ // 其他 status_code 值的任务不被领取
1816
1861
  if (requireVideo) {
1817
1862
  where.push("COALESCE(video_count, 0) > 0");
1818
1863
  }
@@ -1989,124 +2034,433 @@ export function createStore(filePath) {
1989
2034
  return null;
1990
2035
  }
1991
2036
 
1992
- const now = Date.now();
2037
+ if (!db) {
2038
+ const now = Date.now();
1993
2039
 
1994
- // 0. 该客户端有未过期的任务,续期返回
1995
- const ongoing = data.find(
1996
- (u) =>
1997
- u.status === "processing" &&
1998
- u.claimedBy === userId &&
1999
- u.claimedAt &&
2000
- now - u.claimedAt < expireMs,
2001
- );
2002
- if (ongoing) {
2003
- ongoing.claimedAt = now;
2004
- save();
2005
- return {
2006
- uniqueId: ongoing.uniqueId,
2007
- nickname: ongoing.nickname,
2008
- claimedAt: ongoing.claimedAt,
2009
- claimedBy: userId,
2010
- };
2011
- }
2040
+ // 0. 该客户端有未过期的任务,续期返回
2041
+ const ongoing = data.find(
2042
+ (u) =>
2043
+ u.status === "processing" &&
2044
+ u.claimedBy === userId &&
2045
+ u.claimedAt &&
2046
+ now - u.claimedAt < expireMs,
2047
+ );
2048
+ if (ongoing) {
2049
+ ongoing.claimedAt = now;
2050
+ save();
2051
+ return {
2052
+ uniqueId: ongoing.uniqueId,
2053
+ nickname: ongoing.nickname,
2054
+ claimedAt: ongoing.claimedAt,
2055
+ claimedBy: userId,
2056
+ };
2057
+ }
2012
2058
 
2013
- // 按猜测国家梯队排序
2014
- const tier1 = new Set(["PL", "NL", "BE"]);
2015
- const tier2 = new Set(["DE", "FR", "IT", "IE", "ES"]);
2016
- function locationTier(u) {
2017
- const loc = (u.guessedLocation || "").toUpperCase();
2018
- if (tier1.has(loc)) return 0;
2019
- if (tier2.has(loc)) return 1;
2020
- return 2;
2021
- }
2059
+ // 按猜测国家梯队排序
2060
+ const tier1 = new Set(["PL", "NL", "BE"]);
2061
+ const tier2 = new Set(["DE", "FR", "IT", "IE", "ES"]);
2062
+ function locationTier(u) {
2063
+ const loc = (u.guessedLocation || "").toUpperCase();
2064
+ if (tier1.has(loc)) return 0;
2065
+ if (tier2.has(loc)) return 1;
2066
+ return 2;
2067
+ }
2068
+
2069
+ // 国家过滤:如果指定了 locations,只保留 guessedLocation 在列表中的用户
2070
+ function locationFilter(u) {
2071
+ if (!locations || locations.length === 0) return true;
2072
+ return isLocationInList(u.guessedLocation, locations);
2073
+ }
2074
+
2075
+ // 从候选列表中按优先级取第一个:pinned > 超时回收 > seed > ttSeller(仅登录) > follow > other
2076
+ function pickCandidate(candidates) {
2077
+ let next = candidates.find((u) => u.pinned);
2078
+
2079
+ if (!next) {
2080
+ const expired = data.find(
2081
+ (u) =>
2082
+ u.status === "processing" &&
2083
+ u.claimedAt &&
2084
+ now - u.claimedAt > expireMs,
2085
+ );
2086
+ if (expired) {
2087
+ expired.status = "pending";
2088
+ markStatsDirty();
2089
+ delete expired.claimedAt;
2090
+ next = expired;
2091
+ }
2092
+ }
2093
+
2094
+ if (!next) {
2095
+ const seed = candidates.filter(
2096
+ (u) => u.sources && u.sources.includes("seed"),
2097
+ );
2098
+ seed.sort((a, b) => locationTier(a) - locationTier(b));
2099
+ next = seed[0] || null;
2100
+ }
2101
+
2102
+ // 未登录时跳过 ttSeller 优先级
2103
+ if (!next && loggedIn) {
2104
+ const ttSeller = candidates.filter(
2105
+ (u) => u.ttSeller === true && u.verified === false,
2106
+ );
2107
+ ttSeller.sort((a, b) => locationTier(a) - locationTier(b));
2108
+ next = ttSeller[0] || null;
2109
+ }
2110
+
2111
+ if (!next) {
2112
+ const follow = candidates.filter(
2113
+ (u) =>
2114
+ u.sources &&
2115
+ (u.sources.includes("following") ||
2116
+ u.sources.includes("follower")),
2117
+ );
2118
+ follow.sort((a, b) => locationTier(a) - locationTier(b));
2119
+ next = follow[0] || null;
2120
+ }
2121
+
2122
+ if (!next) {
2123
+ candidates.sort((a, b) => locationTier(a) - locationTier(b));
2124
+ next = candidates[0] || null;
2125
+ }
2126
+
2127
+ return next;
2128
+ }
2129
+
2130
+ // 先在有视频的 pending 用户中找;找不到再用全部 pending 用户兜底
2131
+ let pending = data.filter((u) => u.status === "pending");
2132
+ // 应用国家过滤
2133
+ if (locations && locations.length > 0) {
2134
+ pending = pending.filter(locationFilter);
2135
+ }
2136
+ // 未登录客户端不能领取 ttSeller 用户
2137
+ if (!loggedIn) {
2138
+ pending = pending.filter((u) => u.ttSeller !== true);
2139
+ }
2140
+ // status_code 过滤:只领取空值、0 或 209002 的任务
2141
+ pending = pending.filter(
2142
+ (u) =>
2143
+ u.statusCode == null ||
2144
+ u.statusCode === 0 ||
2145
+ (loggedIn && u.statusCode === 209002),
2146
+ );
2147
+ let hasVideo = pending.filter((u) => u.videoCount > 0);
2148
+ const next = pickCandidate(hasVideo) || pickCandidate(pending);
2022
2149
 
2023
- // 国家过滤:如果指定了 locations,只保留 guessedLocation 在列表中的用户
2024
- function locationFilter(u) {
2025
- if (!locations || locations.length === 0) return true;
2026
- return isLocationInList(u.guessedLocation, locations);
2150
+ if (next) {
2151
+ next.status = "processing";
2152
+ markStatsDirty();
2153
+ next.claimedAt = now;
2154
+ next.claimedBy = userId;
2155
+ save();
2156
+ return {
2157
+ uniqueId: next.uniqueId,
2158
+ nickname: next.nickname,
2159
+ claimedAt: next.claimedAt,
2160
+ claimedBy: userId,
2161
+ };
2162
+ }
2163
+ return null;
2027
2164
  }
2028
2165
 
2029
- // 从候选列表中按优先级取第一个:pinned > 超时回收 > seed > ttSeller(仅登录) > follow > other
2030
- function pickCandidate(candidates) {
2031
- let next = candidates.find((u) => u.pinned);
2166
+ return null;
2167
+ }
2032
2168
 
2033
- if (!next) {
2034
- const expired = data.find(
2035
- (u) =>
2036
- u.status === "processing" &&
2037
- u.claimedAt &&
2038
- now - u.claimedAt > expireMs,
2039
- );
2040
- if (expired) {
2041
- expired.status = "pending";
2042
- markStatsDirty();
2043
- delete expired.claimedAt;
2044
- next = expired;
2169
+ function debugClaimNextJob(
2170
+ userId,
2171
+ expireMs = 5 * 60 * 1000,
2172
+ locations = null,
2173
+ loggedIn = true,
2174
+ ) {
2175
+ if (db) {
2176
+ const now = Date.now();
2177
+ const info = {
2178
+ path: "db",
2179
+ userId,
2180
+ expireMs,
2181
+ loggedIn,
2182
+ };
2183
+
2184
+ const ongoingRow = db
2185
+ .prepare(
2186
+ `
2187
+ SELECT *
2188
+ FROM jobs
2189
+ WHERE status = 'processing'
2190
+ AND claimed_by = ?
2191
+ AND claimed_at IS NOT NULL
2192
+ AND ? - claimed_at < ?
2193
+ ORDER BY claimed_at DESC
2194
+ LIMIT 1
2195
+ `,
2196
+ )
2197
+ .get(userId, now, expireMs);
2198
+ info.ongoing = ongoingRow
2199
+ ? {
2200
+ uniqueId: ongoingRow.unique_id,
2201
+ claimedBy: ongoingRow.claimed_by,
2202
+ claimedAt: ongoingRow.claimed_at,
2203
+ }
2204
+ : null;
2205
+
2206
+ const tier1 = new Set(["PL", "NL", "BE"]);
2207
+ const tier2 = new Set(["DE", "FR", "IT", "IE", "ES"]);
2208
+ const normalizedLocations = Array.isArray(locations)
2209
+ ? locations
2210
+ .map((loc) => String(loc).trim().toUpperCase())
2211
+ .filter(Boolean)
2212
+ : [];
2213
+
2214
+ function getLocationGroups() {
2215
+ const selected = normalizedLocations.length
2216
+ ? normalizedLocations
2217
+ : null;
2218
+ const tier1List = selected
2219
+ ? selected.filter((loc) => tier1.has(loc))
2220
+ : [...tier1];
2221
+ const tier2List = selected
2222
+ ? selected.filter((loc) => tier2.has(loc))
2223
+ : [...tier2];
2224
+ const otherList = selected
2225
+ ? selected.filter((loc) => !tier1.has(loc) && !tier2.has(loc))
2226
+ : null;
2227
+ const groups = [];
2228
+ if (tier1List.length > 0)
2229
+ groups.push({ type: "include", values: tier1List });
2230
+ if (tier2List.length > 0)
2231
+ groups.push({ type: "include", values: tier2List });
2232
+ if (selected) {
2233
+ if (otherList.length > 0)
2234
+ groups.push({ type: "include", values: otherList });
2235
+ } else {
2236
+ groups.push({ type: "exclude", values: [...tier1, ...tier2] });
2045
2237
  }
2238
+ return groups;
2046
2239
  }
2047
2240
 
2048
- if (!next) {
2049
- const seed = candidates.filter(
2050
- (u) => u.sources && u.sources.includes("seed"),
2241
+ const locationGroups = getLocationGroups();
2242
+ info.locationGroups = locationGroups;
2243
+
2244
+ function applyLocationGroup(where, args, group) {
2245
+ if (!group) return;
2246
+ if (group.type === "include") {
2247
+ where.push(
2248
+ `UPPER(COALESCE(guessed_location, '')) IN (${group.values.map(() => "?").join(", ")})`,
2249
+ );
2250
+ args.push(...group.values);
2251
+ return;
2252
+ }
2253
+ where.push(
2254
+ `UPPER(COALESCE(guessed_location, '')) NOT IN (${group.values.map(() => "?").join(", ")})`,
2051
2255
  );
2052
- seed.sort((a, b) => locationTier(a) - locationTier(b));
2053
- next = seed[0] || null;
2256
+ args.push(...group.values);
2054
2257
  }
2055
2258
 
2056
- // 未登录时跳过 ttSeller 优先级
2057
- if (!next && loggedIn) {
2058
- const ttSeller = candidates.filter(
2059
- (u) => u.ttSeller === true && u.verified === false,
2060
- );
2061
- ttSeller.sort((a, b) => locationTier(a) - locationTier(b));
2062
- next = ttSeller[0] || null;
2259
+ function queryPendingOne({ requireVideo, group, filters = [] }) {
2260
+ const where = ["status = 'pending'"];
2261
+ const args = [];
2262
+ if (!loggedIn) {
2263
+ where.push("COALESCE(tt_seller, 0) != 1");
2264
+ where.push("(status_code IS NULL OR status_code = 0)");
2265
+ } else {
2266
+ where.push(
2267
+ "(status_code IS NULL OR status_code = 0 OR status_code = 209002)",
2268
+ );
2269
+ }
2270
+ if (requireVideo) {
2271
+ where.push("COALESCE(video_count, 0) > 0");
2272
+ }
2273
+ applyLocationGroup(where, args, group);
2274
+ for (const filter of filters) {
2275
+ where.push(filter);
2276
+ }
2277
+ const sql = `
2278
+ SELECT *
2279
+ FROM jobs
2280
+ WHERE ${where.join(" AND ")}
2281
+ ORDER BY follower_count DESC, created_at ASC, unique_id ASC
2282
+ LIMIT 1
2283
+ `;
2284
+ const row = db.prepare(sql).get(...args);
2285
+ return { row, sql, args };
2063
2286
  }
2064
2287
 
2065
- if (!next) {
2066
- const follow = candidates.filter(
2067
- (u) =>
2068
- u.sources &&
2069
- (u.sources.includes("following") || u.sources.includes("follower")),
2070
- );
2071
- follow.sort((a, b) => locationTier(a) - locationTier(b));
2072
- next = follow[0] || null;
2288
+ function queryPendingByGroup({ requireVideo, group, filters = [] }) {
2289
+ if (group?.type === "include" && group.values.length > 1) {
2290
+ for (const location of group.values) {
2291
+ const ret = queryPendingOne({
2292
+ requireVideo,
2293
+ group: { type: "include", values: [location] },
2294
+ filters,
2295
+ });
2296
+ if (ret.row) return ret;
2297
+ }
2298
+ return { row: null, sql: null, args: [] };
2299
+ }
2300
+ return queryPendingOne({ requireVideo, group, filters });
2073
2301
  }
2074
2302
 
2075
- if (!next) {
2076
- candidates.sort((a, b) => locationTier(a) - locationTier(b));
2077
- next = candidates[0] || null;
2303
+ function findPinnedPending(requireVideo) {
2304
+ const where = ["status = 'pending'", "COALESCE(pinned, 0) = 1"];
2305
+ const args = [];
2306
+ if (!loggedIn) {
2307
+ where.push("COALESCE(tt_seller, 0) != 1");
2308
+ }
2309
+ if (requireVideo) {
2310
+ where.push("COALESCE(video_count, 0) > 0");
2311
+ }
2312
+ if (normalizedLocations.length > 0) {
2313
+ where.push(
2314
+ `UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")})`,
2315
+ );
2316
+ args.push(...normalizedLocations);
2317
+ }
2318
+ const sql = `
2319
+ SELECT *
2320
+ FROM jobs
2321
+ WHERE ${where.join(" AND ")}
2322
+ ORDER BY created_at ASC, unique_id ASC
2323
+ LIMIT 1
2324
+ `;
2325
+ const row = db.prepare(sql).get(...args);
2326
+ return { row, sql, args };
2078
2327
  }
2079
2328
 
2080
- return next;
2081
- }
2329
+ const expiredSql = `
2330
+ SELECT *
2331
+ FROM jobs
2332
+ WHERE status = 'processing'
2333
+ AND claimed_at IS NOT NULL
2334
+ AND ? - claimed_at > ?
2335
+ ORDER BY claimed_at ASC
2336
+ LIMIT 1
2337
+ `;
2338
+ const expiredRow = db.prepare(expiredSql).get(now, expireMs);
2339
+ info.expired = expiredRow
2340
+ ? {
2341
+ uniqueId: expiredRow.unique_id,
2342
+ claimedBy: expiredRow.claimed_by,
2343
+ claimedAt: expiredRow.claimed_at,
2344
+ diffMs: now - expiredRow.claimed_at,
2345
+ }
2346
+ : null;
2082
2347
 
2083
- // 先在有视频的 pending 用户中找;找不到再用全部 pending 用户兜底
2084
- let pending = data.filter((u) => u.status === "pending");
2085
- // 应用国家过滤
2086
- if (locations && locations.length > 0) {
2087
- pending = pending.filter(locationFilter);
2088
- }
2089
- // 未登录客户端不能领取 ttSeller 用户
2090
- if (!loggedIn) {
2091
- pending = pending.filter((u) => u.ttSeller !== true);
2092
- }
2093
- let hasVideo = pending.filter((u) => u.videoCount > 0);
2094
- const next = pickCandidate(hasVideo) || pickCandidate(pending);
2348
+ info.requireVideoPasses = [];
2349
+ for (const requireVideo of [true, false]) {
2350
+ const pass = { requireVideo };
2351
+ const pinned = findPinnedPending(requireVideo);
2352
+ pass.pinned = pinned.row
2353
+ ? {
2354
+ uniqueId: pinned.row.unique_id,
2355
+ sql: pinned.sql,
2356
+ args: pinned.args,
2357
+ }
2358
+ : null;
2095
2359
 
2096
- if (next) {
2097
- next.status = "processing";
2098
- markStatsDirty();
2099
- next.claimedAt = now;
2100
- next.claimedBy = userId;
2101
- save();
2102
- return {
2103
- uniqueId: next.uniqueId,
2104
- nickname: next.nickname,
2105
- claimedAt: next.claimedAt,
2106
- claimedBy: userId,
2107
- };
2360
+ if (!pass.pinned) {
2361
+ for (const group of locationGroups) {
2362
+ const seed = queryPendingByGroup({
2363
+ requireVideo,
2364
+ group,
2365
+ filters: [
2366
+ "COALESCE(pinned, 0) = 0",
2367
+ `instr(COALESCE(sources, ''), '"seed"') > 0`,
2368
+ ],
2369
+ });
2370
+ if (seed.row) {
2371
+ pass.seed = {
2372
+ uniqueId: seed.row.unique_id,
2373
+ group,
2374
+ sql: seed.sql,
2375
+ args: seed.args,
2376
+ };
2377
+ break;
2378
+ }
2379
+ }
2380
+ }
2381
+
2382
+ if (!pass.pinned && !pass.seed && loggedIn) {
2383
+ for (const group of locationGroups) {
2384
+ const seller = queryPendingByGroup({
2385
+ requireVideo,
2386
+ group,
2387
+ filters: [
2388
+ "COALESCE(pinned, 0) = 0",
2389
+ "tt_seller = 1",
2390
+ "verified = 0",
2391
+ ],
2392
+ });
2393
+ if (seller.row) {
2394
+ pass.seller = {
2395
+ uniqueId: seller.row.unique_id,
2396
+ group,
2397
+ sql: seller.sql,
2398
+ args: seller.args,
2399
+ };
2400
+ break;
2401
+ }
2402
+ }
2403
+ }
2404
+
2405
+ if (!pass.pinned && !pass.seed && !pass.seller) {
2406
+ for (const group of locationGroups) {
2407
+ const follow = queryPendingByGroup({
2408
+ requireVideo,
2409
+ group,
2410
+ filters: [
2411
+ "COALESCE(pinned, 0) = 0",
2412
+ `(
2413
+ instr(COALESCE(sources, ''), '"following"') > 0
2414
+ OR instr(COALESCE(sources, ''), '"follower"') > 0
2415
+ )`,
2416
+ ],
2417
+ });
2418
+ if (follow.row) {
2419
+ pass.follow = {
2420
+ uniqueId: follow.row.unique_id,
2421
+ group,
2422
+ sql: follow.sql,
2423
+ args: follow.args,
2424
+ };
2425
+ break;
2426
+ }
2427
+ }
2428
+ }
2429
+
2430
+ if (!pass.pinned && !pass.seed && !pass.seller && !pass.follow) {
2431
+ for (const group of locationGroups) {
2432
+ const other = queryPendingByGroup({
2433
+ requireVideo,
2434
+ group,
2435
+ filters: ["COALESCE(pinned, 0) = 0"],
2436
+ });
2437
+ if (other.row) {
2438
+ pass.other = {
2439
+ uniqueId: other.row.unique_id,
2440
+ group,
2441
+ sql: other.sql,
2442
+ args: other.args,
2443
+ };
2444
+ break;
2445
+ }
2446
+ }
2447
+ }
2448
+
2449
+ info.requireVideoPasses.push(pass);
2450
+ }
2451
+
2452
+ return info;
2108
2453
  }
2109
- return null;
2454
+
2455
+ return {
2456
+ path: "memory",
2457
+ userId,
2458
+ expireMs,
2459
+ loggedIn,
2460
+ totalUsers: data.length,
2461
+ processingUsers: data.filter((u) => u.status === "processing").length,
2462
+ pendingUsers: data.filter((u) => u.status === "pending").length,
2463
+ };
2110
2464
  }
2111
2465
 
2112
2466
  function processDiscoveredUsers(result) {
@@ -2215,6 +2569,7 @@ export function createStore(filePath) {
2215
2569
  }
2216
2570
  }
2217
2571
  }
2572
+ user.restricted = true;
2218
2573
  user.processed = true;
2219
2574
  user.processedAt = Date.now();
2220
2575
  user.sources = [...new Set([...(user.sources || []), "restricted"])];
@@ -2644,7 +2999,9 @@ export function createStore(filePath) {
2644
2999
  u.ttSeller === null || u.ttSeller === undefined || u.ttSeller === "";
2645
3000
  if (!ttSellerEmpty) return false;
2646
3001
  if (
2647
- updateCount === null || updateCount === undefined || updateCount <= 0
3002
+ updateCount === null ||
3003
+ updateCount === undefined ||
3004
+ updateCount <= 0
2648
3005
  ) {
2649
3006
  if (hasCountryFilter) {
2650
3007
  const loc = (u.guessedLocation || "").toUpperCase();
@@ -2686,7 +3043,19 @@ export function createStore(filePath) {
2686
3043
  function batchUpdateUserInfo(updates) {
2687
3044
  if (db) {
2688
3045
  const txn = db.transaction((items) =>
2689
- items.map((item) => updateJobInfo(item.uniqueId, item.info, true)),
3046
+ items.map((item) => {
3047
+ // 处理 { error: true, statusCode: xxx } 的情况
3048
+ const info = item.info;
3049
+ if (info && info.error && info.statusCode !== undefined) {
3050
+ // 只更新 status_code,不更新其他字段
3051
+ return updateJobInfo(
3052
+ item.uniqueId,
3053
+ { statusCode: info.statusCode },
3054
+ true,
3055
+ );
3056
+ }
3057
+ return updateJobInfo(item.uniqueId, info, true);
3058
+ }),
2690
3059
  );
2691
3060
  return txn(updates).map((result, index) =>
2692
3061
  result.error
@@ -2706,15 +3075,20 @@ export function createStore(filePath) {
2706
3075
  results.push({ uniqueId: item.uniqueId, error: "user not found" });
2707
3076
  continue;
2708
3077
  }
2709
- if (item.info) {
2710
- for (const key of Object.keys(item.info)) {
2711
- if (key === "uniqueId" || key === "sources") continue;
3078
+ const info = item.info;
3079
+ if (info && info.error && info.statusCode !== undefined) {
3080
+ // 只更新 status_code
3081
+ user.statusCode = info.statusCode;
3082
+ } else {
3083
+ for (const key of Object.keys(info)) {
3084
+ if (key === "uniqueId" || key === "sources" || key === "error")
3085
+ continue;
2712
3086
  if (
2713
- item.info[key] !== undefined &&
2714
- item.info[key] !== null &&
2715
- item.info[key] !== ""
3087
+ info[key] !== undefined &&
3088
+ info[key] !== null &&
3089
+ info[key] !== ""
2716
3090
  ) {
2717
- user[key] = item.info[key];
3091
+ user[key] = info[key];
2718
3092
  }
2719
3093
  }
2720
3094
  }
@@ -2974,7 +3348,9 @@ export function createStore(filePath) {
2974
3348
  getVideoCount,
2975
3349
  getPendingCommentTasks,
2976
3350
  commitCommentTask,
3351
+ debugClaimNextJob,
2977
3352
  stopBackup,
3353
+ rawQuery,
2978
3354
  data,
2979
3355
  };
2980
3356
  }