channel-worker 2.5.135 → 2.5.136
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 +178 -40
package/package.json
CHANGED
|
@@ -744,6 +744,14 @@ function isNewComposerUrl(url) {
|
|
|
744
744
|
// Chữ trên ô "tải ảnh bìa của mình lên" — khớp NGUYÊN chuỗi (sau khi gộp
|
|
745
745
|
// khoảng trắng) để không dính caption hay nhãn mục "Hình thu nhỏ".
|
|
746
746
|
const THUMB_PICKER_TEXT_RE = /^(?:Chọn hình thu nhỏ|Chọn ảnh thu nhỏ|Thêm hình thu nhỏ|Tải hình thu nhỏ lên|(?:Choose|Select|Add|Upload) (?:a )?(?:custom )?thumbnail)$/i;
|
|
747
|
+
// Biến thể thứ ba (đo thật 23/09, page Gà Vườn Mỗi Ngày, wizard
|
|
748
|
+
// /reels/create/?surface=ADDL_PROFILE_PLUS): KHÔNG có hàng ô, ảnh bìa là một
|
|
749
|
+
// nút nhỏ 72x20 chữ "Ảnh bìa", aria-label "Chọn ảnh bìa", có ở cả bước
|
|
750
|
+
// "Chỉnh sửa" lẫn "Cài đặt". Tách regex riêng: nút này không nằm trong hàng ô
|
|
751
|
+
// nào, đi luồng riêng (applyCoverButtonThumb) và xác minh bằng nội dung ảnh.
|
|
752
|
+
const COVER_BUTTON_RE = /^(?:Chọn ảnh bìa|(?:Choose|Select|Add|Edit) (?:a )?cover(?: photo| image)?)$/i;
|
|
753
|
+
// Tiêu đề hộp thoại có thể mở ra từ nút đó — chưa thấy tận mắt, nhận rộng.
|
|
754
|
+
const COVER_HEAD_RE = /^(?:Chọn ảnh bìa|Ảnh bìa|Chỉnh sửa ảnh bìa|(?:Choose |Select |Edit )?(?:a )?cover(?: photo| image)?)$/i;
|
|
747
755
|
|
|
748
756
|
// Đọc hàng ô ảnh bìa của composer mới. Trả về ảnh chụp trạng thái để so
|
|
749
757
|
// trước/sau khi dán (xem newComposerThumbVerdict), và gắn cờ DOM cho:
|
|
@@ -753,7 +761,7 @@ const THUMB_PICKER_TEXT_RE = /^(?:Chọn hình thu nhỏ|Chọn ảnh thu nhỏ|
|
|
|
753
761
|
// __fbpw_thumb_tile__ — ô thứ `tagIndex` (dùng khi phải bấm chọn lại)
|
|
754
762
|
// Không bao giờ ném: đọc hỏng trả null, như isCustomThumbApplied.
|
|
755
763
|
async function readNewComposerThumbRow(page, { diag = false, tagIndex = -1 } = {}) {
|
|
756
|
-
return page.evaluate(({ pickSrc, diag, tagIndex }) => {
|
|
764
|
+
return page.evaluate(({ pickSrc, coverSrc, diag, tagIndex }) => {
|
|
757
765
|
const PICK = new RegExp(pickSrc, 'i');
|
|
758
766
|
const norm = (s) => String(s || '').replace(/\s+/g, ' ').trim();
|
|
759
767
|
const vis = (el) => {
|
|
@@ -880,6 +888,23 @@ async function readNewComposerThumbRow(page, { diag = false, tagIndex = -1 } = {
|
|
|
880
888
|
}
|
|
881
889
|
const customMarker = [...document.querySelectorAll('[aria-label]')].some((el) =>
|
|
882
890
|
/Gỡ hình thu nhỏ tùy chỉnh|Remove( your)? custom thumbnail/i.test(el.getAttribute('aria-label') || '') && vis(el));
|
|
891
|
+
if (!row && !pickLeaf) {
|
|
892
|
+
// Không có ô "Chọn hình thu nhỏ" → thử nút "Chọn ảnh bìa" (biến thể wizard).
|
|
893
|
+
const COVER = new RegExp(coverSrc, 'i');
|
|
894
|
+
for (const root of roots) {
|
|
895
|
+
const btn = [...root.querySelectorAll('[aria-label]')].find((el) => vis(el)
|
|
896
|
+
&& COVER.test(norm(el.getAttribute('aria-label'))) && !el.closest('[__fbpw_thumb_modal__]'));
|
|
897
|
+
if (!btn) continue;
|
|
898
|
+
let target = btn;
|
|
899
|
+
for (let n = btn, d = 0; n && d < 3; n = n.parentElement, d++) {
|
|
900
|
+
if (n.matches("[role='button'], button, a, [tabindex]")) { target = n; break; }
|
|
901
|
+
}
|
|
902
|
+
target.setAttribute('__fbpw_thumb_pick__', '1');
|
|
903
|
+
const r = btn.getBoundingClientRect();
|
|
904
|
+
return { found: false, pickerPresent: true, mode: 'cover-button', roots: roots.length, customMarker,
|
|
905
|
+
cover: { aria: norm(btn.getAttribute('aria-label')), text: norm(btn.innerText).slice(0, 30), size: `${Math.round(r.width)}x${Math.round(r.height)}` } };
|
|
906
|
+
}
|
|
907
|
+
}
|
|
883
908
|
if (!row) {
|
|
884
909
|
const miss = { found: false, pickerPresent: !!pickLeaf, roots: roots.length, customMarker };
|
|
885
910
|
if (diag) {
|
|
@@ -954,7 +979,7 @@ async function readNewComposerThumbRow(page, { diag = false, tagIndex = -1 } = {
|
|
|
954
979
|
});
|
|
955
980
|
}
|
|
956
981
|
return res;
|
|
957
|
-
}, { pickSrc: THUMB_PICKER_TEXT_RE.source, diag, tagIndex }).catch(() => null);
|
|
982
|
+
}, { pickSrc: THUMB_PICKER_TEXT_RE.source, coverSrc: COVER_BUTTON_RE.source, diag, tagIndex }).catch(() => null);
|
|
958
983
|
}
|
|
959
984
|
|
|
960
985
|
// Ảnh bìa tự chọn đã VÀO composer mới chưa? So hàng ô trước và sau khi dán
|
|
@@ -997,6 +1022,7 @@ function newComposerThumbVerdict(before, after) {
|
|
|
997
1022
|
// Tóm tắt một hàng ô cho log: "5 ô [chọn-ảnh, *khung, khung…] chọn=1 input=image/*".
|
|
998
1023
|
function describeThumbRow(snap) {
|
|
999
1024
|
if (!snap) return 'không đọc được';
|
|
1025
|
+
if (snap.mode === 'cover-button') return `không có hàng ô — nút ảnh bìa "${snap.cover.text || snap.cover.aria}" (aria "${snap.cover.aria}", ${snap.cover.size})`;
|
|
1000
1026
|
if (!snap.found) return `không thấy hàng ô (composer=${snap.roots || 0}, ô chọn-ảnh=${snap.pickerPresent ? 'có' : 'không'})`;
|
|
1001
1027
|
const cells = (snap.tiles || []).map((t) => `${t.selected ? '*' : ''}${t.placeholder ? 'chọn-ảnh' : (t.img ? (/^(blob|data):/.test(t.img) ? 'ảnh-local' : 'khung') : '?')}`);
|
|
1002
1028
|
return `${cells.length} ô [${cells.join(', ')}] chọn=${snap.selectedIndex ?? '?'} input=${snap.input ? snap.input.accept || '?' : 'không'} via=${snap.via}`;
|
|
@@ -1047,8 +1073,10 @@ async function confirmNewComposerThumbDialog(page, log) {
|
|
|
1047
1073
|
// Gắn cờ: __fbpw_thumb_modal__ (khối), _upload__ (nút Tải lên), _save__ (mọi
|
|
1048
1074
|
// nút Lưu, đánh số từ cuối), _input__ (input ảnh). Trả null nếu không thấy.
|
|
1049
1075
|
async function findThumbPickerModal(page) {
|
|
1050
|
-
return page.evaluate(({ pickSrc }) => {
|
|
1076
|
+
return page.evaluate(({ pickSrc, coverHeadSrc }) => {
|
|
1051
1077
|
const PICK = new RegExp(pickSrc, 'i');
|
|
1078
|
+
const COVER_HEAD = new RegExp(coverHeadSrc, 'i');
|
|
1079
|
+
const isHead = (t) => PICK.test(t) || COVER_HEAD.test(t);
|
|
1052
1080
|
const norm = (s) => String(s || '').replace(/\s+/g, ' ').trim();
|
|
1053
1081
|
const vis = (el) => {
|
|
1054
1082
|
const r = el.getBoundingClientRect();
|
|
@@ -1061,17 +1089,18 @@ async function findThumbPickerModal(page) {
|
|
|
1061
1089
|
}
|
|
1062
1090
|
const says = (el, re) => re.test(norm(el.getAttribute('aria-label'))) || re.test(norm(el.innerText));
|
|
1063
1091
|
const UP = /^(Tải lên|Tải ảnh lên|Upload|Upload image)$/i;
|
|
1064
|
-
const SAVE = /^(Lưu|Save)$/i;
|
|
1092
|
+
const SAVE = /^(Lưu|Save|Xong|Done|Áp dụng|Apply)$/i;
|
|
1065
1093
|
const POST = /^(Đăng|Post|Share now)$/i;
|
|
1066
|
-
const heads = [...document.querySelectorAll('[aria-label]')].filter((el) =>
|
|
1094
|
+
const heads = [...document.querySelectorAll('[aria-label]')].filter((el) => isHead(norm(el.getAttribute('aria-label'))) && vis(el));
|
|
1067
1095
|
const tw = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
1068
1096
|
for (let n = tw.nextNode(); n; n = tw.nextNode()) {
|
|
1069
|
-
if (!/hình|thumbnail/i.test(n.data)) continue;
|
|
1097
|
+
if (!/hình|thumbnail|ảnh bìa|cover/i.test(n.data)) continue;
|
|
1070
1098
|
const el = n.parentElement;
|
|
1071
|
-
if (el && vis(el) &&
|
|
1099
|
+
if (el && vis(el) && isHead(norm(el.innerText))) heads.push(el);
|
|
1072
1100
|
}
|
|
1073
1101
|
for (const h of heads) {
|
|
1074
1102
|
if (h.closest('[__fbpw_thumb_row__]')) continue; // chính ô chọn-ảnh trong hàng
|
|
1103
|
+
if (h.closest('[__fbpw_thumb_pick__]')) continue; // chính nút "Chọn ảnh bìa"
|
|
1075
1104
|
let box = h;
|
|
1076
1105
|
for (let d = 0; d < 10 && box && box !== document.body; d++, box = box.parentElement) {
|
|
1077
1106
|
const btns = [...box.querySelectorAll("[role='button'], button")].filter(vis);
|
|
@@ -1102,7 +1131,7 @@ async function findThumbPickerModal(page) {
|
|
|
1102
1131
|
}
|
|
1103
1132
|
}
|
|
1104
1133
|
return null;
|
|
1105
|
-
}, { pickSrc: THUMB_PICKER_TEXT_RE.source }).catch(() => null);
|
|
1134
|
+
}, { pickSrc: THUMB_PICKER_TEXT_RE.source, coverHeadSrc: COVER_HEAD_RE.source }).catch(() => null);
|
|
1106
1135
|
}
|
|
1107
1136
|
|
|
1108
1137
|
// Ảnh xem trước trong hộp thoại có đúng là ảnh bìa của MÌNH không? Thu cả hai
|
|
@@ -1195,7 +1224,7 @@ async function uploadViaThumbPickerModal(page, thumbPath, log, modal, { previewM
|
|
|
1195
1224
|
while (Date.now() - t0 < previewMs) {
|
|
1196
1225
|
await pause(page, 1500);
|
|
1197
1226
|
m = await findThumbPickerModal(page);
|
|
1198
|
-
if (!m) return { ok: true, via, reason: 'hộp thoại tự đóng sau khi nhận ảnh' };
|
|
1227
|
+
if (!m) return { ok: true, via, verified: false, reason: 'hộp thoại tự đóng sau khi nhận ảnh' };
|
|
1199
1228
|
if (!m.preview || m.preview === beforePreview) continue;
|
|
1200
1229
|
// URL đổi rồi — so NỘI DUNG. Lệch nhiều = FB đổi khung chứ chưa phải ảnh
|
|
1201
1230
|
// của mình: chờ tiếp. Không so được (ảnh CDN khoá canvas) thì rơi về luật
|
|
@@ -1232,23 +1261,152 @@ async function uploadViaThumbPickerModal(page, thumbPath, log, modal, { previewM
|
|
|
1232
1261
|
while (Date.now() - t1 < closeMs) {
|
|
1233
1262
|
await pause(page, 1500);
|
|
1234
1263
|
if (await findThumbPickerModal(page)) { gone = 0; continue; }
|
|
1235
|
-
if (++gone >= 2) return { ok: true, via };
|
|
1264
|
+
if (++gone >= 2) return { ok: true, via, verified: !!(lastCmp && lastCmp.diff !== undefined) };
|
|
1236
1265
|
}
|
|
1237
1266
|
log('warn', `[fb-pw] composer mới — hộp thoại "Chọn hình thu nhỏ" chưa đóng ${Math.round(closeMs / 1000)}s sau khi bấm Lưu`);
|
|
1238
1267
|
return { ok: false, reason: 'modal-not-closed' };
|
|
1239
1268
|
}
|
|
1240
1269
|
|
|
1270
|
+
// Bấm nút/ô chọn ảnh bìa (đã gắn cờ __fbpw_thumb_pick__) rồi chờ MỘT trong
|
|
1271
|
+
// hai: hộp chọn file (biến thể mở thẳng) hoặc hộp thoại chọn ảnh (biến thể đo
|
|
1272
|
+
// thật 23/09). Trả { via, verified, failed, reason }: `via` rỗng = chưa đưa được
|
|
1273
|
+
// file vào (người gọi còn đường dự phòng); `failed` = hộp thoại đã mở mà dán hỏng.
|
|
1274
|
+
async function clickPickerAndUpload(page, thumbPath, log) {
|
|
1275
|
+
let chooser = null;
|
|
1276
|
+
const chooserP = page.waitForEvent('filechooser', { timeout: 8000 }).then((c) => { chooser = c; }).catch(() => {});
|
|
1277
|
+
let clickErr = '';
|
|
1278
|
+
await page.locator("[__fbpw_thumb_pick__='1']").first().click({ timeout: 5000 }).catch((e) => { clickErr = String(e && e.message || e).slice(0, 80); });
|
|
1279
|
+
let modal = null;
|
|
1280
|
+
const tClick = Date.now();
|
|
1281
|
+
while (!chooser && !modal && Date.now() - tClick < 8000) {
|
|
1282
|
+
await pause(page, 800);
|
|
1283
|
+
modal = await findThumbPickerModal(page);
|
|
1284
|
+
}
|
|
1285
|
+
void chooserP;
|
|
1286
|
+
if (chooser) {
|
|
1287
|
+
try {
|
|
1288
|
+
await chooser.setFiles(thumbPath);
|
|
1289
|
+
return { via: 'hộp chọn file', verified: false };
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
log('info', `[fb-pw] composer mới — hộp chọn file từ chối ảnh: ${String(e && e.message || e).slice(0, 80)}`);
|
|
1292
|
+
return { via: '' };
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (modal) {
|
|
1296
|
+
const r = await uploadViaThumbPickerModal(page, thumbPath, log, modal);
|
|
1297
|
+
if (!r.ok) {
|
|
1298
|
+
log('warn', `[fb-pw] composer mới — dán qua hộp thoại chọn ảnh bìa KHÔNG xong (${r.reason})`);
|
|
1299
|
+
return { via: '', failed: true, reason: r.reason };
|
|
1300
|
+
}
|
|
1301
|
+
return { via: r.via + (r.reason ? ` (${r.reason})` : ''), verified: !!r.verified };
|
|
1302
|
+
}
|
|
1303
|
+
log('info', `[fb-pw] composer mới — bấm chỗ chọn ảnh bìa không mở hộp chọn file lẫn hộp thoại${clickErr ? `: ${clickErr}` : ''}`);
|
|
1304
|
+
return { via: '' };
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// Ảnh bìa của MÌNH có đang hiện trong composer không? Quét mọi ảnh ≥40px trong
|
|
1308
|
+
// composer (dialog Reel + vùng main của trang tạo), trừ hộp thoại chọn ảnh, so
|
|
1309
|
+
// nội dung 16x16 với file ảnh bìa như previewMatchesThumb. Dùng cho biến thể nút
|
|
1310
|
+
// "Chọn ảnh bìa": không có hàng ô để so trước/sau, nên bằng chứng duy nhất đứng
|
|
1311
|
+
// vững là thấy chính ảnh đó. Ảnh CDN khác nguồn khoá canvas → không tính.
|
|
1312
|
+
async function composerShowsThumb(page, thumbDataUrl) {
|
|
1313
|
+
return page.evaluate(async ({ ours }) => {
|
|
1314
|
+
const vis = (el) => {
|
|
1315
|
+
const r = el.getBoundingClientRect();
|
|
1316
|
+
if (r.width < 8 || r.height < 8) return false;
|
|
1317
|
+
const cs = getComputedStyle(el);
|
|
1318
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
1319
|
+
};
|
|
1320
|
+
const roots = [...document.querySelectorAll("[role='dialog']")].filter((d) => vis(d)
|
|
1321
|
+
&& /thước phim|reel/i.test(`${d.getAttribute('aria-label') || ''}\n${(d.innerText || '').slice(0, 600)}`));
|
|
1322
|
+
const main = document.querySelector("[role='main'], main");
|
|
1323
|
+
if (main && vis(main)) roots.push(main);
|
|
1324
|
+
const sig = (img) => {
|
|
1325
|
+
const c = document.createElement('canvas');
|
|
1326
|
+
c.width = 16; c.height = 16;
|
|
1327
|
+
const x = c.getContext('2d');
|
|
1328
|
+
x.drawImage(img, 0, 0, 16, 16);
|
|
1329
|
+
return x.getImageData(0, 0, 16, 16).data;
|
|
1330
|
+
};
|
|
1331
|
+
const mine = await new Promise((res) => { const i = new Image(); i.onload = () => res(i); i.onerror = () => res(null); i.src = ours; });
|
|
1332
|
+
if (!mine) return { err: 'thumb-not-decodable' };
|
|
1333
|
+
const b = sig(mine);
|
|
1334
|
+
const seen = new Set();
|
|
1335
|
+
let readable = 0, locked = 0, best = null;
|
|
1336
|
+
for (const root of roots) {
|
|
1337
|
+
for (const im of root.querySelectorAll('img')) {
|
|
1338
|
+
if (seen.has(im) || im.closest('[__fbpw_thumb_modal__]')) continue;
|
|
1339
|
+
seen.add(im);
|
|
1340
|
+
const r = im.getBoundingClientRect();
|
|
1341
|
+
if (r.width < 40 || r.height < 40 || !vis(im) || !im.complete || !im.naturalWidth) continue;
|
|
1342
|
+
let a;
|
|
1343
|
+
try { a = sig(im); } catch { locked++; continue; }
|
|
1344
|
+
readable++;
|
|
1345
|
+
let d = 0;
|
|
1346
|
+
for (let k = 0; k < a.length; k += 4) d += Math.abs(a[k] - b[k]) + Math.abs(a[k + 1] - b[k + 1]) + Math.abs(a[k + 2] - b[k + 2]);
|
|
1347
|
+
d = Math.round(d / (256 * 3));
|
|
1348
|
+
if (!best || d < best.diff) best = { diff: d, size: `${Math.round(r.width)}x${Math.round(r.height)}` };
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return { readable, locked, best };
|
|
1352
|
+
}, { ours: thumbDataUrl }).catch((e) => ({ err: String(e && e.message || e).slice(0, 60) }));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Biến thể nút "Chọn ảnh bìa" (wizard, không có hàng ô). Chỉ nhận là đã dán khi:
|
|
1356
|
+
// a) hộp thoại chọn ảnh đã cho thấy ĐÚNG ảnh của mình (so nội dung) rồi mới
|
|
1357
|
+
// Lưu và đóng, hoặc
|
|
1358
|
+
// b) sau khi dán, composer hiện một ảnh giống ảnh bìa của mình (lệch ≤ ngưỡng)
|
|
1359
|
+
// hai nhịp liền, hoặc nút "Gỡ hình thu nhỏ tùy chỉnh" hiện.
|
|
1360
|
+
// Không thì trả không-dán, cổng trước Đăng chặn như mọi biến thể khác.
|
|
1361
|
+
async function applyCoverButtonThumb(page, thumbPath, log, snap, { verifyMs = 30_000 } = {}) {
|
|
1362
|
+
log('info', `[fb-pw] composer mới — ${describeThumbRow(snap)} → dán ảnh bìa qua nút này`);
|
|
1363
|
+
let thumbDataUrl = '';
|
|
1364
|
+
try { thumbDataUrl = `data:image/png;base64,${fs.readFileSync(thumbPath).toString('base64')}`; } catch { /* composerShowsThumb sẽ báo thumb-not-decodable */ }
|
|
1365
|
+
const base = await composerShowsThumb(page, thumbDataUrl);
|
|
1366
|
+
const up = await clickPickerAndUpload(page, thumbPath, log);
|
|
1367
|
+
if (up.failed || !up.via) {
|
|
1368
|
+
const d = await readNewComposerThumbRow(page, { diag: true });
|
|
1369
|
+
log('warn', `[fb-pw] composer mới — nút ảnh bìa: KHÔNG đưa được file vào (${up.reason || 'không mở được chỗ chọn ảnh'})${d && d.inventory ? ` nút=${JSON.stringify(d.inventory)}` : ''}`);
|
|
1370
|
+
return { applied: false, reason: up.reason || 'no-upload' };
|
|
1371
|
+
}
|
|
1372
|
+
if (up.verified) {
|
|
1373
|
+
log('info', `[fb-pw] composer mới — ảnh bìa đã vào qua ${up.via}: hộp thoại cho thấy đúng ảnh của mình rồi mới Lưu`);
|
|
1374
|
+
return { applied: true, reason: 'hộp thoại xác nhận đúng ảnh' };
|
|
1375
|
+
}
|
|
1376
|
+
log('info', `[fb-pw] composer mới — đã đưa file ảnh bìa vào qua ${up.via}, tìm chính ảnh đó trong composer…`);
|
|
1377
|
+
let streak = 0;
|
|
1378
|
+
let scan = null;
|
|
1379
|
+
const deadline = Date.now() + verifyMs;
|
|
1380
|
+
while (Date.now() < deadline) {
|
|
1381
|
+
await pause(page, 2500);
|
|
1382
|
+
if (await isCustomThumbApplied(page)) { if (++streak >= 2) break; continue; }
|
|
1383
|
+
scan = await composerShowsThumb(page, thumbDataUrl);
|
|
1384
|
+
if (scan.best && scan.best.diff <= THUMB_PREVIEW_MAX_DIFF) { if (++streak >= 2) break; continue; }
|
|
1385
|
+
streak = 0;
|
|
1386
|
+
}
|
|
1387
|
+
const fmt = (x) => (x && !x.err ? `đọc được ${x.readable} ảnh, khoá ${x.locked}, gần nhất lệch ${x.best ? `${x.best.diff}/255 (${x.best.size})` : '—'}` : `lỗi ${x && x.err}`);
|
|
1388
|
+
if (streak >= 2) {
|
|
1389
|
+
log('info', `[fb-pw] composer mới — ảnh bìa đã vào: composer hiện đúng ảnh của mình (${fmt(scan)}; trước khi dán: ${fmt(base)})`);
|
|
1390
|
+
return { applied: true, reason: 'composer hiện đúng ảnh' };
|
|
1391
|
+
}
|
|
1392
|
+
const d = await readNewComposerThumbRow(page, { diag: true });
|
|
1393
|
+
log('warn', `[fb-pw] composer mới — nút ảnh bìa: KHÔNG thấy ảnh của mình trong composer sau ${Math.round(verifyMs / 1000)}s (${fmt(scan)}; trước khi dán: ${fmt(base)})${d && d.inventory ? ` nút=${JSON.stringify(d.inventory)}` : ''}`);
|
|
1394
|
+
return { applied: false, reason: 'cover-not-shown' };
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1241
1397
|
// Dán ảnh bìa vào composer mới. Trả { applied, reason }. Không ném: thất bại
|
|
1242
1398
|
// thì cờ customThumbDone không bật và cổng trước Đăng chặn như cũ.
|
|
1243
1399
|
async function applyNewComposerThumb(page, thumbPath, log, { waitPickerMs = 20_000, verifyMs = 30_000 } = {}) {
|
|
1244
1400
|
const t0 = Date.now();
|
|
1245
1401
|
const ready = (s) => !!(s && s.found && s.pickerPresent);
|
|
1402
|
+
const coverMode = (s) => !!(s && s.mode === 'cover-button');
|
|
1246
1403
|
let snap = await readNewComposerThumbRow(page);
|
|
1247
1404
|
// Hàng ảnh bìa chỉ hiện sau khi FB cắt xong khung từ video.
|
|
1248
|
-
while (!ready(snap) && Date.now() - t0 < waitPickerMs) {
|
|
1405
|
+
while (!ready(snap) && !coverMode(snap) && Date.now() - t0 < waitPickerMs) {
|
|
1249
1406
|
await pause(page, 2500);
|
|
1250
1407
|
snap = await readNewComposerThumbRow(page);
|
|
1251
1408
|
}
|
|
1409
|
+
if (coverMode(snap)) return applyCoverButtonThumb(page, thumbPath, log, snap, { verifyMs });
|
|
1252
1410
|
if (!ready(snap)) {
|
|
1253
1411
|
const d = await readNewComposerThumbRow(page, { diag: true });
|
|
1254
1412
|
log('info', `[fb-pw] composer mới — chưa thấy ô "Chọn hình thu nhỏ" sau ${Math.round((Date.now() - t0) / 1000)}s: ${describeThumbRow(d)}${d && d.skeleton ? ` skeleton=${JSON.stringify(d.skeleton)}` : ''}${d && d.inventory ? ` nút=${JSON.stringify(d.inventory)}` : ''}`);
|
|
@@ -1267,34 +1425,9 @@ async function applyNewComposerThumb(page, thumbPath, log, { waitPickerMs = 20_0
|
|
|
1267
1425
|
const before = snap;
|
|
1268
1426
|
log('info', `[fb-pw] composer mới — hàng ảnh bìa trước khi dán: ${describeThumbRow(before)}`);
|
|
1269
1427
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
let chooser = null;
|
|
1274
|
-
const chooserP = page.waitForEvent('filechooser', { timeout: 8000 }).then((c) => { chooser = c; }).catch(() => {});
|
|
1275
|
-
let clickErr = '';
|
|
1276
|
-
await page.locator("[__fbpw_thumb_pick__='1']").first().click({ timeout: 5000 }).catch((e) => { clickErr = String(e && e.message || e).slice(0, 80); });
|
|
1277
|
-
let modal = null;
|
|
1278
|
-
const tClick = Date.now();
|
|
1279
|
-
while (!chooser && !modal && Date.now() - tClick < 8000) {
|
|
1280
|
-
await pause(page, 800);
|
|
1281
|
-
modal = await findThumbPickerModal(page);
|
|
1282
|
-
}
|
|
1283
|
-
if (chooser) {
|
|
1284
|
-
await chooser.setFiles(thumbPath).then(() => { via = 'hộp chọn file'; })
|
|
1285
|
-
.catch((e) => log('info', `[fb-pw] composer mới — hộp chọn file từ chối ảnh: ${String(e && e.message || e).slice(0, 80)}`));
|
|
1286
|
-
} else if (modal) {
|
|
1287
|
-
const r = await uploadViaThumbPickerModal(page, thumbPath, log, modal);
|
|
1288
|
-
if (!r.ok) {
|
|
1289
|
-
const d = await readNewComposerThumbRow(page, { diag: true });
|
|
1290
|
-
log('warn', `[fb-pw] composer mới — dán qua hộp thoại "Chọn hình thu nhỏ" KHÔNG xong (${r.reason}) — hàng ô: ${describeThumbRow(d)}`);
|
|
1291
|
-
return { applied: false, reason: r.reason };
|
|
1292
|
-
}
|
|
1293
|
-
via = r.via + (r.reason ? ` (${r.reason})` : '');
|
|
1294
|
-
} else {
|
|
1295
|
-
log('info', `[fb-pw] composer mới — bấm ô "Chọn hình thu nhỏ" không mở hộp chọn file lẫn hộp thoại${clickErr ? `: ${clickErr}` : ''}`);
|
|
1296
|
-
}
|
|
1297
|
-
void chooserP;
|
|
1428
|
+
const up = await clickPickerAndUpload(page, thumbPath, log);
|
|
1429
|
+
let via = up.via;
|
|
1430
|
+
if (up.failed) return { applied: false, reason: up.reason };
|
|
1298
1431
|
if (!via) {
|
|
1299
1432
|
await readNewComposerThumbRow(page); // cú bấm có thể làm FB dựng lại DOM → gắn cờ lại
|
|
1300
1433
|
const input = page.locator("[__fbpw_thumb_input__='1']").first();
|
|
@@ -1861,7 +1994,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
1861
1994
|
} = payload || {};
|
|
1862
1995
|
if (!video_url) throw new Error('No video_url provided');
|
|
1863
1996
|
|
|
1864
|
-
log('info', '[fb-pw] selectors version=2026.09.23-
|
|
1997
|
+
log('info', '[fb-pw] selectors version=2026.09.23-cover-button');
|
|
1865
1998
|
|
|
1866
1999
|
page.on('dialog', (d) => { d.accept().catch(() => {}); });
|
|
1867
2000
|
|
|
@@ -4340,6 +4473,11 @@ module.exports.__testables = {
|
|
|
4340
4473
|
uploadViaThumbPickerModal,
|
|
4341
4474
|
previewMatchesThumb,
|
|
4342
4475
|
THUMB_PREVIEW_MAX_DIFF,
|
|
4476
|
+
COVER_BUTTON_RE,
|
|
4477
|
+
COVER_HEAD_RE,
|
|
4478
|
+
composerShowsThumb,
|
|
4479
|
+
applyCoverButtonThumb,
|
|
4480
|
+
clickPickerAndUpload,
|
|
4343
4481
|
// Dùng chung cho comment_facebook: script nào trượt giữa chừng cũng cần chụp
|
|
4344
4482
|
// lại màn hình, viết bản thứ hai là sớm muộn hai bên lệch nhau.
|
|
4345
4483
|
dumpFailure,
|