channel-worker 2.5.32 → 2.5.33

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.32",
3
+ "version": "2.5.33",
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": {
@@ -259,9 +259,30 @@ async function readVideoState(page) {
259
259
  }).catch(() => null);
260
260
  }
261
261
 
262
- // Watch whatever reel/video is currently open (after clicking a result): ensure
263
- // the <video> plays, watch a random span but ADVANCE EARLY if a non-looping
264
- // video ends/stalls (don't sit on a finished video). Mid-view scroll. Best-effort.
262
+ // Is the currently-open reel/video SPONSORED (an ad)? Looks for a visible
263
+ // "Được tài trợ"/"Sponsored" label in the viewport (the reel viewer is
264
+ // fullscreen, so a visible sponsored tag means THIS reel is the ad). Best-effort.
265
+ async function isSponsoredReel(page) {
266
+ return page.evaluate(() => {
267
+ const re = /^(được tài trợ|sponsored|tài trợ)$/i;
268
+ for (const el of document.querySelectorAll("a, span, div")) {
269
+ const t = (el.innerText || el.textContent || '').trim();
270
+ if (!re.test(t)) continue;
271
+ if (el.offsetParent === null) continue;
272
+ const r = el.getBoundingClientRect();
273
+ if (r.width > 0 && r.height > 0 && r.top >= 0 && r.top < window.innerHeight && r.left < window.innerWidth) return true;
274
+ }
275
+ return false;
276
+ }).catch(() => false);
277
+ }
278
+
279
+ // Watch whatever reel/video is currently open (after clicking a result). Rules:
280
+ // - SKIP sponsored/ad reels entirely (don't count) — user: ads shouldn't count.
281
+ // - Count ONLY seconds the video actually ADVANCES — a frozen/buffering video
282
+ // no longer accrues watch time (user: "video đơ vẫn đếm giây"). Loop wrap
283
+ // (currentTime jumps back to ~0) counts as progress.
284
+ // - Advance early if a non-looping video ends; bail if frozen too long.
285
+ // Returns true only if real progressing playback was counted.
265
286
  async function watchCurrent(page, minSec, maxSec, log) {
266
287
  try {
267
288
  await page.waitForTimeout(randInt(2500, 4500)); // let the reel viewer mount
@@ -274,27 +295,39 @@ async function watchCurrent(page, minSec, maxSec, log) {
274
295
  }).catch(() => false);
275
296
  if (!hasVideo) { log('info', '[warmup-fb] no <video> after click — skipping'); return false; }
276
297
 
298
+ // Skip ads — don't watch, don't count.
299
+ if (await isSponsoredReel(page)) { log('info', '[warmup-fb] sponsored/ad reel — skipping (not counted)'); return false; }
300
+
277
301
  const watchMs = randInt(minSec, maxSec) * 1000;
278
302
  log('info', `[warmup-fb] watching reel for ~${Math.round(watchMs / 1000)}s (url=${page.url().slice(-28)})`);
279
303
  const TICK = 1000;
280
- let watched = 0, scrolled = false, lastT = -1, stallTicks = 0;
304
+ let watched = 0; // counts ONLY progressing seconds
305
+ let scrolled = false, lastT = -1, frozenTicks = 0, adReChecks = 0;
281
306
  while (watched < watchMs) {
282
307
  const st = await readVideoState(page);
283
- // A non-looping video that ended/stalled stop waiting, go to next.
284
- if (st && !st.loop) {
285
- if (st.ended || (st.duration > 0 && st.currentTime >= st.duration - 0.6)) {
286
- log('info', `[warmup-fb] reel ended at ${Math.round(st.currentTime)}s — advancing`);
287
- break;
288
- }
289
- if (Math.abs(st.currentTime - lastT) < 0.15) { stallTicks++; if (stallTicks >= 6) { log('info', '[warmup-fb] reel stalled — advancing'); break; } }
290
- else stallTicks = 0;
291
- lastT = st.currentTime;
308
+ // Non-looping video finished → go to next.
309
+ if (st && !st.loop && (st.ended || (st.duration > 0 && st.currentTime >= st.duration - 0.6))) {
310
+ log('info', `[warmup-fb] reel ended at ${Math.round(st.currentTime)}s advancing`);
311
+ break;
292
312
  }
293
- if (!scrolled && watched >= watchMs / 2) { await organicScroll(page, randInt(1, 2)); scrolled = true; }
313
+ // Progress = currentTime moved forward, OR wrapped back (loop restart).
314
+ const advanced = !!st && (st.currentTime > lastT + 0.2 || st.currentTime < lastT - 0.5);
315
+ if (advanced) {
316
+ watched += TICK;
317
+ frozenTicks = 0;
318
+ if (!scrolled && watched >= watchMs / 2) { await organicScroll(page, randInt(1, 2)); scrolled = true; }
319
+ } else {
320
+ // Not progressing (frozen / buffering / paused-and-won't-play) — do NOT
321
+ // count this second; bail after ~8s stuck.
322
+ frozenTicks++;
323
+ if (frozenTicks >= 8) { log('info', `[warmup-fb] video frozen ~${frozenTicks}s (counted ${Math.round(watched/1000)}s) — advancing`); break; }
324
+ }
325
+ lastT = st ? st.currentTime : lastT;
326
+ // Re-check sponsored a couple times (a mid-scroll ad reel can swap in).
327
+ if (adReChecks < 2 && watched > 0 && watched % 8000 === 0) { adReChecks++; if (await isSponsoredReel(page)) { log('info', '[warmup-fb] became sponsored mid-watch — advancing'); break; } }
294
328
  await page.waitForTimeout(TICK);
295
- watched += TICK;
296
329
  }
297
- return true;
330
+ return watched > 0; // only a video with real progressing playback counts
298
331
  } catch (e) {
299
332
  log('info', `[warmup-fb] watch failed: ${String(e.message || e).slice(0, 90)}`);
300
333
  return false;