channel-worker 2.5.95 → 2.5.97
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 +160 -33
package/package.json
CHANGED
|
@@ -692,6 +692,93 @@ async function confirmPublishDialog({ firstHit, tagConfirmButton, clickTagged, c
|
|
|
692
692
|
return { confirmed: !pending };
|
|
693
693
|
}
|
|
694
694
|
|
|
695
|
+
// Link tab Reels của Page — dùng ở CẢ HAI đầu lượt đăng (chụp ảnh nền trước
|
|
696
|
+
// khi đăng, quét lại sau khi đăng). Ba chiến lược, giữ nguyên từ bản cũ.
|
|
697
|
+
async function discoverReelsTabUrl(page, log = null) {
|
|
698
|
+
const anchors = await page.evaluate(() => {
|
|
699
|
+
const out = [];
|
|
700
|
+
for (const a of document.querySelectorAll('a[href]')) {
|
|
701
|
+
const href = a.getAttribute('href') || '';
|
|
702
|
+
if (!href || href.startsWith('#') || href.startsWith('javascript:')) continue;
|
|
703
|
+
out.push({ href: href.slice(0, 200), aria: (a.getAttribute('aria-label') || '').slice(0, 80) });
|
|
704
|
+
if (out.length >= 400) break;
|
|
705
|
+
}
|
|
706
|
+
return out;
|
|
707
|
+
}).catch(() => []);
|
|
708
|
+
const url = pickReelsTabUrl(anchors);
|
|
709
|
+
if (!url && log) {
|
|
710
|
+
// Chẩn đoán: in vài href "giống của Page" để lần sau thêm đúng chiến lược.
|
|
711
|
+
const sample = anchors.map((a) => a.href).filter((h) => /^\/(?:profile\.php|\d{8,}|[A-Za-z0-9._-]{3,}\/?(?:\?|$))/.test(h)).slice(0, 8);
|
|
712
|
+
log('info', `[fb-pw] tìm tab Reels: ${anchors.length} anchor, không khớp chiến lược nào; mẫu: ${JSON.stringify(sample)}`);
|
|
713
|
+
}
|
|
714
|
+
return url;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Mọi reel ID đang hiện trên tab Reels, theo thứ tự DOM (mới nhất trước).
|
|
718
|
+
async function scrapeReelTileIds(page) {
|
|
719
|
+
return page.evaluate(() => {
|
|
720
|
+
const out = [];
|
|
721
|
+
for (const a of document.querySelectorAll("a[href*='/reel/']")) {
|
|
722
|
+
const m = (a.getAttribute('href') || '').match(/\/reel\/(\d{8,20})/);
|
|
723
|
+
if (!m) continue;
|
|
724
|
+
const r = a.getBoundingClientRect();
|
|
725
|
+
if (r.width < 8 || r.height < 8) continue;
|
|
726
|
+
if (!out.includes(m[1])) out.push(m[1]);
|
|
727
|
+
}
|
|
728
|
+
return out;
|
|
729
|
+
}).catch(() => []);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// SO TRƯỚC/SAU — nguồn lấy link thứ hai, không phụ thuộc gói mạng.
|
|
733
|
+
// Đo 09/09 sau khi gỡ nhánh "bốc tile đầu": bắt gói mạng trượt 2/3 lượt bấm
|
|
734
|
+
// Đăng thành công → 2 bài thành "mập mờ, chờ người kiểm" dù đã lên thật.
|
|
735
|
+
// Ảnh nền = danh sách reel ID của tab Reels chụp TRƯỚC khi mở composer; sau
|
|
736
|
+
// khi đăng quét lại, ID nào chưa có trong ảnh nền là reel mới. Đúng MỘT ID mới
|
|
737
|
+
// thì mới nhận; 0 hoặc >1 thì trả rỗng (thà rỗng còn hơn sai — luật 19/08).
|
|
738
|
+
// Thuần, test được.
|
|
739
|
+
function diffNewReelIds(beforeIds, afterIds) {
|
|
740
|
+
const before = new Set(beforeIds || []);
|
|
741
|
+
return (afterIds || []).filter((id) => !before.has(id));
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Chữ ký của chính composer (mọi bước của wizard). Dùng để KHÔNG bao giờ bấm
|
|
745
|
+
// nhầm vào nút của composer khi đang định bấm nút của hộp ảnh bìa.
|
|
746
|
+
const COMPOSER_SIGNATURE_RE = /Cài đặt thước phim|Tạo thước phim|Chỉnh sửa thước phim|Reel settings|Create (?:a )?reel|Edit reel/i;
|
|
747
|
+
function isComposerDialogSignature(text) {
|
|
748
|
+
return COMPOSER_SIGNATURE_RE.test(String(text || ''));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Chọn link tab Reels từ danh sách anchor của trang (thuần, test được).
|
|
752
|
+
// Chiến lược 1-3 giữ nguyên từ bản cũ; 4-5 thêm 09/09 vì trên TRANG CHỦ (lúc
|
|
753
|
+
// chụp ảnh nền trước khi đăng) không có anchor profile.php/Dòng thời gian —
|
|
754
|
+
// 2/2 lượt "không tìm được link tab Reels trước khi đăng". Trang chủ của Page
|
|
755
|
+
// có shortcut bên trái trỏ /<id số>/ hoặc /<handle>/, và đôi khi /<id>/reels/.
|
|
756
|
+
function pickReelsTabUrl(anchors) {
|
|
757
|
+
const list = (anchors || []).map((a) => ({ href: String(a.href || ''), aria: String(a.aria || '') }));
|
|
758
|
+
const abs = (h) => (h.startsWith('http') ? h : `https://www.facebook.com${h}`);
|
|
759
|
+
for (const a of list) if (/sk=reels_tab/.test(a.href)) return abs(a.href);
|
|
760
|
+
for (const a of list) {
|
|
761
|
+
const m = a.href.match(/\/profile\.php\?id=(\d+)/);
|
|
762
|
+
if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
|
|
763
|
+
}
|
|
764
|
+
for (const a of list) {
|
|
765
|
+
if (!/Dòng thời gian|Timeline/i.test(a.aria)) continue;
|
|
766
|
+
const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
|
|
767
|
+
if (m && !['profile.php', 'reel', 'video', 'videos', 'reels'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
|
|
768
|
+
}
|
|
769
|
+
// 4. /<id số>/reels/ hoặc /<id số>/ — shortcut của Page trên trang chủ.
|
|
770
|
+
for (const a of list) {
|
|
771
|
+
const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/(\d{10,20})(?:\/reels)?\/?(?:\?|$)/);
|
|
772
|
+
if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
|
|
773
|
+
}
|
|
774
|
+
// 5. /<handle>/reels/ — Page có tên riêng.
|
|
775
|
+
for (const a of list) {
|
|
776
|
+
const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/reels\/?(?:\?|$)/);
|
|
777
|
+
if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
695
782
|
async function hasVisibleReelComposer(page) {
|
|
696
783
|
return page.evaluate(() => {
|
|
697
784
|
for (const dlg of document.querySelectorAll("[role='dialog']")) {
|
|
@@ -828,6 +915,26 @@ async function runOnce({ page, payload, log }) {
|
|
|
828
915
|
log('info', `[fb-pw] browsed the wall for a bit (${bursts} scrolls) before opening the composer`);
|
|
829
916
|
}
|
|
830
917
|
|
|
918
|
+
// 1b) ẢNH NỀN tab Reels — chụp trước khi mở composer để sau khi đăng so
|
|
919
|
+
// trước/sau mà tìm reel mới (xem diffNewReelIds). Một lần goto + quay
|
|
920
|
+
// về, ~6-8s. Hỏng thì bỏ qua, không chặn lượt đăng.
|
|
921
|
+
let reelIdsBefore = null;
|
|
922
|
+
try {
|
|
923
|
+
const tabUrl = await discoverReelsTabUrl(page, log);
|
|
924
|
+
if (tabUrl) {
|
|
925
|
+
await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
926
|
+
await pause(page, 3500);
|
|
927
|
+
reelIdsBefore = await scrapeReelTileIds(page);
|
|
928
|
+
log('info', `[fb-pw] ảnh nền tab Reels trước khi đăng: ${reelIdsBefore.length} reel`);
|
|
929
|
+
await navigateFacebookHome(page, log);
|
|
930
|
+
await pause(page, 2500);
|
|
931
|
+
} else {
|
|
932
|
+
log('warn', '[fb-pw] không tìm được link tab Reels trước khi đăng — bỏ qua ảnh nền');
|
|
933
|
+
}
|
|
934
|
+
} catch (e) {
|
|
935
|
+
log('warn', `[fb-pw] chụp ảnh nền tab Reels hỏng: ${String(e && e.message || e).slice(0, 80)} — bỏ qua`);
|
|
936
|
+
}
|
|
937
|
+
|
|
831
938
|
// 2) Click the "Thước phim" entry inside the Page's "Tạo bài viết" widget.
|
|
832
939
|
// This opens a FRESH Reel composer modal each time (no stale-draft
|
|
833
940
|
// issue that plagued the BS composer). CRITICAL: scope to the "Tạo
|
|
@@ -1795,11 +1902,38 @@ async function runOnce({ page, payload, log }) {
|
|
|
1795
1902
|
for (const sel of saveCandidates) {
|
|
1796
1903
|
const btn = await firstVisible(page.locator(sel), 2);
|
|
1797
1904
|
if (!btn) continue;
|
|
1905
|
+
// CHẨN ĐOÁN 09/09: nghi cú bấm lại này trúng nút "Lưu" (lưu
|
|
1906
|
+
// nháp) của chính composer chứ không phải của hộp ảnh bìa →
|
|
1907
|
+
// FB lưu nháp, đóng composer, về trang chủ = "composer đóng
|
|
1908
|
+
// giữa chừng". Ghi rõ nút nằm trong dialog nào để có bằng
|
|
1909
|
+
// chứng thay vì đoán (đo được từ log, không cần ảnh).
|
|
1910
|
+
const where = await btn.evaluate((el) => {
|
|
1911
|
+
const dlg = el.closest("[role='dialog']");
|
|
1912
|
+
const aria = dlg ? (dlg.getAttribute('aria-label') || '') : '(không trong dialog)';
|
|
1913
|
+
const head = dlg ? (dlg.innerText || '').slice(0, 40).replace(/\s+/g, ' ') : '';
|
|
1914
|
+
return `${aria} | ${head}`;
|
|
1915
|
+
}).catch(() => '?');
|
|
1916
|
+
// ĐÃ CÓ BẰNG CHỨNG (2.5.96, 09/09 23:30, 2/2 lượt): nút "Lưu" bấm
|
|
1917
|
+
// lại nằm trong "Cài đặt thước phim" = nút LƯU NHÁP của
|
|
1918
|
+
// composer, không phải của hộp ảnh bìa (hộp đã đóng, nhưng
|
|
1919
|
+
// isThumbnailEditorVisible vẫn thấy chữ "Chỉnh sửa hình thu
|
|
1920
|
+
// nhỏ" trên pill của composer nên tưởng còn mở). Bấm vào là
|
|
1921
|
+
// FB lưu nháp, đóng composer, về trang chủ → "composer đóng
|
|
1922
|
+
// giữa chừng" (3/5 lượt tối 09/09, kể cả file nén 42 MB).
|
|
1923
|
+
// Nút thuộc composer thì KHÔNG bấm; và vì hộp ảnh bìa không
|
|
1924
|
+
// còn nút Lưu nào nghĩa là nó đã đóng → coi như xong.
|
|
1925
|
+
if (isComposerDialogSignature(where)) {
|
|
1926
|
+
log('warn', `[fb-pw] re-click Lưu BỎ QUA: nút thuộc composer (${where.slice(0, 60)}) — hộp ảnh bìa đã đóng, không bấm lưu nháp`);
|
|
1927
|
+
modalClosed = true;
|
|
1928
|
+
break;
|
|
1929
|
+
}
|
|
1930
|
+
log('info', `[fb-pw] re-click Lưu → nút nằm trong: ${where}`);
|
|
1798
1931
|
try {
|
|
1799
1932
|
await btn.click({ timeout: 2500 });
|
|
1800
1933
|
break;
|
|
1801
1934
|
} catch {}
|
|
1802
1935
|
}
|
|
1936
|
+
if (modalClosed) break;
|
|
1803
1937
|
}
|
|
1804
1938
|
await pause(page, 1000);
|
|
1805
1939
|
}
|
|
@@ -2717,39 +2851,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
2717
2851
|
// 2. Extract Page ID from "Dòng thời gian của X" timeline anchor or
|
|
2718
2852
|
// any /profile.php?id=<id> anchor, then build the reels_tab URL
|
|
2719
2853
|
// 3. Extract Page slug from any /<handle>/ anchor
|
|
2720
|
-
const reelsTabHref = await page
|
|
2721
|
-
// Strategy 1: direct reels_tab link.
|
|
2722
|
-
const direct = document.querySelectorAll("a[href*='sk=reels_tab']");
|
|
2723
|
-
for (const a of direct) {
|
|
2724
|
-
const h = a.getAttribute('href') || '';
|
|
2725
|
-
if (h) return h.startsWith('http') ? h : `https://www.facebook.com${h}`;
|
|
2726
|
-
}
|
|
2727
|
-
// Strategy 2: extract Page ID from a /profile.php?id=<id> anchor.
|
|
2728
|
-
// The "Dòng thời gian của X" anchor and the page-shortcut anchor
|
|
2729
|
-
// both have hrefs like /profile.php?id=61590303065684.
|
|
2730
|
-
const profAnchors = document.querySelectorAll("a[href*='/profile.php?id=']");
|
|
2731
|
-
for (const a of profAnchors) {
|
|
2732
|
-
const h = a.getAttribute('href') || '';
|
|
2733
|
-
const m = h.match(/\/profile\.php\?id=(\d+)/);
|
|
2734
|
-
if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
|
|
2735
|
-
}
|
|
2736
|
-
// Strategy 3: page handle via "Dòng thời gian của X" anchor (no
|
|
2737
|
-
// profile.php — uses /<handle> instead).
|
|
2738
|
-
const tlAnchors = document.querySelectorAll("a[aria-label*='Dòng thời gian'], a[aria-label*='Timeline']");
|
|
2739
|
-
for (const a of tlAnchors) {
|
|
2740
|
-
const h = a.getAttribute('href') || '';
|
|
2741
|
-
// /<handle> or /<handle>/?...
|
|
2742
|
-
const m = h.match(/^\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
|
|
2743
|
-
if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) {
|
|
2744
|
-
return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
|
|
2745
|
-
}
|
|
2746
|
-
if (h.match(/\/profile\.php\?id=(\d+)/)) {
|
|
2747
|
-
const pid = h.match(/\/profile\.php\?id=(\d+)/)[1];
|
|
2748
|
-
return `https://www.facebook.com/profile.php?id=${pid}&sk=reels_tab`;
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
return null;
|
|
2752
|
-
}).catch(() => null);
|
|
2854
|
+
const reelsTabHref = await discoverReelsTabUrl(page);
|
|
2753
2855
|
if (reelsTabHref) {
|
|
2754
2856
|
reelsTabUrl = reelsTabHref;
|
|
2755
2857
|
log('info', `[fb-pw] navigating to page reels tab: ${reelsTabHref.slice(0, 100)}`);
|
|
@@ -2766,6 +2868,17 @@ async function runOnce({ page, payload, log }) {
|
|
|
2766
2868
|
} else {
|
|
2767
2869
|
log('warn', '[fb-pw] reels_tab navigated but no tile with fresh timestamp found');
|
|
2768
2870
|
}
|
|
2871
|
+
// (a.1b) SO TRƯỚC/SAU với ảnh nền chụp trước khi mở composer.
|
|
2872
|
+
if (!postUrl && reelIdsBefore) {
|
|
2873
|
+
const after = await scrapeReelTileIds(page);
|
|
2874
|
+
const fresh2 = diffNewReelIds(reelIdsBefore, after);
|
|
2875
|
+
if (fresh2.length === 1) {
|
|
2876
|
+
postUrl = `https://www.facebook.com/reel/${fresh2[0]}/`;
|
|
2877
|
+
log('info', `[fb-pw] post URL từ so trước/sau tab Reels (trước ${reelIdsBefore.length}, sau ${after.length}, mới 1): ${postUrl}`);
|
|
2878
|
+
} else {
|
|
2879
|
+
log('info', `[fb-pw] so trước/sau tab Reels: trước ${reelIdsBefore.length}, sau ${after.length}, mới ${fresh2.length} — ${fresh2.length ? 'nhiều hơn 1, không nhận' : 'chưa thấy reel mới'}`);
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2769
2882
|
} else {
|
|
2770
2883
|
log('warn', '[fb-pw] no reels_tab link found on page; skipping reels-tab strategy');
|
|
2771
2884
|
}
|
|
@@ -2895,6 +3008,15 @@ async function runOnce({ page, payload, log }) {
|
|
|
2895
3008
|
if (fresh2) {
|
|
2896
3009
|
postUrl = fresh2.href.startsWith('http') ? fresh2.href : `https://www.facebook.com${fresh2.href}`;
|
|
2897
3010
|
log('info', `[fb-pw] post URL from reels_tab RETRY (id=${fresh2.id}, timestamp="${fresh2.timestamp}"): ${postUrl}`);
|
|
3011
|
+
} else if (reelIdsBefore) {
|
|
3012
|
+
const after2 = await scrapeReelTileIds(page);
|
|
3013
|
+
const diff2 = diffNewReelIds(reelIdsBefore, after2);
|
|
3014
|
+
if (diff2.length === 1) {
|
|
3015
|
+
postUrl = `https://www.facebook.com/reel/${diff2[0]}/`;
|
|
3016
|
+
log('info', `[fb-pw] post URL từ so trước/sau tab Reels (lượt chờ 75s; trước ${reelIdsBefore.length}, sau ${after2.length}): ${postUrl}`);
|
|
3017
|
+
} else {
|
|
3018
|
+
log('warn', `[fb-pw] reels_tab retry: vẫn chưa thấy reel mới (so trước/sau: mới ${diff2.length})`);
|
|
3019
|
+
}
|
|
2898
3020
|
} else {
|
|
2899
3021
|
log('warn', '[fb-pw] reels_tab retry: still no fresh tile');
|
|
2900
3022
|
}
|
|
@@ -3028,6 +3150,11 @@ module.exports = { run };
|
|
|
3028
3150
|
module.exports.__testables = {
|
|
3029
3151
|
resilientClick,
|
|
3030
3152
|
confirmPublishDialog,
|
|
3153
|
+
diffNewReelIds,
|
|
3154
|
+
pickReelsTabUrl,
|
|
3155
|
+
isComposerDialogSignature,
|
|
3156
|
+
discoverReelsTabUrl,
|
|
3157
|
+
scrapeReelTileIds,
|
|
3031
3158
|
muteClickInterceptors,
|
|
3032
3159
|
unmuteClickInterceptors,
|
|
3033
3160
|
waitForPublishEnabled,
|