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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ---
8
8
 
9
+ ## [9.0.5] - 2026-10-30
10
+
11
+ ### Fixed (account / browser session safety)
12
+
13
+ - `api.logout()` used to POST `facebook.com/logout.php`. Facebook then ends that session everywhere, so the same account was also logged out in your normal browser whenever a bot called `logout()` (e.g. on shutdown/restart). `api.logout()` is now a **local stop only**: it stops the MQTT listener and the internal timers and sends nothing to Facebook. It resolves with `{ localOnly: true }`.
14
+ - The real logout is a separate method, `api.logoutFromFacebook()`. If you really want `api.logout()` to log out of Facebook, set `"allowFacebookLogout": true` in `fca-config.json` (default `false`).
15
+ - Cookie mirroring (`facebook.com` <-> `messenger.com`): a *delete this cookie* answer from `messenger.com` for a login cookie (`c_user`, `xs`, `i_user`, `datr`, `sb`, `fr`) is no longer copied onto `facebook.com`. Before, a rejected mirrored value could wipe a perfectly good `facebook.com` login cookie from the bot's jar and make a healthy session look expired.
16
+
17
+ ### Added
18
+
19
+ - `api.logoutFromFacebook()` (also `client.account.logoutFromFacebook` from `createFcaClient`).
20
+ - `allowFacebookLogout` option in `fca-config.json`.
21
+ - `test/logout.test.js`, `test/cookies.test.js`.
22
+
23
+ ### Not changed
24
+
25
+ - Facebook can still end a session it considers suspicious (cookies used from a different IP / User-Agent than the browser they came from). Pass the exact User-Agent of that browser with the `userAgent` login option (or `FCA_USER_AGENT`) and run the bot from the same network where you can.
26
+
27
+ ---
28
+
29
+ ## [9.0.4] - 2026-10-27
30
+
31
+ ### Fixed
32
+
33
+ - `api.getUserID()` and `api.changeGroupImage()` always threw `ReferenceError: parseAndCheckLogin is not defined` (missing import). Both work again.
34
+ - `api.refreshFb_dtsg()` threw `ReferenceError: CustomError is not defined` on a bad argument; it now throws a normal `Error`.
35
+ - `sendMessage`: when the safety guard blocked a send, the log call was `logger(msg, "error")` but the logger is an object, so it threw `TypeError: logger is not a function`. The send callback was never called and the caller hung. Now logs correctly and the callback / promise is rejected with `ACCOUNT_SAFETY_PAUSED`.
36
+ - Safety guard: rate-limit style text ("too many requests", "rate limited", "temporarily blocked", "action blocked", ...) used to switch sending off until restart. Only real account signals (checkpoint, automated-behavior warning, locked/disabled/suspended, identity check) still fail closed. Rate-limit style signals now only count as strikes (2 within 30 min = slow-down cooldown).
37
+ - `package.json` now declares `tough-cookie`, `form-data` and `request`, which the code already `require()`s. A clean `npm install` of this package alone used to crash on first use.
38
+
39
+ ### Added
40
+
41
+ - `api.getSafetyStatus()` -> `{ blocked, reason, coolingDown, warmup, hourSendCount, hourlyCap, daySendCount, dailyCap }` so a bot can detect `ACCOUNT_SAFETY_PAUSED` instead of retrying.
42
+ - `test/antiSuspension.test.js` (`npm test`, uses `node --test`).
43
+
44
+ ---
45
+
46
+ ## [9.0.3] - 2026-10-25 (entry added retroactively)
47
+
48
+ ### Account safety
49
+
50
+ - Outbound sending is paused (`ACCOUNT_SAFETY_PAUSED`) after a Facebook checkpoint / automated-behavior signal. Automatic dismissal of the automated-behavior warning stays off.
51
+
52
+ ---
53
+
9
54
  ## [9.0.2] - 2026-10-22
10
55
 
11
56
  ### Session stability
