channel-worker 2.5.69 → 2.5.70

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.69",
3
+ "version": "2.5.70",
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": {
@@ -76,6 +76,22 @@ const REEL_ENTRY_SELECTORS = [
76
76
  // 20 minutes, so give the real upload enough room instead of failing while FB
77
77
  // is visibly still processing it.
78
78
  const PUBLISH_ENABLE_TIMEOUT_MS = 10 * 60 * 1000;
79
+ // Facebook can spend several seconds tearing down the nested custom-thumbnail
80
+ // editor before it mounts the final Reel settings CTA. During that transition
81
+ // there is neither a visible "Tiếp" nor "Đăng" button. The old four-poll
82
+ // (~10s) shortcut returned false just before the CTA appeared, producing a
83
+ // false no-advance failure even though the final blue "Đăng" button was ready.
84
+ const PUBLISH_CTA_ABSENT_GRACE_MS = 30 * 1000;
85
+
86
+ // Text observed in the two thumbnail-editor cohorts. The nested variant has no
87
+ // useful dialog aria-label, so checking only "Chỉnh sửa hình thu nhỏ" misses it
88
+ // while Facebook is processing the saved image.
89
+ const THUMBNAIL_EDITOR_TEXT_MARKERS = [
90
+ 'Chỉnh sửa hình thu nhỏ',
91
+ 'Edit thumbnail',
92
+ 'Tải hình thu nhỏ của riêng bạn lên',
93
+ 'Upload your own thumbnail',
94
+ ];
79
95
 
80
96
  async function firstVisible(locator, max = 5) {
81
97
  const n = Math.min(await locator.count().catch(() => 0), max);
@@ -474,11 +490,14 @@ async function setVideoFile(page, inputHandle, filePath, log, tag = 'fb-pw') {
474
490
  // well past the 30s upload-wait, so findByVerbs (which skips disabled buttons)
475
491
  // finds no publish CTA — and with no "Tiếp" on the last step the loop would
476
492
  // throw no-advance. Poll until a bottom-half publish-verb button is present
477
- // AND enabled. Returns true once enabled; false on timeout, or fast-false if no
478
- // publish button ever appears (we're not actually on the final step).
479
- async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw', { onPoll = null } = {}) {
493
+ // AND enabled. Returns true once enabled; false on timeout, or after a guarded
494
+ // absence window when no publish button ever appears (not the final step).
495
+ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw', {
496
+ onPoll = null,
497
+ absentGraceMs = PUBLISH_CTA_ABSENT_GRACE_MS,
498
+ } = {}) {
480
499
  const deadline = Date.now() + timeoutMs;
481
- let announced = false, sawPresent = false, absent = 0;
500
+ let announced = false, sawPresent = false, absentSince = 0;
482
501
  while (Date.now() < deadline) {
483
502
  // The final metadata form mounts lazily on some FB cohorts. The first
484
503
  // fillMetadata() immediately after Tiếp can run before its textbox exists,
@@ -513,9 +532,16 @@ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw',
513
532
  }
514
533
  if (st.present) {
515
534
  sawPresent = true;
535
+ absentSince = 0;
516
536
  if (!announced) { log('info', `[${tag}] final step: "Đăng" disabled (video still processing) — waiting up to ${Math.round(timeoutMs / 1000)}s…`); announced = true; }
517
- } else if (!sawPresent && ++absent >= 4) {
518
- return { enabled: false, sawPresent: false }; // no publish CTA after ~10s → not the final step
537
+ } else if (!sawPresent) {
538
+ if (!absentSince) absentSince = Date.now();
539
+ // The final settings form mounts lazily after the thumbnail editor closes.
540
+ // Give that transition a real time-based grace period instead of assuming
541
+ // four polls means this is not the final step.
542
+ if (Date.now() - absentSince >= absentGraceMs) {
543
+ return { enabled: false, sawPresent: false };
544
+ }
519
545
  }
520
546
  await pause(page, 2500);
521
547
  }
@@ -523,6 +549,31 @@ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw',
523
549
  return { enabled: false, sawPresent };
524
550
  }
525
551
 
