channel-worker 2.5.95 → 2.5.96

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.95",
3
+ "version": "2.5.96",
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": {
@@ -692,6 +692,64 @@ async function confirmPublishDialog({ firstHit, tagConfirmButton, clickTagged, c
692
692
  return { confirmed: !pending };
693
693
  }
694
694
 
695
+ // Link tab Reels của Page — dùng ở CẢ HAI đầu lượt đăng (chụp ảnh nền trước
696
+ // khi đăng, quét lại sau khi đăng). Ba chiến lược, giữ nguyên từ bản cũ.
697
+ async function discoverReelsTabUrl(page) {
698
+ return page.evaluate(() => {
699
+ const direct = document.querySelectorAll("a[href*='sk=reels_tab']");
700
+ for (const a of direct) {
701
+ const h = a.getAttribute('href') || '';
702
+ if (h) return h.startsWith('http') ? h : `https://www.facebook.com${h}`;
703
+ }
704
+ const profAnchors = document.querySelectorAll("a[href*='/profile.php?id=']");
705
+ for (const a of profAnchors) {
706
+ const h = a.getAttribute('href') || '';
707
+ const m = h.match(/\/profile\.php\?id=(\d+)/);
708
+ if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
709
+ }
710
+ const tlAnchors = document.querySelectorAll("a[aria-label*='Dòng thời gian'], a[aria-label*='Timeline']");
711
+ for (const a of tlAnchors) {
712
+ const h = a.getAttribute('href') || '';
713
+ const m = h.match(/^\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
714
+ if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) {
715
+ return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
716
+ }
717
+ if (h.match(/\/profile\.php\?id=(\d+)/)) {
718
+ const pid = h.match(/\/profile\.php\?id=(\d+)/)[1];
719
+ return `https://www.facebook.com/profile.php?id=${pid}&sk=reels_tab`;
720
+ }
721
+ }
722
+ return null;
723
+ }).catch(() => null);
724
+ }
725
+
726
+ // Mọi reel ID đang hiện trên tab Reels, theo thứ tự DOM (mới nhất trước).
727
+ async function scrapeReelTileIds(page) {
728
+ return page.evaluate(() => {
729
+ const out = [];
730
+ for (const a of document.querySelectorAll("a[href*='/reel/']")) {
731
+ const m = (a.getAttribute('href') || '').match(/\/reel\/(\d{8,20})/);
732
+ if (!m) continue;
733
+ const r = a.getBoundingClientRect();
734
+ if (r.width < 8 || r.height < 8) continue;
735
+ if (!out.includes(m[1])) out.push(m[1]);
736
+ }
737
+ return out;
738
+ }).catch(() => []);
739
+ }
740
+
741
+ // SO TRƯỚC/SAU — nguồn lấy link thứ hai, không phụ thuộc gói mạng.
742
+ // Đo 09/09 sau khi gỡ nhánh "bốc tile đầu": bắt gói mạng trượt 2/3 lượt bấm
743
+ // Đăng thành công → 2 bài thành "mập mờ, chờ người kiểm" dù đã lên thật.
744
+ // Ảnh nền = danh sách reel ID của tab Reels chụp TRƯỚC khi mở composer; sau
745
+ // khi đăng quét lại, ID nào chưa có trong ảnh nền là reel mới. Đúng MỘT ID mới
746
+ // thì mới nhận; 0 hoặc >1 thì trả rỗng (thà rỗng còn hơn sai — luật 19/08).
747
+ // Thuần, test được.
748
+ function diffNewReelIds(beforeIds, afterIds) {
749
+ const before = new Set(beforeIds || []);
750
+ return (afterIds || []).filter((id) => !before.has(id));
751
+ }
752
+
695
753
  async function hasVisibleReelComposer(page) {
696
754
  return page.evaluate(() => {
697
755
  for (const dlg of document.querySelectorAll("[role='dialog']")) {
@@ -828,6 +886,26 @@ async function runOnce({ page, payload, log }) {
828
886
  log('info', `[fb-pw] browsed the wall for a bit (${bursts} scrolls) before opening the composer`);
829
887
  }
830
888
 
889
+ // 1b) ẢNH NỀN tab Reels — chụp trước khi mở composer để sau khi đăng so
890
+ // trước/sau mà tìm reel mới (xem diffNewReelIds). Một lần goto + quay
891
+ // về, ~6-8s. Hỏng thì bỏ qua, không chặn lượt đăng.
892
+ let reelIdsBefore = null;
893
+ try {
894
+ const tabUrl = await discoverReelsTabUrl(page);
895
+ if (tabUrl) {
896
+ await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
897
+ await pause(page, 3500);
898
+ reelIdsBefore = await scrapeReelTileIds(page);
899
+ log('info', `[fb-pw] ảnh nền tab Reels trước khi đăng: ${reelIdsBefore.length} reel`);
900
+ await navigateFacebookHome(page, log);
901
+ await pause(page, 2500);
902
+ } else {
903
+ log('warn', '[fb-pw] không tìm được link tab Reels trước khi đăng — bỏ qua ảnh nền');
904
+ }
905
+ } catch (e) {
906
+ log('warn', `[fb-pw] chụp ảnh nền tab Reels hỏng: ${String(e && e.message || e).slice(0, 80)} — bỏ qua`);
907
+ }
908
+
831
909
  // 2) Click the "Thước phim" entry inside the Page's "Tạo bài viết" widget.
832
910
  // This opens a FRESH Reel composer modal each time (no stale-draft
833
911
  // issue that plagued the BS composer). CRITICAL: scope to the "Tạo
@@ -1795,6 +1873,18 @@ async function runOnce({ page, payload, log }) {
1795
1873
  for (const sel of saveCandidates) {
1796
1874
  const btn = await firstVisible(page.locator(sel), 2);
1797
1875
  if (!btn) continue;
1876
+ // CHẨN ĐOÁN 09/09: nghi cú bấm lại này trúng nút "Lưu" (lưu
1877
+ // nháp) của chính composer chứ không phải của hộp ảnh bìa →
1878
+ // FB lưu nháp, đóng composer, về trang chủ = "composer đóng
1879
+ // giữa chừng". Ghi rõ nút nằm trong dialog nào để có bằng
1880
+ // chứng thay vì đoán (đo được từ log, không cần ảnh).
1881
+ const where = await btn.evaluate((el) => {
1882
+ const dlg = el.closest("[role='dialog']");
1883
+ const aria = dlg ? (dlg.getAttribute('aria-label') || '') : '(không trong dialog)';
1884
+ const head = dlg ? (dlg.innerText || '').slice(0, 40).replace(/\s+/g, ' ') : '';
1885
+ return `${aria} | ${head}`;
1886
+ }).catch(() => '?');
1887
+ log('info', `[fb-pw] re-click Lưu → nút nằm trong: ${where}`);
1798
1888
  try {
1799
1889
  await btn.click({ timeout: 2500 });
1800
1890
  break;
@@ -2717,39 +2807,7 @@ async function runOnce({ page, payload, log }) {
2717
2807
  // 2. Extract Page ID from "Dòng thời gian của X" timeline anchor or
2718
2808
  // any /profile.php?id=<id> anchor, then build the reels_tab URL
2719
2809
  // 3. Extract Page slug from any /<handle>/ anchor
2720
- const reelsTabHref = await page.evaluate(() => {
2721
- // Strategy 1: direct reels_tab link.
2722
- const direct = document.querySelectorAll("a[href*='sk=reels_tab']");
2723
- for (const a of direct) {
2724
- const h = a.getAttribute('href') || '';
2725
- if (h) return h.startsWith('http') ? h : `https://www.facebook.com${h}`;
2726
- }
2727
- // Strategy 2: extract Page ID from a /profile.php?id=<id> anchor.
2728
- // The "Dòng thời gian của X" anchor and the page-shortcut anchor
2729
- // both have hrefs like /profile.php?id=61590303065684.
2730
- const profAnchors = document.querySelectorAll("a[href*='/profile.php?id=']");
2731
- for (const a of profAnchors) {
2732
- const h = a.getAttribute('href') || '';
2733
- const m = h.match(/\/profile\.php\?id=(\d+)/);
2734
- if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
2735
- }
2736
- // Strategy 3: page handle via "Dòng thời gian của X" anchor (no
2737
- // profile.php — uses /<handle> instead).
2738
- const tlAnchors = document.querySelectorAll("a[aria-label*='Dòng thời gian'], a[aria-label*='Timeline']");
2739
- for (const a of tlAnchors) {
2740
- const h = a.getAttribute('href') || '';
2741
- // /<handle> or /<handle>/?...
2742
- const m = h.match(/^\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
2743
- if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) {
2744
- return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
2745
- }
2746
- if (h.match(/\/profile\.php\?id=(\d+)/)) {
2747
- const pid = h.match(/\/profile\.php\?id=(\d+)/)[1];
2748
- return `https://www.facebook.com/profile.php?id=${pid}&sk=reels_tab`;
2749
- }
2750
- }
2751
- return null;
2752
- }).catch(() => null);
2810
+ const reelsTabHref = await discoverReelsTabUrl(page);
2753
2811
  if (reelsTabHref) {
2754
2812
  reelsTabUrl = reelsTabHref;
2755
2813
  log('info', `[fb-pw] navigating to page reels tab: ${reelsTabHref.slice(0, 100)}`);
@@ -2766,6 +2824,17 @@ async function runOnce({ page, payload, log }) {
2766
2824
  } else {
2767
2825
  log('warn', '[fb-pw] reels_tab navigated but no tile with fresh timestamp found');
2768
2826
  }
2827
+ // (a.1b) SO TRƯỚC/SAU với ảnh nền chụp trước khi mở composer.
2828
+ if (!postUrl && reelIdsBefore) {
2829
+ const after = await scrapeReelTileIds(page);
2830
+ const fresh2 = diffNewReelIds(reelIdsBefore, after);
2831
+ if (fresh2.length === 1) {
2832
+ postUrl = `https://www.facebook.com/reel/${fresh2[0]}/`;
2833
+ log('info', `[fb-pw] post URL từ so trước/sau tab Reels (trước ${reelIdsBefore.length}, sau ${after.length}, mới 1): ${postUrl}`);
2834
+ } else {
2835
+ log('info', `[fb-pw] so trước/sau tab Reels: trước ${reelIdsBefore.length}, sau ${after.length}, mới ${fresh2.length} — ${fresh2.length ? 'nhiều hơn 1, không nhận' : 'chưa thấy reel mới'}`);
2836
+ }
2837
+ }
2769
2838
  } else {
2770
2839
  log('warn', '[fb-pw] no reels_tab link found on page; skipping reels-tab strategy');
2771
2840
  }
@@ -2895,6 +2964,15 @@ async function runOnce({ page, payload, log }) {
2895
2964
  if (fresh2) {
2896
2965
  postUrl = fresh2.href.startsWith('http') ? fresh2.href : `https://www.facebook.com${fresh2.href}`;
2897
2966
  log('info', `[fb-pw] post URL from reels_tab RETRY (id=${fresh2.id}, timestamp="${fresh2.timestamp}"): ${postUrl}`);
2967
+ } else if (reelIdsBefore) {
2968
+ const after2 = await scrapeReelTileIds(page);
2969
+ const diff2 = diffNewReelIds(reelIdsBefore, after2);
2970
+ if (diff2.length === 1) {
2971
+ postUrl = `https://www.facebook.com/reel/${diff2[0]}/`;
2972
+ log('info', `[fb-pw] post URL từ so trước/sau tab Reels (lượt chờ 75s; trước ${reelIdsBefore.length}, sau ${after2.length}): ${postUrl}`);
2973
+ } else {
2974
+ log('warn', `[fb-pw] reels_tab retry: vẫn chưa thấy reel mới (so trước/sau: mới ${diff2.length})`);
2975
+ }
2898
2976
  } else {
2899
2977
  log('warn', '[fb-pw] reels_tab retry: still no fresh tile');
2900
2978
  }
@@ -3028,6 +3106,9 @@ module.exports = { run };
3028
3106
  module.exports.__testables = {
3029
3107
  resilientClick,
3030
3108
  confirmPublishDialog,
3109
+ diffNewReelIds,
3110
+ discoverReelsTabUrl,
3111
+ scrapeReelTileIds,
3031
3112
  muteClickInterceptors,
3032
3113
  unmuteClickInterceptors,
3033
3114
  waitForPublishEnabled,