channel-worker 2.5.15 → 2.5.16

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.15",
3
+ "version": "2.5.16",
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": {
@@ -228,6 +228,54 @@ async function setVideoFile(page, inputHandle, filePath, log, tag = 'fb-pw') {
228
228
  await cdpSetInputFiles(page, inputHandle, filePath, log, tag);
229
229
  }
230
230
 
231
+ // On the final "Cài đặt thước phim" (Reel settings) step the only action is
232
+ // "Đăng", which FB renders DISABLED (aria-disabled="true") until it finishes
233
+ // ingesting the uploaded video. For large (>50MB) reup clips that ingest runs
234
+ // well past the 30s upload-wait, so findByVerbs (which skips disabled buttons)
235
+ // finds no publish CTA — and with no "Tiếp" on the last step the loop would
236
+ // throw no-advance. Poll until a bottom-half publish-verb button is present
237
+ // AND enabled. Returns true once enabled; false on timeout, or fast-false if no
238
+ // publish button ever appears (we're not actually on the final step).
239
+ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw') {
240
+ const deadline = Date.now() + timeoutMs;
241
+ let announced = false, sawPresent = false, absent = 0;
242
+ while (Date.now() < deadline) {
243
+ const st = await page.evaluate((vbs) => {
244
+ const dlgs = document.querySelectorAll("[role='dialog']");
245
+ const roots = dlgs.length ? Array.from(dlgs) : [document];
246
+ const vh = window.innerHeight;
247
+ let present = false, enabled = false;
248
+ for (const root of roots) {
249
+ for (const el of root.querySelectorAll("button, [role='button']")) {
250
+ const t = (el.innerText || el.textContent || '').trim();
251
+ if (!vbs.includes(t)) continue;
252
+ const r = el.getBoundingClientRect();
253
+ if (r.width < 8 || r.height < 8) continue;
254
+ if (r.y < vh * 0.4) continue; // bottom-half action button only
255
+ const cs = getComputedStyle(el);
256
+ if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') continue;
257
+ present = true;
258
+ if (!(el.getAttribute('aria-disabled') === 'true' || el.disabled)) enabled = true;
259
+ }
260
+ }
261
+ return { present, enabled };
262
+ }, verbs).catch(() => ({ present: false, enabled: false }));
263
+ if (st.enabled) {
264
+ if (announced) log('info', `[${tag}] "Đăng" is now enabled — video finished processing`);
265
+ return true;
266
+ }
267
+ if (st.present) {
268
+ sawPresent = true;
269
+ if (!announced) { log('info', `[${tag}] final step: "Đăng" disabled (video still processing) — waiting up to ${Math.round(timeoutMs / 1000)}s…`); announced = true; }
270
+ } else if (!sawPresent && ++absent >= 4) {
271
+ return false; // no publish CTA after ~10s → not the final step
272
+ }
273
+ await page.waitForTimeout(2500);
274
+ }
275
+ log('warn', `[${tag}] "Đăng" never became enabled within ${Math.round(timeoutMs / 1000)}s`);
276
+ return false;
277
+ }
278
+
231
279
  async function run({ page, payload, log }) {
232
280
  const {
233
281
  video_url, title, description = '', tags = [],
@@ -235,7 +283,7 @@ async function run({ page, payload, log }) {
235
283
  } = payload || {};
236
284
  if (!video_url) throw new Error('No video_url provided');
237
285
 
238
- log('info', '[fb-pw] selectors version=2026.06.14a-bigfile-cdp');
286
+ log('info', '[fb-pw] selectors version=2026.06.14b-pub-wait');
239
287
 
240
288
  page.on('dialog', (d) => { d.accept().catch(() => {}); });
241
289
 
@@ -858,7 +906,8 @@ async function run({ page, payload, log }) {
858
906
  // iterations, throw with diagnostics.
859
907
  let published = false;
860
908
  let customThumbDone = false;
861
- for (let step = 0; step < 6 && !published; step++) {
909
+ let pubWaitDone = false; // guard: wait for a disabled "Đăng" at most once
910
+ for (let step = 0; step < 7 && !published; step++) {
862
911
  await page.waitForTimeout(3000);
863
912
  await fillMetadata();
864
913
 
@@ -1490,6 +1539,16 @@ async function run({ page, payload, log }) {
1490
1539
  // No publish yet — advance via Tiếp.
1491
1540
  const next = await findByVerbs(nextVerbs);
1492
1541
  if (!next) {
1542
+ // Final "Cài đặt thước phim" step: the only action is "Đăng", which FB
1543
+ // keeps DISABLED while it ingests the video. For >50MB reup clips that
1544
+ // runs past the upload-wait, so the publish branch above found nothing
1545
+ // and there's no "Tiếp". Wait (once) for "Đăng" to enable, then let the
1546
+ // publish branch click it on the next iteration.
1547
+ if (!pubWaitDone) {
1548
+ pubWaitDone = true;
1549
+ log('info', `[fb-pw] no "Tiếp" + no enabled publish at step ${step + 1} — waiting for "Đăng" to enable (large-video processing)…`);
1550
+ if (await waitForPublishEnabled(page, publishVerbs, log, 180_000)) { step--; continue; }
1551
+ }
1493
1552
  await dumpInventory(page, log, `no-advance-${step + 1}`);
1494
1553
  await dumpFailure(page, `no-advance-${step + 1}`, log);
1495
1554
  throw new Error(`FB composer step ${step + 1}: neither publish nor Tiếp button found`);