@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.
|
|
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": "^
|
|
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": "^
|
|
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 ||
|
|
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 (
|
|
37
|
-
// Silently ignore write errors (e.g. read-only FS
|
|
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
|
-
|
|
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;
|
package/src/types/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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++;
|