channel-worker 2.5.15 → 2.5.17
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 +1 -1
- package/scripts/upload_facebook.js +75 -4
package/package.json
CHANGED
|
@@ -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.
|
|
286
|
+
log('info', '[fb-pw] selectors version=2026.06.18a-fb-caption');
|
|
239
287
|
|
|
240
288
|
page.on('dialog', (d) => { d.accept().catch(() => {}); });
|
|
241
289
|
|
|
@@ -477,7 +525,19 @@ async function run({ page, payload, log }) {
|
|
|
477
525
|
// (some FB builds only show them on the final Chia sẻ step). The
|
|
478
526
|
// helper is idempotent: it tracks per-field "done" so we don't
|
|
479
527
|
// re-fill on subsequent steps.
|
|
480
|
-
|
|
528
|
+
// FB Reels page-wall composer has ONLY one caption field ("Mô tả thước
|
|
529
|
+
// phim") — no separate Title or Tags input (those were BS-composer fields,
|
|
530
|
+
// now gone). Per product decision the reel caption = title + hashtags
|
|
531
|
+
// (the description is dropped). Hashtags are collapsed to single #tokens
|
|
532
|
+
// since FB caption hashtags can't contain spaces ("gia dung" → "#giadung").
|
|
533
|
+
const fbHashtags = (tags || [])
|
|
534
|
+
.map(t => '#' + String(t || '').trim().replace(/[#\s]+/g, ''))
|
|
535
|
+
.filter(h => h.length > 1);
|
|
536
|
+
const fbCaption = [String(title || '').trim(), fbHashtags.join(' ')]
|
|
537
|
+
.filter(Boolean).join('\n\n');
|
|
538
|
+
// Pre-mark title+tags "done" so the legacy BS title/tags probes are skipped;
|
|
539
|
+
// only the description field is filled — with fbCaption (title + hashtags).
|
|
540
|
+
const fillState = { title: true, description: false, tags: true };
|
|
481
541
|
const fillMetadata = async () => {
|
|
482
542
|
// Title (Tiêu đề thước phim). Variant-A input is shown by default in the
|
|
483
543
|
// BS composer — NO reveal click needed. Earlier `revealCandidates` clicks
|
|
@@ -629,7 +689,7 @@ async function run({ page, payload, log }) {
|
|
|
629
689
|
if (!f) continue;
|
|
630
690
|
try {
|
|
631
691
|
await f.click({ timeout: 3000 });
|
|
632
|
-
const descText = (description || title || '').toString().slice(0, 2100);
|
|
692
|
+
const descText = (fbCaption || description || title || '').toString().slice(0, 2100);
|
|
633
693
|
if (!descText) break;
|
|
634
694
|
await f.type(descText, { delay: 12 });
|
|
635
695
|
fillState.description = true;
|
|
@@ -858,7 +918,8 @@ async function run({ page, payload, log }) {
|
|
|
858
918
|
// iterations, throw with diagnostics.
|
|
859
919
|
let published = false;
|
|
860
920
|
let customThumbDone = false;
|
|
861
|
-
|
|
921
|
+
let pubWaitDone = false; // guard: wait for a disabled "Đăng" at most once
|
|
922
|
+
for (let step = 0; step < 7 && !published; step++) {
|
|
862
923
|
await page.waitForTimeout(3000);
|
|
863
924
|
await fillMetadata();
|
|
864
925
|
|
|
@@ -1490,6 +1551,16 @@ async function run({ page, payload, log }) {
|
|
|
1490
1551
|
// No publish yet — advance via Tiếp.
|
|
1491
1552
|
const next = await findByVerbs(nextVerbs);
|
|
1492
1553
|
if (!next) {
|
|
1554
|
+
// Final "Cài đặt thước phim" step: the only action is "Đăng", which FB
|
|
1555
|
+
// keeps DISABLED while it ingests the video. For >50MB reup clips that
|
|
1556
|
+
// runs past the upload-wait, so the publish branch above found nothing
|
|
1557
|
+
// and there's no "Tiếp". Wait (once) for "Đăng" to enable, then let the
|
|
1558
|
+
// publish branch click it on the next iteration.
|
|
1559
|
+
if (!pubWaitDone) {
|
|
1560
|
+
pubWaitDone = true;
|
|
1561
|
+
log('info', `[fb-pw] no "Tiếp" + no enabled publish at step ${step + 1} — waiting for "Đăng" to enable (large-video processing)…`);
|
|
1562
|
+
if (await waitForPublishEnabled(page, publishVerbs, log, 180_000)) { step--; continue; }
|
|
1563
|
+
}
|
|
1493
1564
|
await dumpInventory(page, log, `no-advance-${step + 1}`);
|
|
1494
1565
|
await dumpFailure(page, `no-advance-${step + 1}`, log);
|
|
1495
1566
|
throw new Error(`FB composer step ${step + 1}: neither publish nor Tiếp button found`);
|