channel-worker 2.5.103 → 2.5.105
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 +91 -4
package/package.json
CHANGED
|
@@ -651,12 +651,21 @@ async function isCustomThumbApplied(page) {
|
|
|
651
651
|
// xong quyền riêng tư của Thước phim" → Đăng xám mãi; đứng
|
|
652
652
|
// 600s rồi "description field not found" (phụ kiện điện tử
|
|
653
653
|
// 02 10/09, kol du lịch 11/09).
|
|
654
|
+
// fbError : toast ĐEN góc trái dưới "Đã xảy ra lỗi. Việc này có thể là
|
|
655
|
+
// do lỗi kỹ thuật và chúng tôi đang khắc phục rồi." → chính
|
|
656
|
+
// Facebook hỏng và tự gỡ composer. Toast này nằm NGOÀI mọi
|
|
657
|
+
// [role=dialog] nên readComposerText không thấy — đọc bằng
|
|
658
|
+
// readPageAlerts.
|
|
654
659
|
// Thuần, test được; regex neo đầu dòng cho "Đang đăng" để không dính mô tả
|
|
655
660
|
// bài có chữ "đang đăng" giữa câu.
|
|
656
661
|
const COMPOSER_TEXT_RE = {
|
|
657
662
|
rightsCheck: /Không hoàn tất được quy trình kiểm tra bản quyền|couldn['’]t complete (?:the )?(?:rights|copyright) check/i,
|
|
658
663
|
publishing: /(?:^|\n)\s*(?:Đang đăng|Đang xuất bản|Publishing|Posting)\s*(?:\.{3}|…)?\s*(?:\n|$)/i,
|
|
659
664
|
settingsStuck: /Vui lòng đợi tải xong quyền riêng tư của Thước phim|wait (?:for|until) (?:the )?reel(?:'s)? privacy/i,
|
|
665
|
+
// ĐÒI CẢ HAI VẾ trong cùng một quãng ngắn. Chỉ "Đã xảy ra lỗi" thôi thì quá
|
|
666
|
+
// dễ trúng caption bài trên feed (hàm đọc có nhánh quét cả trang), còn cặp
|
|
667
|
+
// "lỗi … lỗi kỹ thuật/khắc phục" là đúng khuôn toast của Facebook.
|
|
668
|
+
fbError: /Đã xảy ra lỗi[\s\S]{0,160}(?:lỗi kỹ thuật|đang khắc phục)|Something went wrong[\s\S]{0,160}technical (?:error|issue|problem)/i,
|
|
660
669
|
};
|
|
661
670
|
function classifyComposerText(txt) {
|
|
662
671
|
const t = String(txt || '');
|
|
@@ -664,6 +673,7 @@ function classifyComposerText(txt) {
|
|
|
664
673
|
rightsCheck: COMPOSER_TEXT_RE.rightsCheck.test(t),
|
|
665
674
|
publishing: COMPOSER_TEXT_RE.publishing.test(t),
|
|
666
675
|
settingsStuck: COMPOSER_TEXT_RE.settingsStuck.test(t),
|
|
676
|
+
fbError: COMPOSER_TEXT_RE.fbError.test(t),
|
|
667
677
|
};
|
|
668
678
|
}
|
|
669
679
|
async function readComposerText(page) {
|
|
@@ -679,6 +689,34 @@ async function readComposerText(page) {
|
|
|
679
689
|
return out.join('\n').slice(0, 8000);
|
|
680
690
|
}).catch(() => '');
|
|
681
691
|
}
|
|
692
|
+
// Chữ trong các lớp nổi NGOÀI composer: toast, alert, status.
|
|
693
|
+
//
|
|
694
|
+
// Vì sao cần: 13/09 ba kênh (kiến 01, kenh-54, kenh-55) cùng chết ở bước lưu
|
|
695
|
+
// ảnh bìa, ảnh dump cả ba đều có TOAST ĐEN góc trái dưới "Đã xảy ra lỗi… lỗi
|
|
696
|
+
// kỹ thuật… đang khắc phục rồi" — tức Facebook tự hỏng rồi gỡ composer. Câu
|
|
697
|
+
// lỗi cũ chỉ nói "composer closed", nên mỗi lần gặp lại phải mở ảnh dump mới
|
|
698
|
+
// biết là phía FB. Toast nằm ngoài [role=dialog] nên readComposerText mù.
|
|
699
|
+
//
|
|
700
|
+
// Quét theo lớp: role=alert/status + gốc portal `ignore-dynamic` (cùng bộ chọn
|
|
701
|
+
// mà dismissToasts đã dùng). Không thấy gì thì mới đọc cả trang — an toàn vì
|
|
702
|
+
// regex fbError đòi hai vế đi liền nhau.
|
|
703
|
+
async function readPageAlerts(page) {
|
|
704
|
+
return page.evaluate(() => {
|
|
705
|
+
const visible = (el) => {
|
|
706
|
+
const r = el.getBoundingClientRect();
|
|
707
|
+
if (r.width < 8 || r.height < 8) return false;
|
|
708
|
+
const cs = getComputedStyle(el);
|
|
709
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none';
|
|
710
|
+
};
|
|
711
|
+
const out = [];
|
|
712
|
+
for (const el of document.querySelectorAll("[role='alert'], [role='status'], div[data-visualcompletion='ignore-dynamic']")) {
|
|
713
|
+
if (visible(el)) out.push(el.innerText || '');
|
|
714
|
+
}
|
|
715
|
+
const picked = out.join('\n').trim();
|
|
716
|
+
return (picked || (document.body ? document.body.innerText || '' : '')).slice(0, 8000);
|
|
717
|
+
}).catch(() => '');
|
|
718
|
+
}
|
|
719
|
+
|
|
682
720
|
const PUBLISHING_WAIT_MS = 5 * 60 * 1000; // "Đang đăng": chờ tới 5 phút, không bấm lại
|
|
683
721
|
const SETTINGS_STUCK_MS = 90 * 1000; // bảng cài đặt trống quá 90s: mở lại composer
|
|
684
722
|
|
|
@@ -690,6 +728,21 @@ function safeComposerRetryError(message) {
|
|
|
690
728
|
return err;
|
|
691
729
|
}
|
|
692
730
|
|
|
731
|
+
// Composer biến mất → nói RÕ vì sao, nếu Facebook có để lại toast lỗi.
|
|
732
|
+
//
|
|
733
|
+
// GIỮ NGUYÊN cụm "composer closed after saving thumbnail" ở cuối câu: luật
|
|
734
|
+
// `composer_closed` bên api/services/PublishRetry khớp theo cụm đó. Worker lên
|
|
735
|
+
// bản mới bằng chép nóng (có hiệu lực ngay), còn API thì deploy riêng — đổi
|
|
736
|
+
// hẳn câu lỗi là lỗi này rơi vào "lỗi lạ" (trần thử lại 1 lần) cho tới khi API
|
|
737
|
+
// kịp lên. Thêm vế giải thích ở ĐẦU, không thay phần đuôi.
|
|
738
|
+
async function composerClosedError(page, log, fallbackMessage) {
|
|
739
|
+
let fbError = false;
|
|
740
|
+
try { fbError = classifyComposerText(await readPageAlerts(page)).fbError; } catch { /* đọc lỗi thì cứ dùng câu cũ */ }
|
|
741
|
+
if (!fbError) return safeComposerRetryError(fallbackMessage);
|
|
742
|
+
log('warn', '[fb-pw] Facebook hiện toast "Đã xảy ra lỗi… lỗi kỹ thuật" — chính FB hỏng rồi gỡ composer, không phải lỗi bài/tài khoản');
|
|
743
|
+
return safeComposerRetryError(`Facebook báo lỗi kỹ thuật ("Đã xảy ra lỗi… chúng tôi đang khắc phục") rồi tự gỡ trình soạn — ${fallbackMessage}`);
|
|
744
|
+
}
|
|
745
|
+
|
|
693
746
|
// 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
|
|
694
747
|
// CÚ CLICK: 23/08 hai lần liên tiếp bot bấm "Tiếp" thì FB nhảy sang xem reel
|
|
695
748
|
// cũ, composer đóng → "final description field not found". Đóng toast trước
|
|
@@ -802,6 +855,18 @@ function diffNewReelIds(beforeIds, afterIds) {
|
|
|
802
855
|
return (afterIds || []).filter((id) => !before.has(id));
|
|
803
856
|
}
|
|
804
857
|
|
|
858
|
+
// URL tab Reels dùng SAU khi đăng: ưu tiên link dò được trên trang hiện tại,
|
|
859
|
+
// không có thì dùng URL đã tìm được lúc chụp ảnh nền. Sau khi bấm Đăng,
|
|
860
|
+
// Facebook hay đưa về /saved/?cref=28 (đo 04/08→13/09: 48/1829 lượt, 74 lần
|
|
861
|
+
// "no reels_tab link found") — trang đó không có link tab Reels, dò lại là
|
|
862
|
+
// trượt và cả so trước/sau lẫn lượt chờ 75s đều bị bỏ → bài lên thật mà thành
|
|
863
|
+
// "mập mờ" (Makeup tại nhà 2 13/09 18:07, thich-lo-mo 12/09). Thuần, test được.
|
|
864
|
+
function pickPostPublishReelsTabUrl({ discovered, remembered }) {
|
|
865
|
+
if (discovered) return { url: discovered, via: 'page' };
|
|
866
|
+
if (remembered) return { url: remembered, via: 'snapshot' };
|
|
867
|
+
return { url: null, via: null };
|
|
868
|
+
}
|
|
869
|
+
|
|
805
870
|
// ID từ gói mạng: tab Reels xác nhận chỉ chứng minh reel THUỘC PAGE MÌNH, không
|
|
806
871
|
// chứng minh nó MỚI. Ca 10/09 16:26 "ẩm thực đồng quê 01": snapshotLen=0 nên cả
|
|
807
872
|
// 40 ID bắt được đều "mới", ID đầu tiên 1600960624772777 có trên tab (là bài
|
|
@@ -895,7 +960,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
895
960
|
} = payload || {};
|
|
896
961
|
if (!video_url) throw new Error('No video_url provided');
|
|
897
962
|
|
|
898
|
-
log('info', '[fb-pw] selectors version=2026.09.
|
|
963
|
+
log('info', '[fb-pw] selectors version=2026.09.13-reels-tab-fallback');
|
|
899
964
|
|
|
900
965
|
page.on('dialog', (d) => { d.accept().catch(() => {}); });
|
|
901
966
|
|
|
@@ -1003,9 +1068,12 @@ async function runOnce({ page, payload, log }) {
|
|
|
1003
1068
|
// trước/sau mà tìm reel mới (xem diffNewReelIds). Một lần goto + quay
|
|
1004
1069
|
// về, ~6-8s. Hỏng thì bỏ qua, không chặn lượt đăng.
|
|
1005
1070
|
let reelIdsBefore = null;
|
|
1071
|
+
// GIỮ LẠI URL tab Reels cho sau khi đăng — xem pickPostPublishReelsTabUrl.
|
|
1072
|
+
let reelsTabUrlBefore = null;
|
|
1006
1073
|
try {
|
|
1007
1074
|
const tabUrl = await discoverReelsTabUrl(page, log);
|
|
1008
1075
|
if (tabUrl) {
|
|
1076
|
+
reelsTabUrlBefore = tabUrl;
|
|
1009
1077
|
await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
1010
1078
|
await pause(page, 3500);
|
|
1011
1079
|
reelIdsBefore = await scrapeReelTileIds(page);
|
|
@@ -2036,7 +2104,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
2036
2104
|
// safe here because the publish branch has not run yet.
|
|
2037
2105
|
if (!(await hasVisibleReelComposer(page))) {
|
|
2038
2106
|
await dumpFailure(page, 'composer-closed-after-thumb-save', log);
|
|
2039
|
-
throw
|
|
2107
|
+
throw await composerClosedError(page, log, 'FB Reel composer closed after saving thumbnail (before publish)');
|
|
2040
2108
|
}
|
|
2041
2109
|
thumbApplied = true;
|
|
2042
2110
|
log('info', `[fb-pw] page-wall thumb — modal closed after save (retries=${retryCount})`);
|
|
@@ -2723,7 +2791,11 @@ async function runOnce({ page, payload, log }) {
|
|
|
2723
2791
|
// FB tự đóng) → CHẠY LẠI cả lượt upload thay vì báo lỗi khó hiểu.
|
|
2724
2792
|
// An toàn vì chưa hề bấm Đăng ở nhánh này.
|
|
2725
2793
|
if (!(await hasVisibleReelComposer(page))) {
|
|
2726
|
-
|
|
2794
|
+
// Câu lỗi phải chứa "composer closed … before publish": luật
|
|
2795
|
+
// `composer_closed` của PublishRetry khớp theo cụm đó. Bản cũ viết
|
|
2796
|
+
// thuần tiếng Việt nên nếu lượt trong phiên cũng hỏng thì lỗi rơi
|
|
2797
|
+
// vào "lỗi lạ" (trần thử lại 1 lần) thay vì được xếp lại 3 lần.
|
|
2798
|
+
throw await composerClosedError(page, log, `FB Reel composer closed (before publish) — đóng giữa chừng ở ${page.url().slice(0, 80)}, chạy lại lượt đăng`);
|
|
2727
2799
|
}
|
|
2728
2800
|
if (!fillState.description) {
|
|
2729
2801
|
throw new Error(`FB final description field not found at step ${step + 1}`);
|
|
@@ -2975,7 +3047,14 @@ async function runOnce({ page, payload, log }) {
|
|
|
2975
3047
|
// 2. Extract Page ID from "Dòng thời gian của X" timeline anchor or
|
|
2976
3048
|
// any /profile.php?id=<id> anchor, then build the reels_tab URL
|
|
2977
3049
|
// 3. Extract Page slug from any /<handle>/ anchor
|
|
2978
|
-
const
|
|
3050
|
+
const picked = pickPostPublishReelsTabUrl({
|
|
3051
|
+
discovered: await discoverReelsTabUrl(page),
|
|
3052
|
+
remembered: reelsTabUrlBefore,
|
|
3053
|
+
});
|
|
3054
|
+
const reelsTabHref = picked.url;
|
|
3055
|
+
if (picked.via === 'snapshot') {
|
|
3056
|
+
log('info', `[fb-pw] trang hiện tại không có link tab Reels (${String(page.url() || '').slice(0, 70)}) — dùng URL đã biết từ lúc chụp ảnh nền`);
|
|
3057
|
+
}
|
|
2979
3058
|
if (reelsTabHref) {
|
|
2980
3059
|
reelsTabUrl = reelsTabHref;
|
|
2981
3060
|
log('info', `[fb-pw] navigating to page reels tab: ${reelsTabHref.slice(0, 100)}`);
|
|
@@ -3256,6 +3335,11 @@ async function runOnce({ page, payload, log }) {
|
|
|
3256
3335
|
// post_url để quyết, hai cờ này để người đọc log biết hỏng ở khâu nào.
|
|
3257
3336
|
confirm_click_failed: confirmClickFailed,
|
|
3258
3337
|
post_url_captured: !!postUrl,
|
|
3338
|
+
// Ảnh nền tab Reels chụp trước khi mở composer + URL tab. API lưu vào
|
|
3339
|
+
// publish_results[].reels_before để đối chiếu xuôi (PublishReconcile)
|
|
3340
|
+
// biết reel nào là CŨ khi lượt này không lấy được link.
|
|
3341
|
+
reels_before: reelIdsBefore ? reelIdsBefore.slice(0, 300) : undefined,
|
|
3342
|
+
reels_tab_url: reelsTabUrlBefore || reelsTabUrl || undefined,
|
|
3259
3343
|
video_path_local: videoPath,
|
|
3260
3344
|
};
|
|
3261
3345
|
} finally {
|
|
@@ -3295,9 +3379,12 @@ module.exports.__testables = {
|
|
|
3295
3379
|
resilientClick,
|
|
3296
3380
|
confirmPublishDialog,
|
|
3297
3381
|
diffNewReelIds,
|
|
3382
|
+
pickPostPublishReelsTabUrl,
|
|
3298
3383
|
pickReelsTabUrl,
|
|
3299
3384
|
networkIdVerdict,
|
|
3300
3385
|
classifyComposerText,
|
|
3386
|
+
readPageAlerts,
|
|
3387
|
+
composerClosedError,
|
|
3301
3388
|
isComposerDialogSignature,
|
|
3302
3389
|
discoverReelsTabUrl,
|
|
3303
3390
|
scrapeReelTileIds,
|