shadowx-fca 2.4.0 → 2.6.0

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/index.js CHANGED
@@ -3,454 +3,699 @@
3
3
  var utils = require("./utils");
4
4
  var cheerio = require("cheerio");
5
5
  var log = require("npmlog");
6
+ var crypto = require("crypto");
6
7
  log.maxRecordSize = 100;
7
8
  var checkVerified = null;
8
9
  const Boolean_Option = ['online', 'selfListen', 'listenEvents', 'updatePresence', 'forceLogin', 'autoMarkDelivery', 'autoMarkRead', 'listenTyping', 'autoReconnect', 'emitReady'];
9
10
  global.ditconmemay = false;
10
11
 
11
12
  function setOptions(globalOptions, options) {
12
- Object.keys(options).map(function (key) {
13
- switch (Boolean_Option.includes(key)) {
14
- case true: {
15
- globalOptions[key] = Boolean(options[key]);
16
- break;
17
- }
18
- case false: {
19
- switch (key) {
20
- case 'pauseLog': {
21
- if (options.pauseLog) log.pause();
22
- else log.resume();
23
- break;
24
- }
25
- case 'logLevel': {
26
- log.level = options.logLevel;
27
- globalOptions.logLevel = options.logLevel;
28
- break;
29
- }
30
- case 'logRecordSize': {
31
- log.maxRecordSize = options.logRecordSize;
32
- globalOptions.logRecordSize = options.logRecordSize;
33
- break;
34
- }
35
- case 'pageID': {
36
- globalOptions.pageID = options.pageID.toString();
37
- break;
38
- }
39
- case 'userAgent': {
40
- globalOptions.userAgent = (options.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');
41
- break;
42
- }
43
- case 'proxy': {
44
- if (typeof options.proxy != "string") {
45
- delete globalOptions.proxy;
46
- utils.setProxy();
47
- } else {
48
- globalOptions.proxy = options.proxy;
49
- utils.setProxy(globalOptions.proxy);
50
- }
51
- break;
52
- }
53
- default: {
54
- log.warn("setOptions", "Unrecognized option given to setOptions: " + key);
55
- break;
56
- }
57
- }
58
- break;
59
- }
60
- }
61
- });
13
+ Object.keys(options).map(function (key) {
14
+ switch (Boolean_Option.includes(key)) {
15
+ case true: {
16
+ globalOptions[key] = Boolean(options[key]);
17
+ break;
18
+ }
19
+ case false: {
20
+ switch (key) {
21
+ case 'pauseLog': {
22
+ if (options.pauseLog) log.pause();
23
+ else log.resume();
24
+ break;
25
+ }
26
+ case 'logLevel': {
27
+ log.level = options.logLevel;
28
+ globalOptions.logLevel = options.logLevel;
29
+ break;
30
+ }
31
+ case 'logRecordSize': {
32
+ log.maxRecordSize = options.logRecordSize;
33
+ globalOptions.logRecordSize = options.logRecordSize;
34
+ break;
35
+ }
36
+ case 'pageID': {
37
+ globalOptions.pageID = options.pageID.toString();
38
+ break;
39
+ }
40
+ case 'userAgent': {
41
+ globalOptions.userAgent = (options.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');
42
+ break;
43
+ }
44
+ case 'proxy': {
45
+ if (typeof options.proxy != "string") {
46
+ delete globalOptions.proxy;
47
+ utils.setProxy();
48
+ } else {
49
+ globalOptions.proxy = options.proxy;
50
+ utils.setProxy(globalOptions.proxy);
51
+ }
52
+ break;
53
+ }
54
+ default: {
55
+ log.warn("setOptions", "Unrecognized option given to setOptions: " + key);
56
+ break;
57
+ }
58
+ }
59
+ break;
60
+ }
61
+ }
62
+ });
62
63
  }
63
64
 
64
65
  function buildAPI(globalOptions, html, jar) {
65
- let fb_dtsg = null;
66
- let irisSeqID = null;
67
- function extractFromHTML() {
68
- try {
69
- const $ = cheerio.load(html);
70
- $('script').each((i, script) => {
71
- if (!fb_dtsg) {
72
- const scriptText = $(script).html() || '';
73
- const patterns = [
74
- /\["DTSGInitialData",\[\],{"token":"([^"]+)"}]/,
75
- /\["DTSGInitData",\[\],{"token":"([^"]+)"/,
76
- /"token":"([^"]+)"/,
77
- /{\\"token\\":\\"([^\\]+)\\"/,
78
- /,\{"token":"([^"]+)"\},\d+\]/,
79
- /"async_get_token":"([^"]+)"/,
80
- /"dtsg":\{"token":"([^"]+)"/,
81
- /DTSGInitialData[^>]+>([^<]+)/
82
- ];
83
- for (const pattern of patterns) {
84
- const match = scriptText.match(pattern);
85
- if (match && match[1]) {
86
- try {
87
- const possibleJson = match[1].replace(/\\"/g, '"');
88
- const parsed = JSON.parse(possibleJson);
89
- fb_dtsg = parsed.token || parsed;
90
- } catch {
91
- fb_dtsg = match[1];
92
- }
93
- if (fb_dtsg) break;
94
- }
95
- }
96
- }
97
- });
98
- if (!fb_dtsg) {
99
- const dtsgInput = $('input[name="fb_dtsg"]').val();
100
- if (dtsgInput) fb_dtsg = dtsgInput;
101
- }
102
- const seqMatches = html.match(/irisSeqID":"([^"]+)"/);
103
- if (seqMatches && seqMatches[1]) {
104
- irisSeqID = seqMatches[1];
105
- }
106
- try {
107
- const jsonMatches = html.match(/\{"dtsg":({[^}]+})/);
108
- if (jsonMatches && jsonMatches[1]) {
109
- const dtsgData = JSON.parse(jsonMatches[1]);
110
- if (dtsgData.token) fb_dtsg = dtsgData.token;
111
- }
112
- } catch { }
113
- if (fb_dtsg) {
114
- log.info("✅ | Found fb_Dtsg");
115
- }
116
- } catch (e) {
117
- console.log("Error finding fb_dtsg:", e);
118
- }
119
- }
120
- extractFromHTML();
121
- var userID;
122
- var cookies = jar.getCookies("https://www.facebook.com");
123
- var userCookie = cookies.find(cookie => cookie.cookieString().startsWith("c_user="));
124
- var tiktikCookie = cookies.find(cookie => cookie.cookieString().startsWith("i_user="));
125
- if (!userCookie && !tiktikCookie) {
126
- return log.error('login', "No cookie found for user, please check login information again");
127
- }
128
- if (html.includes("/checkpoint/block/?next")) {
129
- return log.error('login', "Appstate dead, please replace with a new one!", 'error');
130
- }
131
- userID = (tiktikCookie || userCookie).cookieString().split("=")[1];
132
-
133
- try { clearInterval(checkVerified); } catch (_) { }
134
- const clientID = (Math.random() * 2147483648 | 0).toString(16);
135
- let mqttEndpoint = `wss://edge-chat.facebook.com/chat?region=prn&sid=${userID}`;
136
- let region = "PRN";
66
+ let fb_dtsg = null;
67
+ let irisSeqID = null;
68
+ function extractFromHTML() {
69
+ try {
70
+ const $ = cheerio.load(html);
71
+ $('script').each((i, script) => {
72
+ if (!fb_dtsg) {
73
+ const scriptText = $(script).html() || '';
74
+ const patterns = [
75
+ /\["DTSGInitialData",\[\],{"token":"([^"]+)"}]/,
76
+ /\["DTSGInitData",\[\],{"token":"([^"]+)"/,
77
+ /"token":"([^"]+)"/,
78
+ /{\\"token\\":\\"([^\\]+)\\"/,
79
+ /,\{"token":"([^"]+)"\},\d+\]/,
80
+ /"async_get_token":"([^"]+)"/,
81
+ /"dtsg":\{"token":"([^"]+)"/,
82
+ /DTSGInitialData[^>]+>([^<]+)/
83
+ ];
84
+ for (const pattern of patterns) {
85
+ const match = scriptText.match(pattern);
86
+ if (match && match[1]) {
87
+ try {
88
+ const possibleJson = match[1].replace(/\\"/g, '"');
89
+ const parsed = JSON.parse(possibleJson);
90
+ fb_dtsg = parsed.token || parsed;
91
+ } catch {
92
+ fb_dtsg = match[1];
93
+ }
94
+ if (fb_dtsg) break;
95
+ }
96
+ }
97
+ }
98
+ });
99
+ if (!fb_dtsg) {
100
+ const dtsgInput = $('input[name="fb_dtsg"]').val();
101
+ if (dtsgInput) fb_dtsg = dtsgInput;
102
+ }
103
+ const seqMatches = html.match(/irisSeqID":"([^"]+)"/);
104
+ if (seqMatches && seqMatches[1]) {
105
+ irisSeqID = seqMatches[1];
106
+ }
107
+ try {
108
+ const jsonMatches = html.match(/\{"dtsg":({[^}]+})/);
109
+ if (jsonMatches && jsonMatches[1]) {
110
+ const dtsgData = JSON.parse(jsonMatches[1]);
111
+ if (dtsgData.token) fb_dtsg = dtsgData.token;
112
+ }
113
+ } catch { }
114
+ if (fb_dtsg) {
115
+ log.info("✅ | Found fb_Dtsg");
116
+ }
117
+ } catch (e) {
118
+ console.log("Error finding fb_dtsg:", e);
119
+ }
120
+ }
121
+ extractFromHTML();
122
+ var userID;
123
+ var cookies = jar.getCookies("https://www.facebook.com");
124
+ var userCookie = cookies.find(cookie => cookie.cookieString().startsWith("c_user="));
125
+ var tiktikCookie = cookies.find(cookie => cookie.cookieString().startsWith("i_user="));
126
+ if (!userCookie && !tiktikCookie) {
127
+ return log.error('login', "No cookie found for user, please check login information again");
128
+ }
129
+ if (html.includes("/checkpoint/block/?next")) {
130
+ return log.error('login', "Appstate dead, please replace with a new one!", 'error');
131
+ }
132
+ userID = (tiktikCookie || userCookie).cookieString().split("=")[1];
137
133
 
