channel-worker 2.5.96 → 2.5.98
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 +104 -29
package/package.json
CHANGED
|
@@ -694,33 +694,27 @@ async function confirmPublishDialog({ firstHit, tagConfirmButton, clickTagged, c
|
|
|
694
694
|
|
|
695
695
|
// Link tab Reels của Page — dùng ở CẢ HAI đầu lượt đăng (chụp ảnh nền trước
|
|
696
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) {
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
for (const a of
|
|
701
|
-
const
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
|
|
709
|
-
}
|
|
710
|
-
const tlAnchors = document.querySelectorAll("a[aria-label*='Dòng thời gian'], a[aria-label*='Timeline']");
|
|
711
|
-
for (const a of tlAnchors) {
|
|
712
|
-
const h = a.getAttribute('href') || '';
|
|
713
|
-
const m = h.match(/^\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
|
|
714
|
-
if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) {
|
|
715
|
-
return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
|
|
716
|
-
}
|
|
717
|
-
if (h.match(/\/profile\.php\?id=(\d+)/)) {
|
|
718
|
-
const pid = h.match(/\/profile\.php\?id=(\d+)/)[1];
|
|
719
|
-
return `https://www.facebook.com/profile.php?id=${pid}&sk=reels_tab`;
|
|
720
|
-
}
|
|
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
|
+
// Anchor nằm trong bảng tin / một bài viết → của page KHÁC, không phải
|
|
704
|
+
// của mình (xem ghi chú ở pickReelsTabUrl).
|
|
705
|
+
const inFeed = !!a.closest("[role='feed'], [role='article'], [aria-posinset]");
|
|
706
|
+
out.push({ href: href.slice(0, 200), aria: (a.getAttribute('aria-label') || '').slice(0, 80), inFeed });
|
|
707
|
+
if (out.length >= 400) break;
|
|
721
708
|
}
|
|
722
|
-
return
|
|
723
|
-
}).catch(() =>
|
|
709
|
+
return out;
|
|
710
|
+
}).catch(() => []);
|
|
711
|
+
const url = pickReelsTabUrl(anchors);
|
|
712
|
+
if (!url && log) {
|
|
713
|
+
// Chẩn đoán: in vài href "giống của Page" để lần sau thêm đúng chiến lược.
|
|
714
|
+
const sample = anchors.map((a) => a.href).filter((h) => /^\/(?:profile\.php|\d{8,}|[A-Za-z0-9._-]{3,}\/?(?:\?|$))/.test(h)).slice(0, 8);
|
|
715
|
+
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)}`);
|
|
716
|
+
}
|
|
717
|
+
return url;
|
|
724
718
|
}
|
|
725
719
|
|
|
726
720
|
// Mọi reel ID đang hiện trên tab Reels, theo thứ tự DOM (mới nhất trước).
|
|
@@ -750,6 +744,54 @@ function diffNewReelIds(beforeIds, afterIds) {
|
|
|
750
744
|
return (afterIds || []).filter((id) => !before.has(id));
|
|
751
745
|
}
|
|
752
746
|
|
|
747
|
+
// Chữ ký của chính composer (mọi bước của wizard). Dùng để KHÔNG bao giờ bấm
|
|
748
|
+
// nhầm vào nút của composer khi đang định bấm nút của hộp ảnh bìa.
|
|
749
|
+
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;
|
|
750
|
+
function isComposerDialogSignature(text) {
|
|
751
|
+
return COMPOSER_SIGNATURE_RE.test(String(text || ''));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Chọn link tab Reels từ danh sách anchor của trang (thuần, test được).
|
|
755
|
+
// 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
|
|
756
|
+
// chụp ảnh nền trước khi đăng) không có anchor profile.php/Dòng thời gian —
|
|
757
|
+
// 2/2 lượt "không tìm được link tab Reels trước khi đăng". Trang chủ của Page
|
|
758
|
+
// có shortcut bên trái trỏ /<id số>/ hoặc /<handle>/, và đôi khi /<id>/reels/.
|
|
759
|
+
function pickReelsTabUrl(anchors) {
|
|
760
|
+
const list = (anchors || [])
|
|
761
|
+
.map((a) => ({ href: String(a.href || ''), aria: String(a.aria || ''), inFeed: !!a.inFeed }))
|
|
762
|
+
// ⛔ LOẠI anchor CỦA BÀI TRONG BẢNG TIN — đây là gốc của 2 link sai sáng
|
|
763
|
+
// 10/09 (kênh "kol du lịch 2", reel 1712630710026375 và 1549470359802337
|
|
764
|
+
// hoá ra là bài của PAGE LẠ). Trang chủ đầy bài của page khác; mỗi bài có
|
|
765
|
+
// link /profile.php?id=<page lạ>&__cft__[0]=… nên chiến lược 2 bốc trúng
|
|
766
|
+
// tab Reels của người ta, rồi "so trước/sau" so hai page khác nhau (log:
|
|
767
|
+
// "trước 10, sau 10, mới 10 — nhiều hơn 1, không nhận") và nhánh gói mạng
|
|
768
|
+
// chốt bằng reel gợi ý trên feed. Dấu nhận biết: tham số __cft__/__tn__
|
|
769
|
+
// (tracking của một bài cụ thể) hoặc anchor nằm trong vùng feed/article.
|
|
770
|
+
.filter((a) => !a.inFeed && !/[?&]__cft__|[?&]__tn__/.test(a.href));
|
|
771
|
+
const abs = (h) => (h.startsWith('http') ? h : `https://www.facebook.com${h}`);
|
|
772
|
+
for (const a of list) if (/sk=reels_tab/.test(a.href)) return abs(a.href);
|
|
773
|
+
for (const a of list) {
|
|
774
|
+
const m = a.href.match(/\/profile\.php\?id=(\d+)/);
|
|
775
|
+
if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
|
|
776
|
+
}
|
|
777
|
+
for (const a of list) {
|
|
778
|
+
if (!/Dòng thời gian|Timeline/i.test(a.aria)) continue;
|
|
779
|
+
const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/?(?:\?|$)/);
|
|
780
|
+
if (m && !['profile.php', 'reel', 'video', 'videos', 'reels'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
|
|
781
|
+
}
|
|
782
|
+
// 4. /<id số>/reels/ hoặc /<id số>/ — shortcut của Page trên trang chủ.
|
|
783
|
+
for (const a of list) {
|
|
784
|
+
const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/(\d{10,20})(?:\/reels)?\/?(?:\?|$)/);
|
|
785
|
+
if (m) return `https://www.facebook.com/profile.php?id=${m[1]}&sk=reels_tab`;
|
|
786
|
+
}
|
|
787
|
+
// 5. /<handle>/reels/ — Page có tên riêng.
|
|
788
|
+
for (const a of list) {
|
|
789
|
+
const m = a.href.match(/^(?:https?:\/\/(?:www\.)?facebook\.com)?\/([A-Za-z0-9._-]{3,})\/reels\/?(?:\?|$)/);
|
|
790
|
+
if (m && !['profile.php', 'reel', 'video', 'videos'].includes(m[1])) return `https://www.facebook.com/${m[1]}/?sk=reels_tab`;
|
|
791
|
+
}
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
753
795
|
async function hasVisibleReelComposer(page) {
|
|
754
796
|
return page.evaluate(() => {
|
|
755
797
|
for (const dlg of document.querySelectorAll("[role='dialog']")) {
|
|
@@ -891,7 +933,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
891
933
|
// về, ~6-8s. Hỏng thì bỏ qua, không chặn lượt đăng.
|
|
892
934
|
let reelIdsBefore = null;
|
|
893
935
|
try {
|
|
894
|
-
const tabUrl = await discoverReelsTabUrl(page);
|
|
936
|
+
const tabUrl = await discoverReelsTabUrl(page, log);
|
|
895
937
|
if (tabUrl) {
|
|
896
938
|
await page.goto(tabUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
897
939
|
await pause(page, 3500);
|
|
@@ -1884,12 +1926,27 @@ async function runOnce({ page, payload, log }) {
|
|
|
1884
1926
|
const head = dlg ? (dlg.innerText || '').slice(0, 40).replace(/\s+/g, ' ') : '';
|
|
1885
1927
|
return `${aria} | ${head}`;
|
|
1886
1928
|
}).catch(() => '?');
|
|
1929
|
+
// ĐÃ CÓ BẰNG CHỨNG (2.5.96, 09/09 23:30, 2/2 lượt): nút "Lưu" bấm
|
|
1930
|
+
// lại nằm trong "Cài đặt thước phim" = nút LƯU NHÁP của
|
|
1931
|
+
// composer, không phải của hộp ảnh bìa (hộp đã đóng, nhưng
|
|
1932
|
+
// isThumbnailEditorVisible vẫn thấy chữ "Chỉnh sửa hình thu
|
|
1933
|
+
// nhỏ" trên pill của composer nên tưởng còn mở). Bấm vào là
|
|
1934
|
+
// FB lưu nháp, đóng composer, về trang chủ → "composer đóng
|
|
1935
|
+
// giữa chừng" (3/5 lượt tối 09/09, kể cả file nén 42 MB).
|
|
1936
|
+
// Nút thuộc composer thì KHÔNG bấm; và vì hộp ảnh bìa không
|
|
1937
|
+
// còn nút Lưu nào nghĩa là nó đã đóng → coi như xong.
|
|
1938
|
+
if (isComposerDialogSignature(where)) {
|
|
1939
|
+
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`);
|
|
1940
|
+
modalClosed = true;
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1887
1943
|
log('info', `[fb-pw] re-click Lưu → nút nằm trong: ${where}`);
|
|
1888
1944
|
try {
|
|
1889
1945
|
await btn.click({ timeout: 2500 });
|
|
1890
1946
|
break;
|
|
1891
1947
|
} catch {}
|
|
1892
1948
|
}
|
|
1949
|
+
if (modalClosed) break;
|
|
1893
1950
|
}
|
|
1894
1951
|
await pause(page, 1000);
|
|
1895
1952
|
}
|
|
@@ -2801,6 +2858,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
2801
2858
|
return null;
|
|
2802
2859
|
}).catch(() => null);
|
|
2803
2860
|
let reelsTabUrl = null; // giữ lại cho lượt retry (a.2b)
|
|
2861
|
+
let networkCandidate = null; // ID gói mạng chưa được tab Reels xác nhận
|
|
2804
2862
|
try {
|
|
2805
2863
|
// Find a link to the page's reels tab. Multi-strategy:
|
|
2806
2864
|
// 1. Any existing href containing "sk=reels_tab"
|
|
@@ -2943,9 +3001,21 @@ async function runOnce({ page, payload, log }) {
|
|
|
2943
3001
|
const preSet = new Set(capturedReelIds.slice(0, capturedReelIdsSnapshotLen));
|
|
2944
3002
|
const trulyNew = newIds.filter((id) => !preSet.has(id));
|
|
2945
3003
|
if (trulyNew.length) {
|
|
3004
|
+
// ⚠️ ID bắt từ gói mạng KHÔNG chứng minh reel thuộc page mình: đứng ở
|
|
3005
|
+
// trang chủ, feed tải liên tục reel GỢI Ý của page khác, nên "ID mới
|
|
3006
|
+
// đầu tiên sau mốc" có thể là reel người ta (sáng 10/09: snapshotLen=132,
|
|
3007
|
+
// total=220 → 88 ID mới, cái đầu là reel của page lạ và đã bị ghi vào DB).
|
|
3008
|
+
// Chỉ nhận khi tab Reels CỦA CHÍNH PAGE xác nhận có ID đó; không xác
|
|
3009
|
+
// nhận được thì để làm ứng viên, lượt chờ 75s dưới đây kiểm lại.
|
|
2946
3010
|
const firstNew = trulyNew[0];
|
|
2947
|
-
|
|
2948
|
-
|
|
3011
|
+
const tabIds = reelsTabUrl ? await scrapeReelTileIds(page) : [];
|
|
3012
|
+
if (tabIds.includes(firstNew)) {
|
|
3013
|
+
postUrl = `https://www.facebook.com/reel/${firstNew}/`;
|
|
3014
|
+
log('info', `[fb-pw] post URL from network + tab Reels xác nhận: ${postUrl} (snapshotLen=${capturedReelIdsSnapshotLen} total=${capturedReelIds.length} new=${trulyNew.length})`);
|
|
3015
|
+
} else {
|
|
3016
|
+
networkCandidate = firstNew;
|
|
3017
|
+
log('warn', `[fb-pw] gói mạng cho ID ${firstNew} nhưng tab Reels của page CHƯA xác nhận (${tabIds.length} tile) — giữ làm ứng viên, chờ kiểm lại`);
|
|
3018
|
+
}
|
|
2949
3019
|
}
|
|
2950
3020
|
}
|
|
2951
3021
|
|
|
@@ -2964,6 +3034,9 @@ async function runOnce({ page, payload, log }) {
|
|
|
2964
3034
|
if (fresh2) {
|
|
2965
3035
|
postUrl = fresh2.href.startsWith('http') ? fresh2.href : `https://www.facebook.com${fresh2.href}`;
|
|
2966
3036
|
log('info', `[fb-pw] post URL from reels_tab RETRY (id=${fresh2.id}, timestamp="${fresh2.timestamp}"): ${postUrl}`);
|
|
3037
|
+
} else if (networkCandidate && (await scrapeReelTileIds(page)).includes(networkCandidate)) {
|
|
3038
|
+
postUrl = `https://www.facebook.com/reel/${networkCandidate}/`;
|
|
3039
|
+
log('info', `[fb-pw] post URL: ứng viên gói mạng đã được tab Reels xác nhận sau lượt chờ: ${postUrl}`);
|
|
2967
3040
|
} else if (reelIdsBefore) {
|
|
2968
3041
|
const after2 = await scrapeReelTileIds(page);
|
|
2969
3042
|
const diff2 = diffNewReelIds(reelIdsBefore, after2);
|
|
@@ -3107,6 +3180,8 @@ module.exports.__testables = {
|
|
|
3107
3180
|
resilientClick,
|
|
3108
3181
|
confirmPublishDialog,
|
|
3109
3182
|
diffNewReelIds,
|
|
3183
|
+
pickReelsTabUrl,
|
|
3184
|
+
isComposerDialogSignature,
|
|
3110
3185
|
discoverReelsTabUrl,
|
|
3111
3186
|
scrapeReelTileIds,
|
|
3112
3187
|
muteClickInterceptors,
|