channel-worker 2.5.50 → 2.5.52
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/nurture_facebook.js +93 -1
package/package.json
CHANGED
|
@@ -282,6 +282,11 @@ async function openPostAndRead(page, log) {
|
|
|
282
282
|
for (const a of best.querySelectorAll("a[href*='/posts/'], a[href*='/permalink/'], a[href*='story_fbid'], a[href*='/photo']")) {
|
|
283
283
|
const r = a.getBoundingClientRect();
|
|
284
284
|
if (r.width < 8 || r.height < 8 || a.offsetParent === null) continue;
|
|
285
|
+
// Facebook ships most permalinks with target="_blank": clicked as-is the
|
|
286
|
+
// post lands in a NEW tab while the script keeps driving the old one, so
|
|
287
|
+
// every opened post leaves a dead tab behind. Force same-tab — goBack()
|
|
288
|
+
// below is what returns us to the feed.
|
|
289
|
+
a.setAttribute('target', '_self');
|
|
285
290
|
a.setAttribute('__nur_open__', '1');
|
|
286
291
|
return true;
|
|
287
292
|
}
|
|
@@ -351,6 +356,69 @@ async function followVisiblePage(page, log) {
|
|
|
351
356
|
return ok;
|
|
352
357
|
}
|
|
353
358
|
|
|
359
|
+
// Is this profile browsing AS A PAGE rather than as the person?
|
|
360
|
+
//
|
|
361
|
+
// Why it matters: a Page's feed contains only the Page's own posts, so there is
|
|
362
|
+
// nothing to nurture — measured live: 1 post, feed bottoms out after ~1500px.
|
|
363
|
+
// And a profile already switched into a Page has, by definition, got its Page —
|
|
364
|
+
// which is the whole goal of nurturing. So this ends the session immediately and
|
|
365
|
+
// takes the channel off the schedule.
|
|
366
|
+
//
|
|
367
|
+
// Signal: the left nav carries Page-only entries (Công cụ chuyên nghiệp / Trung
|
|
368
|
+
// tâm quảng cáo / Ads Manager) and lacks the personal "Bạn bè" entry.
|
|
369
|
+
async function detectProfileMode(page) {
|
|
370
|
+
return page.evaluate(() => {
|
|
371
|
+
const navs = [...document.querySelectorAll("[role='navigation']")];
|
|
372
|
+
const navText = navs.map((n) => n.innerText || '').join(' | ');
|
|
373
|
+
const hrefs = navs.flatMap((n) => [...n.querySelectorAll('a[href]')].map((a) => a.getAttribute('href') || ''));
|
|
374
|
+
const proLink = hrefs.some((h) => /professional_dashboard|adsmanager|ad_center|ads\/manage|business\.facebook/i.test(h));
|
|
375
|
+
const proText = /Công cụ chuyên nghiệp|Professional dashboard|Trung tâm quảng cáo|Ad Center|Trình quản lý quảng cáo|Ads Manager/i.test(navText);
|
|
376
|
+
const hasFriends = /\bBạn bè\b|\bFriends\b/i.test(navText);
|
|
377
|
+
return {
|
|
378
|
+
isPage: (proLink || proText) && !hasFriends,
|
|
379
|
+
why: `proLink=${proLink} proText=${proText} friends=${hasFriends}`,
|
|
380
|
+
navSample: navText.replace(/\s+/g, ' ').slice(0, 140),
|
|
381
|
+
};
|
|
382
|
+
}).catch(() => ({ isPage: false, why: 'detect failed', navSample: '' }));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ONE tab, always.
|
|
386
|
+
//
|
|
387
|
+
// Two things pile tabs up behind a session, and neither is ours: NSTBrowser
|
|
388
|
+
// restores whatever the profile had open the last time it was used (publishing
|
|
389
|
+
// leaves Studio/Business tabs), and Facebook opens permalinks, photos and
|
|
390
|
+
// notification links with target="_blank". The script drives pages()[0] and
|
|
391
|
+
// never looks at the rest, so they just sit there — by the third session the
|
|
392
|
+
// window is a wall of tabs.
|
|
393
|
+
//
|
|
394
|
+
// Closing them is safe: nurturing needs exactly one tab (the feed), and the
|
|
395
|
+
// permalink click is forced to same-tab navigation in openPostAndRead.
|
|
396
|
+
async function closeOtherTabs(context, keep, log) {
|
|
397
|
+
const before = context.pages().length;
|
|
398
|
+
let closed = 0;
|
|
399
|
+
for (const p of context.pages()) {
|
|
400
|
+
if (p === keep || p.isClosed()) continue;
|
|
401
|
+
try { await p.close({ runBeforeUnload: false }); closed++; } catch { /* already gone */ }
|
|
402
|
+
}
|
|
403
|
+
// Log even when nothing was closed: "no line" would otherwise read the same
|
|
404
|
+
// as "the fix never loaded", and that is exactly what we need to tell apart.
|
|
405
|
+
if (log) log('info', `[nurture-fb] tab: ${before} lúc vào → đóng ${closed} → còn ${context.pages().length}`);
|
|
406
|
+
return closed;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Anything Facebook manages to pop open mid-session dies on arrival. Registered
|
|
410
|
+
// once per session; the listener is scoped to this context, which the runner
|
|
411
|
+
// detaches from when the script returns.
|
|
412
|
+
function guardSingleTab(context, keep, log) {
|
|
413
|
+
context.on('page', async (p) => {
|
|
414
|
+
if (p === keep || p.isClosed()) return;
|
|
415
|
+
try {
|
|
416
|
+
await p.close({ runBeforeUnload: false });
|
|
417
|
+
log('info', '[nurture-fb] tab mới bị Facebook bung ra — đã đóng ngay');
|
|
418
|
+
} catch { /* raced with its own close */ }
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
354
422
|
// debug:true only — screenshot the feed and hand back a viewable URL. Cheaper
|
|
355
423
|
// than another round of guessing at selectors from log lines.
|
|
356
424
|
async function dumpFeedShot(page, log, tag) {
|
|
@@ -372,7 +440,7 @@ async function dumpFeedShot(page, log, tag) {
|
|
|
372
440
|
}
|
|
373
441
|
|
|
374
442
|
// ─── main ───────────────────────────────────────────────────────────────────
|
|
375
|
-
async function run({ page, payload, log }) {
|
|
443
|
+
async function run({ page, context, payload, log }) {
|
|
376
444
|
const t0 = Date.now();
|
|
377
445
|
const cfg = payload.config || {};
|
|
378
446
|
const phase = Math.max(1, Math.min(3, parseInt(payload.phase, 10) || 1));
|
|
@@ -391,12 +459,32 @@ async function run({ page, payload, log }) {
|
|
|
391
459
|
|
|
392
460
|
page.on('dialog', (d) => { d.accept().catch(() => {}); });
|
|
393
461
|
|
|
462
|
+
// Clear the profile's leftovers BEFORE loading the feed, then keep it clear.
|
|
463
|
+
if (context) {
|
|
464
|
+
await closeOtherTabs(context, page, log);
|
|
465
|
+
guardSingleTab(context, page, log);
|
|
466
|
+
}
|
|
467
|
+
|
|
394
468
|
await page.goto('https://www.facebook.com/', { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
395
469
|
await page.waitForTimeout(randInt(3000, 5000));
|
|
396
470
|
// Guard FIRST — everything below assumes a live logged-in session.
|
|
397
471
|
await assertAccountUsable(page, log);
|
|
398
472
|
await dismissDialogs(page, log);
|
|
399
473
|
|
|
474
|
+
// Already a Page? Then the goal is met — stop here, don't fake a session.
|
|
475
|
+
const mode = await detectProfileMode(page);
|
|
476
|
+
if (payload.debug) log('info', `[nurture-fb][dbg] mode ${mode.why} nav="${mode.navSample}"`);
|
|
477
|
+
if (mode.isPage) {
|
|
478
|
+
log('info', '[nurture-fb] profile đang dùng dưới dạng TRANG (đã có Page) — bỏ qua, không cần nuôi nữa');
|
|
479
|
+
return {
|
|
480
|
+
profile_mode: 'page', skipped: true,
|
|
481
|
+
phase, day_index: dayIndex,
|
|
482
|
+
posts_seen: 0, likes: 0, videos_watched: 0, pages_followed: 0, posts_opened: 0,
|
|
483
|
+
duration_sec: Math.round((Date.now() - t0) / 1000),
|
|
484
|
+
account_status: 'ok',
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
400
488
|
await centerMouse(page); // wheel events need the cursor over the feed column
|
|
401
489
|
if (payload.debug) await dumpFeedShot(page, log, 'feed-start');
|
|
402
490
|
|
|
@@ -482,6 +570,9 @@ async function run({ page, payload, log }) {
|
|
|
482
570
|
// moment activity trips a threshold — better to stop than to keep poking.
|
|
483
571
|
if (ticks % 12 === 0) {
|
|
484
572
|
await assertAccountUsable(page, log);
|
|
573
|
+
// Belt to the context listener: a tab opened as a separate window, or one
|
|
574
|
+
// that slipped in while we were navigating, still gets swept.
|
|
575
|
+
if (context && context.pages().length > 1) await closeOtherTabs(context, page, log);
|
|
485
576
|
log('info', `[nurture-fb] …${Math.round((Date.now() - t0) / 1000)}s: posts=${postsSeen} likes=${likes}/${likesTarget} videos=${videos} scrollY=${lastScan.scrollY} units=${lastScan.articlesInDom} sel=${lastScan.selector}`);
|
|
486
577
|
}
|
|
487
578
|
|
|
@@ -496,6 +587,7 @@ async function run({ page, payload, log }) {
|
|
|
496
587
|
log('info', `[nurture-fb] session done — posts=${postsSeen} likes=${likes}/${likesTarget} videos=${videos} pages=${pagesFollowed} opened=${postsOpened} duration=${durationSec}s`);
|
|
497
588
|
return {
|
|
498
589
|
phase,
|
|
590
|
+
profile_mode: 'personal',
|
|
499
591
|
day_index: dayIndex,
|
|
500
592
|
posts_seen: postsSeen,
|
|
501
593
|
likes,
|