channel-worker 2.5.111 → 2.5.112

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.
@@ -215,6 +215,7 @@ class CommandPoller {
215
215
  page_limit: payload.page_limit ?? 20,
216
216
  delay_ms: payload.delay_ms ?? 2500, // human-like pacing (anti-bot)
217
217
  filters: payload.filters || {},
218
+ log: (m) => console.log(m), // để thấy số tab đã dọn
218
219
  });
219
220
  if (res.status === 'needs_verify') {
220
221
  // Anti-bot bounce — DON'T fail/retry into more captchas. Report cleanly
@@ -304,6 +305,7 @@ class CommandPoller {
304
305
  const res = await scraper.generateOfferLink(conn.context, {
305
306
  items,
306
307
  subIds: Array.isArray(payload.sub_ids) ? payload.sub_ids : [],
308
+ log: (m) => console.log(m), // để thấy số tab đã dọn
307
309
  });
308
310
  if (res.status === 'needs_verify') {
309
311
  // Anti-bot bounce — báo sạch, KHÔNG thử lại để khỏi đâm thêm captcha.
@@ -206,6 +206,51 @@ function findAffiliatePage(pages) {
206
206
  return live.find((p) => urlPath(p.url()).startsWith('/offer')) || live[0] || null;
207
207
  }
208
208
 
209
+ // Tab nào là RÁC của các lượt trước — đóng được mà không mất gì.
210
+ //
211
+ // Chỉ nhận ba loại, cố ý hẹp: trang trắng, tab đang kẹt ở captcha/đăng nhập, và
212
+ // tab Shopee thừa ngoài cái mình sắp dùng. KHÔNG đụng tới tab của trang khác:
213
+ // profile này người vẫn mở tay để xác minh, đóng nhầm là cướp việc của họ.
214
+ function isJunkTab(url, keepUrl) {
215
+ if (url === keepUrl) return false;
216
+ if (!url || url === 'about:blank' || url === 'chrome://newtab/') return true;
217
+ if (isBlockedState(url, null)) return true; // captcha / buyer/login / is_from_login
218
+ try {
219
+ const h = new URL(url).hostname;
220
+ return h === 'affiliate.shopee.vn' || h === 'shopee.vn' || h === 'www.shopee.vn';
221
+ } catch { return false; }
222
+ }
223
+
224
+ // Dọn tab trước khi làm việc.
225
+ //
226
+ // 11/09 đo trên chính profile Shopee1: 16 tab, và tab VỊ TRÍ 0 là captcha cũ từ
227
+ // lượt quét trước — `pages().find(u.includes('affiliate.shopee.vn'))` vớ trúng
228
+ // nó nên máy một mực đòi xác minh trong khi 15 tab kia đăng nhập bình thường.
229
+ // `findAffiliatePage` đã vá chỗ CHỌN, nhưng tab rác vẫn dồn lại mỗi ngày: mỗi
230
+ // lượt bị đá sang captcha để lại một tab, Chrome chậm dần, và người mở profile
231
+ // ra nhìn thì rối.
232
+ //
233
+ // Giữ đúng MỘT tab affiliate (cái sắp dùng) và đóng phần còn lại. Không bao giờ
234
+ // đóng tab cuối cùng — context không còn trang nào thì Chrome của NST có thể tự
235
+ // tắt, mất luôn phiên đăng nhập vừa xác minh tay.
236
+ async function pruneTabs(ctx, { keep = null, log = null } = {}) {
237
+ const pages = ctx.pages() || [];
238
+ if (pages.length <= 1) return 0;
239
+ const keepUrl = keep ? keep.url() : (findAffiliatePage(pages)?.url() || null);
240
+ let closed = 0;
241
+ for (const p of pages) {
242
+ if (p === keep) continue;
243
+ let u = '';
244
+ try { u = p.url(); } catch { continue; }
245
+ if (!isJunkTab(u, keepUrl)) continue;
246
+ if ((ctx.pages() || []).length <= 1) break;
247
+ await p.close().catch(() => {});
248
+ closed++;
249
+ }
250
+ if (closed && log) log(`[shopee] đã đóng ${closed} tab cũ, còn ${(ctx.pages() || []).length}`);
251
+ return closed;
252
+ }
253
+
209
254
  // Apply the Flow-1 hard filters (criteria) to parsed rows. Defaults are lenient;
210
255
  // the controller passes the agreed thresholds (commission/price/sold/rating/imgs).
211
256
  function applyFilters(rows, f = {}) {
@@ -260,6 +305,7 @@ async function scrapeOffers(ctx, {
260
305
  delay_ms = 2500, // human-like pause between calls (anti-bot)
261
306
  filters = {},
262
307
  timeoutMs = 45000,
308
+ log = null,
263
309
  // legacy single-sort param (back-compat with the first build)
264
310
  sort_type = null,
265
311
  } = {}) {
@@ -284,8 +330,10 @@ async function scrapeOffers(ctx, {
284
330
  if (cat?.group && !filters.category_groups) filters = { ...filters, category_groups: [cat.group] };
285
331
  const listType = cat?.list_type || null;
286
332
 
287
- const pagesOpen = ctx.pages();
288
- let page = findAffiliatePage(pagesOpen);
333
+ let page = findAffiliatePage(ctx.pages());
334
+ // Dọn TRƯỚC khi mở thêm: tab rác của lượt trước không giúp gì cho lượt này,
335
+ // mà để lại thì lần sau còn nhiều hơn.
336
+ await pruneTabs(ctx, { keep: page, log }).catch(() => {});
289
337
  let opened = false;
290
338
  if (!page) { page = await ctx.newPage(); opened = true; }
291
339
  try {
@@ -372,7 +420,7 @@ function cleanSubId(v) {
372
420
  // Google Ads, không qua đường ghi nhận affiliate nên không ra hoa hồng).
373
421
  //
374
422
  // subIds: mảng tối đa 5 phần tử. Quy ước của mình: [kênh, video, đợt].
375
- async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 45000 } = {}) {
423
+ async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 45000, log = null } = {}) {
376
424
  const list = (items || [])
377
425
  .map((it) => ({ itemId: String(it.item_id || ''), shopId: Number(it.shop_id) || 0, trace: '' }))
378
426
  .filter((it) => it.itemId && it.shopId);
@@ -381,8 +429,8 @@ async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 450
381
429
  const subs = {};
382
430
  for (let i = 0; i < 5; i++) subs[`subId${i + 1}`] = cleanSubId(subIds[i]);
383
431
 
384
- const pagesOpen = ctx.pages();
385
- let page = findAffiliatePage(pagesOpen);
432
+ let page = findAffiliatePage(ctx.pages());
433
+ await pruneTabs(ctx, { keep: page, log }).catch(() => {});
386
434
  let opened = false;
387
435
  if (!page) { page = await ctx.newPage(); opened = true; }
388
436
  try {
@@ -441,4 +489,4 @@ function parseProductUrl(url = '') {
441
489
  return null;
442
490
  }
443
491
 
444
- module.exports = { ingestProduct, scrapeOffers, generateOfferLink, cleanSubId, parseProductUrl, parseOfferRow, parsePdpItem, categoryGroup, pctToNumber, AFFILIATE_CATEGORIES, applyFilters, isBlockedState, findAffiliatePage };
492
+ module.exports = { ingestProduct, scrapeOffers, generateOfferLink, cleanSubId, parseProductUrl, parseOfferRow, parsePdpItem, categoryGroup, pctToNumber, AFFILIATE_CATEGORIES, applyFilters, isBlockedState, findAffiliatePage, pruneTabs, isJunkTab };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.111",
3
+ "version": "2.5.112",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {