channel-worker 2.5.96 → 2.5.97

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.96",
3
+ "version": "2.5.97",
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": {
@@ -694,33 +694,24 @@ async function confirmPublishDialog({ firstHit, tagConfirmButton, clickTagged, c
694
694
 
695
695
  // Link tab Reels của Page — dùng ở CẢ HAI đầu lượt đăng (chụp ảnh nền trước
696
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
- }
697
+ async function discoverReelsTabUrl(page, log = null) {
698
+ const anchors = await page.evaluate(() => {
699
+ const out = [];
700
+ for (const a of document.querySelectorAll('a[href]')) {
701
+ const href = a.getAttribute('href') || '';
702
+ if (!href || href.startsWith('#') || href.startsWith('javascript:')) continue;
703
+ out.push({ href: href.slice(0, 200), aria: (a.getAttribute('aria-label') || '').slice(0, 80) });
704
+ if (out.length >= 400) break;
721
705
  }
722
- return null;
723
- }).catch(() => null);
706
+ return out;
707
+ }).catch(() => []);
708
+ const url = pickReelsTabUrl(anchors);
709
+ if (!url && log) {
710
+ // Chẩn đoán: in vài href "giống của Page" để lần sau thêm đúng chiến lược.
711
+ const sample = anchors.map((a) => a.href).filter((h) => /^\/(?:profile\.php|\d{8,}|[A-Za-z0-9._-]{3,}\/?(?:\?|$))/.test(h)).slice(0, 8);
712
+ 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)}`);
713
+ }
714
+ return url;
724
715
  }
725
716
 
726
717
  // Mọi reel ID đang hiện trên tab Reels, theo thứ tự DOM (mới nhất trước).
@@ -750,6 +741,44 @@ function diffNewReelIds(beforeIds, afterIds) {
750
741
  return (afterIds || []).filter((id) => !before.has(id));
751
742
  }
752
743
 
744
+ // Chữ ký của chính composer (mọi bước của wizard). Dùng để KHÔNG bao giờ bấm
745
+ // nhầm vào nút của composer khi đang định bấm nút của hộp ảnh bìa.
746
+ const COMPOSER_SIGNATURE_RE = /Cài đặt thước phim|Tạo thước phim|Chỉnh sửa thước phim|Reel settings|Create (?:a )?reel|Edit reel/i;
747
+ function isComposerDialogSignature(text) {
748
+ return COMPOSER_SIGNATURE_RE.test(String(text || ''));
749
+ }
750
+
751
+ // Chọn link tab Reels từ danh sách anchor của trang (thuần, test được).
752
+ // Chiến lược 1-3 giữ nguyên từ bản cũ; 4-5 thêm 09/09 vì trên TRANG CHỦ (lúc
753
+ // chụp ảnh nền trước khi đăng) không có anchor profile.php/Dòng thời gian —
754
+ // 2/2 lượt "không tìm được link tab Reels trước khi đăng". Trang chủ của Page
755
+ // có shortcut bên trái trỏ /<id số>/ hoặc /<handle>/, và đôi khi /<id>/reels/.
756
+ function pickReelsTabUrl(anchors) {
757
+ const list = (anchors || []).map((a) => ({ href: String(a.href || ''), aria: String(a.aria || '') }));
758
+ const abs = (h) => (h.startsWith('http') ? h : `https://www.facebook.com${h}`);
759
+ for (const a of list) if (/sk=reels_tab/.test(a.href)) return abs(a.href);
760
+ for (const a of list) {
761
+ const m = a.href.match(/\/profile\.php\?id=(\d+)/);
762
+ if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
763
+ }
764
+ for (const a of list) {
765
+ if (!/Dòng thời gian|Timeline/i.test(a.aria)) continue;
766
+ const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
767
+ if (m && !['profile.php', 'reel', 'video', 'videos', 'reels'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
768
+ }
769
+ // 4. /<id số>/reels/ hoặc /<id số>/ — shortcut của Page trên trang chủ.
770
+ for (const a of list) {
771
+ const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/(\d{10,20})(?:\/reels)?\/?(?:\?|$)/);
772
+ if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
773
+ }
774
+ // 5. /<handle>/reels/ — Page có tên riêng.
775
+ for (const a of list) {
776
+ const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/reels\/?(?:\?|$)/);
777
+ if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
778
+ }
779
+ return null;
780
+ }
781
+
753
782
  async function hasVisibleReelComposer(page) {
754
783
  return page.evaluate(() => {
755
784
  for (const dlg of document.querySelectorAll("[role='dialog']")) {
@@ -891,7 +920,7 @@ async function runOnce({ page, payload, log }) {
891
920
  // về, ~6-8s. Hỏng thì bỏ qua, không chặn lượt đăng.
892
921
  let reelIdsBefore = null;
893
922
  try {
894
- const tabUrl = await discoverReelsTabUrl(page);
923
+ const tabUrl = await discoverReelsTabUrl(page, log);
895
924
  if (tabUrl) {
896
925
  await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
897
926
  await pause(page, 3500);
@@ -1884,12 +1913,27 @@ async function runOnce({ page, payload, log }) {
1884
1913
  const head = dlg ? (dlg.innerText || '').slice(0, 40).replace(/\s+/g, ' ') : '';
1885
1914
  return `${aria} | ${head}`;
1886
1915
  }).catch(() => '?');
1916
+ // ĐÃ CÓ BẰNG CHỨNG (2.5.96, 09/09 23:30, 2/2 lượt): nút "Lưu" bấm
1917
+ // lại nằm trong "Cài đặt thước phim" = nút LƯU NHÁP của
1918
+ // composer, không phải của hộp ảnh bìa (hộp đã đóng, nhưng
1919
+ // isThumbnailEditorVisible vẫn thấy chữ "Chỉnh sửa hình thu
1920
+ // nhỏ" trên pill của composer nên tưởng còn mở). Bấm vào là
1921
+ // FB lưu nháp, đóng composer, về trang chủ → "composer đóng
1922
+ // giữa chừng" (3/5 lượt tối 09/09, kể cả file nén 42 MB).
1923
+ // Nút thuộc composer thì KHÔNG bấm; và vì hộp ảnh bìa không
1924
+ // còn nút Lưu nào nghĩa là nó đã đóng → coi như xong.
1925
+ if (isComposerDialogSignature(where)) {
1926
+ log('warn', `[fb-pw] re-click Lưu BỎ QUA: nút thuộc composer (${where.slice(0, 60)}) — hộp ảnh bìa đã đóng, không bấm lưu nháp`);
1927
+ modalClosed = true;
1928
+ break;
1929
+ }
1887
1930
  log('info', `[fb-pw] re-click Lưu → nút nằm trong: ${where}`);
1888
1931
  try {
1889
1932
  await btn.click({ timeout: 2500 });
1890
1933
  break;
1891
1934
  } catch {}
1892
1935
  }
1936
+ if (modalClosed) break;
1893
1937
  }
1894
1938
  await pause(page, 1000);
1895
1939
  }
@@ -3107,6 +3151,8 @@ module.exports.__testables = {
3107
3151
  resilientClick,
3108
3152
  confirmPublishDialog,
3109
3153
  diffNewReelIds,
3154
+ pickReelsTabUrl,
3155
+ isComposerDialogSignature,
3110
3156
  discoverReelsTabUrl,
3111
3157
  scrapeReelTileIds,
3112
3158
  muteClickInterceptors,