552
+ async function isThumbnailEditorVisible(page) {
553
+ return page.evaluate((textMarkers) => {
554
+ const isVisible = (el) => {
555
+ const r = el.getBoundingClientRect();
556
+ if (r.width < 8 || r.height < 8) return false;
557
+ const cs = getComputedStyle(el);
558
+ return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
559
+ };
560
+ const markers = textMarkers.map((s) => s.toLocaleLowerCase());
561
+ for (const dlg of document.querySelectorAll("[role='dialog']")) {
562
+ if (!isVisible(dlg)) continue;
563
+ const txt = (dlg.innerText || '').toLocaleLowerCase();
564
+ if (markers.some((marker) => txt.includes(marker))) return true;
565
+ }
566
+ // Some cohorts render the editor as a nested form instead of a dialog.
567
+ // Its upload control is the most stable signal while the save is pending.
568
+ for (const el of document.querySelectorAll('[aria-label]')) {
569
+ if (!isVisible(el)) continue;
570
+ const al = (el.getAttribute('aria-label') || '').toLocaleLowerCase();
571
+ if ((/tải hình thu nhỏ|thumbnail/.test(al)) && (/tải|upload/.test(al))) return true;
572
+ }
573
+ return false;
574
+ }, THUMBNAIL_EDITOR_TEXT_MARKERS).catch(() => false);
575
+ }
576
+
526
577
  const SAFE_RETRY_COMPOSER_CLOSED = 'FB_SAFE_RETRY_COMPOSER_CLOSED';
527
578
 
528
579
  function safeComposerRetryError(message) {
@@ -580,7 +631,7 @@ async function runOnce({ page, payload, log }) {
580
631
  } = payload || {};
581
632
  if (!video_url) throw new Error('No video_url provided');
582
633
 
583
- log('info', '[fb-pw] selectors version=2026.08.28-icon-only-create-route-fallback');
634
+ log('info', '[fb-pw] selectors version=2026.09.01-thumb-final-cta-race');
584
635
 
585
636
  page.on('dialog', (d) => { d.accept().catch(() => {}); });
586
637
 
@@ -1602,6 +1653,7 @@ async function runOnce({ page, payload, log }) {
1602
1653
  // disabled while FB was still hashing the upload).
1603
1654
  const closeDeadline = Date.now() + 60_000;
1604
1655
  let modalClosed = false;
1656
+ let closedPolls = 0;
1605
1657
  let retryCount = 0;
1606
1658
  const RETRY_AFTER_MS = 12_000;
1607
1659
  let lastRetryAt = Date.now();
@@ -1611,27 +1663,16 @@ async function runOnce({ page, payload, log }) {
1611
1663
  // nested form (no own dialog — its upload button, aria
1612
1664
  // "Tải hình thu nhỏ tùy chỉnh lên…", only exists while the
1613
1665
  // editor is showing).
1614
- const still = await page.evaluate(() => {
1615
- const dlgs = document.querySelectorAll("[role='dialog']");
1616
- for (const dlg of dlgs) {
1617
- const txt = (dlg.innerText || '').slice(0, 200);
1618
- if (/Chỉnh sửa hình thu nhỏ|Edit thumbnail/i.test(txt)) {
1619
- const r = dlg.getBoundingClientRect();
1620
- if (r.width > 8 && r.height > 8) return true;
1621
- }
1622
- }
1623
- for (const el of document.querySelectorAll('[aria-label]')) {
1624
- const al = el.getAttribute('aria-label') || '';
1625
- if (!(/Tải hình thu nhỏ|thumbnail/i.test(al) && /Tải|upload/i.test(al))) continue;
1626
- const r = el.getBoundingClientRect();
1627
- if (r.width < 8 || r.height < 8) continue;
1628
- const cs = getComputedStyle(el);
1629
- if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') continue;
1630
- return true;
1631
- }
1632
- return false;
1633
- }).catch(() => false);
1634
- if (!still) { modalClosed = true; break; }
1666
+ const still = await isThumbnailEditorVisible(page);
1667
+ if (!still) {
1668
+ // Require two consecutive closed samples. Facebook briefly
1669
+ // unmounts the nested form while swapping it for a
1670
+ // processing overlay; treating that single gap as closed
1671
+ // races the final "Đăng" button mount.
1672
+ if (++closedPolls >= 2) { modalClosed = true; break; }
1673
+ } else {
1674
+ closedPolls = 0;
1675
+ }
1635
1676
  // Retry Lưu click after RETRY_AFTER_MS without progress —
1636
1677
  // limit to 3 retries (so we still leave time for FB to
1637
1678
  // finish processing on the last attempt).
@@ -2801,11 +2842,14 @@ module.exports.__testables = {
2801
2842
  muteClickInterceptors,
2802
2843
  unmuteClickInterceptors,
2803
2844
  waitForPublishEnabled,
2845
+ isThumbnailEditorVisible,
2804
2846
  hasVisibleReelComposer,
2805
2847
  SAFE_RETRY_COMPOSER_CLOSED,
2806
2848
  navigateFacebookHome,
2807
2849
  navigateFacebookReelCreate,
2808
2850
  PUBLISH_ENABLE_TIMEOUT_MS,
2851
+ PUBLISH_CTA_ABSENT_GRACE_MS,
2852
+ THUMBNAIL_EDITOR_TEXT_MARKERS,
2809
2853
  REEL_CREATE_SELECTORS,
2810
2854
  REEL_ENTRY_SELECTORS,
2811
2855
  ADD_VIDEO_SELECTORS,