@lazyneoaz/metachat 1.0.0 → 1.0.1

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": "@lazyneoaz/metachat",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "commonjs",
5
5
  "types": "src/types/index.d.ts",
6
6
  "description": "Advanced Facebook Chat API client for building Messenger bots — real-time messaging, thread management, MQTT, and session stability.",
@@ -8,8 +8,7 @@
8
8
  "files": [
9
9
  "index.js",
10
10
  "src/",
11
- "LICENSE",
12
- "README.md"
11
+ "LICENSE"
13
12
  ],
14
13
  "repository": {
15
14
  "type": "git",
@@ -41,18 +40,18 @@
41
40
  "axios": "^1.13.5",
42
41
  "axios-cookiejar-support": "^4.0.7",
43
42
  "bluebird": "^3.7.2",
44
- "cheerio": "^1.0.0",
43
+ "cheerio": "^1.0.0-rc.12",
45
44
  "cli-progress": "^3.12.0",
46
45
  "deepdash": "^5.3.9",
47
46
  "duplexify": "^4.1.3",
48
47
  "form-data": "^4.0.4",
49
- "gradient-string": "^3.0.0",
48
+ "gradient-string": "^2.0.2",
50
49
  "https-proxy-agent": "^7.0.6",
51
50
  "jsonpath-plus": "^10.3.0",
52
51
  "lodash": "^4.17.21",
53
52
  "mqtt": "^4.3.8",
54
53
  "node-cron": "^3.0.3",
55
- "ora": "^9.4.0",
54
+ "ora": "^5.4.1",
56
55
  "picocolors": "^1.1.1",
57
56
  "sequelize": "^6.37.5",
58
57
  "sqlite3": "^5.1.7",
@@ -22,10 +22,10 @@ module.exports = function (defaultFuncs, api, ctx) {
22
22
  return function enableAutoSaveAppState(options) {
23
23
  options = options || {};
24
24
  const filePath = options.filePath || path.join(process.cwd(), "appstate.json");
25
- const interval = options.interval || 10 * 60 * 1000;
25
+ const interval = options.interval || 5 * 60 * 1000; // 5 min default (was 10 min)
26
26
  const saveOnLogin = options.saveOnLogin !== false;
27
27
 
28
- function saveAppState() {
28
+ async function saveAppState() {
29
29
  try {
30
30
  const appState = api.getAppState ? api.getAppState() : null;
31
31
  if (!appState || (Array.isArray(appState) && appState.length === 0)) {
@@ -33,9 +33,20 @@ module.exports = function (defaultFuncs, api, ctx) {
33
33
  }
34
34
  const payload = Array.isArray(appState) ? appState : (appState.appState || appState);
35
35
  fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
36
- } catch (err) {
37
- // Silently ignore write errors (e.g. read-only FS in some environments)
36
+ } catch (_) {
37
+ // Silently ignore file-write errors (e.g. read-only FS)
38
38
  }
39
+
40
+ // Keep the database backup in sync so re-login always uses fresh cookies.
41
+ try {
42
+ const { backupAppStateSQL } = require('../database/appStateBackup');
43
+ const jar = (ctx && ctx.jar) ? ctx.jar : require('../utils').getJar();
44
+ const userID = (ctx && ctx.userID) ? ctx.userID :
45
+ (typeof api.getCurrentUserID === 'function' ? api.getCurrentUserID() : null);
46
+ if (jar && userID) {
47
+ await backupAppStateSQL(jar, userID);
48
+ }
49
+ } catch (_) {}
39
50
  }
40
51
 
41
52
  let immediateTimer = null;
@@ -206,7 +206,19 @@ async function loginHelper(credentials, globalOptions, callback, setOptionsFunc,
206
206
  globalAntiSuspension.enableWarmup();
207
207
  } catch (_) {}
208
208
 
209
- const resp = await utils.get(fbLinkFunc(), jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
209
+ let resp = await utils.get(fbLinkFunc(), jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
210
+
211
+ // Auto-dismiss the FBScrapingWarning checkpoint before building the API.
212
+ // If left unhandled this checkpoint escalates to a permanent account ban.
213
+ try {
214
+ const { bypassScrapingWarning } = require('../../utils/checkpointBypass');
215
+ const bypassedResp = await bypassScrapingWarning(jar, globalOptions, null, resp);
216
+ if (bypassedResp) {
217
+ resp = bypassedResp;
218
+ utils.log("LoginHelper", "Session restored after scraping-warning bypass");
219
+ }
220
+ } catch (_) {}
221
+
210
222
  const extractNetData = (html) => {
211
223
  const allScriptsData = [];
212
224
  const scriptRegex = /<script type="application\/json"[^>]*>(.*?)<\/script>/g;
@@ -2,7 +2,7 @@
2
2
  import { ReadStream } from "fs";
3
3
  import { EventEmitter } from "events";
4
4
 
5
- declare module "dhoner-fca" {
5
+ declare module "@lazyneoaz/metachat" {
6
6
  export type UserID = string;
7
7
  export type ThreadID = string;
8
8
  export type MessageID = string;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+
3
+ const utils = require('./index');
4
+
5
+ /**
6
+ * Detects and auto-dismisses Facebook's FBScrapingWarning checkpoint.
7
+ *
8
+ * This checkpoint (at /checkpoint/601051028565049) is triggered when Facebook
9
+ * suspects automated access. If left unhandled it escalates to a permanent
10
+ * account suspension. The fix is to POST the FBScrapingWarningMutation GraphQL
11
+ * call with doc_id 6339492849481770, which silently acknowledges the warning.
12
+ *
13
+ * @param {object} jar - The cookie jar.
14
+ * @param {object} globalOptions - Global request options.
15
+ * @param {string|null} userID - User ID (extracted from cookies/HTML if omitted).
16
+ * @param {object|null} existingResp - An already-fetched homepage response to inspect
17
+ * first. When provided, a fresh fetch is skipped
18
+ * unless the checkpoint is actually detected.
19
+ * @returns {Promise<object|null>} The refreshed response after bypass, or null if no
20
+ * checkpoint was present.
21
+ */
22
+ async function bypassScrapingWarning(jar, globalOptions, userID = null, existingResp = null) {
23
+ try {
24
+ const toStr = (x) => (typeof x === 'string' ? x : JSON.stringify(x || ""));
25
+
26
+ const isCp = (resp) => {
27
+ if (!resp) return false;
28
+ const body = toStr(resp.body || resp.data || "");
29
+ return (
30
+ body.includes('checkpoint/601051028565049') ||
31
+ body.includes('XCheckpointFBScrapingWarningController')
32
+ );
33
+ };
34
+
35
+ // Use the existing response if provided, otherwise fetch the homepage.
36
+ let initial = existingResp || null;
37
+ if (!initial) {
38
+ initial = await utils.get(
39
+ 'https://www.facebook.com/',
40
+ jar, null, globalOptions,
41
+ { noRef: true, _skipSessionInspect: true }
42
+ );
43
+ utils.saveCookies(jar)(initial);
44
+ }
45
+
46
+ if (!isCp(initial)) {
47
+ return null;
48
+ }
49
+
50
+ utils.warn("BYPASS", "FBScrapingWarning checkpoint detected — auto-dismissing...");
51
+
52
+ const html = toStr(initial.body || initial.data || "");
53
+
54
+ // --- Extract user ID ---
55
+ let uid = userID || null;
56
+ if (!uid) {
57
+ try {
58
+ const cookies = jar.getCookiesSync('https://www.facebook.com');
59
+ const pick = (key) => (cookies.find(c => c.key === key) || {}).value;
60
+ uid = pick('i_user') || pick('c_user') || null;
61
+ } catch (_) {}
62
+ }
63
+ if (!uid) {
64
+ const m = html.match(/"USER_ID"\s*:\s*"(\d+)"/) ||
65
+ html.match(/\["CurrentUserInitialData",\[\],\{[^}]*"USER_ID":"(\d+)"/);
66
+ if (m) uid = m[1];
67
+ }
68
+
69
+ // --- Extract CSRF tokens ---
70
+ const grab = (patterns) => {
71
+ for (const re of patterns) {
72
+ const m = html.match(re);
73
+ if (m && m[1]) return m[1];
74
+ }
75
+ return null;
76
+ };
77
+
78
+ const fb_dtsg = grab([
79
+ /"DTSGInitData",\[\],\{"token":"([^"]+)"/,
80
+ /name="fb_dtsg"\s+value="([^"]+)"/,
81
+ /"DTSGInitialData",\[\],\{"token":"([^"]+)"/
82
+ ]);
83
+ const jazoest = grab([
84
+ /name="jazoest"\s+value="([^"]+)"/,
85
+ /jazoest=(\d+)/
86
+ ]);
87
+ const lsd = grab([
88
+ /"LSD",\[\],\{"token":"([^"]+)"/,
89
+ /name="lsd"\s+value="([^"]+)"/
90
+ ]);
91
+
92
+ if (!fb_dtsg || !uid) {
93
+ utils.warn("BYPASS", `Cannot bypass — missing tokens (uid=${uid ? 'ok' : 'missing'}, fb_dtsg=${fb_dtsg ? 'ok' : 'missing'})`);
94
+ return initial;
95
+ }
96
+
97
+ // --- POST FBScrapingWarningMutation ---
98
+ const form = {
99
+ av: uid,
100
+ fb_dtsg,
101
+ jazoest: jazoest || "",
102
+ lsd: lsd || "",
103
+ fb_api_caller_class: "RelayModern",
104
+ fb_api_req_friendly_name: "FBScrapingWarningMutation",
105
+ variables: "{}",
106
+ server_timestamps: "true",
107
+ doc_id: "6339492849481770"
108
+ };
109
+
110
+ const mutResp = await utils.post(
111
+ 'https://www.facebook.com/api/graphql/',
112
+ jar, form, globalOptions,
113
+ { noRef: true, _skipSessionInspect: true }
114
+ );
115
+ utils.saveCookies(jar)(mutResp);
116
+
117
+ // --- Re-fetch homepage to confirm checkpoint is gone ---
118
+ const refreshed = await utils.get(
119
+ 'https://www.facebook.com/',
120
+ jar, null, globalOptions,
121
+ { noRef: true, _skipSessionInspect: true }
122
+ );
123
+ utils.saveCookies(jar)(refreshed);
124
+
125
+ if (isCp(refreshed)) {
126
+ utils.warn("BYPASS", "Checkpoint still present after FBScrapingWarningMutation — may need manual intervention");
127
+ } else {
128
+ utils.log("BYPASS", "FBScrapingWarning checkpoint dismissed. Session restored.");
129
+ }
130
+
131
+ return refreshed;
132
+ } catch (err) {
133
+ utils.error("BYPASS", `bypassScrapingWarning error: ${err && err.message ? err.message : String(err)}`);
134
+ return null;
135
+ }
136
+ }
137
+
138
+ module.exports = { bypassScrapingWarning };
@@ -140,13 +140,29 @@ class TokenRefreshManager {
140
140
  const RETRY_DELAYS = [2000, 5000, 10000];
141
141
 
142
142
  try {
143
- const resp = await utils.get(fbLink, ctx.jar, null, ctx.globalOptions, { noRef: true });
144
-
145
- const html = resp.body;
143
+ let resp = await utils.get(fbLink, ctx.jar, null, ctx.globalOptions, { noRef: true });
144
+ utils.saveCookies(ctx.jar)(resp);
145
+
146
+ let html = resp.body;
146
147
  if (!html) {
147
148
  throw new Error("Empty response from Facebook");
148
149
  }
149
150
 
151
+ // Auto-dismiss FBScrapingWarning before any other checks
152
+ const isScrapingWarning = html.includes('XCheckpointFBScrapingWarningController') ||
153
+ html.includes('checkpoint/601051028565049');
154
+ if (isScrapingWarning) {
155
+ try {
156
+ const { bypassScrapingWarning } = require('./checkpointBypass');
157
+ const bypassed = await bypassScrapingWarning(ctx.jar, ctx.globalOptions, ctx.userID, resp);
158
+ if (bypassed) {
159
+ resp = bypassed;
160
+ html = resp.body || html;
161
+ utils.log("TokenRefresh", "Scraping-warning checkpoint dismissed during token refresh");
162
+ }
163
+ } catch (_) {}
164
+ }
165
+
150
166
  // Precise check - broad html.includes("login") is a false positive because
151
167
  // Facebook includes the word "login" all over authenticated pages too.
152
168
  const isLoginPage = html.includes('<form id="login_form"') ||
@@ -193,12 +209,19 @@ class TokenRefreshManager {
193
209
 
194
210
  this.lastRefresh = Date.now();
195
211
  this.failureCount = 0;
212
+
213
+ // Persist fresh cookies and update the DB backup so re-login
214
+ // attempts always use the newest session state.
196
215
  try {
197
216
  if (globalAutoReLoginManager && globalAutoReLoginManager.isEnabled && globalAutoReLoginManager.isEnabled()) {
198
217
  const appState = utils.getAppState(ctx.jar);
199
218
  globalAutoReLoginManager.updateAppState(appState);
200
219
  }
201
220
  } catch (_) {}
221
+ try {
222
+ const { backupAppStateSQL } = require('../database/appStateBackup');
223
+ await backupAppStateSQL(ctx.jar, ctx.userID);
224
+ } catch (_) {}
202
225
  return true;
203
226
  } catch (error) {
204
227
  this.failureCount++;