channel-worker 2.5.94 → 2.5.95

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.
@@ -397,6 +397,16 @@ class CommandPoller {
397
397
 
398
398
  const keepOpen = await this._keepOpenCfg();
399
399
 
400
+ // Nhịp còn-sống SUỐT lúc kịch bản chạy (không chỉ lúc xếp hàng). Bộ dọn của
401
+ // API khai tử lệnh đăng 'running' quá 20 phút không cập nhật; một lượt
402
+ // upload FB thật có thể đứng chờ nút Đăng tới 600s cộng các bước trước đó
403
+ // là vượt 20 phút — ca Chuyện Nhà Nông 09/09 21:46 chạy 17 phút, updatedAt
404
+ // vẫn là 21:46. Chạm vào lệnh mỗi 2 phút là đủ, thưa hơn nhiều so với API.
405
+ const RUN_BEAT_MS = 2 * 60 * 1000;
406
+ const beat = setInterval(() => {
407
+ this.api.updateCommand(command._id, { status: 'running' }).catch(() => {});
408
+ }, RUN_BEAT_MS);
409
+
400
410
  try {
401
411
  const result = await runPlaywrightScript({
402
412
  nst: this.nst,
@@ -415,6 +425,7 @@ class CommandPoller {
415
425
  console.error(`[commands/pw] ${command.type} failed: ${err.message}`);
416
426
  await this.api.postCommandResult(command._id, { status: 'failed', error: String(err.message || err).slice(0, 500) });
417
427
  } finally {
428
+ clearInterval(beat);
418
429
  // Always release the per-profile mutex — even on throw — or sibling
419
430
  // pw cmds for the same profile would hang forever.
420
431
  if (this._pwInFlight) this._pwInFlight.delete(profileId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.94",
3
+ "version": "2.5.95",
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": {
@@ -510,7 +510,7 @@ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw',
510
510
  const dlgs = document.querySelectorAll("[role='dialog']");
511
511
  const roots = dlgs.length ? Array.from(dlgs) : [document];
512
512
  const vh = window.innerHeight;
513
- let present = false, enabled = false;
513
+ let present = false, enabled = false, rejected = '';
514
514
  for (const root of roots) {
515
515
  for (const el of root.querySelectorAll("button, [role='button']")) {
516
516
  const t = (el.innerText || el.textContent || '').trim();
@@ -523,9 +523,22 @@ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw',
523
523
  present = true;
524
524
  if (!(el.getAttribute('aria-disabled') === 'true' || el.disabled)) enabled = true;
525
525
  }
526
+ // FB TỪ CHỐI FILE: dòng đỏ ngay trên nút Lưu/Đăng, khung xem trước
527
+ // trống. Ảnh dump 09/09 21:49 (Góc Trại Gà, file 58 MB, lành, decode
528
+ // sạch): "Không thể tải file của bạn lên: fb-….mp4". Nút Đăng sẽ KHÔNG
529
+ // BAO GIỜ bật — chờ đủ 600s rồi mới báo "remained disabled" là đốt 10
530
+ // phút profile và dễ bị bộ dọn 20 phút khai tử. Đo 24h: 9 ca, 8 ca là
531
+ // file >50 MB (đi đường CDP setFileInputFiles).
532
+ const txt = (root.innerText || '').slice(0, 4000);
533
+ const m = /Không thể tải (?:file|tệp)[^\n]{0,120}|couldn['’]t upload your (?:file|video)[^\n]{0,80}|Unable to upload[^\n]{0,80}/i.exec(txt);
534
+ if (m) rejected = m[0].trim();
526
535
  }
527
- return { present, enabled };
528
- }, verbs).catch(() => ({ present: false, enabled: false }));
536
+ return { present, enabled, rejected };
537
+ }, verbs).catch(() => ({ present: false, enabled: false, rejected: '' }));
538
+ if (st.rejected) {
539
+ log('warn', `[${tag}] Facebook TỪ CHỐI file: "${st.rejected.slice(0, 120)}" — nút "Đăng" sẽ không bật, dừng chờ ngay`);
540
+ return { enabled: false, sawPresent: true, rejected: st.rejected };
541
+ }
529
542
  if (st.enabled) {
530
543
  if (announced) log('info', `[${tag}] "Đăng" is now enabled — video finished processing`);
531
544
  return { enabled: true, sawPresent: true };
@@ -534,6 +547,19 @@ async function waitForPublishEnabled(page, verbs, log, timeoutMs, tag = 'fb-pw',
534
547
  sawPresent = true;
535
548
  absentSince = 0;
536
549
  if (!announced) { log('info', `[${tag}] final step: "Đăng" disabled (video still processing) — waiting up to ${Math.round(timeoutMs / 1000)}s…`); announced = true; }
550
+ } else if (sawPresent) {
551
+ // ĐÃ THẤY nút rồi mà giờ biến mất = composer đã đóng (FB nhảy về trang
552
+ // chủ / toast cướp click / người mở profile bằng tay). Trước đây nhánh này
553
+ // không có: worker đứng đếm đủ 600s trên một hộp thoại không còn tồn tại
554
+ // rồi mới báo "composer vanished — retrying" (09/09 22:20, chủ tịch nhìn
555
+ // profile Chuyện Nhà Nông thấy đang ở trang chủ trong khi log vẫn ghi
556
+ // "waiting up to 600s"). Cho một khoảng ân hạn ngắn vì FB có lúc dựng lại
557
+ // dialog, rồi trả về để call site chạy lại lượt upload ngay.
558
+ if (!absentSince) absentSince = Date.now();
559
+ if (Date.now() - absentSince >= absentGraceMs) {
560
+ log('warn', `[${tag}] nút "Đăng" đã thấy rồi mà biến mất ${Math.round(absentGraceMs / 1000)}s — composer đã đóng, không chờ hết ${Math.round(timeoutMs / 1000)}s nữa`);
561
+ return { enabled: false, sawPresent: true, gone: true };
562
+ }
537
563
  } else if (!sawPresent) {
538
564
  if (!absentSince) absentSince = Date.now();
539
565
  // The final settings form mounts lazily after the thumbnail editor closes.
@@ -2454,6 +2480,11 @@ async function runOnce({ page, payload, log }) {
2454
2480
  if (waitResult.enabled) { step--; continue; }
2455
2481
  await dumpInventory(page, log, `no-advance-${step + 1}`);
2456
2482
  await dumpFailure(page, `no-advance-${step + 1}`, log);
2483
+ if (waitResult.rejected) {
2484
+ // Mã FB_PUBLISH_ để run() không đi dò tài khoản (không phải lỗi
2485
+ // đăng nhập), và PublishRetry phân loại đúng "file bị từ chối".
2486
+ throw new Error(`FB_PUBLISH_REJECTED: Facebook từ chối file video ("${waitResult.rejected.slice(0, 100)}") — thường gặp với file >50 MB đi đường CDP; nén/render lại nhỏ hơn rồi đăng`);
2487
+ }
2457
2488
  // Composer biến mất (URL nhảy sang /reel/… vì toast cướp click, hoặc
2458
2489
  // FB tự đóng) → CHẠY LẠI cả lượt upload thay vì báo lỗi khó hiểu.
2459
2490
  // An toàn vì chưa hề bấm Đăng ở nhánh này.