138
- try {
139
- const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
140
- if (endpointMatch.input.includes("601051028565049")) {
141
- console.log(`login error due to automatic account`);
142
- ditconmemay = true;
143
- }
144
- if (endpointMatch) {
145
- mqttEndpoint = endpointMatch[1].replace(/\\\//g, '/');
146
- const url = new URL(mqttEndpoint);
147
- region = url.searchParams.get('region')?.toUpperCase() || "PRN";
148
- }
149
- } catch (e) {
150
- console.log('Using default MQTT endpoint');
151
- }
152
- var ctx = {
153
- userID: userID,
154
- jar: jar,
155
- clientID: clientID,
156
- globalOptions: globalOptions,
157
- loggedIn: true,
158
- access_token: 'NONE',
159
- clientMutationId: 0,
160
- mqttClient: undefined,
161
- lastSeqId: irisSeqID,
162
- syncToken: undefined,
163
- mqttEndpoint: mqttEndpoint,
164
- region: region,
165
- firstListen: true,
166
- fb_dtsg: fb_dtsg,
167
- req_ID: 0,
168
- callback_Task: {},
169
- wsReqNumber: 0,
170
- wsTaskNumber: 0,
171
- reqCallbacks: {}
172
- };
173
- var api = {
174
- setOptions: setOptions.bind(null, globalOptions),
175
- getAppState: () => utils.getAppState(jar),
176
- postFormData: (url, body) => utils.makeDefaults(html, userID, ctx).postFormData(url, ctx.jar, body)
177
- };
178
- var defaultFuncs = utils.makeDefaults(html, userID, ctx);
179
- api.postFormData = function (url, body) {
180
- return defaultFuncs.postFormData(url, ctx.jar, body);
181
- };
182
- api.getFreshDtsg = async function () {
183
- try {
184
- const res = await defaultFuncs.get('https://www.facebook.com/', jar, null, globalOptions);
185
- const $ = cheerio.load(res.body);
186
- let newDtsg;
187
- const patterns = [
188
- /\["DTSGInitialData",\[\],{"token":"([^"]+)"}]/,
189
- /\["DTSGInitData",\[\],{"token":"([^"]+)"/,
190
- /"token":"([^"]+)"/,
191
- /name="fb_dtsg" value="([^"]+)"/
192
- ];
134
+ try { clearInterval(checkVerified); } catch (_) { }
135
+ const clientID = (Math.random() * 2147483648 | 0).toString(16);
136
+ let mqttEndpoint = `wss://edge-chat.facebook.com/chat?region=prn&sid=${userID}`;
137
+ let region = "PRN";
193
138
 
