channel-worker 2.5.77 → 2.5.78

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.77",
3
+ "version": "2.5.78",
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": {
@@ -48,13 +48,35 @@ async function findReelsTabHref(page) {
48
48
  // ở mọi tab của page/profile) — không tốn thêm lượt mở profile nào. Ô
49
49
  // platform_accounts.stats.followers có sẵn từ lâu nhưng trống 279/279 kênh vì
50
50
  // chưa từng có ai ghi vào.
51
+ //
52
+ // KHÔNG quét cả body rồi lấy match ĐẦU TIÊN: đo thật 2026-09-05 trên kênh
53
+ // "Đồ chơi trẻ em 01" ra đúng "1" — trong trang có sẵn chuỗi "… 1 người theo
54
+ // dõi" ở chỗ khác, nó chặn trước số thật của header. Thứ tự ưu tiên:
55
+ // 1. link /followers của chính header (nguồn chắc nhất)
56
+ // 2. nếu không có, quét phần ĐẦU trang (header nằm ở đây) và lấy số LỚN NHẤT
57
+ // Trả về cả danh sách ứng viên để log soi được khi Facebook đổi giao diện.
58
+ const FOLLOWER_RE = /([\d][\d.,]*\s*(?:N|Tr|K|M|B|nghìn|triệu|tỷ)?)\s*(?:người theo dõi|người follow|followers)/gi;
59
+
51
60
  async function scrapeFollowers(page) {
52
- return page.evaluate(() => {
53
- const txt = document.body?.innerText || '';
54
- // "1,2 N người theo dõi" · "12.345 người theo dõi" · "1.2K followers"
55
- const m = /([\d][\d.,]*\s*(?:N|Tr|K|M|B|nghìn|triệu|tỷ)?)\s*(?:người theo dõi|followers)/i.exec(txt);
56
- return m ? m[1].trim() : null;
57
- }).catch(() => null);
61
+ return page.evaluate((reSrc) => {
62
+ const out = { picked: null, from: '', candidates: [] };
63
+ const mk = () => new RegExp(reSrc, 'gi');
64
+
65
+ // 1. Link "N người theo dõi" trong header — href chứa /followers.
66
+ for (const a of document.querySelectorAll("a[href*='followers']")) {
67
+ const t = (a.innerText || '').replace(/\s+/g, ' ').trim();
68
+ const m = mk().exec(t);
69
+ if (m) { out.picked = m[1].trim(); out.from = 'link'; out.candidates.push(t.slice(0, 60)); return out; }
70
+ }
71
+
72
+ // 2. Phần đầu trang: header đứng trên cùng, nên cắt bớt đuôi để tránh vớ
73
+ // phải chuỗi trong danh sách gợi ý/bình luận phía dưới.
74
+ const head = (document.body?.innerText || '').slice(0, 3000);
75
+ let m; const re = mk();
76
+ while ((m = re.exec(head))) out.candidates.push(m[1].trim());
77
+ if (out.candidates.length) { out.from = 'head'; out.picked = out.candidates.slice(); }
78
+ return out;
79
+ }, FOLLOWER_RE.source).catch(() => ({ picked: null, from: 'error', candidates: [] }));
58
80
  }
59
81
 
60
82
  async function scrapeTiles(page) {
@@ -104,9 +126,12 @@ async function run({ page, payload, log }) {
104
126
  // lượt đọc view, hai việc độc lập nhau.
105
127
  let followers = null;
106
128
  if (mode === 'deep') {
107
- const raw = await scrapeFollowers(page);
108
- followers = parseViews(raw);
109
- log('info', raw ? `[fb-stats] follower: "${raw}" ${followers}` : '[fb-stats] không đọc được số người theo dõi');
129
+ const f = await scrapeFollowers(page);
130
+ // Từ link header thì tin thẳng; quét phần đầu trang thì lấy số LỚN NHẤT
131
+ // trong các ứng viên chuỗi nhiễu ("1 người theo dõi") luôn thua số thật.
132
+ const nums = (Array.isArray(f.picked) ? f.picked : [f.picked]).map(parseViews).filter((n) => Number.isFinite(n) && n > 0);
133
+ followers = nums.length ? Math.max(...nums) : null;
134
+ log('info', `[fb-stats] follower: ${followers ?? 'không đọc được'} (nguồn ${f.from || 'none'}, ứng viên: ${JSON.stringify(f.candidates).slice(0, 200)})`);
110
135
  }
111
136
 
112
137
  // Cuộn để tải thêm tile (mỗi lần ~12 reel). 6 lần ≈ 60-80 reel gần nhất — đủ