shadowx-fca 2.4.0 → 2.5.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,700 @@
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
+ '882a8490361da98702bf97a021ddc14d|62f8ce9f74b12f84c123cc23437a4a32'
284
+ ];
285
+
286
+ const randomApiKey = API_KEYS[Math.floor(Math.random() * API_KEYS.length)];
287
+ const [appId, clientToken] = randomApiKey.includes('|') ? randomApiKey.split('|') : ['350685531728', randomApiKey];
288
+
289
+ const pax = Math.random() > 0.5 ? "PWD_BROWSER" : "PWD_FB4A";
290
+ const adid = [...Array(16)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
291
+ const device_id = crypto.randomUUID();
292
+ const family_device_id = crypto.randomUUID();
293
+ const machine_id = crypto.randomBytes(16).toString('hex');
294
+ const session_id = crypto.randomBytes(12).toString('hex');
295
+
296
+ const locales = {
297
+ "en_US": { code: "US", tz: "-240" },
298
+ "en_GB": { code: "GB", tz: "0" },
299
+ "fr_FR": { code: "FR", tz: "60" },
300
+ "de_DE": { code: "DE", tz: "60" },
301
+ "es_ES": { code: "ES", tz: "60" },
302
+ "it_IT": { code: "IT", tz: "60" },
303
+ "pt_BR": { code: "BR", tz: "-180" }
304
+ };
305
+
306
+ const localeKeys = Object.keys(locales);
307
+ const country_locale = localeKeys[Math.floor(Math.random() * localeKeys.length)];
308
+ const localeData = locales[country_locale];
309
+
310
+ const timestamp = Math.floor(Date.now() / 1000);
311
+ const connectionToken = `${appId}|${clientToken}`;
312
+ const jazoest = calculateJazoest(clientToken);
313
+
314
+ const data = {
315
+ adid: adid,
316
+ format: 'json',
317
+ device_id: device_id,
318
+ email: email,
319
+ enc_password: `#${pax}:0:${timestamp}:${password}`,
320
+ generate_analytics_claims: '1',
321
+ generate_session_cookies: '1',
322
+ generate_machine_id: '1',
323
+ machine_id: machine_id,
324
+ session_id: session_id,
325
+ credentials_type: 'password',
326
+ source: 'login',
327
+ error_detail_type: 'button_with_disabled',
328
+ enroll_misauth: 'false',
329
+ currently_logged_in_userid: '0',
330
+ locale: country_locale,
331
+ client_country_code: localeData.code,
332
+ timezone_offset: localeData.tz,
333
+ screen_resolution: '1920x1080',
334
+ available_screen_resolution: '1920x1040',
335
+ fb_api_req_friendly_name: 'authenticate',
336
+ api_key: clientToken,
337
+ access_token: connectionToken,
338
+ jazoest: jazoest,
339
+ meta_inf_fbmeta: '',
340
+ skip_api_login: 'false',
341
+ fb_api_caller_class: 'com.facebook.fblogin',
342
+ cpl: 'true',
343
+ try_num: '1',
344
+ family_device_id: family_device_id,
345
+ community_id: ''
346
+ };
347
+
348
+ const userAgents = [
349
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
350
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
351
+ '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'
352
+ ];
353
+
354
+ const userAgent = userAgents[Math.floor(Math.random() * userAgents.length)];
355
+
356
+ const headers = {
357
+ 'User-Agent': userAgent,
358
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
359
+ 'Accept-Language': country_locale.replace('_', '-') + ',en-US;q=0.9',
360
+ 'Accept-Encoding': 'gzip, deflate, br',
361
+ 'Content-Type': 'application/x-www-form-urlencoded',
362
+ 'Host': 'graph.facebook.com',
363
+ 'Origin': 'https://www.facebook.com',
364
+ 'Referer': 'https://www.facebook.com/',
365
+ 'Connection': 'keep-alive',
366
+ 'Sec-Fetch-Dest': 'empty',
367
+ 'Sec-Fetch-Mode': 'cors',
368
+ 'Sec-Fetch-Site': 'same-site',
369
+ 'sec-ch-ua': '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
370
+ 'sec-ch-ua-mobile': '?0',
371
+ 'sec-ch-ua-platform': '"Windows"',
372
+ 'Cache-Control': 'no-cache',
373
+ 'Pragma': 'no-cache',
374
+ 'X-FB-Net-HNI': String(Math.floor(Math.random() * 90000) + 10000),
375
+ 'X-FB-SIM-HNI': String(Math.floor(Math.random() * 90000) + 10000),
376
+ 'Authorization': `OAuth ${connectionToken}`,
377
+ 'X-FB-Connection-Type': 'WIFI',
378
+ 'X-Tigon-Is-Retry': 'False',
379
+ 'x-fb-session-id': `nid=${session_id};pid=Main;tid=132;nc=1;fc=0;bc=0;cid=${clientToken}`,
380
+ 'x-fb-device-group': '5120',
381
+ 'X-FB-Friendly-Name': 'authenticate',
382
+ 'X-FB-Request-Analytics-Tags': 'graphservice',
383
+ 'X-FB-HTTP-Engine': 'Liger',
384
+ 'X-FB-Client-IP': 'True',
385
+ 'X-FB-Server-Cluster': 'True',
386
+ 'x-fb-connection-token': connectionToken
387
+ };
388
+
389
+ log.info("login", "Attempting modern authentication...");
390
+
391
+ const response = await utils.post(
392
+ "https://b-graph.facebook.com/auth/login",
393
+ jar,
394
+ data,
395
+ loginOptions,
396
+ null,
397
+ headers
398
+ );
399
+
400
+ return response;
401
+ }
402
+
403
+ // Handle 2FA checkpoint
404
+ async function handleCheckpoint(checkpointUrl, jar, loginOptions) {
405
+ log.info("login", "Login approvals are on. Expect an SMS shortly with a code to use for log in");
406
+
407
+ return new Promise((resolve, reject) => {
408
+ const submit2FA = async (code) => {
409
+ try {
410
+ const checkpointRes = await utils.get(checkpointUrl, jar, null, loginOptions);
411
+ const $ = cheerio.load(checkpointRes.body);
412
+
413
+ let checkpointForm = [];
414
+ $("form input").each((i, v) => checkpointForm.push({ val: $(v).val(), name: $(v).attr("name") }));
415
+ checkpointForm = checkpointForm.filter(v => v.val && v.val.length);
416
+ const form = utils.arrToForm(checkpointForm);
417
+
418
+ form.approvals_code = code;
419
+ form['submit[Continue]'] = $("#checkpointSubmitButton").html() || "Continue";
420
+
421
+ const approvalRes = await utils.post(
422
+ checkpointUrl,
423
+ jar,
424
+ form,
425
+ loginOptions
426
+ );
427
+
428
+ await utils.saveCookies(jar)(approvalRes);
429
+
430
+ const $approval = cheerio.load(approvalRes.body);
431
+ const approvalError = $approval("#approvals_code").parent().attr("data-xui-error");
432
+ if (approvalError) {
433
+ reject(new Error("Invalid 2FA code."));
434
+ return;
435
+ }
436
+
437
+ form.name_action_selected = 'save_device';
438
+ const finalRes = await utils.post(
439
+ checkpointUrl,
440
+ jar,
441
+ form,
442
+ loginOptions
443
+ );
444
+
445
+ await utils.saveCookies(jar)(finalRes);
446
+ resolve(true);
447
+ } catch (error) {
448
+ reject(error);
449
+ }
450
+ };
451
+
452
+ reject({
453
+ error: 'login-approval',
454
+ continue: submit2FA
455
+ });
456
+ });
457
+ }
458
+
459
+ function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
460
+ return async function (res) {
461
+ try {
462
+ const html = res.body;
463
+
464
+ // Try modern authentication first
465
+ let loginSuccess = false;
466
+ let checkpointUrl = null;
467
+
468
+ try {
469
+ const modernRes = await modernAuthenticate(email, password, jar, loginOptions);
470
+ await utils.saveCookies(jar)(modernRes);
471
+
472
+ let responseBody;
473
+ try {
474
+ responseBody = JSON.parse(modernRes.body);
475
+ } catch (e) {
476
+ responseBody = { error: true };
477
+ }
478
+
479
+ if (responseBody.session_cookies) {
480
+ responseBody.session_cookies.forEach(cookie => {
481
+ jar.setCookie(
482
+ `${cookie.name}=${cookie.value}; Domain=.facebook.com; Path=/; Secure; HttpOnly`,
483
+ "https://www.facebook.com"
484
+ );
485
+ });
486
+ loginSuccess = true;
487
+ log.info("login", "Modern authentication successful");
488
+ } else if (responseBody.error && responseBody.error.code === 401) {
489
+ log.info("login", "Modern auth failed, falling back to traditional login");
490
+ } else if (responseBody.login_approval_url) {
491
+ checkpointUrl = responseBody.login_approval_url;
492
+ }
493
+ } catch (e) {
494
+ log.info("login", "Modern auth error, falling back to traditional: " + e.message);
495
+ }
496
+
497
+ // Handle checkpoint if needed
498
+ if (checkpointUrl) {
499
+ await handleCheckpoint(checkpointUrl, jar, loginOptions);
500
+ loginSuccess = true;
501
+ }
502
+
503
+ // Fallback to traditional login if modern fails
504
+ if (!loginSuccess) {
505
+ const $ = cheerio.load(html);
506
+ let arr = [];
507
+ $("#login_form input").each((i, v) => arr.push({ val: $(v).val(), name: $(v).attr("name") }));
508
+ arr = arr.filter(v => v.val && v.val.length);
509
+ let form = utils.arrToForm(arr);
510
+ form.lsd = utils.getFrom(html, "[\"LSD\",[],{\"token\":\"", "\"");
511
+ form.lgndim = Buffer.from(JSON.stringify({ w: 1440, h: 900, aw: 1440, ah: 834, c: 24 })).toString('base64');
512
+ form.email = email;
513
+ form.pass = password;
514
+ form.default_persistent = '0';
515
+ form.lgnrnd = utils.getFrom(html, "name=\"lgnrnd\" value=\"", "\"");
516
+ form.locale = 'en_US';
517
+ form.timezone = '240';
518
+ form.lgnjs = Math.floor(Date.now() / 1000);
519
+
520
+ const willBeCookies = html.split("\"_js_");
521
+ willBeCookies.slice(1).forEach(val => {
522
+ try {
523
+ const cookieData = JSON.parse("[\"" + utils.getFrom(val, "", "]") + "]");
524
+ jar.setCookie(utils.formatCookie(cookieData, "facebook"), "https://www.facebook.com");
525
+ } catch (e) {}
526
+ });
527
+
528
+ log.info("login", "Logging in via traditional method...");
529
+ const loginRes = await utils.post(
530
+ "https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110",
531
+ jar,
532
+ form,
533
+ loginOptions
534
+ );
535
+ await utils.saveCookies(jar)(loginRes);
536
+
537
+ const headers = loginRes.headers;
538
+ if (!headers.location) throw new Error("Wrong username/password.");
539
+
540
+ if (headers.location.includes('https://www.facebook.com/checkpoint/')) {
541
+ await handleCheckpoint(headers.location, jar, loginOptions);
542
+ }
543
+ }
544
+
545
+ // Final verification
546
+ await utils.get('https://www.facebook.com/', jar, null, loginOptions);
547
+
548
+ // Add delay to avoid rate limiting
549
+ await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 3000));
550
+
551
+ return await utils.saveCookies(jar);
552
+
553
+ } catch (error) {
554
+ if (error.error === 'login-approval') {
555
+ callback(error);
556
+ return;
557
+ }
558
+ callback(error);
559
+ }
560
+ };
561
+ }
321
562
 