194
- $('script').each((i, script) => {
195
- if (!newDtsg) {
196
- const scriptText = $(script).html() || '';
197
- for (const pattern of patterns) {
198
- const match = scriptText.match(pattern);
199
- if (match && match[1]) {
200
- newDtsg = match[1];
201
- break;
202
- }
203
- }
204
- }
205
- });
139
+ try {
140
+ const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
141
+ if (endpointMatch && endpointMatch.input && endpointMatch.input.includes("601051028565049")) {
142
+ console.log(`login error due to automatic account`);
143
+ ditconmemay = true;
144
+ }
145
+ if (endpointMatch) {
146
+ mqttEndpoint = endpointMatch[1].replace(/\\\//g, '/');
147
+ const url = new URL(mqttEndpoint);
148
+ region = url.searchParams.get('region')?.toUpperCase() || "PRN";
149
+ }
150
+ } catch (e) {
151
+ console.log('Using default MQTT endpoint');
152
+ }
153
+ var ctx = {
154
+ userID: userID,
155
+ jar: jar,
156
+ clientID: clientID,
157
+ globalOptions: globalOptions,
158
+ loggedIn: true,
159
+ access_token: 'NONE',
160
+ clientMutationId: 0,
161
+ mqttClient: undefined,
162
+ lastSeqId: irisSeqID,
163
+ syncToken: undefined,
164
+ mqttEndpoint: mqttEndpoint,
165
+ region: region,
166
+ firstListen: true,
167
+ fb_dtsg: fb_dtsg,
168
+ req_ID: 0,
169
+ callback_Task: {},
170
+ wsReqNumber: 0,
171
+ wsTaskNumber: 0,
172
+ reqCallbacks: {}
173
+ };
174
+ var api = {
175
+ setOptions: setOptions.bind(null, globalOptions),
176
+ getAppState: () => utils.getAppState(jar),
177
+ postFormData: (url, body) => utils.makeDefaults(html, userID, ctx).postFormData(url, ctx.jar, body)
178
+ };
179
+ var defaultFuncs = utils.makeDefaults(html, userID, ctx);
180
+ api.postFormData = function (url, body) {
181
+ return defaultFuncs.postFormData(url, ctx.jar, body);
182
+ };
183
+ api.getFreshDtsg = async function () {
184
+ try {
185
+ const res = await defaultFuncs.get('https://www.facebook.com/', jar, null, globalOptions);
186
+ const $ = cheerio.load(res.body);
187
+ let newDtsg;
188
+ const patterns = [
189
+ /\["DTSGInitialData",\[\],{"token":"([^"]+)"}]/,
190
+ /\["DTSGInitData",\[\],{"token":"([^"]+)"/,
191
+ /"token":"([^"]+)"/,
192
+ /name="fb_dtsg" value="([^"]+)"/
193
+ ];
206
194
 
207
- if (!newDtsg) {
208
- newDtsg = $('input[name="fb_dtsg"]').val();
209
- }
195
+ $('script').each((i, script) => {
196
+ if (!newDtsg) {
197
+ const scriptText = $(script).html() || '';
198
+ for (const pattern of patterns) {
199
+ const match = scriptText.match(pattern);
200
+ if (match && match[1]) {
201
+ newDtsg = match[1];
202
+ break;
203
+ }
204
+ }
205
+ }
206
+ });
210
207
 
