channel-worker 2.5.7 → 2.5.9

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.7",
3
+ "version": "2.5.9",
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": {
@@ -321,21 +321,49 @@ async function run({ page, payload, log }) {
321
321
  // 3) Dismiss any onboarding/tutorial dialog.
322
322
  await dismissBsOnboarding(page, log);
323
323
 
324
- // 3) Click "Thêm video" opens the native OS file picker. Catch the
325
- // filechooser event and inject the video file path. Multiple aria/text
326
- // candidates so we survive minor i18n drift.
327
- log('info', '[fb-pw] click "Thêm video" + filechooser…');
324
+ // 3) Click "Thêm video" / "Tải lên" INSIDE the Reels modal opens the
325
+ // native OS file picker. Catch the filechooser event and inject the
326
+ // video file path.
327
+ //
328
+ // CRITICAL: scope every candidate to the Reels modal ([role='dialog']
329
+ // titled "Tạo thước phim"). The page-wall background still has a
330
+ // "Thêm video" / "Ảnh-video" button in the "Tạo bài viết" widget —
331
+ // without scoping, the locator can match that instead, which opens
332
+ // the regular "Tạo bài viết" composer ON TOP of the Reels modal and
333
+ // the rest of the script lands in the wrong composer.
334
+ log('info', '[fb-pw] click "Thêm video" / "Tải lên" inside Reels modal + filechooser…');
335
+ // Locate the Reels modal first. Two title variants (vi + en).
336
+ const reelsDialog = page
337
+ .locator("[role='dialog']")
338
+ .filter({ has: page.locator(":scope :text-matches('^Tạo thước phim$|^Create reel$|^Create a reel$', 'i')") })
339
+ .first();
340
+ if (!(await reelsDialog.isVisible({ timeout: 6000 }).catch(() => false))) {
341
+ await dumpInventory(page, log, 'no-reels-modal');
342
+ await dumpFailure(page, 'no-reels-modal', log);
343
+ throw new Error('FB Reels modal "Tạo thước phim" did not open after clicking Thước phim entry');
344
+ }
345
+ // Candidates inside the modal only:
346
+ // - "Tải lên" — the explicit blue upload button at the bottom (preferred)
347
+ // - "Thêm video" — the drop-zone label (clickable on some variants)
348
+ // - English equivalents
328
349
  const addVideoCandidates = [
329
- "div[role='button']:has-text('Thêm video')",
330
- "div[role='button']:has-text('Add video')",
331
- "[aria-label='Thêm video']",
332
- "[aria-label='Add video']",
350
+ reelsDialog.locator("div[role='button']:has-text('Tải lên')"),
351
+ reelsDialog.locator("button:has-text('Tải lên')"),
352
+ reelsDialog.locator("[aria-label='Tải lên']"),
353
+ reelsDialog.locator("div[role='button']:has-text('Upload')"),
354
+ reelsDialog.locator("[aria-label='Upload']"),
355
+ reelsDialog.locator("div[role='button']:has-text('Thêm video')"),
356
+ reelsDialog.locator("div[role='button']:has-text('Add video')"),
357
+ reelsDialog.locator("[aria-label='Thêm video']"),
358
+ reelsDialog.locator("[aria-label='Add video']"),
333
359
  ];
334
360
  let videoSet = false;
335
- for (const sel of addVideoCandidates) {
361
+ for (const loc of addVideoCandidates) {
336
362
  if (videoSet) break;
337
- const btn = await firstVisible(page.locator(sel), 3);
338
- if (!btn) continue;
363
+ const count = await loc.count().catch(() => 0);
364
+ if (count === 0) continue;
365
+ const btn = loc.first();
366
+ if (!(await btn.isVisible().catch(() => false))) continue;
339
367
  try {
340
368
  const [chooser] = await Promise.all([
341
369
  page.waitForEvent('filechooser', { timeout: 8000 }),
@@ -343,27 +371,27 @@ async function run({ page, payload, log }) {
343
371
  ]);
344
372
  await chooser.setFiles(videoPath);
345
373
  videoSet = true;
346
- log('info', `[fb-pw] video file set via "${sel}"`);
374
+ log('info', `[fb-pw] video file set via modal-scoped "${(await btn.innerText().catch(() => '')).slice(0, 30)}" button`);
347
375
  } catch (e) {
348
- log('info', `[fb-pw] "Thêm video" via "${sel}" did not open picker: ${e.message.slice(0, 80)}`);
376
+ log('info', `[fb-pw] modal upload candidate did not open picker: ${e.message.slice(0, 80)}`);
349
377
  }
350
378
  }
351
379
  if (!videoSet) {
352
- // Fallback — some BS variants expose the hidden input directly. Try
353
- // setInputFiles on input[type='file'] anywhere on the page that accepts video.
354
- const fi = page.locator("input[type='file']").last();
380
+ // Fallback — find an input[type='file'] that lives INSIDE the Reels
381
+ // modal (not the page-wall composer's hidden input).
382
+ const fi = reelsDialog.locator("input[type='file']").last();
355
383
  if (await fi.count().catch(() => 0) > 0) {
356
384
  try {
357
385
  await fi.setInputFiles(videoPath);
358
386
  videoSet = true;
359
- log('info', `[fb-pw] video file set via fallback input[type='file']`);
360
- } catch (e) { log('info', `[fb-pw] fallback input setInputFiles failed: ${e.message.slice(0, 80)}`); }
387
+ log('info', '[fb-pw] video file set via modal-scoped fallback input[type=file]');
388
+ } catch (e) { log('info', `[fb-pw] modal fallback input setInputFiles failed: ${e.message.slice(0, 80)}`); }
361
389
  }
362
390
  }
363
391
  if (!videoSet) {
364
392
  await dumpInventory(page, log, 'no-add-video');
365
393
  await dumpFailure(page, 'no-add-video', log);
366
- throw new Error('FB "Thêm video" trigger not found — could not start video upload');
394
+ throw new Error('FB Reels modal: "Thêm video"/"Tải lên" trigger not found — could not start video upload');
367
395
  }
368
396
 
369
397
  // 4) Wait for the video to finish processing on FB's side before we can
@@ -1271,6 +1299,17 @@ async function run({ page, payload, log }) {
1271
1299
  throw new Error(`FB publish: thumbnail_url provided but the "Chỉnh sửa hình thu nhỏ" overlay was never detected before publish (step ${step + 1}). Refusing to ship without custom thumb. Inspect dump screenshots in Temp\\cm-worker-pw\\.`);
1272
1300
  }
1273
1301
  log('info', `[fb-pw] click publish "${pub.verb}" via "${pub.sel}" (step ${step + 1})`);
1302
+ // DRY-RUN MODE (temporary, for QA): skip the actual publish click so
1303
+ // we can verify the upstream flow (modal scoping, video upload, thumb
1304
+ // step, metadata fill) without spawning real Reels every iteration.
1305
+ // Throws a recognisable error so the daemon marks the cmd failed
1306
+ // without retrying. REMOVE this block once the page-wall composer
1307
+ // fix is verified end-to-end.
1308
+ if (process.env.FB_PW_DRY_RUN === '1' || payload._dry_run === true) {
1309
+ await dumpInventory(page, log, `dry-run-at-publish-${step + 1}`);
1310
+ await dumpFailure(page, `dry-run-at-publish-${step + 1}`, log);
1311
+ throw new Error(`[fb-pw] DRY-RUN: reached publish step ${step + 1} successfully. Pub button found via "${pub.sel}" verb="${pub.verb}". Thumb=${customThumbDone ? 'uploaded' : 'NOT uploaded'}. NOT clicking. Inspect dump screenshots in Temp\\cm-worker-pw\\.`);
1312
+ }
1274
1313
  // Snapshot the captured-IDs list RIGHT BEFORE the publish click. The
1275
1314
  // network listener captures from EVERY graph response, including the
1276
1315
  // pre-publish page wall feed (~100+ IDs). Only IDs captured AFTER