channel-worker 2.5.35 → 2.5.37

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.35",
3
+ "version": "2.5.37",
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": {
@@ -221,45 +221,86 @@ async function run({ page, payload, log }) {
221
221
 
222
222
  // 6) Click "Đăng".
223
223
  log('info', '[fbphoto] clicking publish…');
224
- const clickPublish = async () => page.evaluate((vbs) => {
225
- const dlg = document.querySelector("[role='dialog']") || document;
226
- for (const v of vbs) {
227
- for (const b of dlg.querySelectorAll("div[role='button'], button, [role='button']")) {
228
- const t = (b.innerText || '').trim();
229
- const a = (b.getAttribute('aria-label') || '').trim();
230
- if ((t === v || a === v) && b.getAttribute('aria-disabled') !== 'true') {
231
- b.setAttribute('__fbphoto_pub__', '1');
232
- return true;
224
+ // Tag the first VISIBLE + ENABLED button matching `verbs`. Mirrors the
225
+ // proven upload_facebook.js findByVerbs: searches ALL [role='dialog']
226
+ // (not just the first — FB renders extra hidden dialogs), matches
227
+ // innerText||textContent (button label often lives in a nested span), then
228
+ // falls back to the whole document. Returns the matched verb, or null.
229
+ const tagCta = async (verbs) => page.evaluate((vbs) => {
230
+ const tryScope = (roots, verb) => {
231
+ for (const root of roots) {
232
+ let best = null;
233
+ for (const el of root.querySelectorAll("button, [role='button']")) {
234
+ const t = (el.innerText || el.textContent || '').trim();
235
+ const a = (el.getAttribute('aria-label') || '').trim();
236
+ if (t !== verb && a !== verb) continue;
237
+ const r = el.getBoundingClientRect();
238
+ if (r.width < 8 || r.height < 8) continue;
239
+ const cs = getComputedStyle(el);
240
+ if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') continue;
241
+ if (el.getAttribute('aria-disabled') === 'true' || el.disabled) continue;
242
+ best = el;
233
243
  }
244
+ if (best) return best;
234
245
  }
246
+ return null;
247
+ };
248
+ for (const v of vbs) {
249
+ const dlgs = Array.from(document.querySelectorAll("[role='dialog']"));
250
+ let el = tryScope(dlgs, v) || tryScope([document], v);
251
+ if (el) { el.setAttribute('__fbphoto_cta__', '1'); return v; }
235
252
  }
236
- return false;
237
- }, publishVerbs).catch(() => false);
253
+ return null;
254
+ }, verbs).catch(() => null);
255
+ const clickTaggedCta = async () => {
256
+ await page.locator("[__fbphoto_cta__='1']").click({ timeout: 5000 }).catch(() => {});
257
+ await page.evaluate(() => document.querySelectorAll('[__fbphoto_cta__]').forEach((e) => e.removeAttribute('__fbphoto_cta__'))).catch(() => {});
258
+ };
238
259
 
239
- // Wait for the publish button to enable (image still processing).
240
- let clicked = false;
241
- for (let i = 0; i < 30 && !clicked; i++) {
242
- if (await clickPublish()) {
243
- await page.locator("[__fbphoto_pub__='1']").click({ timeout: 5000 }).catch(() => {});
244
- await page.evaluate(() => document.querySelectorAll('[__fbphoto_pub__]').forEach((e) => e.removeAttribute('__fbphoto_pub__'))).catch(() => {});
245
- clicked = true;
246
- } else {
247
- await page.waitForTimeout(2000);
260
+ // Step loop: the FB photo composer is MULTI-STEP "Tiếp" advances through
261
+ // audience/settings screens; only the LAST step shows "Đăng"/"Chia sẻ ngay"
262
+ // (the failing run stopped here: it saw a "Tiếp", not a publish button).
263
+ // Prefer a publish button when present; else advance via "Tiếp". Retry a
264
+ // few times when neither is enabled yet (FB disables while ingesting).
265
+ const nextVerbs = ['Tiếp', 'Tiếp theo', 'Next', 'Continue'];
266
+ let published = false;
267
+ let stalls = 0;
268
+ for (let step = 0; step < 8 && !published; step++) {
269
+ const pubHit = await tagCta(publishVerbs);
270
+ if (pubHit) { log('info', `[fbphoto] publish via "${pubHit}" (step ${step + 1})`); await clickTaggedCta(); published = true; break; }
271
+ const nextHit = await tagCta(nextVerbs);
272
+ if (nextHit) { log('info', `[fbphoto] advance via "${nextHit}" (step ${step + 1})`); await clickTaggedCta(); await page.waitForTimeout(2800); stalls = 0; continue; }
273
+ // Neither publish nor Tiếp enabled — FB still ingesting the photo. Wait.
274
+ if (++stalls > 10) break; // ~30s of no CTA → give up
275
+ if (stalls === 1 || stalls === 6) {
276
+ // Diagnostic: what buttons ARE in the composer right now?
277
+ const btns = await page.evaluate(() => {
278
+ const out = [];
279
+ for (const dlg of document.querySelectorAll("[role='dialog']")) {
280
+ for (const el of dlg.querySelectorAll("button, [role='button']")) {
281
+ const t = (el.innerText || el.textContent || '').trim().slice(0, 30);
282
+ const r = el.getBoundingClientRect();
283
+ if (t && r.width > 20 && r.height > 8) out.push(`${t}${el.getAttribute('aria-disabled') === 'true' ? '(disabled)' : ''}`);
284
+ }
285
+ }
286
+ return [...new Set(out)].slice(0, 20);
287
+ }).catch(() => []);
288
+ log('info', `[fbphoto] composer buttons: ${JSON.stringify(btns)}`);
248
289
  }
290
+ log('info', `[fbphoto] no CTA yet at step ${step + 1} — waiting…`);
291
+ step--; // don't count a pure wait against the step budget
292
+ await page.waitForTimeout(3000);
249
293
  }
250
- if (!clicked) {
294
+ if (!published) {
251
295
  await dumpFailure(page, 'no-publish-btn', log);
252
- throw new Error('FB: nút "Đăng" không khả dụng (ảnh chưa xử lý xong / không tìm thấy).');
296
+ throw new Error('FB: không tìm thấy nút "Đăng" sau các bước "Tiếp" (composer đổi layout?).');
253
297
  }
254
298
 
255
- // 7) Verify commit + retry — a click can register without FB posting.
299
+ // Verify commit + retry — a click can register without FB posting.
256
300
  await page.waitForTimeout(6000);
257
301
  for (let commitTry = 0; commitTry < 3 && (await composerStillOpen(page, publishVerbs)); commitTry++) {
258
- log('warn', `[fbphoto] composer still open — re-clicking "Đăng" (retry ${commitTry + 1}/3)`);
259
- if (await clickPublish()) {
260
- await page.locator("[__fbphoto_pub__='1']").click({ timeout: 5000 }).catch(() => {});
261
- await page.evaluate(() => document.querySelectorAll('[__fbphoto_pub__]').forEach((e) => e.removeAttribute('__fbphoto_pub__'))).catch(() => {});
262
- }
302
+ log('warn', `[fbphoto] composer still open — re-clicking publish (retry ${commitTry + 1}/3)`);
303
+ if (await tagCta(publishVerbs)) await clickTaggedCta();
263
304
  await page.waitForTimeout(6000);
264
305
  }
265
306
  if (await composerStillOpen(page, publishVerbs)) {