channel-worker 2.5.121 → 2.5.123
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
|
@@ -640,7 +640,34 @@ async function isCustomThumbApplied(page) {
|
|
|
640
640
|
if (/Gỡ hình thu nhỏ tùy chỉnh|Remove( your)? custom thumbnail/i.test(al) && vis(el)) return true;
|
|
641
641
|
}
|
|
642
642
|
return false;
|
|
643
|
-
|
|
643
|
+
// `null` = KHÔNG ĐO ĐƯỢC (page.evaluate ném giữa lúc FB dựng lại panel).
|
|
644
|
+
// Bản cũ nuốt thành `false` = "không có thumb", và một mẫu đơn lẻ như thế
|
|
645
|
+
// được quyền bấm Hủy = GỠ ảnh bìa vừa dán. Xem thumbVerdict().
|
|
646
|
+
}).catch(() => null);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Một lần đo KHÔNG được quyết định hành động PHÁ. Đo trên log prod 20/09/2026:
|
|
650
|
+
// trong 152 lượt "hết giờ chờ hộp ảnh bìa đóng", 138 lượt đo thấy thumb đang
|
|
651
|
+
// áp rồi đăng bình thường, còn 14 lượt mẫu CUỐI trả "không áp" (trong khi suốt
|
|
652
|
+
// vòng chờ trước đó mẫu nào cũng "đang áp") → rơi vào nhánh ESC+Hủy, gỡ mất
|
|
653
|
+
// ảnh bìa, rồi chết ở cổng "Custom thumbnail required but not applied".
|
|
654
|
+
// 'applied' — có ít nhất một mẫu thấy nút "Gỡ hình thu nhỏ tùy chỉnh"
|
|
655
|
+
// 'unknown' — không mẫu nào thấy, nhưng có mẫu đo hỏng
|
|
656
|
+
// 'absent' — mọi mẫu đo được và đều không thấy
|
|
657
|
+
function thumbVerdict(samples) {
|
|
658
|
+
const list = Array.isArray(samples) ? samples : [];
|
|
659
|
+
if (list.some((s) => s === true)) return 'applied';
|
|
660
|
+
if (list.some((s) => s === null || s === undefined)) return 'unknown';
|
|
661
|
+
return 'absent';
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async function sampleCustomThumb(page, { samples = 3, gapMs = 1500 } = {}) {
|
|
665
|
+
const out = [];
|
|
666
|
+
for (let i = 0; i < samples; i++) {
|
|
667
|
+
if (i) await pause(page, gapMs);
|
|
668
|
+
out.push(await isCustomThumbApplied(page));
|
|
669
|
+
}
|
|
670
|
+
return { verdict: thumbVerdict(out), samples: out };
|
|
644
671
|
}
|
|
645
672
|
|
|
646
673
|
// Composer đang ở trạng thái gì? Đọc từ CHỮ trong hộp thoại — ba chữ ký nhìn
|
|
@@ -2136,6 +2163,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
2136
2163
|
// doesn't close within 12s of polling, RE-CLICK Lưu (in case
|
|
2137
2164
|
// the first click didn't register because the button was
|
|
2138
2165
|
// disabled while FB was still hashing the upload).
|
|
2166
|
+
let thumbTimeoutSt = { verdict: 'unknown', samples: [] };
|
|
2139
2167
|
const closeDeadline = Date.now() + 60_000;
|
|
2140
2168
|
let modalClosed = false;
|
|
2141
2169
|
let closedPolls = 0;
|
|
@@ -2230,7 +2258,11 @@ async function runOnce({ page, payload, log }) {
|
|
|
2230
2258
|
}
|
|
2231
2259
|
thumbApplied = true;
|
|
2232
2260
|
log('info', `[fb-pw] page-wall thumb — modal closed after save (retries=${retryCount})`);
|
|
2233
|
-
} else if (await
|
|
2261
|
+
} else if (await (async () => {
|
|
2262
|
+
// Giữ kết quả đo để ghi vào log: bằng chứng vì sao KHÔNG Hủy.
|
|
2263
|
+
thumbTimeoutSt = await sampleCustomThumb(page);
|
|
2264
|
+
return thumbTimeoutSt.verdict !== 'absent';
|
|
2265
|
+
})()) {
|
|
2234
2266
|
// Hết giờ chờ đóng nhưng bằng chứng áp thumb còn đó — tuyệt
|
|
2235
2267
|
// đối không ESC/Hủy (Hủy trên panel này là GỠ thumb).
|
|
2236
2268
|
//
|
|
@@ -2241,10 +2273,10 @@ async function runOnce({ page, payload, log }) {
|
|
|
2241
2273
|
// "Custom thumbnail required but not applied" (đo 04/09/2026,
|
|
2242
2274
|
// 3 lượt liên tiếp, log ghi rõ variant=modal). Nút "Gỡ" là
|
|
2243
2275
|
// bằng chứng vật lý thumb đang áp, biến thể nào cũng đúng.
|
|
2244
|
-
log('info', `[fb-pw] page-wall thumb — hết giờ chờ đóng
|
|
2276
|
+
log('info', `[fb-pw] page-wall thumb — hết giờ chờ đóng, đo 3 nhịp ${JSON.stringify(thumbTimeoutSt.samples)} → ${thumbTimeoutSt.verdict} (variant=${thumbEditorVia}) → giữ panel, coi như dán xong, KHÔNG Hủy`);
|
|
2245
2277
|
thumbApplied = true;
|
|
2246
2278
|
} else {
|
|
2247
|
-
log('warn',
|
|
2279
|
+
log('warn', `[fb-pw] page-wall thumb — modal still open 60s after Lưu click, đo 3 nhịp ${JSON.stringify(thumbTimeoutSt.samples)} → CHẮC CHẮN chưa áp; ESC + click Hủy fallback`);
|
|
2248
2280
|
// Chụp lại trước khi ESC/Hủy: đây là nhánh phá lượt nhiều
|
|
2249
2281
|
// nhất mà lại không có ảnh nào để soi (04/09/2026).
|
|
2250
2282
|
await dumpFailure(page, 'thumb-modal-stuck-before-cancel', log);
|
|
@@ -2625,8 +2657,9 @@ async function runOnce({ page, payload, log }) {
|
|
|
2625
2657
|
// customThumbDone lỡ false trong khi nút "Gỡ hình thu nhỏ tùy chỉnh"
|
|
2626
2658
|
// ngay trên bước này chứng minh thumb ĐANG được áp — cả 5 lượt fail
|
|
2627
2659
|
// 02-03/09 của idea 6a965a32 đều ở trạng thái này (diag 158386).
|
|
2628
|
-
|
|
2629
|
-
|
|
2660
|
+
const gateSt = await sampleCustomThumb(page);
|
|
2661
|
+
if (gateSt.verdict === 'applied') {
|
|
2662
|
+
log('info', `[fb-pw] pre-publish: nút "Gỡ hình thu nhỏ tùy chỉnh" đang hiện (${JSON.stringify(gateSt.samples)}) — thumb đã áp dù cờ chưa bật, tiếp tục đăng`);
|
|
2630
2663
|
customThumbDone = true;
|
|
2631
2664
|
} else {
|
|
2632
2665
|
// Defensive: we normally fail at the caption step (before any Tiếp)
|
|
@@ -3501,6 +3534,9 @@ module.exports = { run };
|
|
|
3501
3534
|
// Exposed so the overlay-click escalation can be exercised against a synthetic
|
|
3502
3535
|
// page (toast covering the CTA) without driving the whole upload flow.
|
|
3503
3536
|
module.exports.__testables = {
|
|
3537
|
+
// Phán quyết "ảnh bìa đã áp chưa" từ nhiều mẫu đo — một mẫu đo hỏng không
|
|
3538
|
+
// được biến thành "chưa áp" rồi kéo theo hành động PHÁ (Hủy = gỡ ảnh bìa).
|
|
3539
|
+
thumbVerdict,
|
|
3504
3540
|
// Dùng chung cho comment_facebook: script nào trượt giữa chừng cũng cần chụp
|
|
3505
3541
|
// lại màn hình, viết bản thứ hai là sớm muộn hai bên lệch nhau.
|
|
3506
3542
|
dumpFailure,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// caption hơn, API tự biết là không đủ để ghép và để nguyên cho người.
|
|
12
12
|
//
|
|
13
13
|
// Payload: { channel_id, post_ids: ['1234567890', …] }
|
|
14
|
-
// Trả về: { captions: [{ post_id, og, page_text, url }], read, asked }
|
|
14
|
+
// Trả về: { captions: [{ post_id, og, page_text, blocks, url }], read, asked }
|
|
15
15
|
//
|
|
16
16
|
// Việc SO SÁNH nằm ở API (services/ReelCaptionMatch) chứ không ở đây: một bảng
|
|
17
17
|
// luật, test offline được, và worker không cần biết tiêu đề của idea.
|
|
@@ -23,17 +23,52 @@ const SETTLE_MS = 4000;
|
|
|
23
23
|
// một kênh hiếm khi quá vài cái; nhiều hơn thì để lượt sau, đừng giữ profile lâu.
|
|
24
24
|
const MAX_POSTS = 8;
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
26
|
+
// Đuôi Facebook thêm vào khi cắt caption dài. Phải gỡ, không thì khối chữ
|
|
27
|
+
// không còn là "khúc đầu của tiêu đề" và luật ghép theo tiền tố trượt.
|
|
28
|
+
const MORE_TAIL_RE = /(?:[…\.]{1,3}\s*)?(?:see more|xem thêm|see translation|xem bản dịch)\s*$/i;
|
|
29
|
+
|
|
30
|
+
function cleanBlock(s) {
|
|
31
|
+
return String(s == null ? '' : s)
|
|
32
|
+
.replace(/\s+/g, ' ')
|
|
33
|
+
.trim()
|
|
34
|
+
.replace(MORE_TAIL_RE, '')
|
|
35
|
+
.replace(/[…\s]+$/, '')
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Lấy mọi đường có thể có caption. ĐO TẬN TAY 20/09/2026 trên reel
|
|
40
|
+
// 4007094836262842 (kênh "tán gái", profile kenh-25), trang mở trong phiên ĐÃ
|
|
41
|
+
// ĐĂNG NHẬP:
|
|
42
|
+
// - `document.querySelectorAll('meta[property]')` → **RỖNG**. Trang reel của
|
|
43
|
+
// phiên đăng nhập không có thẻ og nào, nên og luôn 0 ký tự. Giữ lại vì
|
|
44
|
+
// phiên khác (chưa đăng nhập, share link) vẫn có.
|
|
45
|
+
// - `[role='main']` chỉ có **18 ký tự** — caption KHÔNG nằm trong đó. Bản cũ
|
|
46
|
+
// đọc đúng chỗ này nên 3 bài "đăng mập mờ" của kênh treo 2 ngày, ghép=0.
|
|
47
|
+
// - `body.innerText` (331 ký tự) CÓ caption nhưng dính rác đầu trang
|
|
48
|
+
// ("Number of unread notifications 20+ 119 433K 2.4K 1.9K <tên trang> …").
|
|
49
|
+
// - Khối `span[dir='auto']` dài nhất CHÍNH LÀ caption, kèm đuôi "… See more".
|
|
50
|
+
// Nên: trả cả ba — og, page_text (đổi sang body), và `blocks` = các khối
|
|
51
|
+
// dir=auto đã gỡ đuôi, để API so khớp trên từng khối (ReelCaptionMatch).
|
|
28
52
|
async function readCaption(page) {
|
|
29
53
|
return page.evaluate(() => {
|
|
54
|
+
const flat = (t) => String(t || '').replace(/\s+/g, ' ').trim();
|
|
30
55
|
const meta = (p) => (document.querySelector(`meta[property="${p}"]`) || {}).content || '';
|
|
31
|
-
const
|
|
56
|
+
const blocks = [...document.querySelectorAll("span[dir='auto'], div[dir='auto']")]
|
|
57
|
+
.map((el) => flat(el.innerText))
|
|
58
|
+
.filter((t) => t.length >= 20)
|
|
59
|
+
.sort((a, b) => b.length - a.length)
|
|
60
|
+
.slice(0, 6);
|
|
32
61
|
return {
|
|
33
62
|
og: (meta('og:description') || meta('og:title') || '').slice(0, 1000),
|
|
34
|
-
page_text:
|
|
63
|
+
page_text: flat(document.body && document.body.innerText).slice(0, 1500),
|
|
64
|
+
blocks,
|
|
35
65
|
};
|
|
36
|
-
}).
|
|
66
|
+
}).then((c) => ({
|
|
67
|
+
og: cleanBlock(c.og).slice(0, 1000),
|
|
68
|
+
page_text: c.page_text,
|
|
69
|
+
// Bỏ trùng sau khi gỡ đuôi: khối cha và khối con thường ra cùng một chuỗi.
|
|
70
|
+
blocks: [...new Set((c.blocks || []).map(cleanBlock).filter((t) => t.length >= 20))].slice(0, 6),
|
|
71
|
+
})).catch(() => ({ og: '', page_text: '', blocks: [] }));
|
|
37
72
|
}
|
|
38
73
|
|
|
39
74
|
async function run({ page, payload = {}, log }) {
|
|
@@ -50,9 +85,10 @@ async function run({ page, payload = {}, log }) {
|
|
|
50
85
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: NAV_TIMEOUT_MS });
|
|
51
86
|
await page.waitForTimeout(SETTLE_MS);
|
|
52
87
|
const c = await readCaption(page);
|
|
53
|
-
captions.push({ post_id: id, og: c.og, page_text: c.page_text, url });
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
captions.push({ post_id: id, og: c.og, page_text: c.page_text, blocks: c.blocks, url });
|
|
89
|
+
const best = (c.blocks && c.blocks[0]) || c.og || '';
|
|
90
|
+
log('info', `[fb-verify] reel ${id}: og ${c.og.length} ký tự, trang ${c.page_text.length} ký tự, `
|
|
91
|
+
+ `${(c.blocks || []).length} khối` + (best ? ` — "${best.slice(0, 60)}"` : ' — KHÔNG đọc được chữ nào'));
|
|
56
92
|
} catch (e) {
|
|
57
93
|
// Một reel đọc hỏng KHÔNG được giết cả lượt: các reel còn lại vẫn ghép được.
|
|
58
94
|
log('warn', `[fb-verify] reel ${id} đọc hỏng: ${String(e && e.message || e).slice(0, 90)}`);
|
|
@@ -63,4 +99,4 @@ async function run({ page, payload = {}, log }) {
|
|
|
63
99
|
}
|
|
64
100
|
|
|
65
101
|
module.exports = { run };
|
|
66
|
-
module.exports.__testables = { readCaption, MAX_POSTS, SETTLE_MS };
|
|
102
|
+
module.exports.__testables = { readCaption, cleanBlock, MORE_TAIL_RE, MAX_POSTS, SETTLE_MS };
|