211
- return newDtsg;
212
- } catch (e) {
213
- console.log("Error getting fresh dtsg:", e);
214
- return null;
215
- }
216
- };
217
-
218
- require('fs').readdirSync(__dirname + '/src/').filter(v => v.endsWith('.js')).forEach(v => { api[v.replace('.js', '')] = require(`./src/${v}`)(utils.makeDefaults(html, userID, ctx), api, ctx); });
219
- api.listen = api.listenMqtt;
220
- return {
221
- ctx,
222
- defaultFuncs,
223
- api
224
- };
208
+ if (!newDtsg) {
209
+ newDtsg = $('input[name="fb_dtsg"]').val();
210
+ }
211
+
212
+ return newDtsg;
213
+ } catch (e) {
214
+ console.log("Error getting fresh dtsg:", e);
215
+ return null;
216
+ }
217
+ };
218
+
219
+ require('fs').readdirSync(__dirname + '/src/').filter(v => v.endsWith('.js')).forEach(v => { api[v.replace('.js', '')] = require(`./src/${v}`)(utils.makeDefaults(html, userID, ctx), api, ctx); });
220
+ api.listen = api.listenMqtt;
221
+ return {
222
+ ctx,
223
+ defaultFuncs,
224
+ api
225
+ };
225
226
  }
226
227
 
227
- function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
228
- return async function (res) {
229
- try {
230
- const html = res.body;
231
- const $ = cheerio.load(html);
232
- let arr = [];
233
- $("#login_form input").each((i, v) => arr.push({ val: $(v).val(), name: $(v).attr("name") }));
234
- arr = arr.filter(v => v.val && v.val.length);
235
- let form = utils.arrToForm(arr);
236
- form.lsd = utils.getFrom(html, "[\"LSD\",[],{\"token\":\"", "\"}");
237
- form.lgndim = Buffer.from(JSON.stringify({ w: 1440, h: 900, aw: 1440, ah: 834, c: 24 })).toString('base64');
238
- form.email = email;
239
- form.pass = password;
240
- form.default_persistent = '0';
241
- form.lgnrnd = utils.getFrom(html, "name=\"lgnrnd\" value=\"", "\"");
242
- form.locale = 'en_US';
243
- form.timezone = '240';
244
- form.lgnjs = Math.floor(Date.now() / 1000);
245
- const willBeCookies = html.split("\"_js_");
246
- willBeCookies.slice(1).forEach(val => {
247
- const cookieData = JSON.parse("[\"" + utils.getFrom(val, "", "]") + "]");
248
- jar.setCookie(utils.formatCookie(cookieData, "facebook"), "https://www.facebook.com");
249
- });
250
- log.info("login", "Logging in...");
251
- const loginRes = await utils.post(
252
- "https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110",
253
- jar,
254
- form,
255
- loginOptions
256
- );
257
- await utils.saveCookies(jar)(loginRes);
258
- const headers = loginRes.headers;
259
- if (!headers.location) throw new Error("Wrong username/password.");
260
- if (headers.location.includes('https://www.facebook.com/checkpoint/')) {
261
- log.info("login", "You have login approvals turned on.");
262
- const checkpointRes = await utils.get(headers.location, jar, null, loginOptions);
263
- await utils.saveCookies(jar)(checkpointRes);
264
- const checkpointHtml = checkpointRes.body;
265
- const $ = cheerio.load(checkpointHtml);
266
- let checkpointForm = [];
267
- $("form input").each((i, v) => checkpointForm.push({ val: $(v).val(), name: $(v).attr("name") }));
268
- checkpointForm = checkpointForm.filter(v => v.val && v.val.length);
269
- const form = utils.arrToForm(checkpointForm);
270
- if (checkpointHtml.includes("checkpoint/?next")) {
271
- return new Promise((resolve, reject) => {
272
- const submit2FA = async (code) => {
273
- try {
274
- form.approvals_code = code;
275
- form['submit[Continue]'] = $("#checkpointSubmitButton").html();
276
- const approvalRes = await utils.post(
277
- "https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php",
278
- jar,
279
- form,
280
- loginOptions
281
- );
282
- await utils.saveCookies(jar)(approvalRes);
283
- const approvalError = $("#approvals_code").parent().attr("data-xui-error");
284
- if (approvalError) throw new Error("Invalid 2FA code.");
285
- form.name_action_selected = 'dont_save';
286
- const finalRes = await utils.post(
287
- "https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php",
288
- jar,
289
- form,
290
- loginOptions
291
- );
292
- await utils.saveCookies(jar)(finalRes);
293
- const appState = utils.getAppState(jar);
294
- resolve(await loginHelper(appState, email, password, loginOptions, callback));
295
- } catch (error) {
296
- reject(error);
297
- }
298
- };
299
- throw {
300
- error: 'login-approval',
301
- continue: submit2FA
302
- };
303
- });
304
- }
305
- if (!loginOptions.forceLogin) throw new Error("Couldn't login. Facebook might have blocked this account.");
306
- form['submit[This was me]'] = checkpointHtml.includes("Suspicious Login Attempt") ? "This was me" : "This Is Okay";
307
- await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form, loginOptions);
308
- form.name_action_selected = 'save_device';
309
- const reviewRes = await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form, loginOptions);
310
- const appState = utils.getAppState(jar);
311
- return await loginHelper(appState, email, password, loginOptions, callback);
312
- }
313
- await utils.get('https://www.facebook.com/', jar, null, loginOptions);
314
- return await utils.saveCookies(jar);
315
- } catch (error) {
316
- callback(error);
317
- }
318
- };
228
+ // Session keeper to prevent logout
229
+ function startSessionKeeper(api, ctx) {
230
+ // Keep session alive with random activity
231
+ const keepAliveInterval = setInterval(async () => {
232
+ try {
233
+ const actions = [
234
+ async () => {
235
+ const res = await utils.get('https://www.facebook.com/', ctx.jar, null, ctx.globalOptions);
236
+ return res;
237
+ },
238
+ async () => {
239
+ const newDtsg = await api.getFreshDtsg();
240
+ if (newDtsg) ctx.fb_dtsg = newDtsg;
241
+ }
242
+ ];
243
+ const randomAction = actions[Math.floor(Math.random() * actions.length)];
244
+ await randomAction();
245
+ log.verbose("session", "Keep-alive ping sent");
246
+ } catch (e) {
247
+ log.verbose("session", "Keep-alive failed: " + e.message);
248
+ }
249
+ }, 240000 + Math.floor(Math.random() * 120000)); // 4-6 minutes
250
+
251
+ // Refresh DTSG periodically
252
+ const dtsgInterval = setInterval(async () => {
253
+ try {
254
+ const newDtsg = await api.getFreshDtsg();
255
+ if (newDtsg) {
256
+ ctx.fb_dtsg = newDtsg;
257
+ log.info("session", "DTSG refreshed");
258
+ }
259
+ } catch (e) {
260
+ log.verbose("session", "DTSG refresh failed");
261
+ }
262
+ }, 1800000); // 30 minutes
263
+
264
+ // Store intervals for cleanup
265
+ ctx._keepAliveInterval = keepAliveInterval;
266
+ ctx._dtsgInterval = dtsgInterval;
267
+ }
268
+
269
+ // Calculate jazoest for Facebook anti-bot
270
+ function calculateJazoest(token) {
271
+ let sum = 0;
272
+ for (let i = 0; i < token.length; i++) {
273
+ sum += token.charCodeAt(i);
274
+ }
275
+ return sum.toString();
319
276
  }
