channel-worker 2.5.107 → 2.5.108

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": "channel-worker",
3
- "version": "2.5.107",
3
+ "version": "2.5.108",
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": {
@@ -38,7 +38,7 @@ function parseViews(txt) {
38
38
  // 09/09 tìm được tab để so trước/sau. Bản riêng còn thiếu cả bộ lọc "anchor
39
39
  // của bài trong bảng tin" (gốc 2 link page lạ sáng 10/09) nên có thể quét
40
40
  // nhầm tab Reels của page KHÁC mà không hay.
41
- const { discoverReelsTabUrl } = require('./upload_facebook').__testables;
41
+ const { discoverReelsTabUrl, discoverReelsTabUrls, openReelsTabWithTiles } = require('./upload_facebook').__testables;
42
42
 
43
43
  // Số người theo dõi của page, đọc ngay trên chính trang đang đứng (header hiện
44
44
  // ở mọi tab của page/profile) — không tốn thêm lượt mở profile nào. Ô
@@ -507,17 +507,21 @@ async function run({ page, payload, log }) {
507
507
  const scrolls = Number.isFinite(Number(payload?.scroll)) && Number(payload.scroll) > 0
508
508
  ? Number(payload.scroll)
509
509
  : (mode === 'fresh' ? 2 : 6);
510
- let tabUrl = null;
511
- if (page_url && /sk=reels_tab/.test(page_url)) tabUrl = page_url;
512
- if (!tabUrl) {
510
+ let candidates = [];
511
+ if (page_url && /sk=reels_tab/.test(page_url)) candidates = [page_url];
512
+ if (!candidates.length) {
513
513
  await page.goto(page_url || 'https://www.facebook.com/', { waitUntil: 'domcontentloaded', timeout: 45_000 });
514
514
  await page.waitForTimeout(4000);
515
- tabUrl = await discoverReelsTabUrl(page, log); // có log → in mẫu href khi không khớp
515
+ candidates = await discoverReelsTabUrls(page, log); // có log → in mẫu href khi không khớp
516
516
  }
517
- if (!tabUrl) throw new Error('không tìm được link tab Reels của page');
518
- log('info', `[fb-stats] ${mode} · reels_tab: ${tabUrl.slice(0, 100)}`);
519
- await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 });
520
- await page.waitForTimeout(5000);
517
+ if (!candidates.length) throw new Error('không tìm được link tab Reels của page');
518
+ // Thử lần lượt tới khi thấy tile: cùng một page có hai dạng URL mà chỉ một
519
+ // dạng render tab Reels. Ca "Chuyện Nghề Nhà Nông" 14/09 — dạng
520
+ // profile.php?id=… ra 0 tile suốt 4 lượt quét (kênh mất trắng số liệu view,
521
+ // và bộ đối chiếu xuôi cũng mù theo), dạng /chuyennghenhanong/ ra 17 tile.
522
+ const opened = await openReelsTabWithTiles(page, candidates, { log, settleMs: 5000, tag: '[fb-stats]' });
523
+ log('info', `[fb-stats] ${mode} · reels_tab: ${String(opened.url || '').slice(0, 100)}`
524
+ + (candidates.length > 1 ? ` (ứng viên ${opened.tried}/${candidates.length})` : ''));
521
525
 
522
526
  // Follower chỉ đọc ở lượt nền: lượt bám bài chạy 2-3 lần/ngày mà con số này
523
527
  // gần như không đổi trong ngày. Đọc hỏng thì bỏ qua — KHÔNG được làm hỏng
@@ -560,4 +564,4 @@ async function run({ page, payload, log }) {
560
564
  return { ok: true, mode, tiles: tiles.length, written: d?.written || 0, matched: d?.matched || 0, rising: d?.rising || 0, alerts: d?.alerts || 0, followers };
561
565
  }
562
566
 
563
- module.exports = { run, __testables: { parseViews, scrapeFollowers, parseExact, parseVnPostTime, runInsights, dumpInsightsFailure, discoverReelsTabUrl } };
567
+ module.exports = { run, __testables: { parseViews, scrapeFollowers, parseExact, parseVnPostTime, runInsights, dumpInsightsFailure, discoverReelsTabUrl, discoverReelsTabUrls, openReelsTabWithTiles } };
@@ -803,10 +803,10 @@ async function confirmPublishDialog({ firstHit, tagConfirmButton, clickTagged, c
803
803
  return { confirmed: !pending };
804
804
  }
805
805
 
806
- // Link tab Reels của Page dùng CẢ HAI đầu lượt đăng (chụp ảnh nền trước
807
- // khi đăng, quét lại sau khi đăng). Ba chiến lược, giữ nguyên từ bản cũ.
808
- async function discoverReelsTabUrl(page, log = null) {
809
- const anchors = await page.evaluate(() => {
806
+ // Mọi anchor trên trang hiện tại (href + aria + nằm trong bảng tin không)
807
+ // nguyên liệu cho pickReelsTabUrls. Tách riêng để bộ chạy một lượt evaluate.
808
+ async function scrapePageAnchors(page) {
809
+ return page.evaluate(() => {
810
810
  const out = [];
811
811
  for (const a of document.querySelectorAll('a[href]')) {
812
812
  const href = a.getAttribute('href') || '';
@@ -819,13 +819,47 @@ async function discoverReelsTabUrl(page, log = null) {
819
819
  }
820
820
  return out;
821
821
  }).catch(() => []);
822
- const url = pickReelsTabUrl(anchors);
823
- if (!url && log) {
822
+ }
823
+
824
+ // MỌI ứng viên URL tab Reels của page, theo thứ tự ưu tiên. Trả danh sách chứ
825
+ // không phải một cái, vì cùng một page có thể có HAI dạng URL mà chỉ MỘT dạng
826
+ // render tile — ca "Chuyện Nghề Nhà Nông" 14/09: `/profile.php?id=615922598169
827
+ // 21&sk=reels_tab` ra 0 tile ở cả 4 lượt quét liên tiếp (kênh mù hẳn, mất luôn
828
+ // số liệu view), trong khi `/chuyennghenhanong/?sk=reels_tab` ra 17 tile. Trước
829
+ // đó nó vẫn chạy đúng bằng dạng handle; anchor trên trang đổi là dạng
830
+ // profile.php thắng chiến lược 2 và cả bộ đo lẫn bộ lấy link cùng mù.
831
+ async function discoverReelsTabUrls(page, log = null) {
832
+ const anchors = await scrapePageAnchors(page);
833
+ const urls = pickReelsTabUrls(anchors);
834
+ if (!urls.length && log) {
824
835
  // Chẩn đoán: in vài href "giống của Page" để lần sau thêm đúng chiến lược.
825
836
  const sample = anchors.map((a) => a.href).filter((h) => /^\/(?:profile\.php|\d{8,}|[A-Za-z0-9._-]{3,}\/?(?:\?|$))/.test(h)).slice(0, 8);
826
837
  log('info', `[fb-pw] tìm tab Reels: ${anchors.length} anchor, không khớp chiến lược nào; mẫu: ${JSON.stringify(sample)}`);
827
838
  }
828
- return url;
839
+ return urls;
840
+ }
841
+ async function discoverReelsTabUrl(page, log = null) {
842
+ return (await discoverReelsTabUrls(page, log))[0] || null;
843
+ }
844
+
845
+ // Mở tab Reels: thử lần lượt từng ứng viên tới khi thấy tile. `verified` = URL
846
+ // này CHẮC CHẮN render được tab Reels của page — bằng chứng mạnh hơn "dò được
847
+ // một link trông giống", nên lượt lấy link sau khi đăng ưu tiên nó.
848
+ // Page thật sự chưa có reel nào cũng ra verified=false; khi đó ids rỗng là
849
+ // đúng, không phải hỏng.
850
+ async function openReelsTabWithTiles(page, urls, { log = null, settleMs = 4000, wait = null, tag = '[fb-pw]' } = {}) {
851
+ const list = [...new Set((urls || []).filter(Boolean))];
852
+ let lastUrl = null;
853
+ for (let i = 0; i < list.length; i++) {
854
+ const url = list[i];
855
+ lastUrl = url;
856
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45_000 });
857
+ if (wait) await wait(page, settleMs); else await page.waitForTimeout(settleMs);
858
+ const ids = await scrapeReelTileIds(page);
859
+ if (ids.length) return { url, ids, verified: true, tried: i + 1 };
860
+ if (log && i + 1 < list.length) log('warn', `${tag} tab Reels ${url.slice(0, 80)} không có tile nào — thử ứng viên kế (${i + 2}/${list.length})`);
861
+ }
862
+ return { url: lastUrl, ids: [], verified: false, tried: list.length };
829
863
  }
830
864
 
831
865
  // Mọi reel ID đang hiện trên tab Reels, theo thứ tự DOM (mới nhất trước).
@@ -861,7 +895,11 @@ function diffNewReelIds(beforeIds, afterIds) {
861
895
  // "no reels_tab link found") — trang đó không có link tab Reels, dò lại là
862
896
  // trượt và cả so trước/sau lẫn lượt chờ 75s đều bị bỏ → bài lên thật mà thành
863
897
  // "mập mờ" (Makeup tại nhà 2 13/09 18:07, thich-lo-mo 12/09). Thuần, test được.
864
- function pickPostPublishReelsTabUrl({ discovered, remembered }) {
898
+ function pickPostPublishReelsTabUrl({ discovered, remembered, rememberedVerified = false }) {
899
+ // URL đã CHỨNG MINH có tile lúc chụp ảnh nền thắng cả link dò được trên trang
900
+ // hiện tại: cùng một page có hai dạng URL mà chỉ một dạng render tab Reels
901
+ // (xem discoverReelsTabUrls), và dò lại sau khi đăng dễ bốc trúng dạng hỏng.
902
+ if (rememberedVerified && remembered) return { url: remembered, via: 'snapshot-verified' };
865
903
  if (discovered) return { url: discovered, via: 'page' };
866
904
  if (remembered) return { url: remembered, via: 'snapshot' };
867
905
  return { url: null, via: null };
@@ -892,7 +930,9 @@ function isComposerDialogSignature(text) {
892
930
  // chụp ảnh nền trước khi đăng) không có anchor profile.php/Dòng thời gian —
893
931
  // 2/2 lượt "không tìm được link tab Reels trước khi đăng". Trang chủ của Page
894
932
  // có shortcut bên trái trỏ /<id số>/ hoặc /<handle>/, và đôi khi /<id>/reels/.
895
- function pickReelsTabUrl(anchors) {
933
+ function pickReelsTabUrls(anchors) {
934
+ const out = [];
935
+ const add = (u) => { if (u && !out.includes(u)) out.push(u); };
896
936
  const list = (anchors || [])
897
937
  .map((a) => ({ href: String(a.href || ''), aria: String(a.aria || ''), inFeed: !!a.inFeed }))
898
938
  // ⛔ LOẠI anchor CỦA BÀI TRONG BẢNG TIN — đây là gốc của 2 link sai sáng
@@ -905,27 +945,31 @@ function pickReelsTabUrl(anchors) {
905
945
  // (tracking của một bài cụ thể) hoặc anchor nằm trong vùng feed/article.
906
946
  .filter((a) => !a.inFeed && !/[?&]__cft__|[?&]__tn__/.test(a.href));
907
947
  const abs = (h) => (h.startsWith('http') ? h : `https://www.facebook.com${h}`);
908
- for (const a of list) if (/sk=reels_tab/.test(a.href)) return abs(a.href);
948
+ for (const a of list) if (/sk=reels_tab/.test(a.href)) add(abs(a.href));
909
949
  for (const a of list) {
910
950
  const m = a.href.match(/\/profile\.php\?id=(\d+)/);
911
- if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
951
+ if (m) add(`https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`);
912
952
  }
913
953
  for (const a of list) {
914
954
  if (!/Dòng thời gian|Timeline/i.test(a.aria)) continue;
915
955
  const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
916
- if (m && !['profile.php', 'reel', 'video', 'videos', 'reels'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
956
+ if (m && !['profile.php', 'reel', 'video', 'videos', 'reels'].includes(m[1])) add(`https://www.facebook.com/${m[1]}/?sk=reels_tab`);
917
957
  }
918
958
  // 4. /<id số>/reels/ hoặc /<id số>/ — shortcut của Page trên trang chủ.
919
959
  for (const a of list) {
920
960
  const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/(\d{10,20})(?:\/reels)?\/?(?:\?|$)/);
921
- if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
961
+ if (m) add(`https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`);
922
962
  }
923
963
  // 5. /<handle>/reels/ — Page có tên riêng.
924
964
  for (const a of list) {
925
965
  const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/reels\/?(?:\?|$)/);
926
- if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
966
+ if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) add(`https://www.facebook.com/${m[1]}/?sk=reels_tab`);
927
967
  }
928
- return null;
968
+ return out;
969
+ }
970
+ // Ứng viên đầu bảng (giữ cho chỗ gọi cũ / test cũ).
971
+ function pickReelsTabUrl(anchors) {
972
+ return pickReelsTabUrls(anchors)[0] || null;
929
973
  }
930
974
 
931
975
  async function hasVisibleReelComposer(page) {
@@ -960,7 +1004,7 @@ async function runOnce({ page, payload, log }) {
960
1004
  } = payload || {};
961
1005
  if (!video_url) throw new Error('No video_url provided');
962
1006
 
963
- log('info', '[fb-pw] selectors version=2026.09.13-reels-tab-fallback');
1007
+ log('info', '[fb-pw] selectors version=2026.09.14-reels-tab-candidates');
964
1008
 
965
1009
  page.on('dialog', (d) => { d.accept().catch(() => {}); });
966
1010
 
@@ -1070,14 +1114,16 @@ async function runOnce({ page, payload, log }) {
1070
1114
  let reelIdsBefore = null;
1071
1115
  // GIỮ LẠI URL tab Reels cho sau khi đăng — xem pickPostPublishReelsTabUrl.
1072
1116
  let reelsTabUrlBefore = null;
1117
+ let reelsTabVerifiedBefore = false;
1073
1118
  try {
1074
- const tabUrl = await discoverReelsTabUrl(page, log);
1075
- if (tabUrl) {
1076
- reelsTabUrlBefore = tabUrl;
1077
- await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
1078
- await pause(page, 3500);
1079
- reelIdsBefore = await scrapeReelTileIds(page);
1080
- log('info', `[fb-pw] ảnh nền tab Reels trước khi đăng: ${reelIdsBefore.length} reel`);
1119
+ const candidates = await discoverReelsTabUrls(page, log);
1120
+ if (candidates.length) {
1121
+ const opened = await openReelsTabWithTiles(page, candidates, { log, settleMs: 3500, wait: pause });
1122
+ reelsTabUrlBefore = opened.url;
1123
+ reelsTabVerifiedBefore = opened.verified;
1124
+ reelIdsBefore = opened.ids;
1125
+ log('info', `[fb-pw] ảnh nền tab Reels trước khi đăng: ${reelIdsBefore.length} reel`
1126
+ + (candidates.length > 1 ? ` (ứng viên ${opened.tried}/${candidates.length})` : ''));
1081
1127
  await navigateFacebookHome(page, log);
1082
1128
  await pause(page, 2500);
1083
1129
  } else {
@@ -3050,10 +3096,13 @@ async function runOnce({ page, payload, log }) {
3050
3096
  const picked = pickPostPublishReelsTabUrl({
3051
3097
  discovered: await discoverReelsTabUrl(page),
3052
3098
  remembered: reelsTabUrlBefore,
3099
+ rememberedVerified: reelsTabVerifiedBefore,
3053
3100
  });
3054
3101
  const reelsTabHref = picked.url;
3055
3102
  if (picked.via === 'snapshot') {
3056
3103
  log('info', `[fb-pw] trang hiện tại không có link tab Reels (${String(page.url() || '').slice(0, 70)}) — dùng URL đã biết từ lúc chụp ảnh nền`);
3104
+ } else if (picked.via === 'snapshot-verified') {
3105
+ log('info', '[fb-pw] dùng URL tab Reels ĐÃ chứng minh có tile lúc chụp ảnh nền (dạng URL kia của cùng page có thể ra 0 tile)');
3057
3106
  }
3058
3107
  if (reelsTabHref) {
3059
3108
  reelsTabUrl = reelsTabHref;
@@ -3381,6 +3430,9 @@ module.exports.__testables = {
3381
3430
  diffNewReelIds,
3382
3431
  pickPostPublishReelsTabUrl,
3383
3432
  pickReelsTabUrl,
3433
+ pickReelsTabUrls,
3434
+ discoverReelsTabUrls,
3435
+ openReelsTabWithTiles,
3384
3436
  networkIdVerdict,
3385
3437
  classifyComposerText,
3386
3438
  readPageAlerts,