channel-worker 2.5.112 → 2.5.113

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.112",
3
+ "version": "2.5.113",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {
@@ -25,6 +25,25 @@
25
25
  const { assertAccountUsable, readPublishBlock } = require('./lib/fb-guard');
26
26
  const { humanClick, humanType, pause, randInt } = require('./lib/human');
27
27
 
28
+ // Chụp màn hình lúc trượt — mượn của upload_facebook, nhưng require MUỘN và có
29
+ // đường lùi.
30
+ //
31
+ // Vì sao không require ở đầu file: `lib/playwright-runner` chỉ xoá require.cache
32
+ // của ĐÚNG script được gọi. Chép nóng cả hai file vào máy thì bản
33
+ // comment_facebook mới được nạp lại ngay, còn upload_facebook có thể vẫn là bản
34
+ // CŨ còn nằm trong cache từ lượt đăng trước — bản cũ chưa export `dumpFailure`,
35
+ // require ở đầu file là cả script chết ngay dòng đầu. Chụp được ảnh thì tốt,
36
+ // không chụp được thì thôi, đừng vì nó mà hỏng cả lượt thả bình luận.
37
+ async function dumpFailure(page, tag, log) {
38
+ try {
39
+ const f = require('./upload_facebook').__testables?.dumpFailure;
40
+ if (typeof f === 'function') await f(page, tag, log);
41
+ else log('info', '[fb-cmt] bản upload_facebook đang nạp chưa có dumpFailure — bỏ qua bước chụp màn hình');
42
+ } catch (e) {
43
+ log('info', `[fb-cmt] không chụp được màn hình: ${String(e.message || e).slice(0, 80)}`);
44
+ }
45
+ }
46
+
28
47
  const NAV_TIMEOUT_MS = 45_000;
29
48
  // Bình luận hiện ra gần như tức thì, nhưng lượt đầu sau khi mở bài thì Facebook
30
49
  // còn đang dựng khung bình luận. 20 giây là dư cho trường hợp chậm nhất đo được
@@ -94,6 +113,34 @@ async function openCommentsIfNeeded(page, log) {
94
113
  // Facebook đặt tên tác giả ngay trong aria-label của khối bình luận
95
114
  // ("Bình luận của <Tên>" / "Comment by <Name>") — đọc thẳng ở đó chắc hơn dò
96
115
  // anchor đầu tiên, vì trong khối còn có link tới bài, tới trang được nhắc tên.
116
+ // ĐẾM số chỗ trên trang có chứa chuỗi link, BỎ QUA ô soạn.
117
+ //
118
+ // Đây là cách xác minh chắc hơn hẳn việc dò cấu trúc bình luận: mình đo TRƯỚC
119
+ // khi gõ và ĐO LẠI sau khi gửi, thấy tăng lên là bình luận đã lên. Không cần
120
+ // biết Facebook dựng khung bình luận bằng thẻ gì, cũng không vỡ khi họ đổi.
121
+ //
122
+ // Vì sao phải đổi (15/09): bản đầu đòi khối bình luận mang nhãn
123
+ // `aria-label="Bình luận của <Tên>"` — đó là cấu trúc bài trên BẢNG TIN. Trang
124
+ // reel dựng khác, nên 5/5 lượt thả thật đều báo "không đọc lại thấy" trong khi
125
+ // chủ tịch mở bài ra thì bình luận NẰM ĐÓ. Suýt nữa thì mỗi bài hai bình luận
126
+ // vì nút hiện "Thả lại".
127
+ async function countLinkOnPage(page, link) {
128
+ return page.evaluate((needle) => {
129
+ let n = 0;
130
+ const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
131
+ while (walk.nextNode()) {
132
+ const t = walk.currentNode;
133
+ if (!t.nodeValue || !t.nodeValue.includes(needle)) continue;
134
+ // Chữ mình vừa gõ trong ô soạn không tính.
135
+ if (t.parentElement && t.parentElement.closest("[contenteditable='true']")) continue;
136
+ n++;
137
+ }
138
+ return n;
139
+ }, link).catch(() => -1);
140
+ }
141
+
142
+ // Cố lấy TÊN người bình luận + permalink. Best-effort thuần: không lấy được thì
143
+ // để trống chứ KHÔNG coi là thất bại — xác minh đã do phép đếm ở trên lo.
97
144
  async function findPostedComment(page, link) {
98
145
  return page.evaluate((needle) => {
99
146
  const out = { found: false, author: '', comment_url: '' };
@@ -121,16 +168,22 @@ async function findPostedComment(page, link) {
121
168
  }, link).catch(() => ({ found: false, author: '', comment_url: '' }));
122
169
  }
123
170
 
124
- async function waitForPostedComment(page, link, log, timeoutMs = VERIFY_TIMEOUT_MS) {
171
+ async function waitForPostedComment(page, link, log, timeoutMs = VERIFY_TIMEOUT_MS, baseline = 0) {
125
172
  const deadline = Date.now() + timeoutMs;
126
- let last = { found: false, author: '', comment_url: '' };
173
+ let meta = { found: false, author: '', comment_url: '' };
127
174
  while (Date.now() < deadline) {
128
- last = await findPostedComment(page, link);
129
- if (last.found) return last;
175
+ const n = await countLinkOnPage(page, link);
176
+ // Tăng so với lúc trước khi gõ = bình luận đã lên. Đây mới là bằng chứng;
177
+ // tên tác giả và permalink chỉ là thông tin kèm theo.
178
+ if (n > baseline) {
179
+ meta = await findPostedComment(page, link);
180
+ return { ...meta, found: true, count: n };
181
+ }
130
182
  await page.waitForTimeout(1200);
131
183
  }
132
- log('warn', `[fb-cmt] hết ${Math.round(timeoutMs / 1000)}s vẫn chưa đọc lại thấy bình luận mang link`);
133
- return last;
184
+ const n = await countLinkOnPage(page, link);
185
+ log('warn', `[fb-cmt] hết ${Math.round(timeoutMs / 1000)}s: số chỗ có link vẫn là ${n} (trước khi gõ ${baseline}) — chưa thấy bình luận lên`);
186
+ return { ...meta, found: false, count: n };
134
187
  }
135
188
 
136
189
  // ─── ghim ───────────────────────────────────────────────────────────────────
@@ -217,6 +270,11 @@ async function run({ page, payload = {}, log }) {
217
270
  throw new Error(`Không tìm thấy ô soạn bình luận trên ${postUrl} (đã thử ${BOX_SELECTORS.length} nhãn) — nhiều khả năng bài tắt bình luận hoặc Facebook đổi giao diện`);
218
271
  }
219
272
 
273
+ // Đo NỀN trước khi gõ: link có thể đã nằm sẵn trong caption, hoặc bài đã
274
+ // được thả bình luận từ lượt trước.
275
+ const baseline = Math.max(await countLinkOnPage(page, link), 0);
276
+ if (baseline > 0) log('info', `[fb-cmt] link đã xuất hiện ${baseline} chỗ trên bài từ trước — chỉ tính là xong khi tăng thêm`);
277
+
220
278
  await box.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(() => {});
221
279
  await humanClick(page, box).catch(async () => { await box.click({ timeout: 5000 }); });
222
280
  await pause(page, 900);
@@ -240,9 +298,12 @@ async function run({ page, payload = {}, log }) {
240
298
  throw new Error(`FB_COMMENT_RATE_LIMITED: Facebook chặn bình luận vì tần suất/nghi spam — bình luận KHÔNG được nhận. Chuỗi bắt được: "${block}". Dừng thả link trên kênh này cho tới khi người kiểm tra.`);
241
299
  }
242
300
 
243
- const found = await waitForPostedComment(page, link, log);
301
+ const found = await waitForPostedComment(page, link, log, VERIFY_TIMEOUT_MS, baseline);
244
302
  if (!found.found) {
245
303
  await assertAccountUsable(page, log);
304
+ // Chụp lại màn hình: lần trước trượt xác minh mà không có gì để soi, phải
305
+ // đợi chủ tịch tự mở Facebook mới biết bình luận có lên hay không.
306
+ await dumpFailure(page, 'cmt-verify', log);
246
307
  // KHÔNG ném lỗi kiểu "có thể đã thả": trả về verified=false để API ghi
247
308
  // 'failed' kèm nguyên văn. Ném lỗi ở đây thì lượt chạy lại sẽ gõ thêm một
248
309
  // bình luận nữa, mà bình luận trước có thể đã nằm đó rồi.
@@ -268,4 +329,4 @@ async function run({ page, payload = {}, log }) {
268
329
  }
269
330
 
270
331
  module.exports = { run };
271
- module.exports.__testables = { findPostedComment, findCommentBox, BOX_SELECTORS, sel };
332
+ module.exports.__testables = { findPostedComment, countLinkOnPage, waitForPostedComment, findCommentBox, BOX_SELECTORS, sel };
@@ -3425,6 +3425,9 @@ module.exports = { run };
3425
3425
  // Exposed so the overlay-click escalation can be exercised against a synthetic
3426
3426
  // page (toast covering the CTA) without driving the whole upload flow.
3427
3427
  module.exports.__testables = {
3428
+ // Dùng chung cho comment_facebook: script nào trượt giữa chừng cũng cần chụp
3429
+ // lại màn hình, viết bản thứ hai là sớm muộn hai bên lệch nhau.
3430
+ dumpFailure,
3428
3431
  resilientClick,
3429
3432
  confirmPublishDialog,
3430
3433
  diffNewReelIds,