channel-worker 2.5.57 → 2.5.59

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.57",
3
+ "version": "2.5.59",
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": {
@@ -127,8 +127,10 @@ async function humanWheel(page, dy) {
127
127
  // space / punctuation, a rare typo fixed with Backspace. Uses keyboard.type
128
128
  // for the character itself so IME/Unicode (Vietnamese) goes through unchanged.
129
129
  async function humanType(page, text, { typo = 0.025, base = 95 } = {}) {
130
- for (let i = 0; i < text.length; i++) {
131
- const ch = text[i];
130
+ // Duyệt theo CODE POINT, không theo đơn vị UTF-16: emoji ngoài BMP (👶 = cặp
131
+ // surrogate D83D DC76) mà gõ từng nửa là FB nhận 2 ký tự lỗi "��" (founder
132
+ // bắt được 2026-08-23 trên caption Reels); ✨ (1 đơn vị) thì không sao.
133
+ for (const ch of Array.from(String(text || ''))) {
132
134
  if (typo > 0 && chance(typo) && /[a-z]/i.test(ch)) {
133
135
  const wrong = String.fromCharCode(ch.charCodeAt(0) + (chance(0.5) ? 1 : -1));
134
136
  await page.keyboard.type(wrong);
@@ -414,6 +414,31 @@ function safeComposerRetryError(message) {
414
414
  return err;
415
415
  }
416
416
 
417
+ // Toast "Thông báo mới" (ai đó thích/bình luận reel) nổi lên góc trái và ĂN
418
+ // CÚ CLICK: 23/08 hai lần liên tiếp bot bấm "Tiếp" thì FB nhảy sang xem reel
419
+ // cũ, composer đóng → "final description field not found". Đóng toast trước
420
+ // mỗi bước bấm; không có toast thì trả 0, rất rẻ.
421
+ async function dismissToasts(page) {
422
+ return page.evaluate(() => {
423
+ let closed = 0;
424
+ const isToast = (el) => {
425
+ const t = (el.innerText || '').slice(0, 120);
426
+ return /Thông báo mới|New notification|vừa bình luận|thích thước phim|liked your/i.test(t);
427
+ };
428
+ const roots = [...document.querySelectorAll("[role='dialog'], [role='alert'], [role='status'], div[data-visualcompletion='ignore-dynamic']")];
429
+ for (const r of roots) {
430
+ const rect = r.getBoundingClientRect();
431
+ if (rect.width < 40 || rect.height < 30) continue;
432
+ if (!isToast(r)) continue;
433
+ // Nút đóng: aria-label Đóng/Close, hoặc <div role=button> chứa dấu X.
434
+ const btn = r.querySelector("[aria-label='Đóng'], [aria-label='Close'], [aria-label*='óng thông báo']")
435
+ || [...r.querySelectorAll("[role='button'], button")].find((b) => (b.getAttribute('aria-label') || '').length < 12);
436
+ if (btn) { btn.click(); closed++; }
437
+ }
438
+ return closed;
439
+ }).catch(() => 0);
440
+ }
441
+
417
442
  async function hasVisibleReelComposer(page) {
418
443
  return page.evaluate(() => {
419
444
  for (const dlg of document.querySelectorAll("[role='dialog']")) {
@@ -926,7 +951,7 @@ async function runOnce({ page, payload, log }) {
926
951
  if (!f) continue;
927
952
  try {
928
953
  await f.click({ timeout: 3000 });
929
- const descText = (fbCaption || description || title || '').toString().slice(0, 2100);
954
+ const descText = Array.from((fbCaption || description || title || '').toString()).slice(0, 2100).join(''); // cắt theo code point, không chặt đôi emoji
930
955
  if (!descText) break;
931
956
  await humanType(page, descText, { typo: 0, base: descText.length > 600 ? 60 : 95 }); // long captions: faster cadence, still keystrokes
932
957
  // Blur commits the Lexical/React value and re-runs the form's
@@ -971,7 +996,7 @@ async function runOnce({ page, payload, log }) {
971
996
  if (probe) {
972
997
  try {
973
998
  const f = page.locator(probe);
974
- const descText = (fbCaption || description || title || '').toString().slice(0, 2100);
999
+ const descText = Array.from((fbCaption || description || title || '').toString()).slice(0, 2100).join(''); // cắt theo code point, không chặt đôi emoji
975
1000
  await f.click({ timeout: 3000 });
976
1001
  await humanType(page, descText, { typo: 0, base: descText.length > 600 ? 60 : 95 }); // long captions: faster cadence, still keystrokes
977
1002
  await page.keyboard.press('Tab').catch(() => {});
@@ -2043,6 +2068,9 @@ async function runOnce({ page, payload, log }) {
2043
2068
  }
2044
2069
 
2045
2070
  // No publish yet — advance via Tiếp.
2071
+ // Đóng toast thông báo TRƯỚC: nó nổi đúng vùng thao tác và cướp cú click.
2072
+ const _toasts = await dismissToasts(page);
2073
+ if (_toasts) log('info', `[fb-pw] đóng ${_toasts} toast thông báo trước khi bấm Tiếp`);
2046
2074
  const next = await findByVerbs(nextVerbs);
2047
2075
  if (!next) {
2048
2076
  // Final "Cài đặt thước phim" step: the only action is "Đăng", which FB
@@ -2062,6 +2090,12 @@ async function runOnce({ page, payload, log }) {
2062
2090
  if (waitResult.enabled) { step--; continue; }
2063
2091
  await dumpInventory(page, log, `no-advance-${step + 1}`);
2064
2092
  await dumpFailure(page, `no-advance-${step + 1}`, log);
2093
+ // Composer biến mất (URL nhảy sang /reel/… vì toast cướp click, hoặc
2094
+ // FB tự đóng) → CHẠY LẠI cả lượt upload thay vì báo lỗi khó hiểu.
2095
+ // An toàn vì chưa hề bấm Đăng ở nhánh này.
2096
+ if (!(await hasVisibleReelComposer(page))) {
2097
+ throw safeComposerRetryError(`FB composer đóng giữa chừng (đang ở ${page.url().slice(0, 80)}) — chạy lại lượt đăng`);
2098
+ }
2065
2099
  if (!fillState.description) {
2066
2100
  throw new Error(`FB final description field not found at step ${step + 1}`);
2067
2101
  }