hridoy-ultimate 9.0.2 → 9.0.4

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.
@@ -1,25 +1,37 @@
1
1
  "use strict";
2
2
 
3
- // A small pool of current, plausible desktop Chrome versions to rotate
4
- // through. A single frozen version shared by every process using this
5
- // package is itself a fingerprint — thousands of unrelated accounts all
6
- // claiming the exact same browser build is a trivially detectable pattern.
7
- // Keep this list current; entries that are far behind Chrome's real release
8
- // timeline are themselves a signal.
9
- const CHROME_VERSIONS = [
10
- "131.0.6778.140",
11
- "132.0.6834.111",
12
- "133.0.6943.98",
13
- "134.0.6998.117",
14
- "135.0.7049.85"
15
- ];
16
-
17
- function pickChromeVersion() {
18
- return CHROME_VERSIONS[Math.floor(Math.random() * CHROME_VERSIONS.length)];
3
+ // Fallback fingerprint, used ONLY when no real browser User-Agent is configured.
4
+ //
5
+ // Real Chrome sends a "reduced" User-Agent (Chrome/<major>.0.0.0) since v101, so the
6
+ // major version is all a believable UA needs. A hard-coded list of versions goes
7
+ // stale within weeks (the old pool here was ~2 years behind and a stale browser build
8
+ // is itself a signal), so the major is estimated from today's date instead.
9
+ //
10
+ // The best option by far is still to pass the exact User-Agent of the browser the
11
+ // cookies were exported from: via the `userAgent` login option, or the FCA_USER_AGENT
12
+ // environment variable.
13
+ const MS_DAY = 24 * 60 * 60 * 1000;
14
+ // Anchors from Chrome's published schedule: 151 stable on 2026-07-28 (4-week cadence),
15
+ // 153 on 2026-09-08, then one major every 2 weeks.
16
+ const ANCHOR_151 = Date.UTC(2026, 6, 28);
17
+ const ANCHOR_153 = Date.UTC(2026, 8, 8);
18
+
19
+ function estimateStableMajor(now) {
20
+ const t = typeof now === "number" ? now : Date.now();
21
+ if (t >= ANCHOR_153) return 153 + Math.floor((t - ANCHOR_153) / (14 * MS_DAY));
22
+ if (t >= ANCHOR_151) return 151 + Math.floor((t - ANCHOR_151) / (28 * MS_DAY));
23
+ // before the anchors: count back one major per 4 weeks (never below 130)
24
+ return Math.max(130, 151 - Math.ceil((ANCHOR_151 - t) / (28 * MS_DAY)));
25
+ }
26
+
27
+ // Real users are usually a release behind (staged auto-update), so use stable - 1.
28
+ function pickChromeMajor(now) {
29
+ return estimateStableMajor(now) - 1;
19
30
  }
20
31
 
21
- function buildUserAgent(version) {
22
- return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`;
32
+ function buildUserAgent(major) {
33
+ // Reduced UA format used by real Chrome: Chrome/<major>.0.0.0
34
+ return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${major}.0.0.0 Safari/537.36`;
23
35
  }
24
36
 
25
37
  // Chrome's Sec-Ch-Ua "GREASE" brand slot rotates through a small set of
@@ -47,30 +59,54 @@ function fingerprintFile() {
47
59
  return path.join(process.cwd(), "data", "session", "fca-fingerprint.json");
48
60
  }
49
61
 
62
+ function fromUserAgentString(ua, source) {
63
+ const m = String(ua || "").match(/Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
64
+ return {
65
+ userAgent: String(ua).trim(),
66
+ version: m ? m[0].split("/")[1] : "",
67
+ major: m ? parseInt(m[1], 10) : 0,
68
+ source
69
+ };
70
+ }
71
+
50
72
  function getFingerprint() {
51
- if (!cachedFingerprint) {
52
- try {
53
- const saved = JSON.parse(fs.readFileSync(fingerprintFile(), "utf8"));
54
- if (saved && CHROME_VERSIONS.includes(saved.version)) {
55
- cachedFingerprint = {
56
- userAgent: buildUserAgent(saved.version),
57
- version: saved.version,
58
- major: parseInt(saved.version.split(".")[0], 10)
59
- };
60
- }
61
- } catch (_) { /* first run */ }
73
+ if (cachedFingerprint) return cachedFingerprint;
74
+
75
+ // 1) explicit environment override (same meaning as the userAgent login option)
76
+ const envUA = process.env.FCA_USER_AGENT;
77
+ if (envUA && envUA.trim()) {
78
+ cachedFingerprint = fromUserAgentString(envUA, "env");
79
+ return cachedFingerprint;
62
80
  }
81
+
82
+ const wanted = pickChromeMajor();
83
+
84
+ // 2) the fingerprint saved by an earlier run, as long as it is not far behind a
85
+ // current browser (a few majors behind is normal; two years behind is not)
86
+ try {
87
+ const saved = JSON.parse(fs.readFileSync(fingerprintFile(), "utf8"));
88
+ const savedMajor = saved && parseInt(String(saved.major || saved.version || "").split(".")[0], 10);
89
+ if (savedMajor && wanted - savedMajor <= 6 && savedMajor <= wanted + 1) {
90
+ cachedFingerprint = {
91
+ userAgent: buildUserAgent(savedMajor),
92
+ version: savedMajor + ".0.0.0",
93
+ major: savedMajor,
94
+ source: "saved"
95
+ };
96
+ }
97
+ } catch (_) { /* first run */ }
98
+
99
+ // 3) estimate from the date and remember it
63
100
  if (!cachedFingerprint) {
64
- const version = pickChromeVersion();
65
- const major = parseInt(version.split(".")[0], 10);
66
101
  cachedFingerprint = {
67
- userAgent: buildUserAgent(version),
68
- version,
69
- major
102
+ userAgent: buildUserAgent(wanted),
103
+ version: wanted + ".0.0.0",
104
+ major: wanted,
105
+ source: "estimated"
70
106
  };
71
107
  try {
72
108
  fs.mkdirSync(path.dirname(fingerprintFile()), { recursive: true });
73
- fs.writeFileSync(fingerprintFile(), JSON.stringify({ version }), "utf8");
109
+ fs.writeFileSync(fingerprintFile(), JSON.stringify({ major: wanted }), "utf8");
74
110
  } catch (_) { /* read-only fs: fall back to per-process */ }
75
111
  }
76
112
  return cachedFingerprint;
@@ -125,5 +161,8 @@ function clientHintsFromUA(ua) {
125
161
  module.exports = {
126
162
  getDefaultUserAgent,
127
163
  getFingerprint,
128
- clientHintsFromUA
164
+ clientHintsFromUA,
165
+ estimateStableMajor,
166
+ pickChromeMajor,
167
+ _resetForTests: () => { cachedFingerprint = null; }
129
168
  };
@@ -186,24 +186,13 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
186
186
  ) {
187
187
  globalAntiSuspension.detectSuspensionSignal("automated behavior checkpoint scraping warning");
188
188
  emit("checkpoint", { type: "scraping_warning", res: resData });
189
- // Dismiss it the same way a human tapping "Dismiss" on that screen
190
- // would — an unacknowledged warning left sitting is worse than one
191
- // that's been cleared. Fire-and-forget: this must never block or
192
- // fail the actual response being parsed.
193
- http
194
- .post("https://www.facebook.com/api/graphql/", ctx.jar, {
195
- av: ctx.userID,
196
- __user: ctx.userID,
197
- fb_dtsg: ctx.fb_dtsg || "",
198
- fb_api_caller_class: "RelayModern",
199
- fb_api_req_friendly_name: "FBScrapingWarningMutation",
200
- server_timestamps: "true",
201
- doc_id: "6339492849481770",
202
- variables: "{}"
203
- }, ctx.globalOptions, ctx)
204
- .then(() => logger("Dismissed automated-behavior warning", "info"))
205
- .catch((e) => logger(`Dismissing automated-behavior warning failed (non-fatal): ${e && e.message ? e.message : String(e)}`, "warn"));
206
- return await maybeAutoLogin(resData, res?.config);
189
+ logger("Facebook automated-behavior warning detected. Automatic dismissal is disabled; stop the bot and review the account manually.", "error");
190
+ // This is NOT a logged-out session. The old code ran the "session expired" path here,
191
+ // which made the bot declare itself logged out and restart (a fresh login) for nothing.
192
+ const err = new Error("Facebook showed an automated-behavior warning (needs a human to dismiss it)");
193
+ err.error = "automation_warning";
194
+ err.res = resData;
195
+ throw err;
207
196
  }
208
197
  if (
209
198
  resStr.includes("https://www.facebook.com/login.php?") ||