surya-sahil-fca 1.0.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.
@@ -0,0 +1,747 @@
1
+ "use strict";
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const models = require("../src/database/models");
5
+ const logger = require("../func/logger");
6
+ const { get, post, jar, makeDefaults } = require("../src/utils/request");
7
+ const { saveCookies, getAppState } = require("../src/utils/client");
8
+ const { getFrom } = require("../src/utils/constants");
9
+ const { loadConfig } = require("./config");
10
+ const { config } = loadConfig();
11
+ const { v4: uuidv4 } = require("uuid");
12
+ const { CookieJar } = require("tough-cookie");
13
+ const { wrapper } = require("axios-cookiejar-support");
14
+ const axiosBase = require("axios");
15
+ const qs = require("querystring");
16
+ const crypto = require("crypto");
17
+ const { TOTP } = require("totp-generator");
18
+
19
+ // Convert raw cookie string to appState
20
+ function parseCookieString(cookieString) {
21
+ const cookies = {};
22
+ cookieString.split(";").forEach(cookie => {
23
+ const [key, value] = cookie.split("=").map(c => c.trim());
24
+ if (key && value) cookies[key] = decodeURIComponent(value);
25
+ });
26
+ return cookies;
27
+ }
28
+
29
+ function convertToAppState(cookies) {
30
+ const appState = [];
31
+ const currentDate = new Date().toISOString();
32
+ for (const [key, value] of Object.entries(cookies)) {
33
+ appState.push({
34
+ key,
35
+ value,
36
+ domain: "facebook.com",
37
+ path: "/",
38
+ hostOnly: false,
39
+ creation: currentDate,
40
+ lastAccessed: currentDate
41
+ });
42
+ }
43
+ return appState;
44
+ }
45
+
46
+ const regions = [
47
+ { code: "PRN", name: "Pacific Northwest Region", location: "Khu vực Tây Bắc Thái Bình Dương" },
48
+ { code: "VLL", name: "Valley Region", location: "Valley" },
49
+ { code: "ASH", name: "Ashburn Region", location: "Ashburn" },
50
+ { code: "DFW", name: "Dallas/Fort Worth Region", location: "Dallas/Fort Worth" },
51
+ { code: "LLA", name: "Los Angeles Region", location: "Los Angeles" },
52
+ { code: "FRA", name: "Frankfurt", location: "Frankfurt" },
53
+ { code: "SIN", name: "Singapore", location: "Singapore" },
54
+ { code: "NRT", name: "Tokyo", location: "Japan" },
55
+ { code: "HKG", name: "Hong Kong", location: "Hong Kong" },
56
+ { code: "SYD", name: "Sydney", location: "Sydney" },
57
+ { code: "PNB", name: "Pacific Northwest - Beta", location: "Pacific Northwest " }
58
+ ];
59
+
60
+ const REGION_MAP = new Map(regions.map(r => [r.code, r]));
61
+
62
+ function parseRegion(html) {
63
+ try {
64
+ const m1 = html.match(/"endpoint":"([^"]+)"/);
65
+ const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
66
+ const raw = (m1 && m1[1]) || (m2 && m2[1]);
67
+ if (!raw) return "PRN";
68
+ const endpoint = raw.replace(/\\\//g, "/");
69
+ const url = new URL(endpoint);
70
+ const rp = url.searchParams ? url.searchParams.get("region") : null;
71
+ return rp ? rp.toUpperCase() : "PRN";
72
+ } catch {
73
+ return "PRN";
74
+ }
75
+ }
76
+
77
+ function mask(s, keep = 3) {
78
+ if (!s) return "";
79
+ const n = s.length;
80
+ return n <= keep ? "*".repeat(n) : s.slice(0, keep) + "*".repeat(Math.max(0, n - keep));
81
+ }
82
+
83
+ function md5(s) {
84
+ return crypto.createHash("md5").update(s).digest("hex");
85
+ }
86
+
87
+ function randomString(length = 24) {
88
+ let s = "abcdefghijklmnopqrstuvwxyz";
89
+ let out = s.charAt(Math.floor(Math.random() * s.length));
90
+ for (let i = 1; i < length; i++) out += "abcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(36 * Math.random()));
91
+ return out;
92
+ }
93
+
94
+ function sortObject(o) {
95
+ const keys = Object.keys(o).sort();
96
+ const x = {};
97
+ for (const k of keys) x[k] = o[k];
98
+ return x;
99
+ }
100
+
101
+ function encodeSig(obj) {
102
+ let data = "";
103
+ for (const k of Object.keys(obj)) data += `${k}=${obj[k]}`;
104
+ return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
105
+ }
106
+
107
+ function rand(min, max) {
108
+ return Math.floor(Math.random() * (max - min + 1)) + min;
109
+ }
110
+
111
+ function choice(arr) {
112
+ return arr[Math.floor(Math.random() * arr.length)];
113
+ }
114
+
115
+ function randomBuildId() {
116
+ const prefixes = ["QP1A", "RP1A", "SP1A", "TP1A", "UP1A", "AP4A"];
117
+ return `${choice(prefixes)}.${rand(180000, 250000)}.${rand(10, 99)}`;
118
+ }
119
+
120
+ function randomResolution() {
121
+ const presets = [{ w: 720, h: 1280, d: 2.0 }, { w: 1080, h: 1920, d: 2.625 }, { w: 1080, h: 2400, d: 3.0 }, { w: 1440, h: 3040, d: 3.5 }, { w: 1440, h: 3200, d: 4.0 }];
122
+ return choice(presets);
123
+ }
124
+
125
+ function randomFbav() {
126
+ return `${rand(390, 499)}.${rand(0, 3)}.${rand(0, 2)}.${rand(10, 60)}.${rand(100, 999)}`;
127
+ }
128
+
129
+ function randomOrcaUA() {
130
+ const androidVersions = ["8.1.0", "9", "10", "11", "12", "13", "14"];
131
+ const devices = [{ brand: "samsung", model: "SM-G996B" }, { brand: "samsung", model: "SM-S908E" }, { brand: "Xiaomi", model: "M2101K9AG" }, { brand: "OPPO", model: "CPH2219" }, { brand: "vivo", model: "V2109" }, { brand: "HUAWEI", model: "VOG-L29" }, { brand: "asus", model: "ASUS_I001DA" }, { brand: "Google", model: "Pixel 6" }, { brand: "realme", model: "RMX2170" }];
132
+ const carriers = ["Viettel Telecom", "Mobifone", "Vinaphone", "T-Mobile", "Verizon", "AT&T", "Telkomsel", "Jio", "NTT DOCOMO", "Vodafone", "Orange"];
133
+ const locales = ["vi_VN", "en_US", "en_GB", "id_ID", "th_TH", "fr_FR", "de_DE", "es_ES", "pt_BR"];
134
+ const archs = ["arm64-v8a", "armeabi-v7a"];
135
+ const a = choice(androidVersions);
136
+ const d = choice(devices);
137
+ const b = randomBuildId();
138
+ const r = randomResolution();
139
+ const fbav = randomFbav();
140
+ const fbbv = rand(320000000, 520000000);
141
+ const arch = `${choice(archs)}:${choice(archs)}`;
142
+ const ua = `Dalvik/2.1.0 (Linux; U; Android ${a}; ${d.model} Build/${b}) [FBAN/Orca-Android;FBAV/${fbav};FBPN/com.facebook.orca;FBLC/${choice(locales)};FBBV/${fbbv};FBCR/${choice(carriers)};FBMF/${d.brand};FBBD/${d.brand};FBDV/${d.model};FBSV/${a};FBCA/${arch};FBDM={density=${r.d.toFixed(1)},width=${r.w},height=${r.h}};FB_FW/1;]`;
143
+ return ua;
144
+ }
145
+
146
+ const MOBILE_UA = randomOrcaUA();
147
+
148
+ function buildHeaders(url, extra = {}) {
149
+ const u = new URL(url);
150
+ return { "content-type": "application/x-www-form-urlencoded", "x-fb-http-engine": "Liger", "user-agent": MOBILE_UA, Host: u.host, Origin: "https://www.facebook.com", Referer: "https://www.facebook.com/", Connection: "keep-alive", ...extra };
151
+ }
152
+
153
+ const genTotp = async secret => {
154
+ const cleaned = String(secret || "").replace(/\s+/g, "").toUpperCase();
155
+ const r = await TOTP.generate(cleaned);
156
+ return typeof r === "object" ? r.otp : r;
157
+ };
158
+
159
+ function normalizeCookieHeaderString(s) {
160
+ let str = String(s || "").trim();
161
+ if (!str) return [];
162
+ if (/^cookie\s*:/i.test(str)) str = str.replace(/^cookie\s*:/i, "").trim();
163
+ str = str.replace(/\r?\n/g, " ").replace(/\s*;\s*/g, ";");
164
+ const parts = str.split(";").map(v => v.trim()).filter(Boolean);
165
+ const out = [];
166
+ for (const p of parts) {
167
+ const eq = p.indexOf("=");
168
+ if (eq <= 0) continue;
169
+ const k = p.slice(0, eq).trim();
170
+ const v = p.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1");
171
+ if (!k) continue;
172
+ out.push(`${k}=${v}`);
173
+ }
174
+ return out;
175
+ }
176
+
177
+ function setJarFromPairs(j, pairs, domain) {
178
+ const expires = new Date(Date.now() + 31536e6).toUTCString();
179
+ for (const kv of pairs) {
180
+ const cookieStr = `${kv}; expires=${expires}; domain=${domain}; path=/;`;
181
+ try {
182
+ if (typeof j.setCookieSync === "function") j.setCookieSync(cookieStr, "https://www.facebook.com");
183
+ else j.setCookie(cookieStr, "https://www.facebook.com");
184
+ } catch { }
185
+ }
186
+ }
187
+
188
+ function cookieHeaderFromJar(j) {
189
+ const urls = ["https://www.facebook.com", "https://www.messenger.com"];
190
+ const seen = new Set();
191
+ const parts = [];
192
+ for (const u of urls) {
193
+ let s = "";
194
+ try {
195
+ s = typeof j.getCookieStringSync === "function" ? j.getCookieStringSync(u) : "";
196
+ } catch { }
197
+ if (!s) continue;
198
+ for (const kv of s.split(";")) {
199
+ const t = kv.trim();
200
+ const name = t.split("=")[0];
201
+ if (!name || seen.has(name)) continue;
202
+ seen.add(name);
203
+ parts.push(t);
204
+ }
205
+ }
206
+ return parts.join("; ");
207
+ }
208
+
209
+ let uniqueIndexEnsured = false;
210
+
211
+ function getBackupModel() {
212
+ if (!models || !models.sequelize || !models.Sequelize) return null;
213
+ const sequelize = models.sequelize;
214
+ const { DataTypes } = models.Sequelize;
215
+ if (sequelize.models && sequelize.models.AppStateBackup) return sequelize.models.AppStateBackup;
216
+ const dialect = typeof sequelize.getDialect === "function" ? sequelize.getDialect() : "sqlite";
217
+ const LongText = (dialect === "mysql" || dialect === "mariadb") ? DataTypes.TEXT("long") : DataTypes.TEXT;
218
+ const AppStateBackup = sequelize.define(
219
+ "AppStateBackup",
220
+ {
221
+ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
222
+ userID: { type: DataTypes.STRING, allowNull: false },
223
+ type: { type: DataTypes.STRING, allowNull: false },
224
+ data: { type: LongText }
225
+ },
226
+ { tableName: "app_state_backups", timestamps: true, indexes: [{ unique: true, fields: ["userID", "type"] }] }
227
+ );
228
+ return AppStateBackup;
229
+ }
230
+
231
+ async function ensureUniqueIndex(sequelize) {
232
+ if (uniqueIndexEnsured) return;
233
+ try {
234
+ await sequelize.getQueryInterface().addIndex("app_state_backups", ["userID", "type"], { unique: true, name: "app_state_user_type_unique" });
235
+ } catch { }
236
+ uniqueIndexEnsured = true;
237
+ }
238
+
239
+ async function upsertBackup(Model, userID, type, data) {
240
+ const where = { userID: String(userID || ""), type };
241
+ const row = await Model.findOne({ where });
242
+ if (row) {
243
+ await row.update({ data });
244
+ logger(`Overwrote existing ${type} backup for user ${where.userID}`, "info");
245
+ return;
246
+ }
247
+ await Model.create({ ...where, data });
248
+ logger(`Created new ${type} backup for user ${where.userID}`, "info");
249
+ }
250
+
251
+ async function backupAppStateSQL(j, userID) {
252
+ try {
253
+ const Model = getBackupModel();
254
+ if (!Model) return;
255
+ await Model.sync();
256
+ await ensureUniqueIndex(models.sequelize);
257
+ const appJson = getAppState(j);
258
+ const ck = cookieHeaderFromJar(j);
259
+ await upsertBackup(Model, userID, "appstate", JSON.stringify(appJson));
260
+ await upsertBackup(Model, userID, "cookie", ck);
261
+ try {
262
+ const out = path.join(process.cwd(), "appstate.json");
263
+ fs.writeFileSync(out, JSON.stringify(appJson, null, 2));
264
+ } catch { }
265
+ logger("Backup stored (overwrite mode)", "info");
266
+ } catch (e) {
267
+ logger(`Failed to save appstate backup ${e && e.message ? e.message : String(e)}`, "warn");
268
+ }
269
+ }
270
+
271
+ async function getLatestBackup(userID, type) {
272
+ try {
273
+ const Model = getBackupModel();
274
+ if (!Model) return null;
275
+ const row = await Model.findOne({ where: { userID: String(userID || ""), type } });
276
+ return row ? row.data : null;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+
282
+ async function getLatestBackupAny(type) {
283
+ try {
284
+ const Model = getBackupModel();
285
+ if (!Model) return null;
286
+ const row = await Model.findOne({ where: { type }, order: [["updatedAt", "DESC"]] });
287
+ return row ? row.data : null;
288
+ } catch {
289
+ return null;
290
+ }
291
+ }
292
+
293
+ const MESSENGER_USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 9; ASUS_Z01QD Build/PQ3A.190605.003) [FBAN/Orca-Android;FBAV/391.2.0.20.404;FBPN/com.facebook.orca;FBLC/vi_VN;FBBV/437533963;FBCR/Viettel Telecom;FBMF/asus;FBBD/asus;FBDV/ASUS_Z01QD;FBSV/9;FBCA/x86:armeabi-v7a;FBDM={density=1.5,width=1600,height=900};FB_FW/1;]";
294
+
295
+ function encodesig(obj) {
296
+ let data = "";
297
+ Object.keys(obj).forEach(k => { data += `${k}=${obj[k]}`; });
298
+ return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
299
+ }
300
+
301
+ function sort(obj) {
302
+ const keys = Object.keys(obj).sort();
303
+ const out = {};
304
+ for (const k of keys) out[k] = obj[k];
305
+ return out;
306
+ }
307
+
308
+ async function setJarCookies(j, appstate) {
309
+ const tasks = [];
310
+ for (const c of appstate) {
311
+ const cookieName = c.name || c.key;
312
+ const cookieValue = c.value;
313
+ if (!cookieName || cookieValue === undefined) continue;
314
+
315
+ const cookieDomain = c.domain || ".facebook.com";
316
+ const cookiePath = c.path || "/";
317
+ const dom = cookieDomain.replace(/^\./, "");
318
+
319
+ let expiresStr = "";
320
+ if (c.expires) {
321
+ const expiresDate = typeof c.expires === "number" ? new Date(c.expires) : new Date(c.expires);
322
+ expiresStr = `; expires=${expiresDate.toUTCString()}`;
323
+ }
324
+
325
+ const str = `${cookieName}=${cookieValue}${expiresStr}; Domain=${cookieDomain}; Path=${cookiePath};`;
326
+
327
+ const base1 = `http://${dom}${cookiePath}`;
328
+ const base2 = `https://${dom}${cookiePath}`;
329
+ const base3 = `http://www.${dom}${cookiePath}`;
330
+ const base4 = `https://www.${dom}${cookiePath}`;
331
+
332
+ tasks.push(j.setCookie(str, base1).catch(() => { }));
333
+ tasks.push(j.setCookie(str, base2).catch(() => { }));
334
+ tasks.push(j.setCookie(str, base3).catch(() => { }));
335
+ tasks.push(j.setCookie(str, base4).catch(() => { }));
336
+ }
337
+ await Promise.all(tasks);
338
+ }
339
+
340
+ async function hydrateJarFromDB(userID) {
341
+ try {
342
+ let ck = null;
343
+ let app = null;
344
+ if (userID) {
345
+ ck = await getLatestBackup(userID, "cookie");
346
+ app = await getLatestBackup(userID, "appstate");
347
+ } else {
348
+ ck = await getLatestBackupAny("cookie");
349
+ app = await getLatestBackupAny("appstate");
350
+ }
351
+ if (ck) {
352
+ const pairs = normalizeCookieHeaderString(ck);
353
+ if (pairs.length) {
354
+ setJarFromPairs(jar, pairs, ".facebook.com");
355
+ return true;
356
+ }
357
+ }
358
+ if (app) {
359
+ let parsed = null;
360
+ try {
361
+ parsed = JSON.parse(app);
362
+ } catch { }
363
+ if (Array.isArray(parsed)) {
364
+ const pairs = parsed.map(c => [c.name || c.key, c.value].join("="));
365
+ setJarFromPairs(jar, pairs, ".facebook.com");
366
+ return true;
367
+ }
368
+ }
369
+ return false;
370
+ } catch {
371
+ return false;
372
+ }
373
+ }
374
+
375
+ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false) {
376
+ const getUID = cs =>
377
+ cs.find(c => c.key === "i_user")?.value ||
378
+ cs.find(c => c.key === "c_user")?.value ||
379
+ cs.find(c => c.name === "i_user")?.value ||
380
+ cs.find(c => c.name === "c_user")?.value;
381
+ const htmlUID = body => {
382
+ const s = typeof body === "string" ? body : String(body ?? "");
383
+ return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
384
+ };
385
+ let userID = getUID(currentCookies);
386
+ if (!userID) {
387
+ userID = htmlUID(currentHtml);
388
+ }
389
+ if (userID) return { html: currentHtml, cookies: currentCookies, userID };
390
+ if (hadAppStateInput) {
391
+ const isCheckpoint = currentHtml.includes("/checkpoint/block/?next");
392
+ if (!isCheckpoint) {
393
+ try {
394
+ const refreshedCookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
395
+ userID = getUID(refreshedCookies);
396
+ if (userID) {
397
+ return { html: currentHtml, cookies: refreshedCookies, userID };
398
+ }
399
+ } catch { }
400
+ throw new Error("Missing user cookie from provided appState");
401
+ }
402
+ }
403
+ const hydrated = await hydrateJarFromDB(null);
404
+ if (hydrated) {
405
+ logger("AppState backup live — proceeding to login", "info");
406
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
407
+ const resB = (await ctxRef.bypassAutomation(initial, jar)) || initial;
408
+ const htmlB = resB && resB.data ? resB.data : "";
409
+ if (htmlB.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
410
+ const cookiesB = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
411
+ const uidB = getUID(cookiesB);
412
+ if (uidB) return { html: htmlB, cookies: cookiesB, userID: uidB };
413
+ }
414
+ throw new Error("AppState backup die — Auto-login is disabled");
415
+ }
416
+
417
+ function loginHelper(appState, Cookie, email, password, globalOptions, callback) {
418
+ try {
419
+ const domain = ".facebook.com";
420
+ const extractUIDFromAppState = (appStateInput) => {
421
+ if (!appStateInput) return null;
422
+ let parsed = appStateInput;
423
+ if (typeof appStateInput === "string") {
424
+ try {
425
+ parsed = JSON.parse(appStateInput);
426
+ } catch {
427
+ return null;
428
+ }
429
+ }
430
+ if (Array.isArray(parsed)) {
431
+ const cUser = parsed.find(c => (c.key === "c_user" || c.name === "c_user"));
432
+ if (cUser) return cUser.value;
433
+ const iUser = parsed.find(c => (c.key === "i_user" || c.name === "i_user"));
434
+ if (iUser) return iUser.value;
435
+ }
436
+ return null;
437
+ };
438
+ let userIDFromAppState = extractUIDFromAppState(appState);
439
+ (async () => {
440
+ try {
441
+ if (appState) {
442
+ if (Array.isArray(appState) && appState.some(c => c.name)) {
443
+ appState = appState.map(c => {
444
+ if (c.name && !c.key) {
445
+ c.key = c.name;
446
+ delete c.name;
447
+ }
448
+ return c;
449
+ });
450
+ } else if (typeof appState === "string") {
451
+ let parsed = appState;
452
+ try {
453
+ parsed = JSON.parse(appState);
454
+ } catch { }
455
+
456
+ if (Array.isArray(parsed)) {
457
+ appState = parsed;
458
+ } else {
459
+ // Parse cookie string and convert to appState
460
+ const cookies = parseCookieString(appState);
461
+ appState = convertToAppState(cookies);
462
+ }
463
+ }
464
+
465
+ if (Array.isArray(appState)) {
466
+ await setJarCookies(jar, appState);
467
+ } else {
468
+ throw new Error("Invalid appState format");
469
+ }
470
+ }
471
+ if (Cookie) {
472
+ let cookiePairs = [];
473
+ if (typeof Cookie === "string") {
474
+ // Convert cookie string to appState
475
+ const cookies = parseCookieString(Cookie);
476
+ const appStateFromCookie = convertToAppState(cookies);
477
+ await setJarCookies(jar, appStateFromCookie);
478
+ return;
479
+ } else if (Array.isArray(Cookie)) cookiePairs = Cookie.map(String).filter(Boolean);
480
+ else if (Cookie && typeof Cookie === "object") cookiePairs = Object.entries(Cookie).map(([k, v]) => `${k}=${v}`);
481
+ if (cookiePairs.length) setJarFromPairs(jar, cookiePairs, domain);
482
+ }
483
+ } catch (e) {
484
+ return callback(e);
485
+ }
486
+ const ctx = { globalOptions, options: globalOptions, reconnectAttempts: 0 };
487
+ ctx.bypassAutomation = async function (resp, j) {
488
+ global.fca = global.fca || {};
489
+ global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
490
+ const s = x => (typeof x === "string" ? x : String(x ?? ""));
491
+ const u = r => r?.request?.res?.responseUrl || (r?.config?.baseURL ? new URL(r.config.url || "/", r.config.baseURL).toString() : r?.config?.url || "");
492
+ const isCp = r => typeof u(r) === "string" && u(r).includes("checkpoint/601051028565049");
493
+ const cookieUID = async () => {
494
+ try {
495
+ const cookies = typeof j?.getCookies === "function" ? await j.getCookies("https://www.facebook.com") : [];
496
+ return cookies.find(c => c.key === "i_user")?.value || cookies.find(c => c.key === "c_user")?.value;
497
+ } catch { return undefined; }
498
+ };
499
+ const htmlUID = body => s(body).match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s(body).match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
500
+ const getUID = async body => (await cookieUID()) || htmlUID(body);
501
+ const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
502
+ const bypass = async body => {
503
+ const b = s(body);
504
+ const UID = await getUID(b);
505
+ const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
506
+ const jazoest = getFrom(b, 'name="jazoest" value="', '"') || getFrom(b, "jazoest=", '",') || b.match(/name="jazoest"\s+value="([^"]+)"/)?.[1];
507
+ const lsd = getFrom(b, '["LSD",[],{"token":"', '"}') || b.match(/name="lsd"\s+value="([^"]+)"/)?.[1];
508
+ const form = { av: UID, fb_dtsg, jazoest, lsd, fb_api_caller_class: "RelayModern", fb_api_req_friendly_name: "FBScrapingWarningMutation", variables: "{}", server_timestamps: true, doc_id: 6339492849481770 };
509
+ await post("https://www.facebook.com/api/graphql/", j, form, null, this.options).then(saveCookies(j));
510
+ logger("Facebook automation warning detected, handling...", "warn");
511
+ this.reconnectAttempts = 0;
512
+ };
513
+ try {
514
+ if (resp) {
515
+ if (isCp(resp)) {
516
+ await bypass(s(resp.data));
517
+ const refreshed = await refreshJar();
518
+ if (isCp(refreshed)) logger("Checkpoint still present after refresh", "warn");
519
+ else logger("Bypass complete, cookies refreshed", "info");
520
+ return refreshed;
521
+ }
522
+ return resp;
523
+ }
524
+ const first = await get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
525
+ if (isCp(first)) {
526
+ await bypass(s(first.data));
527
+ const refreshed = await refreshJar();
528
+ if (!isCp(refreshed)) logger("Bypass complete, cookies refreshed", "info");
529
+ else logger("Checkpoint still present after refresh", "warn");
530
+ return refreshed;
531
+ }
532
+ return first;
533
+ } catch (e) {
534
+ logger(`Bypass automation error: ${e && e.message ? e.message : String(e)}`, "error");
535
+ return resp;
536
+ }
537
+ };
538
+ if (appState || Cookie) {
539
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
540
+ return (await ctx.bypassAutomation(initial, jar)) || initial;
541
+ }
542
+ const hydrated = await hydrateJarFromDB(null);
543
+ if (hydrated) {
544
+ logger("AppState backup live — proceeding to login", "info");
545
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
546
+ return (await ctx.bypassAutomation(initial, jar)) || initial;
547
+ }
548
+ logger("AppState backup die — proceeding to email/password login", "warn");
549
+ return get("https://www.facebook.com/", null, null, globalOptions)
550
+ .then(saveCookies(jar));
551
+ })()
552
+ .then(async function (res) {
553
+ const ctx = {};
554
+ ctx.options = globalOptions;
555
+ ctx.bypassAutomation = async function (resp, j) {
556
+ global.fca = global.fca || {};
557
+ global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
558
+ const s = x => (typeof x === "string" ? x : String(x ?? ""));
559
+ const u = r => r?.request?.res?.responseUrl || (r?.config?.baseURL ? new URL(r.config.url || "/", r.config.baseURL).toString() : r?.config?.url || "");
560
+ const isCp = r => typeof u(r) === "string" && u(r).includes("checkpoint/601051028565049");
561
+ const cookieUID = async () => {
562
+ try {
563
+ const cookies = typeof j?.getCookies === "function" ? await j.getCookies("https://www.facebook.com") : [];
564
+ return cookies.find(c => c.key === "i_user")?.value || cookies.find(c => c.key === "c_user")?.value;
565
+ } catch { return undefined; }
566
+ };
567
+ const htmlUID = body => s(body).match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s(body).match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
568
+ const getUID = async body => (await cookieUID()) || htmlUID(body);
569
+ const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
570
+ const bypass = async body => {
571
+ const b = s(body);
572
+ const UID = await getUID(b);
573
+ const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
574
+ const jazoest = getFrom(b, 'name="jazoest" value="', '"') || getFrom(b, "jazoest=", '",') || b.match(/name="jazoest"\s+value="([^"]+)"/)?.[1];
575
+ const lsd = getFrom(b, '["LSD",[],{"token":"', '"}') || b.match(/name="lsd"\s+value="([^"]+)"/)?.[1];
576
+ const form = { av: UID, fb_dtsg, jazoest, lsd, fb_api_caller_class: "RelayModern", fb_api_req_friendly_name: "FBScrapingWarningMutation", variables: "{}", server_timestamps: true, doc_id: 6339492849481770 };
577
+ await post("https://www.facebook.com/api/graphql/", j, form, null, this.options).then(saveCookies(j));
578
+ logger("Facebook automation warning detected, handling...", "warn");
579
+ };
580
+ try {
581
+ if (res && isCp(res)) {
582
+ await bypass(s(res.data));
583
+ const refreshed = await refreshJar();
584
+ if (!isCp(refreshed)) logger("Bypass complete, cookies refreshed", "info");
585
+ return refreshed;
586
+ }
587
+ logger("No checkpoint detected", "info");
588
+ return res;
589
+ } catch {
590
+ return res;
591
+ }
592
+ };
593
+ const processed = (await ctx.bypassAutomation(res, jar)) || res;
594
+ let html = processed && processed.data ? processed.data : "";
595
+ let cookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
596
+ const getUIDFromCookies = cs =>
597
+ cs.find(c => c.key === "i_user")?.value ||
598
+ cs.find(c => c.key === "c_user")?.value ||
599
+ cs.find(c => c.name === "i_user")?.value ||
600
+ cs.find(c => c.name === "c_user")?.value;
601
+ const getUIDFromHTML = body => {
602
+ const s = typeof body === "string" ? body : String(body ?? "");
603
+ return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
604
+ };
605
+ let userID = getUIDFromCookies(cookies);
606
+ if (!userID) {
607
+ userID = getUIDFromHTML(html);
608
+ }
609
+ if (!userID && userIDFromAppState) {
610
+ userID = userIDFromAppState;
611
+ }
612
+ if (!userID) {
613
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
614
+ html = retried.html;
615
+ cookies = retried.cookies;
616
+ userID = retried.userID;
617
+ }
618
+ if (html.includes("/checkpoint/block/?next")) {
619
+ logger("Appstate die, vui lòng thay cái mới!", "error");
620
+ throw new Error("Checkpoint");
621
+ }
622
+ let mqttEndpoint;
623
+ let region = "PRN";
624
+ let fb_dtsg;
625
+ let irisSeqID;
626
+ try {
627
+ const m1 = html.match(/"endpoint":"([^"]+)"/);
628
+ const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
629
+ const raw = (m1 && m1[1]) || (m2 && m2[1]);
630
+ if (raw) mqttEndpoint = raw.replace(/\\\//g, "/");
631
+ region = parseRegion(html);
632
+ const rinfo = REGION_MAP.get(region);
633
+ if (rinfo) logger(`Server region ${region} - ${rinfo.name}`, "info");
634
+ else logger(`Server region ${region}`, "info");
635
+ } catch {
636
+ logger("Not MQTT endpoint", "warn");
637
+ }
638
+ try {
639
+ const userDataMatch = String(html).match(/\["CurrentUserInitialData",\[\],({.*?}),\d+\]/);
640
+ if (userDataMatch) {
641
+ const info = JSON.parse(userDataMatch[1]);
642
+ logger(`Đăng nhập tài khoản: ${info.NAME} (${info.USER_ID})`, "info");
643
+ } else if (userID) {
644
+ logger(`ID người dùng: ${userID}`, "info");
645
+ }
646
+ } catch { }
647
+ const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
648
+ if (tokenMatch) fb_dtsg = tokenMatch[1];
649
+ try {
650
+ if (userID) await backupAppStateSQL(jar, userID);
651
+ } catch { }
652
+ Promise.resolve()
653
+ .then(function () {
654
+ if (models && models.sequelize && typeof models.sequelize.authenticate === "function") {
655
+ return models.sequelize.authenticate();
656
+ }
657
+ })
658
+ .then(function () {
659
+ if (models && typeof models.syncAll === "function") {
660
+ return models.syncAll();
661
+ }
662
+ })
663
+ .catch(function (error) {
664
+ console.error(error);
665
+ console.error("Database connection failed:", error && error.message ? error.message : String(error));
666
+ });
667
+ logger("surya-sahil-fca - Streamlined Facebook Chat API", "info");
668
+ const ctxMain = {
669
+ userID,
670
+ jar,
671
+ globalOptions,
672
+ loggedIn: true,
673
+ access_token: "NONE",
674
+ clientMutationId: 0,
675
+ mqttClient: undefined,
676
+ lastSeqId: irisSeqID,
677
+ syncToken: undefined,
678
+ mqttEndpoint,
679
+ region,
680
+ firstListen: true,
681
+ fb_dtsg,
682
+ clientID: ((Math.random() * 2147483648) | 0).toString(16),
683
+ clientId: getFrom(html, '["MqttWebDeviceID",[],{"clientID":"', '"}') || "",
684
+ wsReqNumber: 0,
685
+ wsTaskNumber: 0,
686
+ tasks: new Map()
687
+ };
688
+ ctxMain.options = globalOptions;
689
+ ctxMain.bypassAutomation = ctx.bypassAutomation.bind(ctxMain);
690
+ const api = {
691
+ setOptions: require("./options").setOptions.bind(null, globalOptions),
692
+ getCookies: function () {
693
+ return cookieHeaderFromJar(jar);
694
+ },
695
+ getAppState: function () {
696
+ return getAppState(jar);
697
+ },
698
+ getLatestAppStateFromDB: async function (uid = userID) {
699
+ const data = await getLatestBackup(uid, "appstate");
700
+ return data ? JSON.parse(data) : null;
701
+ },
702
+ getLatestCookieFromDB: async function (uid = userID) {
703
+ return await getLatestBackup(uid, "cookie");
704
+ }
705
+ };
706
+ const defaultFuncs = makeDefaults(html, userID, ctxMain);
707
+ const srcRoot = path.join(__dirname, "../src/api");
708
+ let loaded = 0;
709
+ let skipped = 0;
710
+ fs.readdirSync(srcRoot, { withFileTypes: true }).forEach((sub) => {
711
+ if (!sub.isDirectory()) return;
712
+ const subDir = path.join(srcRoot, sub.name);
713
+ fs.readdirSync(subDir, { withFileTypes: true }).forEach((entry) => {
714
+ if (!entry.isFile() || !entry.name.endsWith(".js")) return;
715
+ const p = path.join(subDir, entry.name);
716
+ const key = path.basename(entry.name, ".js");
717
+ if (api[key]) {
718
+ skipped++;
719
+ return;
720
+ }
721
+ api[key] = require(p)(defaultFuncs, api, ctxMain);
722
+ loaded++;
723
+ });
724
+ });
725
+ logger(`Loaded ${loaded} FCA API methods${skipped ? `, skipped ${skipped} duplicates` : ""}`);
726
+ if (api.listenMqtt) api.listen = api.listenMqtt;
727
+ if (api.refreshFb_dtsg) {
728
+ setInterval(function () {
729
+ api.refreshFb_dtsg().then(function () {
730
+ logger("Successfully refreshed fb_dtsg");
731
+ }).catch(function () {
732
+ logger("An error occurred while refreshing fb_dtsg", "error");
733
+ });
734
+ }, 86400000);
735
+ }
736
+ logger("Login successful!");
737
+ callback(null, api);
738
+ })
739
+ .catch(function (e) {
740
+ callback(e);
741
+ });
742
+ } catch (e) {
743
+ callback(e);
744
+ }
745
+ }
746
+
747
+ module.exports = loginHelper;