channel-worker 2.5.102 → 2.5.103

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.
@@ -173,11 +173,39 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
173
173
  // or logged out — in either case the offer API returns 404/empty and we must
174
174
  // STOP (not keep hammering) and ask the operator to re-verify via VNC.
175
175
  function isBlockedState(pageUrl, res) {
176
- if (/\/verify\/captcha|\/buyer\/login|is_from_login/.test(pageUrl || '')) return true;
177
176
  if (res && (res.status === 403 || res.status === 404)) return true;
177
+ let u;
178
+ try { u = new URL(pageUrl || ''); } catch { return false; }
179
+ // CHỈ xét path + query của CHÍNH trang này. Regex quét cả chuỗi URL đọc nhầm
180
+ // tham số `next=` của trang khác — xem ghi chú ở findAffiliatePage.
181
+ if (/^\/(verify\/captcha|buyer\/login)/.test(u.pathname)) return true;
182
+ if (u.searchParams.has('is_from_login')) return true;
178
183
  return false;
179
184
  }
180
185
 
186
+ // Tab affiliate nào đang mở — so bằng HOSTNAME, không bằng chuỗi con.
187
+ //
188
+ // 11/09/2026, đo trên profile Shopee1 thật: tab captcha cũ
189
+ // `shopee.vn/verify/captcha?…&next=…%252Faffiliate.shopee.vn%252Foffer…`
190
+ // (mã hoá hai lần) chứa nguyên chuỗi "affiliate.shopee.vn", nên
191
+ // `pages().find(p => p.url().includes('affiliate.shopee.vn'))` vớ trúng ĐÚNG
192
+ // cái tab hỏng đó — nó nằm ở vị trí 0 trong 16 tab. isBlockedState đọc
193
+ // `/verify/captcha` rồi kết luận "session ở captcha", trong khi 15 tab còn lại
194
+ // đang đăng nhập bình thường (dashboard, conversion_report, offer). Chủ tịch mở
195
+ // profile thấy vẫn login mà máy một mực đòi xác minh — hai bên nhìn hai tab
196
+ // khác nhau.
197
+ //
198
+ // Bỏ QUA tab đang ở trạng thái chặn khi chọn: phiên chết thật thì không tab nào
199
+ // sống sót, hàm trả null và bên gọi mở tab mới — lượt goto sau đó vẫn bị đá về
200
+ // captcha nên vẫn báo needs_verify đúng như cũ.
201
+ function urlPath(u) { try { return new URL(u).pathname; } catch { return ''; } }
202
+ function isAffiliateUrl(u) { try { return new URL(u).hostname === 'affiliate.shopee.vn'; } catch { return false; } }
203
+
204
+ function findAffiliatePage(pages) {
205
+ const live = (pages || []).filter((p) => isAffiliateUrl(p.url()) && !isBlockedState(p.url(), null));
206
+ return live.find((p) => urlPath(p.url()).startsWith('/offer')) || live[0] || null;
207
+ }
208
+
181
209
  // Apply the Flow-1 hard filters (criteria) to parsed rows. Defaults are lenient;
182
210
  // the controller passes the agreed thresholds (commission/price/sold/rating/imgs).
183
211
  function applyFilters(rows, f = {}) {
@@ -237,11 +265,11 @@ async function scrapeOffers(ctx, {
237
265
  const listType = cat?.list_type || null;
238
266
 
239
267
  const pagesOpen = ctx.pages();
240
- let page = pagesOpen.find((p) => p.url().includes('affiliate.shopee.vn'));
268
+ let page = findAffiliatePage(pagesOpen);
241
269
  let opened = false;
242
270
  if (!page) { page = await ctx.newPage(); opened = true; }
243
271
  try {
244
- if (!page.url().includes('affiliate.shopee.vn/offer')) {
272
+ if (!urlPath(page.url()).startsWith('/offer')) {
245
273
  // 'networkidle' (playwright), KHÔNG 'networkidle2' (tên của puppeteer —
246
274
  // playwright coi là giá trị lạ và ném lỗi).
247
275
  await page.goto('https://affiliate.shopee.vn/offer/product_offer', { waitUntil: 'networkidle', timeout: timeoutMs }).catch(() => {});
@@ -326,11 +354,11 @@ async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 450
326
354
  for (let i = 0; i < 5; i++) subs[`subId${i + 1}`] = cleanSubId(subIds[i]);
327
355
 
328
356
  const pagesOpen = ctx.pages();
329
- let page = pagesOpen.find((p) => p.url().includes('affiliate.shopee.vn'));
357
+ let page = findAffiliatePage(pagesOpen);
330
358
  let opened = false;
331
359
  if (!page) { page = await ctx.newPage(); opened = true; }
332
360
  try {
333
- if (!page.url().includes('affiliate.shopee.vn')) {
361
+ if (!isAffiliateUrl(page.url())) {
334
362
  await page.goto('https://affiliate.shopee.vn/offer/product_offer', { waitUntil: 'networkidle', timeout: timeoutMs }).catch(() => {});
335
363
  await sleep(1500);
336
364
  }
@@ -376,11 +404,13 @@ async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 450
376
404
  // Parse a Shopee product URL → { shop_id, item_id }. Accepts
377
405
  // /product/<shop>/<item> and i.<shop>.<item> forms.
378
406
  function parseProductUrl(url = '') {
379
- let m = url.match(/\/product\/(\d+)\/(\d+)/);
407
+ // `/opaanlp/` đích của link affiliate (đo 12/09: link rút gọn s.shopee.vn
408
+ // đá về đúng dạng này) — vẫn là một sản phẩm, vẫn lấy được shop/item.
409
+ let m = url.match(/\/(?:product|opaanlp)\/(\d+)\/(\d+)/);
380
410
  if (m) return { shop_id: m[1], item_id: m[2] };
381
411
  m = url.match(/i\.(\d+)\.(\d+)/);
382
412
  if (m) return { shop_id: m[1], item_id: m[2] };
383
413
  return null;
384
414
  }
385
415
 
386
- module.exports = { ingestProduct, scrapeOffers, generateOfferLink, cleanSubId, parseProductUrl, parseOfferRow, parsePdpItem, categoryGroup, pctToNumber, AFFILIATE_CATEGORIES, applyFilters };
416
+ module.exports = { ingestProduct, scrapeOffers, generateOfferLink, cleanSubId, parseProductUrl, parseOfferRow, parsePdpItem, categoryGroup, pctToNumber, AFFILIATE_CATEGORIES, applyFilters, isBlockedState, findAffiliatePage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.102",
3
+ "version": "2.5.103",
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": {