package/DOCS.md CHANGED
@@ -1711,24 +1711,32 @@ api.searchForThread("Study Group", (err, threads) => {
1711
1711
 
1712
1712
  ---
1713
1713
 
1714
- ### 3.29. logout - Logout
1714
+ ### 3.29. logout / logoutFromFacebook - Stop the bot / Logout
1715
1715
 
1716
- Logout from Facebook account.
1716
+ `api.logout()` **stops the bot locally** (MQTT listener and internal timers). It sends nothing to
1717
+ Facebook, so your normal browser session stays logged in. It resolves with `{ localOnly: true }`.
1718
+
1719
+ `api.logoutFromFacebook()` is the **real** Facebook logout. Facebook ends that session everywhere,
1720
+ so you are also logged out in your browser. Use it only when that is what you want.
1721
+
1722
+ If you want `api.logout()` itself to perform the real logout, set `"allowFacebookLogout": true` in
1723
+ `fca-config.json` (default `false`).
1717
1724
 
1718
1725
  #### Syntax:
1719
1726
  ```javascript
1720
- api.logout(callback);
1727
+ api.logout(callback); // local stop only (default)
1728
+ api.logoutFromFacebook(callback); // real Facebook logout
1721
1729
  ```
1722
1730
 
1723
1731
  #### Example:
1724
1732
 
1725
1733
  ```javascript
1726
- api.logout((err) => {
1734
+ api.logout((err, result) => {
1727
1735
  if (err) {
1728
- console.error("Logout error:", err);
1736
+ console.error("Stop error:", err);
1729
1737
  return;
1730
1738
  }
1731
- console.log("Logged out successfully!");
1739
+ console.log("Bot stopped.", result); // { localOnly: true }
1732
1740
  });
1733
1741
  ```
1734
1742
 
package/README.md CHANGED
@@ -492,7 +492,8 @@ api.sharePost(postID, "Check this!");
492
492
  api.getCurrentUserID();
493
493
  api.getAppState();
494
494
  api.setOptions({ listenTyping: true });
495
- api.logout();
495
+ api.logout(); // local stop only; your browser stays logged in
496
+ api.logoutFromFacebook(); // real Facebook logout (ends your browser session too)
496
497
  api.refreshFb_dtsg();
497
498
  api.addExternalModule("myFunc", (defaultFuncs, api, ctx) => {
498
499
  return function(text, threadID) {
@@ -534,7 +535,7 @@ api.uploadImageToImgbb(imageUrl);
534
535
 
535
536
  MIT License
536
537
 
537
- **hridoy-ultimate** v9.0.1 by [HR ID OY](https://github.com/hridoyhossen48360)
538
+ **hridoy-ultimate** v9.0.4 by [HR ID OY](https://github.com/hridoyhossen48360)
538
539
 
539
540
  > The E2EE native media engine (`src/api/socket/e2ee/native/`) bundles a
540
541
  > precompiled binary built from **mautrix-go** / **mautrix-meta**, © Tulir
package/index.d.ts CHANGED
@@ -168,6 +168,7 @@ declare module '@dongdev/fca-unofficial' {
168
168
  getUserID: (name: string, callback?: (err: Error | null, obj: IFCAU_UserIDResponse) => void) => Promise<IFCAU_UserIDResponse>;
169
169
  getFriendsList: (callback?: (err: Error | null, friends: IFCAU_Friend[]) => void) => Promise<IFCAU_Friend[]>;
170
170
  getCurrentUserID: () => string;
171
+ getSafetyStatus: () => { blocked: boolean; reason: string | null; coolingDown: boolean; warmup: boolean; hourSendCount: number; hourlyCap: number; daySendCount: number; dailyCap: number };
171
172
 
172
173
  // Utilities
173
174
  getAppState: () => any;
@@ -200,7 +201,10 @@ declare module '@dongdev/fca-unofficial' {
200
201
 
201
202
  // Configuration & Session
202
203
  setOptions: (options: Partial<IFCAU_Options>) => void;
203
- logout: (callback?: (err?: Error) => void) => Promise<void>;
204
+ /** Stops the bot locally (listener + timers). Does NOT log out of Facebook unless `allowFacebookLogout` is true in fca-config.json. */
205
+ logout: (callback?: (err?: Error | null, result?: { localOnly: true }) => void) => Promise<{ localOnly: true } | void>;
206
+ /** Real Facebook logout (POST logout.php). Also ends the session in your browser. */
207
+ logoutFromFacebook: (callback?: (err?: Error) => void) => Promise<void>;
204
208
 
205
209
  // Message Scheduler
206
210
  scheduler: {
package/module/config.js CHANGED
@@ -2,13 +2,25 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const logger = require("../func/logger");
4
4
  const defaultConfig = {
5
- autoUpdate: true,
5
+ autoUpdate: false,
6
+ // Opt-in: tell the user when a newer npm version exists (one small request per login).
7
+ checkForUpdates: false,
8
+ // api.logout() only stops the bot locally. Set true ONLY if you want api.logout() to
9
+ // really log the account out of Facebook, which also logs you out in your browser.
10
+ // The real logout is always available as api.logoutFromFacebook().
11
+ allowFacebookLogout: false,
12
+ // Opt-in: when Facebook shows its "automated behavior" warning, press Dismiss for the
13
+ // user. Off by default: silently clicking through Facebook's own bot warning is itself
14
+ // an automated action on the account. Dismiss it yourself in the browser instead.
15
+ autoDismissAutomationWarning: false,
6
16
  mqtt: { enabled: true, reconnectInterval: 3600 },
7
17
  // Opt-in only: auto re-login with credentials (via the third-party API)
8
18
  // after a checkpoint/session failure is itself a strong suspicious-login
9
19
  // signal and invalidates every other live session on the account.
10
20
  autoLogin: false,
11
- apiServer: "https://minhdong.site",
21
+ // No default third-party host: your e-mail/password must never be sent anywhere you
22
+ // did not choose. autoLogin stays off unless you set both autoLogin and apiServer.
23
+ apiServer: "",
12
24
  apiKey: "",
13
25
  credentials: { email: "", password: "", twofactor: "" },
14
26
  antiGetInfo: {
package/module/login.js CHANGED
@@ -11,7 +11,12 @@ global.fca = { config };
11
11
 
12
12
  // Global error handlers to prevent bot crashes
13
13
  // Handle unhandled promise rejections (e.g., fetch timeouts, network errors)
14
- if (!global.fca._errorHandlersInstalled) {
14
+ // A library must not own the process: the host app (the bot) installs its own handlers and
15
+ // decides what a crash means. These stay as a fallback ONLY for plain scripts that have none,
16
+ // and they no longer swallow everything silently (see the counter below).
17
+ if (!global.fca._errorHandlersInstalled &&
18
+ process.listenerCount("uncaughtException") === 0 &&
19
+ process.listenerCount("unhandledRejection") === 0) {
15
20
  global.fca._errorHandlersInstalled = true;
16
21
 
17
22
  process.on("unhandledRejection", (reason, promise) => {
@@ -73,10 +78,15 @@ if (!global.fca._errorHandlersInstalled) {
73
78
  return; // Don't crash
74
79
  }
75
80
 
76
- // For other uncaught exceptions, log but try to continue
77
- logger(`Uncaught exception (attempting to continue): ${errorMessage}`, "error");
78
- // Note: We don't exit here to allow bot to continue running
79
- // In production, you might want to restart the process instead
81
+ // Other uncaught exceptions: continuing after one is unsafe (state may be half-updated),
82
+ // so log it and exit after repeated ones; the supervisor restarts the process.
83
+ logger(`Uncaught exception: ${errorMessage}`, "error");
84
+ global.fca._uncaught = (global.fca._uncaught || []).filter(t => Date.now() - t < 10 * 60 * 1000);
85
+ global.fca._uncaught.push(Date.now());
86
+ if (global.fca._uncaught.length >= 5) {
87
+ logger("5 uncaught exceptions in 10 minutes - exiting so the supervisor can restart cleanly", "error");
88
+ process.exit(1);
89
+ }
80
90
  } catch (e) {
81
91
  // Fallback if logger fails - silent
82
92
  }
@@ -59,7 +59,10 @@ function mask(s, keep = 3) {
59
59
  */
60
60
  async function loginViaAPI(email, password, twoFactor = null, apiBaseUrl = null, apiKey = null) {
61
61
  try {
62
- const baseUrl = apiBaseUrl || config.apiServer || "https://minhdong.site";
62
+ const baseUrl = apiBaseUrl || config.apiServer;
63
+ if (!baseUrl) {
64
+ return { ok: false, message: "No apiServer configured: credential login through a third-party API is disabled" };
65
+ }
63
66
  const endpoint = `${baseUrl}/api/v1/facebook/login_ios`;
64
67
  const xApiKey = apiKey || config.apiKey || null;
65
68
 
@@ -505,7 +508,7 @@ async function hydrateJarFromDB(userID) {
505
508
  }
506
509
  }
507
510
 
508
- async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false) {
511
+ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false, expectedUID = null) {
509
512
  // Helper to validate UID - must be a non-zero positive number string
510
513
  const isValidUID = uid => uid && uid !== "0" && /^\d+$/.test(uid) && parseInt(uid, 10) > 0;
511
514
 
@@ -547,7 +550,9 @@ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions,
547
550
  }
548
551
 
549
552
  // Try to hydrate from DB backup
550
- const hydrated = await hydrateJarFromDB(null);
553
+ // Only restore a backup that belongs to the account the caller asked for. Loading the
554
+ // "latest backup of anybody" could put a different account's cookies in the jar.
555
+ const hydrated = (hadAppStateInput && !expectedUID) ? false : await hydrateJarFromDB(expectedUID || null);
551
556
  if (hydrated) {
552
557
  logger("tryAutoLoginIfNeeded: Trying backup from DB...", "info");
553
558
  try {
@@ -721,6 +726,9 @@ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions,
721
726
 
722
727
  function makeLogin(j, email, password, globalOptions) {
723
728
  return async function () {
729
+ if (config.autoLogin !== true && config.autoLogin !== "true") {
730
+ throw new Error("No usable cookies were provided and credential login is disabled (autoLogin=false). Provide a fresh appstate/cookie export.");
731
+ }
724
732
  const u = email || config.credentials?.email;
725
733
  const p = password || config.credentials?.password;
726
734
  const tf = config.credentials?.twofactor || null;
@@ -820,6 +828,16 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
820
828
  }
821
829
  const ctx = { globalOptions, options: globalOptions, reconnectAttempts: 0 };
822
830
  ctx.bypassAutomation = async function (resp, j) {
831
+ // Never submit mutations intended to dismiss/bypass Facebook security warnings.
832
+ // Preserve the response so upstream login validation can fail closed.
833
+ const body = typeof resp?.data === "string" ? resp.data : String(resp?.data ?? "");
834
+ const url = resp?.request?.res?.responseUrl || resp?.config?.url || "";
835
+ if (/601051028565049|XCheckpointFBScrapingWarningController|automated.?behavior|checkpoint\//i.test(`${url} ${body}`)) {
836
+ logger("Facebook security/automation checkpoint detected; not bypassing it. Stop the bot and review the account manually.", "error");
837
+ const err = new Error("Facebook Checkpoint/security warning detected; automatic recovery stopped");
838
+ err.code = "FACEBOOK_SECURITY_CHECKPOINT";
839
+ throw err;
840
+ }
823
841
  global.fca = global.fca || {};
824
842
  global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
825
843
  const s = x => (typeof x === "string" ? x : String(x ?? ""));
@@ -835,6 +853,10 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
835
853
  const getUID = async body => (await cookieUID()) || htmlUID(body);
836
854
  const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
837
855
  const bypass = async body => {
856
+ if (config.autoDismissAutomationWarning !== true) {
857
+ logger("Facebook is showing its automated-behavior warning. Not dismissing it automatically: open facebook.com in your browser, dismiss it yourself, then restart. (Set autoDismissAutomationWarning=true in fca-config.json to dismiss it automatically.)", "warn");
858
+ return;
859
+ }
838
860
  const b = s(body);
839
861
  const UID = await getUID(b);
840
862
  const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
@@ -866,7 +888,8 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
866
888
  }
867
889
  return first;
868
890
  } catch (e) {
869
- logger(`Bypass automation error: ${e && e.message ? e.message : String(e)}`, "error");
891
+ logger(`Facebook security handling stopped: ${e && e.message ? e.message : String(e)}`, "error");
892
+ if (e && e.code === "FACEBOOK_SECURITY_CHECKPOINT") throw e;
870
893
  return resp;
871
894
  }
872
895
  };
@@ -905,6 +928,15 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
905
928
  const ctx = {};
906
929
  ctx.options = globalOptions;
907
930
  ctx.bypassAutomation = async function (resp, j) {
931
+ // Never submit mutations intended to dismiss/bypass Facebook security warnings.
932
+ const body = typeof resp?.data === "string" ? resp.data : String(resp?.data ?? "");
933
+ const url = resp?.request?.res?.responseUrl || resp?.config?.url || "";
934
+ if (/601051028565049|XCheckpointFBScrapingWarningController|automated.?behavior|checkpoint\//i.test(`${url} ${body}`)) {
935
+ logger("Facebook security/automation checkpoint detected; not bypassing it. Stop the bot and review the account manually.", "error");
936
+ const err = new Error("Facebook Checkpoint/security warning detected; automatic recovery stopped");
937
+ err.code = "FACEBOOK_SECURITY_CHECKPOINT";
938
+ throw err;
939
+ }
908
940
  global.fca = global.fca || {};
909
941
  global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
910
942
  const s = x => (typeof x === "string" ? x : String(x ?? ""));
@@ -920,6 +952,10 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
920
952
  const getUID = async body => (await cookieUID()) || htmlUID(body);
921
953
  const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
922
954
  const bypass = async body => {
955
+ if (config.autoDismissAutomationWarning !== true) {
956
+ logger("Facebook is showing its automated-behavior warning. Not dismissing it automatically (see autoDismissAutomationWarning in fca-config.json).", "warn");
957
+ return;
958
+ }
923
959
  const b = s(body);
924
960
  const UID = await getUID(b);
925
961
  const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
@@ -938,7 +974,8 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
938
974
  }
939
975
  logger("✅ No checkpoint detected — session is clean.", "info");
940
976
  return res;
941
- } catch {
977
+ } catch (e) {
978
+ if (e && e.code === "FACEBOOK_SECURITY_CHECKPOINT") throw e;
942
979
  return res;
943
980
  }
944
981
  };
@@ -970,7 +1007,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
970
1007
  if (!isValidUID(userID)) {
971
1008
  logger("Invalid userID detected (missing or 0), attempting auto-login...", "warn");
972
1009
  // Pass hadAppStateInput=true if appState/Cookie was originally provided
973
- const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
1010
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie), userIDFromAppState);
974
1011
  html = retried.html;
975
1012
  cookies = retried.cookies;
976
1013
  userID = retried.userID;
@@ -1089,7 +1126,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
1089
1126
  if (!isValidUID(info.USER_ID)) {
1090
1127
  logger("Facebook response shows invalid USER_ID (0 or empty), session is dead!", "warn");
1091
1128
  // Force trigger auto-login
1092
- const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
1129
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie), userIDFromAppState);
1093
1130
  html = retried.html;
1094
1131
  cookies = retried.cookies;
1095
1132
  userID = retried.userID;
@@ -1358,8 +1395,10 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
1358
1395
  }
1359
1396
 
1360
1397
  try {
1361
- const { checkForUpdate } = require("../src/utils/versionCheck");
1362
- checkForUpdate(logger);
1398
+ if (config.checkForUpdates === true) {
1399
+ const { checkForUpdate } = require("../src/utils/versionCheck");
1400
+ checkForUpdate(logger);
1401
+ }
1363
1402
  } catch (_) {}
1364
1403
  logger("🚀 Login successful! Bot is ready.");
1365
1404
  callback(null, api);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hridoy-ultimate",
3
- "version": "9.0.2",
3
+ "version": "9.0.4",
4
4
  "description": "Facebook Chat API by HR ID OY | Stable • Auto Re-login • Full E2EE Support — send messages, media, reactions & more in encrypted chats, hassle-free",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -9,7 +9,8 @@
9
9
  "require": "./index.js",
10
10
  "default": "./index.js",
11
11
  "types": "./index.d.ts"
12
- }
12
+ },
13
+ "./package.json": "./package.json"
13
14
  },
14
15
  "files": [
15
16
  "index.js",
@@ -24,7 +25,7 @@
24
25
  "src/app/"
25
26
  ],
26
27
  "scripts": {
27
- "test": "mocha",
28
+ "test": "node --test",
28
29
  "lint": "eslint ."
29
30
  },
30
31
  "repository": {
@@ -66,7 +67,7 @@
66
67
  },
67
68
  "homepage": "https://github.com/hridoyhossen48360/hridoy-ultimate#readme",
68
69
  "engines": {
69
- "node": ">=12.0.0"
70
+ "node": ">=18"
70
71
  },
71
72
  "dependencies": {
72
73
  "axios": "^1.13.5",
@@ -75,21 +76,23 @@
75
76
  "chalk": "^4.1.2",
76
77
  "cheerio": "1.0.0-rc.12",
77
78
  "duplexify": "^4.1.3",
79
+ "form-data": "^4.0.0",
78
80
  "gradient-string": "^2.0.2",
79
81
  "https-proxy-agent": "^4.0.0",
82
+ "koffi": "^3.0.2",
83
+ "mime": "^3.0.0",
80
84
  "mqtt": "^5.10.1",
85
+ "@noble/curves": "^2.2.0",
86
+ "@noble/hashes": "^2.2.0",
87
+ "protobufjs": "^8.4.0",
88
+ "request": "^2.88.2",
81
89
  "sequelize": "^6.37.6",
90
+ "@signalapp/libsignal-client": "0.70.0",
82
91
  "sqlite3": "^5.1.7",
83
92
  "totp-generator": "^1.0.0",
93
+ "tough-cookie": "^5.0.0",
84
94
  "ws": "^8.18.0",
85
- "@noble/curves": "^2.2.0",
86
- "@noble/hashes": "^2.2.0",
87
- "@signalapp/libsignal-client": "0.70.0",
88
- "mime": "^3.0.0",
89
- "protobufjs": "^8.4.0",
90
- "koffi": "^3.0.2",
91
- "yumi-json-bigint": "^1.0.0",
92
- "fca-unofficial": "github:VangBanLaNhat/fca-unofficial"
95
+ "yumi-json-bigint": "^1.0.0"
93
96
  },
94
97
  "devDependencies": {
95
98
  "eslint": "^8.50.0",
@@ -98,5 +101,8 @@
98
101
  "publishConfig": {
99
102
  "access": "public",
100
103
  "registry": "https://registry.npmjs.org/"
104
+ },
105
+ "optionalDependencies": {
106
+ "fca-unofficial": "github:VangBanLaNhat/fca-unofficial"
101
107
  }
102
108
  }
@@ -20,14 +20,17 @@ module.exports = function (defaultFuncs, api, ctx) {
20
20
  // Save function
21
21
  function saveAppState() {
22
22
  try {
23
- const appState = api.getAppState();
24
- if (!appState || !appState.appState || appState.appState.length === 0) {
25
- logger("AppState is empty, skipping save", "warn");
23
+ const raw = api.getAppState();
24
+ const appState = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.appState) ? raw.appState : null);
25
+ if (!appState || appState.length < 5) {
26
+ logger("AppState is empty or too small, skipping save", "warn");
26
27
  return;
27
28
  }
28
29
 
29
- const data = JSON.stringify(appState, null, 2);
30
- fs.writeFileSync(filePath, data, "utf8");
30
+ // write-then-rename so a crash mid-write can never leave a truncated cookie file
31
+ const tmp = filePath + ".tmp";
32
+ fs.writeFileSync(tmp, JSON.stringify(appState, null, 2), "utf8");
33
+ fs.renameSync(tmp, filePath);
31
34
  logger(`AppState saved to ${filePath}`, "info");
32
35
  } catch (error) {
33
36
  logger(`Error saving AppState: ${error && error.message ? error.message : String(error)}`, "error");
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ const { globalAntiSuspension } = require("../../utils/antiSuspension");
4
+
5
+ /**
6
+ * api.getSafetyStatus() -> {
7
+ * blocked, reason, coolingDown, warmup, hourSendCount, hourlyCap, daySendCount, dailyCap
8
+ * }
9
+ * `blocked` is true after Facebook showed a checkpoint / automated-behavior / disabled
10
+ * signal: sendMessage then rejects with err.code === "ACCOUNT_SAFETY_PAUSED" until the
11
+ * process is restarted after you reviewed the account by hand.
12
+ */
13
+ module.exports = function (defaultFuncs, api, ctx) {
14
+ return function getSafetyStatus() {
15
+ const st = globalAntiSuspension.getStats();
16
+ return {
17
+ blocked: !!st.safetyBlocked,
18
+ reason: st.safetyReason || null,
19
+ coolingDown: !!st.coolingDown,
20
+ warmup: !!st.warmup,
21
+ hourSendCount: st.hourSendCount,
22
+ hourlyCap: st.hourlyCap,
23
+ daySendCount: st.daySendCount,
24
+ dailyCap: st.dailyCap
25
+ };
26
+ };
27
+ };
@@ -1,75 +1,86 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * logout - stop this bot session WITHOUT logging the account out of Facebook.
5
+ *
6
+ * Older FCA builds POSTed facebook.com/logout.php here. That ends the session
7
+ * server-side, so the same account is logged out in your own browser as well,
8
+ * which is the opposite of what a bot owner wants when they just stop the bot.
9
+ *
10
+ * Default behaviour (since 9.0.5): local stop only
11
+ * - stops the MQTT listener,
12
+ * - cancels the fb_dtsg refresh timer,
13
+ * - stops sessionGuard's timer,
14
+ * - never sends anything to Facebook.
15
+ *
16
+ * The real Facebook logout is api.logoutFromFacebook(). It can also be made the
17
+ * behaviour of api.logout() by setting `"allowFacebookLogout": true` in
18
+ * fca-config.json (explicit opt-in).
19
+ */
20
+
3
21
  const log = require("../../../func/logAdapter");
4
- const { parseAndCheckLogin } = require("../../utils/client");
5
- const { getFrom } = require("../../utils/constants");
6
- const { saveCookies } = require("../../utils/client");
7
- module.exports = function(defaultFuncs, api, ctx) {
22
+ const { loadConfig } = require("../../../module/config");
23
+
24
+ module.exports = function (defaultFuncs, api, ctx) {
25
+ function stopListener() {
26
+ return new Promise(resolve => {
27
+ let done = false;
28
+ const finish = () => { if (!done) { done = true; resolve(); } };
29
+ try {
30
+ if (typeof api.stopListening === "function") {
31
+ api.stopListening(finish);
32
+ // never hang a shutdown on a listener that was never started
33
+ setTimeout(finish, 6000).unref?.();
34
+ } else {
35
+ finish();
36
+ }
37
+ } catch (_) {
38
+ finish();
39
+ }
40
+ });
41
+ }
42
+
43
+ function stopTimers() {
44
+ try {
45
+ if (ctx._dtsgRefreshInterval) {
46
+ clearInterval(ctx._dtsgRefreshInterval);
47
+ ctx._dtsgRefreshInterval = null;
48
+ }
49
+ } catch (_) { /* ignore */ }
50
+ try {
51
+ if (typeof api.stopSessionGuard === "function") api.stopSessionGuard();
52
+ } catch (_) { /* ignore */ }
53
+ }
54
+
8
55
  return function logout(callback) {
9
- let resolveFunc = function() {};
10
- let rejectFunc = function() {};
11
- const returnPromise = new Promise(function(resolve, reject) {
56
+ let resolveFunc = function () {};
57
+ let rejectFunc = function () {};
58
+ const returnPromise = new Promise(function (resolve, reject) {
12
59
  resolveFunc = resolve;
13
60
  rejectFunc = reject;
14
61
  });
15
62
 
16
- if (!callback) {
17
- callback = function(err, friendList) {
18
- if (err) {
19
- return rejectFunc(err);
20
- }
21
- resolveFunc(friendList);
63
+ if (typeof callback !== "function") {
64
+ callback = function (err, result) {
65
+ if (err) return rejectFunc(err);
66
+ resolveFunc(result);
22
67
  };
23
68
  }
24
69
 
25
- const form = {
26
- pmid: "0"
27
- };
28
-
29
- defaultFuncs
30
- .post(
31
- "https://www.facebook.com/bluebar/modern_settings_menu/?help_type=364455653583099&show_contextual_help=1",
32
- ctx.jar,
33
- form
34
- )
35
- .then(parseAndCheckLogin(ctx, defaultFuncs))
36
- .then(function(resData) {
37
- const elem = resData.jsmods.instances[0][2][0].filter(function(v) {
38
- return v.value === "logout";
39
- })[0];
40
-
41
- const html = resData.jsmods.markup.filter(function(v) {
42
- return v[0] === elem.markup.__m;
43
- })[0][1].__html;
44
-
45
- const form = {
46
- fb_dtsg: getFrom(html, '"fb_dtsg" value="', '"'),
47
- ref: getFrom(html, '"ref" value="', '"'),
48
- h: getFrom(html, '"h" value="', '"')
49
- };
50
-
51
- return defaultFuncs
52
- .post("https://www.facebook.com/logout.php", ctx.jar, form)
53
- .then(saveCookies(ctx.jar));
54
- })
55
- .then(function(res) {
56
- if (!res.headers) {
57
- throw { error: "An error occurred when logging out." };
58
- }
70
+ const { config } = loadConfig();
71
+ if (config && config.allowFacebookLogout === true && typeof api.logoutFromFacebook === "function") {
72
+ log.warn("logout", "allowFacebookLogout is true: logging out of Facebook for real (your browser session ends too).");
73
+ api.logoutFromFacebook(callback);
74
+ return returnPromise;
75
+ }
59
76
 
60
- return defaultFuncs
61
- .get(res.headers.location, ctx.jar)
62
- .then(saveCookies(ctx.jar));
63
- })
64
- .then(function() {
65
- ctx.loggedIn = false;
66
- log.info("logout", "Logged out successfully.");
67
- callback();
77
+ log.info("logout", "Stopping locally. The Facebook session is NOT ended (use api.logoutFromFacebook() for a real logout).");
78
+ stopListener()
79
+ .then(() => {
80
+ stopTimers();
81
+ callback(null, { localOnly: true });
68
82
  })
69
- .catch(function(err) {
70
- log.error("logout", err);
71
- return callback(err);
72
- });
83
+ .catch(err => callback(err));
73
84
 
74
85
  return returnPromise;
75
86
  };