322
563
  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
- }
564
+ let mainPromise = null;
565
+ const jar = utils.getJar();
566
+
567
+ if (appState) {
568
+ try {
569
+ appState = JSON.parse(appState);
570
+ } catch (e) {
571
+ try {
572
+ appState = appState;
573
+ } catch (e) {
574
+ return callback(new Error("Failed to parse appState"));
575
+ }
576
+ }
335
577
 
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
- });
578
+ try {
579
+ appState.forEach(c => {
580
+ const str = `${c.key}=${c.value}; expires=${c.expires}; domain=${c.domain}; path=${c.path};`;
581
+ jar.setCookie(str, "http://" + c.domain);
582
+ });
341
583
 
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
- }
584
+ mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
585
+ .then(utils.saveCookies(jar));
586
+ } catch (e) {
587
+ process.exit(0);
588
+ }
589
+ } else {
590
+ mainPromise = utils
591
+ .get("https://www.facebook.com/", null, null, globalOptions, { noRef: true })
592
+ .then(utils.saveCookies(jar))
593
+ .then(makeLogin(jar, email, password, globalOptions, callback, prCallback))
594
+ .then(() => utils.get('https://www.facebook.com/', jar, null, globalOptions).then(utils.saveCookies(jar)));
595
+ }
354
596
 
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
- }
597
+ function handleRedirect(res) {
598
+ const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
599
+ const redirect = reg.exec(res.body);
600
+ if (redirect && redirect[1]) {
601
+ return utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
602
+ }
603
+ return res;
604
+ }
363
605
 
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
- });
606
+ let ctx, api;
607
+ mainPromise = mainPromise
608
+ .then(handleRedirect)
609
+ .then(res => {
610
+ const mobileAgentRegex = /MPageLoadClientMetrics/gs;
611
+ if (!mobileAgentRegex.test(res.body)) {
612
+ 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";
613
+ return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
614
+ }
615
+ return res;
616
+ })
617
+ .then(handleRedirect)
618
+ .then(res => {
619
+ const html = res.body;
620
+ const Obj = buildAPI(globalOptions, html, jar);
621
+ ctx = Obj.ctx;
622
+ api = Obj.api;
623
+ return res;
624
+ });
383
625
 
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
- }
626
+ if (globalOptions.pageID) {
627
+ mainPromise = mainPromise
628
+ .then(() => utils.get(`https://www.facebook.com/${globalOptions.pageID}/messages/?section=messages&subsection=inbox`, jar, null, globalOptions))
629
+ .then(resData => {
630
+ let url = utils.getFrom(resData.body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
631
+ url = url.substring(0, url.length - 1);
632
+ return utils.get('https://www.facebook.com' + url, jar, null, globalOptions);
633
+ });
634
+ }
393
635
 
394
- mainPromise
395
- .then(async () => {
396
- log.info('Login successful');
397
- callback(null, api);
398
- })
399
- .catch(e => {
400
- callback(e);
401
- });
636
+ mainPromise
637
+ .then(async () => {
638
+ log.info('Login successful');
639
+
640
+ // Start session keeper to prevent logout
641
+ startSessionKeeper(api, ctx);
642
+
643
+ callback(null, api);
644
+ })
645
+ .catch(e => {
646
+ callback(e);
647
+ });
402
648
  }