320
277
 
278
+ // Modern authentication function
279
+ async function modernAuthenticate(email, password, jar, loginOptions) {
280
+ const API_KEYS = [
281
+ '350685531728|62f8ce9f74b12f84c123cc23437a4a32',
282
+ '256002347743983|374e60f8b9bb6b8cbb30f78030438895'
283
+ ];
284
+
285
+ const randomApiKey = API_KEYS[Math.floor(Math.random() * API_KEYS.length)];
286
+ const [appId, clientToken] = randomApiKey.includes('|') ? randomApiKey.split('|') : ['350685531728', randomApiKey];
287
+
288
+ const pax = Math.random() > 0.5 ? "PWD_BROWSER" : "PWD_FB4A";
289
+ const adid = [...Array(16)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
290
+ const device_id = crypto.randomUUID();
291
+ const family_device_id = crypto.randomUUID();
292
+ const machine_id = crypto.randomBytes(16).toString('hex');
293
+ const session_id = crypto.randomBytes(12).toString('hex');
294
+
295
+ const locales = {
296
+ "en_US": { code: "US", tz: "-240" },
297
+ "en_GB": { code: "GB", tz: "0" },
298
+ "fr_FR": { code: "FR", tz: "60" },
299
+ "de_DE": { code: "DE", tz: "60" },
300
+ "es_ES": { code: "ES", tz: "60" },
301
+ "it_IT": { code: "IT", tz: "60" },
302
+ "pt_BR": { code: "BR", tz: "-180" }
303
+ };
304
+
305
+ const localeKeys = Object.keys(locales);
306
+ const country_locale = localeKeys[Math.floor(Math.random() * localeKeys.length)];
307
+ const localeData = locales[country_locale];
308
+
309
+ const timestamp = Math.floor(Date.now() / 1000);
310
+ const connectionToken = `${appId}|${clientToken}`;
311
+ const jazoest = calculateJazoest(clientToken);
312
+
313
+ const data = {
314
+ adid: adid,
315
+ format: 'json',
316
+ device_id: device_id,
317
+ email: email,
318
+ enc_password: `#${pax}:0:${timestamp}:${password}`,
319
+ generate_analytics_claims: '1',
320
+ generate_session_cookies: '1',
321
+ generate_machine_id: '1',
322
+ machine_id: machine_id,
323
+ session_id: session_id,
324
+ credentials_type: 'password',
325
+ source: 'login',
326
+ error_detail_type: 'button_with_disabled',
327
+ enroll_misauth: 'false',
328
+ currently_logged_in_userid: '0',
329
+ locale: country_locale,
330
+ client_country_code: localeData.code,
331
+ timezone_offset: localeData.tz,
332
+ screen_resolution: '1920x1080',
333
+ available_screen_resolution: '1920x1040',
334
+ fb_api_req_friendly_name: 'authenticate',
335
+ api_key: clientToken,
336
+ access_token: connectionToken,
337
+ jazoest: jazoest,
338
+ meta_inf_fbmeta: '',
339
+ skip_api_login: 'false',
340
+ fb_api_caller_class: 'com.facebook.fblogin',
341
+ cpl: 'true',
342
+ try_num: '1',
343
+ family_device_id: family_device_id,
344
+ community_id: ''
345
+ };
346
+
347
+ const userAgents = [
348
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
349
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
350
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
351
+ ];
352
+
353
+ const userAgent = userAgents[Math.floor(Math.random() * userAgents.length)];
354
+
355
+ const headers = {
356
+ 'User-Agent': userAgent,
357
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
358
+ 'Accept-Language': country_locale.replace('_', '-') + ',en-US;q=0.9',
359
+ 'Accept-Encoding': 'gzip, deflate, br',
360
+ 'Content-Type': 'application/x-www-form-urlencoded',
361
+ 'Host': 'graph.facebook.com',
362
+ 'Origin': 'https://www.facebook.com',
363
+ 'Referer': 'https://www.facebook.com/',
364
+ 'Connection': 'keep-alive',
365
+ 'Sec-Fetch-Dest': 'empty',
366
+ 'Sec-Fetch-Mode': 'cors',
367
+ 'Sec-Fetch-Site': 'same-site',
368
+ 'sec-ch-ua': '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
369
+ 'sec-ch-ua-mobile': '?0',
370
+ 'sec-ch-ua-platform': '"Windows"',
371
+ 'Cache-Control': 'no-cache',
372
+ 'Pragma': 'no-cache',
373
+ 'X-FB-Net-HNI': String(Math.floor(Math.random() * 90000) + 10000),
374
+ 'X-FB-SIM-HNI': String(Math.floor(Math.random() * 90000) + 10000),
375
+ 'Authorization': `OAuth ${connectionToken}`,
376
+ 'X-FB-Connection-Type': 'WIFI',
377
+ 'X-Tigon-Is-Retry': 'False',
378
+ 'x-fb-session-id': `nid=${session_id};pid=Main;tid=132;nc=1;fc=0;bc=0;cid=${clientToken}`,
379
+ 'x-fb-device-group': '5120',
380
+ 'X-FB-Friendly-Name': 'authenticate',
381
+ 'X-FB-Request-Analytics-Tags': 'graphservice',
382
+ 'X-FB-HTTP-Engine': 'Liger',
383
+ 'X-FB-Client-IP': 'True',
384
+ 'X-FB-Server-Cluster': 'True',
385
+ 'x-fb-connection-token': connectionToken
386
+ };
387
+
388
+ log.info("login", "Attempting modern authentication...");
389
+
390
+ const response = await utils.post(
391
+ "https://b-graph.facebook.com/auth/login",
392
+ jar,
393
+ data,
394
+ loginOptions,
395
+ null,
396
+ headers
397
+ );
398
+
399
+ return response;
400
+ }
401
+
402
+ // Handle 2FA checkpoint
403
+ async function handleCheckpoint(checkpointUrl, jar, loginOptions) {
404
+ log.info("login", "Login approvals are on. Expect an SMS shortly with a code to use for log in");
405
+
406
+ return new Promise((resolve, reject) => {
407
+ const submit2FA = async (code) => {
408
+ try {
409
+ const checkpointRes = await utils.get(checkpointUrl, jar, null, loginOptions);
410
+ const $ = cheerio.load(checkpointRes.body);
411
+
412
+ let checkpointForm = [];
413
+ $("form input").each((i, v) => checkpointForm.push({ val: $(v).val(), name: $(v).attr("name") }));
414
+ checkpointForm = checkpointForm.filter(v => v.val && v.val.length);
415
+ const form = utils.arrToForm(checkpointForm);
416
+
417
+ form.approvals_code = code;
418
+ form['submit[Continue]'] = $("#checkpointSubmitButton").html() || "Continue";
419
+
420
+ const approvalRes = await utils.post(
421
+ checkpointUrl,
422
+ jar,
423
+ form,
424
+ loginOptions
425
+ );
426
+
427
+ await utils.saveCookies(jar)(approvalRes);
428
+
429
+ const $approval = cheerio.load(approvalRes.body);
430
+ const approvalError = $approval("#approvals_code").parent().attr("data-xui-error");
431
+ if (approvalError) {
432
+ reject(new Error("Invalid 2FA code."));
433
+ return;
434
+ }
435
+
436
+ form.name_action_selected = 'save_device';
437
+ const finalRes = await utils.post(
438
+ checkpointUrl,
439
+ jar,
440
+ form,
441
+ loginOptions
442
+ );
443
+
444
+ await utils.saveCookies(jar)(finalRes);
445
+ resolve(true);
446
+ } catch (error) {
447
+ reject(error);
448
+ }
449
+ };
450
+
451
+ reject({
452
+ error: 'login-approval',
453
+ continue: submit2FA
454
+ });
455
+ });
456
+ }
457
+
458
+ function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
459
+ return async function (res) {
460
+ try {
461
+ const html = res.body;
462
+
463
+ // Try modern authentication first
464
+ let loginSuccess = false;
465
+ let checkpointUrl = null;
466
+
467
+ try {
468
+ const modernRes = await modernAuthenticate(email, password, jar, loginOptions);
469
+ await utils.saveCookies(jar)(modernRes);
470
+
471
+ let responseBody;
472
+ try {
473
+ responseBody = JSON.parse(modernRes.body);
474
+ } catch (e) {
475
+ responseBody = { error: true };
476
+ }
477
+
478
+ if (responseBody.session_cookies) {
479
+ responseBody.session_cookies.forEach(cookie => {
480
+ jar.setCookie(
481
+ `${cookie.name}=${cookie.value}; Domain=.facebook.com; Path=/; Secure; HttpOnly`,
482
+ "https://www.facebook.com"
483
+ );
484
+ });
485
+ loginSuccess = true;
486
+ log.info("login", "Modern authentication successful");
487
+ } else if (responseBody.error && responseBody.error.code === 401) {
488
+ log.info("login", "Modern auth failed, falling back to traditional login");
489
+ } else if (responseBody.login_approval_url) {
490
+ checkpointUrl = responseBody.login_approval_url;
491
+ }
492
+ } catch (e) {
493
+ log.info("login", "Modern auth error, falling back to traditional: " + e.message);
494
+ }
495
+
496
+ // Handle checkpoint if needed
497
+ if (checkpointUrl) {
498
+ await handleCheckpoint(checkpointUrl, jar, loginOptions);
499
+ loginSuccess = true;
500
+ }
501
+
502
+ // Fallback to traditional login if modern fails
503
+ if (!loginSuccess) {
504
+ const $ = cheerio.load(html);
505
+ let arr = [];
506
+ $("#login_form input").each((i, v) => arr.push({ val: $(v).val(), name: $(v).attr("name") }));
507
+ arr = arr.filter(v => v.val && v.val.length);
508
+ let form = utils.arrToForm(arr);
509
+ form.lsd = utils.getFrom(html, "[\"LSD\",[],{\"token\":\"", "\"");
510
+ form.lgndim = Buffer.from(JSON.stringify({ w: 1440, h: 900, aw: 1440, ah: 834, c: 24 })).toString('base64');
511
+ form.email = email;
512
+ form.pass = password;
513
+ form.default_persistent = '0';
514
+ form.lgnrnd = utils.getFrom(html, "name=\"lgnrnd\" value=\"", "\"");
515
+ form.locale = 'en_US';
516
+ form.timezone = '240';
517
+ form.lgnjs = Math.floor(Date.now() / 1000);
518
+
519
+ const willBeCookies = html.split("\"_js_");
520
+ willBeCookies.slice(1).forEach(val => {
521
+ try {
522
+ const cookieData = JSON.parse("[\"" + utils.getFrom(val, "", "]") + "]");
523
+ jar.setCookie(utils.formatCookie(cookieData, "facebook"), "https://www.facebook.com");
524
+ } catch (e) {}
525
+ });
526
+
527
+ log.info("login", "Logging in via traditional method...");
528
+ const loginRes = await utils.post(
529
+ "https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110",
530
+ jar,
531
+ form,
532
+ loginOptions
533
+ );
534
+ await utils.saveCookies(jar)(loginRes);
535
+
536
+ const headers = loginRes.headers;
537
+ if (!headers.location) throw new Error("Wrong username/password.");
538
+
539
+ if (headers.location.includes('https://www.facebook.com/checkpoint/')) {
540
+ await handleCheckpoint(headers.location, jar, loginOptions);
541
+ }
542
+ }
543
+
544
+ // Final verification
545
+ await utils.get('https://www.facebook.com/', jar, null, loginOptions);
546
+
547
+ // Add delay to avoid rate limiting
548
+ await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 3000));
549
+
550
+ return await utils.saveCookies(jar);
551
+
552
+ } catch (error) {
553
+ if (error.error === 'login-approval') {
554
+ callback(error);
555
+ return;
556
+ }
557
+ callback(error);
558
+ }
559
+ };
560
+ }
321
561
 
