channel-worker 2.5.45 → 2.5.46

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.45",
3
+ "version": "2.5.46",
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,44 @@ const { assertAccountUsable } = require('./lib/fb-guard');
25
25
  function randInt(min, max) { return Math.floor(min + Math.random() * (max - min + 1)); }
26
26
  function chance(p) { return Math.random() < p; }
27
27
 
28
+ // Park the cursor over the middle of the feed column. MUST be done before any
29
+ // wheel event: mouse.wheel scrolls whatever is under the pointer, and the
30
+ // pointer starts at (0,0) — FB's fixed top-nav — so every wheel tick landed on
31
+ // a non-scrolling element. Measured on the first live session: 3 posts in 9.6
32
+ // minutes, six "feed idle" bailouts, because the page never actually moved.
33
+ async function centerMouse(page) {
34
+ const vp = await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight })).catch(() => null);
35
+ if (!vp) return;
36
+ const jitter = (n) => n + randInt(-40, 40);
37
+ await page.mouse.move(jitter(Math.floor(vp.w / 2)), jitter(Math.floor(vp.h / 2))).catch(() => {});
38
+ }
39
+
40
+ // Scroll by `dy` and VERIFY the page moved. Falls back through three rungs:
41
+ // wheel → re-center the cursor and wheel again → window.scrollBy. Returns the
42
+ // distance actually scrolled.
43
+ async function humanScroll(page, dy) {
44
+ const readY = () => page.evaluate(() => window.scrollY || document.documentElement.scrollTop || 0).catch(() => 0);
45
+ const before = await readY();
46
+ await page.mouse.wheel(0, dy).catch(() => {});
47
+ await page.waitForTimeout(randInt(350, 700));
48
+ let after = await readY();
49
+ if (Math.abs(after - before) < 5) {
50
+ await centerMouse(page);
51
+ await page.mouse.wheel(0, dy).catch(() => {});
52
+ await page.waitForTimeout(randInt(350, 700));
53
+ after = await readY();
54
+ }
55
+ if (Math.abs(after - before) < 5) {
56
+ // Last resort — a programmatic scroll. Not a trusted event, but scrolling
57
+ // isn't gated on trust the way clicks are, and a feed that never moves is
58
+ // worth less than a feed that moves synthetically.
59
+ await page.evaluate((d) => window.scrollBy(0, d), dy).catch(() => {});
60
+ await page.waitForTimeout(randInt(300, 600));
61
+ after = await readY();
62
+ }
63
+ return Math.abs(after - before);
64
+ }
65
+
28
66
  // Dismiss FB cookie / cross-sell / "not now" popups. Best-effort, never throws.
29
67
  // Never touches the composer or a reel viewer.
30
68
  async function dismissDialogs(page, log) {
@@ -211,8 +249,9 @@ async function openPostAndRead(page, log) {
211
249
  await page.waitForTimeout(randInt(2500, 4000));
212
250
 
213
251
  // Read comments: a couple of small scrolls with human pauses.
252
+ await centerMouse(page);
214
253
  for (let i = 0; i < randInt(2, 4); i++) {
215
- await page.mouse.wheel(0, randInt(200, 600)).catch(() => {});
254
+ await humanScroll(page, randInt(200, 600));
216
255
  await page.waitForTimeout(randInt(1500, 4000));
217
256
  }
218
257
  log('info', '[nurture-fb] opened a post and read comments');
@@ -288,9 +327,11 @@ async function run({ page, payload, log }) {
288
327
  await assertAccountUsable(page, log);
289
328
  await dismissDialogs(page, log);
290
329
 
330
+ await centerMouse(page); // wheel events need the cursor over the feed column
331
+
291
332
  let postsSeen = 0, likes = 0, videos = 0, pagesFollowed = 0, postsOpened = 0;
292
333
  let postsSinceLike = likeGap; // allow the first like once enough posts scroll by
293
- let ticks = 0, emptyTicks = 0;
334
+ let ticks = 0, emptyTicks = 0, stuckScrolls = 0;
294
335
  const deadline = t0 + sessionSec * 1000;
295
336
 
296
337
  while (Date.now() < deadline) {
@@ -339,16 +380,26 @@ async function run({ page, payload, log }) {
339
380
  if (roll < 0.15) {
340
381
  await page.waitForTimeout(randInt(6000, 20000)); // stop and read
341
382
  } else if (roll < 0.23) {
342
- await page.mouse.wheel(0, -randInt(150, 400)).catch(() => {});
383
+ await humanScroll(page, -randInt(150, 400)); // re-read what just passed
343
384
  await page.waitForTimeout(randInt(1200, 3000));
344
385
  } else {
345
- await page.mouse.wheel(0, randInt(250, 900)).catch(() => {});
386
+ const moved = await humanScroll(page, randInt(250, 900));
387
+ if (moved < 5) stuckScrolls++; else stuckScrolls = 0;
346
388
  await page.waitForTimeout(randInt(800, 4000));
347
389
  }
348
390
 
349
391
  // Mid-session health check (~ every 12 ticks). FB throws a checkpoint the
350
392
  // moment activity trips a threshold — better to stop than to keep poking.
351
- if (ticks % 12 === 0) await assertAccountUsable(page, log);
393
+ if (ticks % 12 === 0) {
394
+ await assertAccountUsable(page, log);
395
+ log('info', `[nurture-fb] …${Math.round((Date.now() - t0) / 1000)}s: posts=${postsSeen} likes=${likes}/${likesTarget} videos=${videos}`);
396
+ }
397
+
398
+ // Page refuses to move at all → this is not a nurture session, it's ten
399
+ // minutes of pretending. Fail loudly instead of banking a fake session.
400
+ if (stuckScrolls >= 20) {
401
+ throw new Error(`nurture-fb: trang không cuộn được sau ${stuckScrolls} lần thử (posts=${postsSeen}) — layout FB đổi hoặc feed không tải`);
402
+ }
352
403
  }
353
404
 
354
405
  const durationSec = Math.round((Date.now() - t0) / 1000);