403
649
 
404
-
405
650
  function login(loginData, options, callback) {
406
- if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
407
- callback = options;
408
- options = {};
409
- }
651
+ if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
652
+ callback = options;
653
+ options = {};
654
+ }
410
655
 
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
- };
656
+ var globalOptions = {
657
+ selfListen: false,
658
+ listenEvents: false,
659
+ listenTyping: false,
660
+ updatePresence: false,
661
+ forceLogin: false,
662
+ autoMarkDelivery: false,
663
+ autoMarkRead: false,
664
+ autoReconnect: true,
665
+ logRecordSize: 100,
666
+ online: true,
667
+ emitReady: false,
668
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
669
+ };
425
670
 
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
- }
671
+ var prCallback = null;
672
+ var returnPromise = null;
673
+
674
+ if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
675
+ var rejectFunc = null;
676
+ var resolveFunc = null;
677
+ returnPromise = new Promise(function (resolve, reject) {
678
+ resolveFunc = resolve;
679
+ rejectFunc = reject;
680
+ });
681
+ prCallback = function (error, api) {
682
+ if (error) return rejectFunc(error);
683
+ return resolveFunc(api);
684
+ };
685
+ callback = prCallback;
686
+ }
440
687
 
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;
688
+ if (loginData.email && loginData.password) {
689
+ setOptions(globalOptions, {
690
+ logLevel: "silent",
691
+ forceLogin: true,
692
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
693
+ });
694
+ loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
695
+ } else if (loginData.appState) {
696
+ setOptions(globalOptions, options);
697
+ return loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
698
+ }
699
+ return returnPromise;
453
700
  }
454
701
 
455
-
456
- module.exports = login;
702
+ module.exports = login;