322
562
  function loginHelper(appState, email, password, globalOptions, callback, prCallback) {
323
- let mainPromise = null;
324
- const jar = utils.getJar();
325
- if (appState) {
326
- try {
327
- appState = JSON.parse(appState);
328
- } catch (e) {
329
- try {
330
- appState = appState;
331
- } catch (e) {
332
- return callback(new Error("Failed to parse appState"));
333
- }
334
- }
563
+ let mainPromise = null;
564
+ const jar = utils.getJar();
565
+
566
+ if (appState) {
567
+ try {
568
+ appState = JSON.parse(appState);
569
+ } catch (e) {
570
+ try {
571
+ appState = appState;
572
+ } catch (e) {
573
+ return callback(new Error("Failed to parse appState"));
574
+ }
575
+ }
335
576
 
336
- try {
337
- appState.forEach(c => {
338
- const str = `${c.key}=${c.value}; expires=${c.expires}; domain=${c.domain}; path=${c.path};`;
339
- jar.setCookie(str, "http://" + c.domain);
340
- });
577
+ try {
578
+ appState.forEach(c => {
579
+ const str = `${c.key}=${c.value}; expires=${c.expires}; domain=${c.domain}; path=${c.path};`;
580
+ jar.setCookie(str, "http://" + c.domain);
581
+ });
341
582
 
342
- mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
343
- .then(utils.saveCookies(jar));
344
- } catch (e) {
345
- process.exit(0);
346
- }
347
- } else {
348
- mainPromise = utils
349
- .get("https://www.facebook.com/", null, null, globalOptions, { noRef: true })
350
- .then(utils.saveCookies(jar))
351
- .then(makeLogin(jar, email, password, globalOptions, callback, prCallback))
352
- .then(() => utils.get('https://www.facebook.com/', jar, null, globalOptions).then(utils.saveCookies(jar)));
353
- }
583
+ mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
584
+ .then(utils.saveCookies(jar));
585
+ } catch (e) {
586
+ process.exit(0);
587
+ }
588
+ } else {
589
+ mainPromise = utils
590
+ .get("https://www.facebook.com/", null, null, globalOptions, { noRef: true })
591
+ .then(utils.saveCookies(jar))
592
+ .then(makeLogin(jar, email, password, globalOptions, callback, prCallback))
593
+ .then(() => utils.get('https://www.facebook.com/', jar, null, globalOptions).then(utils.saveCookies(jar)));
594
+ }
354
595
 
355
- function handleRedirect(res) {
356
- const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
357
- const redirect = reg.exec(res.body);
358
- if (redirect && redirect[1]) {
359
- return utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
360
- }
361
- return res;
362
- }
596
+ function handleRedirect(res) {
597
+ const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
598
+ const redirect = reg.exec(res.body);
599
+ if (redirect && redirect[1]) {
600
+ return utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
601
+ }
602
+ return res;
603
+ }
363
604
 
364
- let ctx, api;
365
- mainPromise = mainPromise
366
- .then(handleRedirect)
367
- .then(res => {
368
- const mobileAgentRegex = /MPageLoadClientMetrics/gs;
369
- if (!mobileAgentRegex.test(res.body)) {
370
- globalOptions.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36";
371
- return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
372
- }
373
- return res;
374
- })
375
- .then(handleRedirect)
376
- .then(res => {
377
- const html = res.body;
378
- const Obj = buildAPI(globalOptions, html, jar);
379
- ctx = Obj.ctx;
380
- api = Obj.api;
381
- return res;
382
- });
605
+ let ctx, api;
606
+ mainPromise = mainPromise
607
+ .then(handleRedirect)
608
+ .then(res => {
609
+ const mobileAgentRegex = /MPageLoadClientMetrics/gs;
610
+ if (!mobileAgentRegex.test(res.body)) {
611
+ globalOptions.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
612
+ return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
613
+ }
614
+ return res;
615
+ })
616
+ .then(handleRedirect)
617
+ .then(res => {
618
+ const html = res.body;
619
+ const Obj = buildAPI(globalOptions, html, jar);
620
+ ctx = Obj.ctx;
621
+ api = Obj.api;
622
+ return res;
623
+ });
383
624
 
384
- if (globalOptions.pageID) {
385
- mainPromise = mainPromise
386
- .then(() => utils.get(`https://www.facebook.com/${globalOptions.pageID}/messages/?section=messages&subsection=inbox`, jar, null, globalOptions))
387
- .then(resData => {
388
- let url = utils.getFrom(resData.body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
389
- url = url.substring(0, url.length - 1);
390
- return utils.get('https://www.facebook.com' + url, jar, null, globalOptions);
391
- });
392
- }
625
+ if (globalOptions.pageID) {
626
+ mainPromise = mainPromise
627
+ .then(() => utils.get(`https://www.facebook.com/${globalOptions.pageID}/messages/?section=messages&subsection=inbox`, jar, null, globalOptions))
628
+ .then(resData => {
629
+ let url = utils.getFrom(resData.body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
630
+ url = url.substring(0, url.length - 1);
631
+ return utils.get('https://www.facebook.com' + url, jar, null, globalOptions);
632
+ });
633
+ }
393
634
 
394
- mainPromise
395
- .then(async () => {
396
- log.info('Login successful');
397
- callback(null, api);
398
- })
399
- .catch(e => {
400
- callback(e);
401
- });
635
+ mainPromise
636
+ .then(async () => {
637
+ log.info('Login successful');
638
+
639
+ // Start session keeper to prevent logout
640
+ startSessionKeeper(api, ctx);
641
+
642
+ callback(null, api);
643
+ })
644
+ .catch(e => {
645
+ callback(e);
646
+ });
402
647
  }
403
648
 
404
-
405
649
  function login(loginData, options, callback) {
406
- if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
407
- callback = options;
408
- options = {};
409
- }
650
+ if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
651
+ callback = options;
652
+ options = {};
653
+ }
410
654
 
411
- var globalOptions = {
412
- selfListen: false,
413
- listenEvents: false,
414
- listenTyping: false,
415
- updatePresence: false,
416
- forceLogin: false,
417
- autoMarkDelivery: false,
418
- autoMarkRead: false,
419
- autoReconnect: true,
420
- logRecordSize: 100,
421
- online: true,
422
- emitReady: false,
423
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
424
- };
655
+ var globalOptions = {
656
+ selfListen: false,
657
+ listenEvents: false,
658
+ listenTyping: false,
659
+ updatePresence: false,
660
+ forceLogin: false,
661
+ autoMarkDelivery: false,
662
+ autoMarkRead: false,
663
+ autoReconnect: true,
664
+ logRecordSize: 100,
665
+ online: true,
666
+ emitReady: false,
667
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
668
+ };
425
669
 
426
- var prCallback = null;
427
- if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
428
- var rejectFunc = null;
429
- var resolveFunc = null;
430
- var returnPromise = new Promise(function (resolve, reject) {
431
- resolveFunc = resolve;
432
- rejectFunc = reject;
433
- });
434
- prCallback = function (error, api) {
435
- if (error) return rejectFunc(error);
436
- return resolveFunc(api);
437
- };
438
- callback = prCallback;
439
- }
670
+ var prCallback = null;
671
+ var returnPromise = null;
672
+
673
+ if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
674
+ var rejectFunc = null;
675
+ var resolveFunc = null;
676
+ returnPromise = new Promise(function (resolve, reject) {
677
+ resolveFunc = resolve;
678
+ rejectFunc = reject;
679
+ });
680
+ prCallback = function (error, api) {
681
+ if (error) return rejectFunc(error);
682
+ return resolveFunc(api);
683
+ };
684
+ callback = prCallback;
685
+ }
440
686
 
441
- if (loginData.email && loginData.password) {
442
- setOptions(globalOptions, {
443
- logLevel: "silent",
444
- forceLogin: true,
445
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
446
- });
447
- loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
448
- } else if (loginData.appState) {
449
- setOptions(globalOptions, options);
450
- return loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
451
- }
452
- return returnPromise;
687
+ if (loginData.email && loginData.password) {
688
+ setOptions(globalOptions, {
689
+ logLevel: "silent",
690
+ forceLogin: true,
691
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
692
+ });
693
+ loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
694
+ } else if (loginData.appState) {
695
+ setOptions(globalOptions, options);
696
+ return loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
697
+ }
698
+ return returnPromise;
453
699
  }
454
700
 
455
-
456
- module.exports = login;
701
+ module.exports = login;