@cexy/hoonfca 1.0.4 → 1.0.6

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/DOCS.md +2636 -0
  3. package/LICENSE-MIT +21 -21
  4. package/README.md +632 -0
  5. package/func/checkUpdate.js +231 -0
  6. package/func/logger.js +48 -0
  7. package/index.d.ts +731 -605
  8. package/index.js +6 -774
  9. package/module/config.js +67 -0
  10. package/module/login.js +154 -0
  11. package/module/loginHelper.js +1034 -0
  12. package/module/options.js +54 -0
  13. package/package.json +54 -19
  14. package/src/api/action/addExternalModule.js +25 -0
  15. package/src/api/action/changeAvatar.js +137 -0
  16. package/src/{changeBio.js → api/action/changeBio.js} +75 -76
  17. package/src/api/action/enableAutoSaveAppState.js +73 -0
  18. package/src/{getCurrentUserID.js → api/action/getCurrentUserID.js} +7 -7
  19. package/src/{handleFriendRequest.js → api/action/handleFriendRequest.js} +57 -57
  20. package/src/api/action/logout.js +76 -0
  21. package/src/api/action/refreshFb_dtsg.js +48 -0
  22. package/src/api/action/setPostReaction.js +106 -0
  23. package/src/api/action/unfriend.js +54 -0
  24. package/src/api/http/httpGet.js +46 -0
  25. package/src/api/http/httpPost.js +52 -0
  26. package/src/api/http/postFormData.js +47 -0
  27. package/src/api/messaging/addUserToGroup.js +68 -0
  28. package/src/api/messaging/changeAdminStatus.js +122 -0
  29. package/src/api/messaging/changeArchivedStatus.js +55 -0
  30. package/src/api/messaging/changeBlockedStatus.js +48 -0
  31. package/src/api/messaging/changeGroupImage.js +90 -0
  32. package/src/api/messaging/changeNickname.js +70 -0
  33. package/src/api/messaging/changeThreadColor.js +79 -0
  34. package/src/api/messaging/changeThreadEmoji.js +106 -0
  35. package/src/{createNewGroup.js → api/messaging/createNewGroup.js} +88 -88
  36. package/src/api/messaging/createPoll.js +43 -0
  37. package/src/api/messaging/createThemeAI.js +98 -0
  38. package/src/{deleteMessage.js → api/messaging/deleteMessage.js} +56 -56
  39. package/src/{deleteThread.js → api/messaging/deleteThread.js} +56 -56
  40. package/src/api/messaging/editMessage.js +68 -0
  41. package/src/api/messaging/forwardAttachment.js +51 -0
  42. package/src/{getEmojiUrl.js → api/messaging/getEmojiUrl.js} +8 -8
  43. package/src/{getFriendsList.js → api/messaging/getFriendsList.js} +82 -84
  44. package/src/api/messaging/getMessage.js +829 -0
  45. package/src/api/messaging/getThemePictures.js +62 -0
  46. package/src/api/messaging/handleMessageRequest.js +65 -0
  47. package/src/{markAsDelivered.js → api/messaging/markAsDelivered.js} +57 -58
  48. package/src/{markAsRead.js → api/messaging/markAsRead.js} +88 -88
  49. package/src/{markAsReadAll.js → api/messaging/markAsReadAll.js} +49 -50
  50. package/src/api/messaging/markAsSeen.js +61 -0
  51. package/src/{muteThread.js → api/messaging/muteThread.js} +50 -52
  52. package/src/api/messaging/removeUserFromGroup.js +106 -0
  53. package/src/{resolvePhotoUrl.js → api/messaging/resolvePhotoUrl.js} +43 -45
  54. package/src/api/messaging/scheduler.js +264 -0
  55. package/src/{searchForThread.js → api/messaging/searchForThread.js} +52 -53
  56. package/src/api/messaging/sendMessage.js +272 -0
  57. package/src/api/messaging/sendTypingIndicator.js +67 -0
  58. package/src/api/messaging/setMessageReaction.js +76 -0
  59. package/src/api/messaging/setTitle.js +119 -0
  60. package/src/{shareContact.js → api/messaging/shareContact.js} +49 -53
  61. package/src/api/messaging/threadColors.js +128 -0
  62. package/src/api/messaging/unsendMessage.js +81 -0
  63. package/src/api/messaging/uploadAttachment.js +94 -0
  64. package/src/api/socket/core/connectMqtt.js +255 -0
  65. package/src/api/socket/core/emitAuth.js +106 -0
  66. package/src/api/socket/core/getSeqID.js +40 -0
  67. package/src/api/socket/core/getTaskResponseData.js +22 -0
  68. package/src/api/socket/core/markDelivery.js +12 -0
  69. package/src/api/socket/core/parseDelta.js +391 -0
  70. package/src/api/socket/detail/buildStream.js +208 -0
  71. package/src/api/socket/detail/constants.js +24 -0
  72. package/src/api/socket/listenMqtt.js +364 -0
  73. package/src/api/socket/middleware/index.js +216 -0
  74. package/src/{getThreadHistory.js → api/threads/getThreadHistory.js} +664 -647
  75. package/src/api/threads/getThreadInfo.js +438 -0
  76. package/src/{getThreadList.js → api/threads/getThreadList.js} +293 -292
  77. package/src/{getThreadPictures.js → api/threads/getThreadPictures.js} +78 -79
  78. package/src/{getUserID.js → api/users/getUserID.js} +65 -66
  79. package/src/api/users/getUserInfo.js +327 -0
  80. package/src/api/users/getUserInfoV2.js +134 -0
  81. package/src/core/sendReqMqtt.js +96 -0
  82. package/src/database/models/index.js +87 -0
  83. package/src/database/models/thread.js +45 -0
  84. package/src/database/models/user.js +46 -0
  85. package/src/database/threadData.js +98 -0
  86. package/src/database/userData.js +89 -0
  87. package/src/utils/client.js +320 -0
  88. package/src/utils/constants.js +23 -0
  89. package/{utils.js → src/utils/format.js} +1115 -1447
  90. package/src/utils/headers.js +115 -0
  91. package/src/utils/request.js +305 -0
  92. package/.travis.yml +0 -6
  93. package/src/addExternalModule.js +0 -23
  94. package/src/addUserToGroup.js +0 -113
  95. package/src/changeAdminStatus.js +0 -95
  96. package/src/changeApprovalMode.js +0 -79
  97. package/src/changeArchivedStatus.js +0 -55
  98. package/src/changeBlockedStatus.js +0 -47
  99. package/src/changeBlockedStatusMqtt.js +0 -86
  100. package/src/changeGroupImage.js +0 -133
  101. package/src/changeNickname.js +0 -59
  102. package/src/changeThreadColor.js +0 -71
  103. package/src/changeThreadEmoji.js +0 -55
  104. package/src/createPoll.js +0 -130
  105. package/src/editMessage.js +0 -63
  106. package/src/forwardAttachment.js +0 -60
  107. package/src/forwardMessage.js +0 -63
  108. package/src/getGroupsList.js +0 -83
  109. package/src/getMarketplace.js +0 -84
  110. package/src/getNotifications.js +0 -86
  111. package/src/getPagesList.js +0 -83
  112. package/src/getStories.js +0 -88
  113. package/src/getThreadInfo.js +0 -222
  114. package/src/getUserInfo.js +0 -128
  115. package/src/handleMessageRequest.js +0 -65
  116. package/src/httpGet.js +0 -58
  117. package/src/httpPost.js +0 -58
  118. package/src/listenMqtt.js +0 -1018
  119. package/src/logout.js +0 -75
  120. package/src/markAsSeen.js +0 -61
  121. package/src/pinMessage.js +0 -61
  122. package/src/removeUserFromGroup.js +0 -79
  123. package/src/searchUsers.js +0 -89
  124. package/src/sendComment.js +0 -161
  125. package/src/sendMessage.js +0 -457
  126. package/src/sendTypingIndicator.js +0 -179
  127. package/src/setBio.js +0 -86
  128. package/src/setMessageReaction.js +0 -186
  129. package/src/setPostReaction.js +0 -71
  130. package/src/setTheme.js +0 -313
  131. package/src/setTitle.js +0 -90
  132. package/src/shareLink.js +0 -62
  133. package/src/threadColors.js +0 -57
  134. package/src/unfriend.js +0 -53
  135. package/src/unsendMessage.js +0 -104
@@ -0,0 +1,1034 @@
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
+ const regions = [
20
+ { code: "PRN", name: "Pacific Northwest Region", location: "Khu vực Tây Bắc Thái Bình Dương" },
21
+ { code: "VLL", name: "Valley Region", location: "Valley" },
22
+ { code: "ASH", name: "Ashburn Region", location: "Ashburn" },
23
+ { code: "DFW", name: "Dallas/Fort Worth Region", location: "Dallas/Fort Worth" },
24
+ { code: "LLA", name: "Los Angeles Region", location: "Los Angeles" },
25
+ { code: "FRA", name: "Frankfurt", location: "Frankfurt" },
26
+ { code: "SIN", name: "Singapore", location: "Singapore" },
27
+ { code: "NRT", name: "Tokyo", location: "Japan" },
28
+ { code: "HKG", name: "Hong Kong", location: "Hong Kong" },
29
+ { code: "SYD", name: "Sydney", location: "Sydney" },
30
+ { code: "PNB", name: "Pacific Northwest - Beta", location: "Pacific Northwest " }
31
+ ];
32
+
33
+ const REGION_MAP = new Map(regions.map(r => [r.code, r]));
34
+
35
+ function parseRegion(html) {
36
+ try {
37
+ const m1 = html.match(/"endpoint":"([^"]+)"/);
38
+ const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
39
+ const raw = (m1 && m1[1]) || (m2 && m2[1]);
40
+ if (!raw) return "PRN";
41
+ const endpoint = raw.replace(/\\\//g, "/");
42
+ const url = new URL(endpoint);
43
+ const rp = url.searchParams ? url.searchParams.get("region") : null;
44
+ return rp ? rp.toUpperCase() : "PRN";
45
+ } catch {
46
+ return "PRN";
47
+ }
48
+ }
49
+
50
+ function mask(s, keep = 3) {
51
+ if (!s) return "";
52
+ const n = s.length;
53
+ return n <= keep ? "*".repeat(n) : s.slice(0, keep) + "*".repeat(Math.max(0, n - keep));
54
+ }
55
+
56
+ function md5(s) {
57
+ return crypto.createHash("md5").update(s).digest("hex");
58
+ }
59
+
60
+ function randomString(length = 24) {
61
+ let s = "abcdefghijklmnopqrstuvwxyz";
62
+ let out = s.charAt(Math.floor(Math.random() * s.length));
63
+ for (let i = 1; i < length; i++) out += "abcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(36 * Math.random()));
64
+ return out;
65
+ }
66
+
67
+ function sortObject(o) {
68
+ const keys = Object.keys(o).sort();
69
+ const x = {};
70
+ for (const k of keys) x[k] = o[k];
71
+ return x;
72
+ }
73
+
74
+ function encodeSig(obj) {
75
+ let data = "";
76
+ for (const k of Object.keys(obj)) data += `${k}=${obj[k]}`;
77
+ return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
78
+ }
79
+
80
+ function rand(min, max) {
81
+ return Math.floor(Math.random() * (max - min + 1)) + min;
82
+ }
83
+
84
+ function choice(arr) {
85
+ return arr[Math.floor(Math.random() * arr.length)];
86
+ }
87
+
88
+ function randomBuildId() {
89
+ const prefixes = ["QP1A", "RP1A", "SP1A", "TP1A", "UP1A", "AP4A"];
90
+ return `${choice(prefixes)}.${rand(180000, 250000)}.${rand(10, 99)}`;
91
+ }
92
+
93
+ function randomResolution() {
94
+ 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 }];
95
+ return choice(presets);
96
+ }
97
+
98
+ function randomFbav() {
99
+ return `${rand(390, 499)}.${rand(0, 3)}.${rand(0, 2)}.${rand(10, 60)}.${rand(100, 999)}`;
100
+ }
101
+
102
+ function randomOrcaUA() {
103
+ const androidVersions = ["8.1.0", "9", "10", "11", "12", "13", "14"];
104
+ 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" }];
105
+ const carriers = ["Viettel Telecom", "Mobifone", "Vinaphone", "T-Mobile", "Verizon", "AT&T", "Telkomsel", "Jio", "NTT DOCOMO", "Vodafone", "Orange"];
106
+ const locales = ["vi_VN", "en_US", "en_GB", "id_ID", "th_TH", "fr_FR", "de_DE", "es_ES", "pt_BR"];
107
+ const archs = ["arm64-v8a", "armeabi-v7a"];
108
+ const a = choice(androidVersions);
109
+ const d = choice(devices);
110
+ const b = randomBuildId();
111
+ const r = randomResolution();
112
+ const fbav = randomFbav();
113
+ const fbbv = rand(320000000, 520000000);
114
+ const arch = `${choice(archs)}:${choice(archs)}`;
115
+ 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;]`;
116
+ return ua;
117
+ }
118
+
119
+ const MOBILE_UA = randomOrcaUA();
120
+
121
+ function buildHeaders(url, extra = {}) {
122
+ const u = new URL(url);
123
+ 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 };
124
+ }
125
+
126
+ const genTotp = async secret => {
127
+ const cleaned = String(secret || "").replace(/\s+/g, "").toUpperCase();
128
+ const r = await TOTP.generate(cleaned);
129
+ return typeof r === "object" ? r.otp : r;
130
+ };
131
+
132
+ function normalizeCookieHeaderString(s) {
133
+ let str = String(s || "").trim();
134
+ if (!str) return [];
135
+ if (/^cookie\s*:/i.test(str)) str = str.replace(/^cookie\s*:/i, "").trim();
136
+ str = str.replace(/\r?\n/g, " ").replace(/\s*;\s*/g, ";");
137
+ const parts = str.split(";").map(v => v.trim()).filter(Boolean);
138
+ const out = [];
139
+ for (const p of parts) {
140
+ const eq = p.indexOf("=");
141
+ if (eq <= 0) continue;
142
+ const k = p.slice(0, eq).trim();
143
+ const v = p.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1");
144
+ if (!k) continue;
145
+ out.push(`${k}=${v}`);
146
+ }
147
+ return out;
148
+ }
149
+
150
+ function setJarFromPairs(j, pairs, domain) {
151
+ const expires = new Date(Date.now() + 31536e6).toUTCString();
152
+ for (const kv of pairs) {
153
+ const cookieStr = `${kv}; expires=${expires}; domain=${domain}; path=/;`;
154
+ try {
155
+ if (typeof j.setCookieSync === "function") j.setCookieSync(cookieStr, "https://www.facebook.com");
156
+ else j.setCookie(cookieStr, "https://www.facebook.com");
157
+ } catch { }
158
+ }
159
+ }
160
+
161
+ function cookieHeaderFromJar(j) {
162
+ const urls = ["https://www.facebook.com", "https://www.messenger.com"];
163
+ const seen = new Set();
164
+ const parts = [];
165
+ for (const u of urls) {
166
+ let s = "";
167
+ try {
168
+ s = typeof j.getCookieStringSync === "function" ? j.getCookieStringSync(u) : "";
169
+ } catch { }
170
+ if (!s) continue;
171
+ for (const kv of s.split(";")) {
172
+ const t = kv.trim();
173
+ const name = t.split("=")[0];
174
+ if (!name || seen.has(name)) continue;
175
+ seen.add(name);
176
+ parts.push(t);
177
+ }
178
+ }
179
+ return parts.join("; ");
180
+ }
181
+
182
+ let uniqueIndexEnsured = false;
183
+
184
+ function getBackupModel() {
185
+ try {
186
+ if (!models || !models.sequelize || !models.Sequelize) return null;
187
+ const sequelize = models.sequelize;
188
+
189
+ // Validate that sequelize is a proper Sequelize instance
190
+ if (!sequelize || typeof sequelize.define !== "function") return null;
191
+
192
+ const { DataTypes } = models.Sequelize;
193
+ if (sequelize.models && sequelize.models.AppStateBackup) return sequelize.models.AppStateBackup;
194
+ const dialect = typeof sequelize.getDialect === "function" ? sequelize.getDialect() : "sqlite";
195
+ const LongText = (dialect === "mysql" || dialect === "mariadb") ? DataTypes.TEXT("long") : DataTypes.TEXT;
196
+
197
+ try {
198
+ const AppStateBackup = sequelize.define(
199
+ "AppStateBackup",
200
+ {
201
+ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
202
+ userID: { type: DataTypes.STRING, allowNull: false },
203
+ type: { type: DataTypes.STRING, allowNull: false },
204
+ data: { type: LongText }
205
+ },
206
+ { tableName: "app_state_backups", timestamps: true, indexes: [{ unique: true, fields: ["userID", "type"] }] }
207
+ );
208
+ return AppStateBackup;
209
+ } catch (defineError) {
210
+ // If define fails, log and return null
211
+ logger(`Failed to define AppStateBackup model: ${defineError && defineError.message ? defineError.message : String(defineError)}`, "warn");
212
+ return null;
213
+ }
214
+ } catch (e) {
215
+ // Silently handle any errors in getBackupModel
216
+ return null;
217
+ }
218
+ }
219
+
220
+ async function ensureUniqueIndex(sequelize) {
221
+ if (uniqueIndexEnsured || !sequelize) return;
222
+ try {
223
+ if (typeof sequelize.getQueryInterface !== "function") return;
224
+ await sequelize.getQueryInterface().addIndex("app_state_backups", ["userID", "type"], { unique: true, name: "app_state_user_type_unique" });
225
+ } catch { }
226
+ uniqueIndexEnsured = true;
227
+ }
228
+
229
+ async function upsertBackup(Model, userID, type, data) {
230
+ const where = { userID: String(userID || ""), type };
231
+ const row = await Model.findOne({ where });
232
+ if (row) {
233
+ await row.update({ data });
234
+ logger(`Overwrote existing ${type} backup for user ${where.userID}`, "info");
235
+ return;
236
+ }
237
+ await Model.create({ ...where, data });
238
+ logger(`Created new ${type} backup for user ${where.userID}`, "info");
239
+ }
240
+
241
+ async function backupAppStateSQL(j, userID) {
242
+ try {
243
+ const Model = getBackupModel();
244
+ if (!Model) return;
245
+ if (!models || !models.sequelize) return;
246
+ await Model.sync();
247
+ await ensureUniqueIndex(models.sequelize);
248
+ const appJson = getAppState(j);
249
+ const ck = cookieHeaderFromJar(j);
250
+ await upsertBackup(Model, userID, "appstate", JSON.stringify(appJson));
251
+ await upsertBackup(Model, userID, "cookie", ck);
252
+ try {
253
+ const out = path.join(process.cwd(), "appstate.json");
254
+ fs.writeFileSync(out, JSON.stringify(appJson, null, 2));
255
+ } catch { }
256
+ logger("Backup stored (overwrite mode)", "info");
257
+ } catch (e) {
258
+ logger(`Failed to save appstate backup ${e && e.message ? e.message : String(e)}`, "warn");
259
+ }
260
+ }
261
+
262
+ async function getLatestBackup(userID, type) {
263
+ try {
264
+ const Model = getBackupModel();
265
+ if (!Model) return null;
266
+ const row = await Model.findOne({ where: { userID: String(userID || ""), type } });
267
+ return row ? row.data : null;
268
+ } catch {
269
+ return null;
270
+ }
271
+ }
272
+
273
+ async function getLatestBackupAny(type) {
274
+ try {
275
+ const Model = getBackupModel();
276
+ if (!Model) return null;
277
+ const row = await Model.findOne({ where: { type }, order: [["updatedAt", "DESC"]] });
278
+ return row ? row.data : null;
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+
284
+ 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;]";
285
+
286
+ function encodesig(obj) {
287
+ let data = "";
288
+ Object.keys(obj).forEach(k => { data += `${k}=${obj[k]}`; });
289
+ return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
290
+ }
291
+
292
+ function sort(obj) {
293
+ const keys = Object.keys(obj).sort();
294
+ const out = {};
295
+ for (const k of keys) out[k] = obj[k];
296
+ return out;
297
+ }
298
+
299
+ async function setJarCookies(j, appstate) {
300
+ const tasks = [];
301
+ for (const c of appstate) {
302
+ const cookieName = c.name || c.key;
303
+ const cookieValue = c.value;
304
+ if (!cookieName || cookieValue === undefined) continue;
305
+
306
+ const cookieDomain = c.domain || ".facebook.com";
307
+ const cookiePath = c.path || "/";
308
+ const dom = cookieDomain.replace(/^\./, "");
309
+
310
+ // Handle expirationDate (can be in seconds or milliseconds)
311
+ let expiresStr = "";
312
+ if (c.expirationDate !== undefined) {
313
+ let expiresDate;
314
+ if (typeof c.expirationDate === "number") {
315
+ // If expirationDate is less than a year from now in seconds, treat as seconds
316
+ // Otherwise treat as milliseconds
317
+ const now = Date.now();
318
+ const oneYearInMs = 365 * 24 * 60 * 60 * 1000;
319
+ if (c.expirationDate < (now + oneYearInMs) / 1000) {
320
+ expiresDate = new Date(c.expirationDate * 1000);
321
+ } else {
322
+ expiresDate = new Date(c.expirationDate);
323
+ }
324
+ } else {
325
+ expiresDate = new Date(c.expirationDate);
326
+ }
327
+ expiresStr = `; expires=${expiresDate.toUTCString()}`;
328
+ } else if (c.expires) {
329
+ const expiresDate = typeof c.expires === "number" ? new Date(c.expires) : new Date(c.expires);
330
+ expiresStr = `; expires=${expiresDate.toUTCString()}`;
331
+ }
332
+
333
+ // Helper function to build cookie string
334
+ const buildCookieString = (domainOverride = null) => {
335
+ const domain = domainOverride || cookieDomain;
336
+ let cookieParts = [`${cookieName}=${cookieValue}${expiresStr}`];
337
+ cookieParts.push(`Domain=${domain}`);
338
+ cookieParts.push(`Path=${cookiePath}`);
339
+
340
+ // Add Secure flag if secure is true
341
+ if (c.secure === true) {
342
+ cookieParts.push("Secure");
343
+ }
344
+
345
+ // Add HttpOnly flag if httpOnly is true
346
+ if (c.httpOnly === true) {
347
+ cookieParts.push("HttpOnly");
348
+ }
349
+
350
+ // Add SameSite attribute if provided
351
+ if (c.sameSite) {
352
+ const sameSiteValue = String(c.sameSite).toLowerCase();
353
+ if (["strict", "lax", "none"].includes(sameSiteValue)) {
354
+ cookieParts.push(`SameSite=${sameSiteValue.charAt(0).toUpperCase() + sameSiteValue.slice(1)}`);
355
+ }
356
+ }
357
+
358
+ return cookieParts.join("; ");
359
+ };
360
+
361
+ // Determine target URLs and cookie strings based on domain
362
+ const cookieConfigs = [];
363
+
364
+ // For .facebook.com domain, set for both facebook.com and messenger.com
365
+ if (cookieDomain === ".facebook.com" || cookieDomain === "facebook.com") {
366
+ // Set for facebook.com with .facebook.com domain
367
+ cookieConfigs.push({ url: `http://${dom}${cookiePath}`, cookieStr: buildCookieString() });
368
+ cookieConfigs.push({ url: `https://${dom}${cookiePath}`, cookieStr: buildCookieString() });
369
+ cookieConfigs.push({ url: `http://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
370
+ cookieConfigs.push({ url: `https://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
371
+
372
+ // Set for messenger.com with .messenger.com domain (or without domain for host-only)
373
+ // Use .messenger.com domain to allow cross-subdomain sharing
374
+ cookieConfigs.push({ url: `http://messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
375
+ cookieConfigs.push({ url: `https://messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
376
+ cookieConfigs.push({ url: `http://www.messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
377
+ cookieConfigs.push({ url: `https://www.messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
378
+ } else if (cookieDomain === ".messenger.com" || cookieDomain === "messenger.com") {
379
+ // Set for messenger.com only
380
+ const messengerDom = cookieDomain.replace(/^\./, "");
381
+ cookieConfigs.push({ url: `http://${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
382
+ cookieConfigs.push({ url: `https://${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
383
+ cookieConfigs.push({ url: `http://www.${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
384
+ cookieConfigs.push({ url: `https://www.${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
385
+ } else {
386
+ // For other domains, set normally
387
+ cookieConfigs.push({ url: `http://${dom}${cookiePath}`, cookieStr: buildCookieString() });
388
+ cookieConfigs.push({ url: `https://${dom}${cookiePath}`, cookieStr: buildCookieString() });
389
+ cookieConfigs.push({ url: `http://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
390
+ cookieConfigs.push({ url: `https://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
391
+ }
392
+
393
+ // Set cookie for all target URLs, silently catch domain errors
394
+ for (const config of cookieConfigs) {
395
+ tasks.push(j.setCookie(config.cookieStr, config.url).catch((err) => {
396
+ // Silently ignore domain mismatch errors for cross-domain cookies
397
+ // These are expected when setting cookies across domains
398
+ if (err && err.message && err.message.includes("Cookie not in this host's domain")) {
399
+ return; // Expected error, ignore
400
+ }
401
+ // Log other errors but don't throw
402
+ return;
403
+ }));
404
+ }
405
+ }
406
+ await Promise.all(tasks);
407
+ }
408
+
409
+ async function loginViaGraph(username, password, twofactorSecretOrCode, i_user, externalJar) {
410
+ const cookieJar = externalJar instanceof CookieJar ? externalJar : new CookieJar();
411
+ const client = wrapper(axiosBase.create({ jar: cookieJar, withCredentials: true, timeout: 30000, validateStatus: () => true }));
412
+ const device_id = uuidv4();
413
+ const family_device_id = device_id;
414
+ const machine_id = randomString(24);
415
+ const base = {
416
+ adid: "00000000-0000-0000-0000-000000000000",
417
+ format: "json",
418
+ device_id,
419
+ email: username,
420
+ password,
421
+ generate_analytics_claim: "1",
422
+ community_id: "",
423
+ cpl: "true",
424
+ try_num: "1",
425
+ family_device_id,
426
+ secure_family_device_id: "",
427
+ credentials_type: "password",
428
+ enroll_misauth: "false",
429
+ generate_session_cookies: "1",
430
+ source: "login",
431
+ generate_machine_id: "1",
432
+ jazoest: "22297",
433
+ meta_inf_fbmeta: "NO_FILE",
434
+ advertiser_id: "00000000-0000-0000-0000-000000000000",
435
+ currently_logged_in_userid: "0",
436
+ locale: "vi_VN",
437
+ client_country_code: "VN",
438
+ fb_api_req_friendly_name: "authenticate",
439
+ fb_api_caller_class: "AuthOperations$PasswordAuthOperation",
440
+ api_key: "256002347743983",
441
+ access_token: "256002347743983|374e60f8b9bb6b8cbb30f78030438895"
442
+ };
443
+ const headers = {
444
+ "User-Agent": MESSENGER_USER_AGENT,
445
+ "Accept-Encoding": "gzip, deflate",
446
+ "Content-Type": "application/x-www-form-urlencoded",
447
+ "x-fb-connection-quality": "EXCELLENT",
448
+ "x-fb-sim-hni": "45204",
449
+ "x-fb-net-hni": "45204",
450
+ "x-fb-connection-type": "WIFI",
451
+ "x-tigon-is-retry": "False",
452
+ "x-fb-friendly-name": "authenticate",
453
+ "x-fb-request-analytics-tags": '{"network_tags":{"retry_attempt":"0"},"application_tags":"unknown"}',
454
+ "x-fb-http-engine": "Liger",
455
+ "x-fb-client-ip": "True",
456
+ "x-fb-server-cluster": "True",
457
+ authorization: `OAuth ${base.access_token}`
458
+ };
459
+ const form1 = { ...base };
460
+ form1.sig = encodesig(sort(form1));
461
+ const res1 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form1), headers });
462
+ if (res1.status === 200 && res1.data && res1.data.session_cookies) {
463
+ const appstate = res1.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
464
+ const cUserCookie = appstate.find(c => c.key === "c_user");
465
+ if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
466
+ else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
467
+ await setJarCookies(cookieJar, appstate);
468
+ let eaau = null;
469
+ let eaad6v7 = null;
470
+ try {
471
+ const r1 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res1.data.access_token}&new_app_id=350685531728`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res1.data.access_token}` } });
472
+ eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
473
+ } catch { }
474
+ try {
475
+ const r2 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res1.data.access_token}&new_app_id=275254692598279`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res1.data.access_token}` } });
476
+ eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
477
+ } catch { }
478
+ return { ok: true, cookies: appstate.map(c => ({ key: c.key, value: c.value })), jar: cookieJar, access_token_mess: res1.data.access_token || null, access_token: eaau, access_token_eaad6v7: eaad6v7, uid: res1.data.uid || cUserCookie?.value || null, session_key: res1.data.session_key || null };
479
+ }
480
+ const err = res1 && res1.data && res1.data.error ? res1.data.error : {};
481
+ if (err && err.code === 406) {
482
+ const data = err.error_data || {};
483
+ let code = null;
484
+ if (twofactorSecretOrCode && /^\d{6}$/.test(String(twofactorSecretOrCode))) code = String(twofactorSecretOrCode);
485
+ else if (twofactorSecretOrCode) {
486
+ try {
487
+ const clean = decodeURI(twofactorSecretOrCode).replace(/\s+/g, "").toUpperCase();
488
+ const { otp } = await TOTP.generate(clean);
489
+ code = otp;
490
+ } catch { }
491
+ } else if (config.credentials?.twofactor) {
492
+ const { otp } = await TOTP.generate(String(config.credentials.twofactor).replace(/\s+/g, "").toUpperCase());
493
+ code = otp;
494
+ }
495
+ if (!code) return { ok: false, message: "2FA required" };
496
+ const form2 = {
497
+ ...base,
498
+ credentials_type: "two_factor",
499
+ twofactor_code: code,
500
+ userid: data.uid || username,
501
+ first_factor: data.login_first_factor || "",
502
+ machine_id: data.machine_id || machine_id
503
+ };
504
+ form2.sig = encodesig(sort(form2));
505
+ const res2 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form2), headers });
506
+ if (res2.status === 200 && res2.data && res2.data.session_cookies) {
507
+ const appstate = res2.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
508
+ const cUserCookie = appstate.find(c => c.key === "c_user");
509
+ if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
510
+ else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
511
+ await setJarCookies(cookieJar, appstate);
512
+ let eaau = null;
513
+ let eaad6v7 = null;
514
+ try {
515
+ const r1 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res2.data.access_token}&new_app_id=350685531728`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res2.data.access_token}` } });
516
+ eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
517
+ } catch { }
518
+ try {
519
+ const r2 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res2.data.access_token}&new_app_id=275254692598279`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res2.data.access_token}` } });
520
+ eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
521
+ } catch { }
522
+ return { ok: true, cookies: appstate.map(c => ({ key: c.key, value: c.value })), jar: cookieJar, access_token_mess: res2.data.access_token || null, access_token: eaau, access_token_eaad6v7: eaad6v7, uid: res2.data.uid || cUserCookie?.value || null, session_key: res2.data.session_key || null };
523
+ }
524
+ return { ok: false, message: "2FA failed" };
525
+ }
526
+ return { ok: false, message: "Login failed" };
527
+ }
528
+
529
+ async function tokens(username, password, twofactor = null) {
530
+ const t0 = process.hrtime.bigint();
531
+ if (!username || !password) return { status: false, message: "Please provide email and password" };
532
+ logger(`AUTO-LOGIN: Initialize login ${mask(username, 2)}`, "info");
533
+ const res = await loginViaGraph(username, password, twofactor, null, jar);
534
+ if (res && res.ok && Array.isArray(res.cookies)) {
535
+ logger(`AUTO-LOGIN: Login success ${res.cookies.length} cookies`, "info");
536
+ const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
537
+ logger(`Done success login ${Math.round(t1)}ms`, "info");
538
+ return { status: true, cookies: res.cookies };
539
+ }
540
+ if (res && res.message === "2FA required") {
541
+ logger("AUTO-LOGIN: 2FA required but secret missing", "warn");
542
+ return { status: false, message: "Please provide the 2FA secret!" };
543
+ }
544
+ return { status: false, message: res && res.message ? res.message : "Login failed" };
545
+ }
546
+
547
+ async function hydrateJarFromDB(userID) {
548
+ try {
549
+ let ck = null;
550
+ let app = null;
551
+ if (userID) {
552
+ ck = await getLatestBackup(userID, "cookie");
553
+ app = await getLatestBackup(userID, "appstate");
554
+ } else {
555
+ ck = await getLatestBackupAny("cookie");
556
+ app = await getLatestBackupAny("appstate");
557
+ }
558
+ if (ck) {
559
+ const pairs = normalizeCookieHeaderString(ck);
560
+ if (pairs.length) {
561
+ setJarFromPairs(jar, pairs, ".facebook.com");
562
+ return true;
563
+ }
564
+ }
565
+ if (app) {
566
+ let parsed = null;
567
+ try {
568
+ parsed = JSON.parse(app);
569
+ } catch { }
570
+ if (Array.isArray(parsed)) {
571
+ const pairs = parsed.map(c => [c.name || c.key, c.value].join("="));
572
+ setJarFromPairs(jar, pairs, ".facebook.com");
573
+ return true;
574
+ }
575
+ }
576
+ return false;
577
+ } catch {
578
+ return false;
579
+ }
580
+ }
581
+
582
+ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false) {
583
+ const getUID = cs =>
584
+ cs.find(c => c.key === "i_user")?.value ||
585
+ cs.find(c => c.key === "c_user")?.value ||
586
+ cs.find(c => c.name === "i_user")?.value ||
587
+ cs.find(c => c.name === "c_user")?.value;
588
+ const htmlUID = body => {
589
+ const s = typeof body === "string" ? body : String(body ?? "");
590
+ return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
591
+ };
592
+ let userID = getUID(currentCookies);
593
+ // Also try to extract userID from HTML if not found in cookies
594
+ if (!userID) {
595
+ userID = htmlUID(currentHtml);
596
+ }
597
+ if (userID) return { html: currentHtml, cookies: currentCookies, userID };
598
+ // If appState/Cookie was provided and is not dead (not checkpointed), skip backup
599
+ if (hadAppStateInput) {
600
+ const isCheckpoint = currentHtml.includes("/checkpoint/block/?next");
601
+ if (!isCheckpoint) {
602
+ // AppState provided and not checkpointed, but userID not found
603
+ // This might be a temporary issue - try to refresh cookies from jar
604
+ try {
605
+ const refreshedCookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
606
+ userID = getUID(refreshedCookies);
607
+ if (userID) {
608
+ return { html: currentHtml, cookies: refreshedCookies, userID };
609
+ }
610
+ } catch { }
611
+ // If still no userID, skip backup and throw error
612
+ throw new Error("Missing user cookie from provided appState");
613
+ }
614
+ // AppState is dead (checkpointed), proceed to backup/email login
615
+ }
616
+ const hydrated = await hydrateJarFromDB(null);
617
+ if (hydrated) {
618
+ logger("AppState backup live — proceeding to login", "info");
619
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
620
+ const resB = (await ctxRef.bypassAutomation(initial, jar)) || initial;
621
+ const htmlB = resB && resB.data ? resB.data : "";
622
+ if (htmlB.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
623
+ const cookiesB = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
624
+ const uidB = getUID(cookiesB);
625
+ if (uidB) return { html: htmlB, cookies: cookiesB, userID: uidB };
626
+ }
627
+ if (config.autoLogin !== true) throw new Error("AppState backup die — Auto-login is disabled");
628
+ logger("AppState backup die — proceeding to email/password login", "warn");
629
+ const u = config.credentials?.email;
630
+ const p = config.credentials?.password;
631
+ const tf = config.credentials?.twofactor || null;
632
+ if (!u || !p) throw new Error("Missing user cookie");
633
+ const r = await tokens(u, p, tf);
634
+ if (!(r && r.status && Array.isArray(r.cookies))) throw new Error(r && r.message ? r.message : "Login failed");
635
+ const pairs = r.cookies.map(c => `${c.key || c.name}=${c.value}`);
636
+ setJarFromPairs(jar, pairs, ".facebook.com");
637
+ const initial2 = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
638
+ const res2 = (await ctxRef.bypassAutomation(initial2, jar)) || initial2;
639
+ const html2 = res2 && res2.data ? res2.data : "";
640
+ if (html2.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
641
+ const cookies2 = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
642
+ const uid2 = getUID(cookies2);
643
+ if (!uid2) throw new Error("Login failed");
644
+ return { html: html2, cookies: cookies2, userID: uid2 };
645
+ }
646
+
647
+ function makeLogin(j, email, password, globalOptions) {
648
+ return async function () {
649
+ const u = email || config.credentials?.email;
650
+ const p = password || config.credentials?.password;
651
+ const tf = config.credentials?.twofactor || null;
652
+ if (!u || !p) return;
653
+ const r = await tokens(u, p, tf);
654
+ if (r && r.status && Array.isArray(r.cookies)) {
655
+ const pairs = r.cookies.map(c => `${c.key || c.name}=${c.value}`);
656
+ setJarFromPairs(j, pairs, ".facebook.com");
657
+ await get("https://www.facebook.com/", j, null, globalOptions).then(saveCookies(j));
658
+ } else {
659
+ throw new Error(r && r.message ? r.message : "Login failed");
660
+ }
661
+ };
662
+ }
663
+
664
+ function loginHelper(appState, Cookie, email, password, globalOptions, callback) {
665
+ try {
666
+ const domain = ".facebook.com";
667
+ // Helper to extract userID from appState input
668
+ const extractUIDFromAppState = (appStateInput) => {
669
+ if (!appStateInput) return null;
670
+ let parsed = appStateInput;
671
+ if (typeof appStateInput === "string") {
672
+ try {
673
+ parsed = JSON.parse(appStateInput);
674
+ } catch {
675
+ return null;
676
+ }
677
+ }
678
+ if (Array.isArray(parsed)) {
679
+ const cUser = parsed.find(c => (c.key === "c_user" || c.name === "c_user"));
680
+ if (cUser) return cUser.value;
681
+ const iUser = parsed.find(c => (c.key === "i_user" || c.name === "i_user"));
682
+ if (iUser) return iUser.value;
683
+ }
684
+ return null;
685
+ };
686
+ let userIDFromAppState = extractUIDFromAppState(appState);
687
+ (async () => {
688
+ try {
689
+ if (appState) {
690
+ // Check and convert cookie to appState format
691
+ if (Array.isArray(appState) && appState.some(c => c.name)) {
692
+ // Convert name to key if needed
693
+ appState = appState.map(c => {
694
+ if (c.name && !c.key) {
695
+ c.key = c.name;
696
+ delete c.name;
697
+ }
698
+ return c;
699
+ });
700
+ } else if (typeof appState === "string") {
701
+ // Try to parse as JSON first
702
+ let parsed = appState;
703
+ try {
704
+ parsed = JSON.parse(appState);
705
+ } catch { }
706
+
707
+ if (Array.isArray(parsed)) {
708
+ // Already parsed as array, use it
709
+ appState = parsed;
710
+ } else {
711
+ // Parse string cookie format (key=value; key2=value2)
712
+ const arrayAppState = [];
713
+ appState.split(';').forEach(c => {
714
+ const [key, value] = c.split('=');
715
+ if (key && value) {
716
+ arrayAppState.push({
717
+ key: key.trim(),
718
+ value: value.trim(),
719
+ domain: ".facebook.com",
720
+ path: "/",
721
+ expires: new Date().getTime() + 1000 * 60 * 60 * 24 * 365
722
+ });
723
+ }
724
+ });
725
+ appState = arrayAppState;
726
+ }
727
+ }
728
+
729
+ // Set cookies into jar with individual domain/path
730
+ if (Array.isArray(appState)) {
731
+ await setJarCookies(jar, appState);
732
+ } else {
733
+ throw new Error("Invalid appState format");
734
+ }
735
+ }
736
+ if (Cookie) {
737
+ let cookiePairs = [];
738
+ if (typeof Cookie === "string") cookiePairs = normalizeCookieHeaderString(Cookie);
739
+ else if (Array.isArray(Cookie)) cookiePairs = Cookie.map(String).filter(Boolean);
740
+ else if (Cookie && typeof Cookie === "object") cookiePairs = Object.entries(Cookie).map(([k, v]) => `${k}=${v}`);
741
+ if (cookiePairs.length) setJarFromPairs(jar, cookiePairs, domain);
742
+ }
743
+ } catch (e) {
744
+ return callback(e);
745
+ }
746
+ const ctx = { globalOptions, options: globalOptions, reconnectAttempts: 0 };
747
+ ctx.bypassAutomation = async function (resp, j) {
748
+ global.fca = global.fca || {};
749
+ global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
750
+ const s = x => (typeof x === "string" ? x : String(x ?? ""));
751
+ const u = r => r?.request?.res?.responseUrl || (r?.config?.baseURL ? new URL(r.config.url || "/", r.config.baseURL).toString() : r?.config?.url || "");
752
+ const isCp = r => typeof u(r) === "string" && u(r).includes("checkpoint/601051028565049");
753
+ const cookieUID = async () => {
754
+ try {
755
+ const cookies = typeof j?.getCookies === "function" ? await j.getCookies("https://www.facebook.com") : [];
756
+ return cookies.find(c => c.key === "i_user")?.value || cookies.find(c => c.key === "c_user")?.value;
757
+ } catch { return undefined; }
758
+ };
759
+ const htmlUID = body => s(body).match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s(body).match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
760
+ const getUID = async body => (await cookieUID()) || htmlUID(body);
761
+ const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
762
+ const bypass = async body => {
763
+ const b = s(body);
764
+ const UID = await getUID(b);
765
+ const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
766
+ const jazoest = getFrom(b, 'name="jazoest" value="', '"') || getFrom(b, "jazoest=", '",') || b.match(/name="jazoest"\s+value="([^"]+)"/)?.[1];
767
+ const lsd = getFrom(b, '["LSD",[],{"token":"', '"}') || b.match(/name="lsd"\s+value="([^"]+)"/)?.[1];
768
+ 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 };
769
+ await post("https://www.facebook.com/api/graphql/", j, form, null, this.options).then(saveCookies(j));
770
+ logger("Facebook automation warning detected, handling...", "warn");
771
+ this.reconnectAttempts = 0;
772
+ };
773
+ try {
774
+ if (resp) {
775
+ if (isCp(resp)) {
776
+ await bypass(s(resp.data));
777
+ const refreshed = await refreshJar();
778
+ if (isCp(refreshed)) logger("Checkpoint still present after refresh", "warn");
779
+ else logger("Bypass complete, cookies refreshed", "info");
780
+ return refreshed;
781
+ }
782
+ return resp;
783
+ }
784
+ const first = await get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
785
+ if (isCp(first)) {
786
+ await bypass(s(first.data));
787
+ const refreshed = await refreshJar();
788
+ if (!isCp(refreshed)) logger("Bypass complete, cookies refreshed", "info");
789
+ else logger("Checkpoint still present after refresh", "warn");
790
+ return refreshed;
791
+ }
792
+ return first;
793
+ } catch (e) {
794
+ logger(`Bypass automation error: ${e && e.message ? e.message : String(e)}`, "error");
795
+ return resp;
796
+ }
797
+ };
798
+ if (appState || Cookie) {
799
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
800
+ return (await ctx.bypassAutomation(initial, jar)) || initial;
801
+ }
802
+ const hydrated = await hydrateJarFromDB(null);
803
+ if (hydrated) {
804
+ logger("AppState backup live — proceeding to login", "info");
805
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
806
+ return (await ctx.bypassAutomation(initial, jar)) || initial;
807
+ }
808
+ logger("AppState backup die — proceeding to email/password login", "warn");
809
+ return get("https://www.facebook.com/", null, null, globalOptions)
810
+ .then(saveCookies(jar))
811
+ .then(makeLogin(jar, email, password, globalOptions))
812
+ .then(function () {
813
+ return get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
814
+ });
815
+ })()
816
+ .then(async function (res) {
817
+ const ctx = {};
818
+ ctx.options = globalOptions;
819
+ ctx.bypassAutomation = async function (resp, j) {
820
+ global.fca = global.fca || {};
821
+ global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
822
+ const s = x => (typeof x === "string" ? x : String(x ?? ""));
823
+ const u = r => r?.request?.res?.responseUrl || (r?.config?.baseURL ? new URL(r.config.url || "/", r.config.baseURL).toString() : r?.config?.url || "");
824
+ const isCp = r => typeof u(r) === "string" && u(r).includes("checkpoint/601051028565049");
825
+ const cookieUID = async () => {
826
+ try {
827
+ const cookies = typeof j?.getCookies === "function" ? await j.getCookies("https://www.facebook.com") : [];
828
+ return cookies.find(c => c.key === "i_user")?.value || cookies.find(c => c.key === "c_user")?.value;
829
+ } catch { return undefined; }
830
+ };
831
+ const htmlUID = body => s(body).match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s(body).match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
832
+ const getUID = async body => (await cookieUID()) || htmlUID(body);
833
+ const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
834
+ const bypass = async body => {
835
+ const b = s(body);
836
+ const UID = await getUID(b);
837
+ const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
838
+ const jazoest = getFrom(b, 'name="jazoest" value="', '"') || getFrom(b, "jazoest=", '",') || b.match(/name="jazoest"\s+value="([^"]+)"/)?.[1];
839
+ const lsd = getFrom(b, '["LSD",[],{"token":"', '"}') || b.match(/name="lsd"\s+value="([^"]+)"/)?.[1];
840
+ 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 };
841
+ await post("https://www.facebook.com/api/graphql/", j, form, null, this.options).then(saveCookies(j));
842
+ logger("Facebook automation warning detected, handling...", "warn");
843
+ };
844
+ try {
845
+ if (res && isCp(res)) {
846
+ await bypass(s(res.data));
847
+ const refreshed = await refreshJar();
848
+ if (!isCp(refreshed)) logger("Bypass complete, cookies refreshed", "info");
849
+ return refreshed;
850
+ }
851
+ logger("No checkpoint detected", "info");
852
+ return res;
853
+ } catch {
854
+ return res;
855
+ }
856
+ };
857
+ const processed = (await ctx.bypassAutomation(res, jar)) || res;
858
+ let html = processed && processed.data ? processed.data : "";
859
+ let cookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
860
+ const getUIDFromCookies = cs =>
861
+ cs.find(c => c.key === "i_user")?.value ||
862
+ cs.find(c => c.key === "c_user")?.value ||
863
+ cs.find(c => c.name === "i_user")?.value ||
864
+ cs.find(c => c.name === "c_user")?.value;
865
+ const getUIDFromHTML = body => {
866
+ const s = typeof body === "string" ? body : String(body ?? "");
867
+ return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
868
+ };
869
+ let userID = getUIDFromCookies(cookies);
870
+ // Also try to extract userID from HTML if not found in cookies
871
+ if (!userID) {
872
+ userID = getUIDFromHTML(html);
873
+ }
874
+ // If still not found and appState was provided, use userID from appState input as fallback
875
+ if (!userID && userIDFromAppState) {
876
+ userID = userIDFromAppState;
877
+ }
878
+ if (!userID) {
879
+ // Pass hadAppStateInput=true if appState/Cookie was originally provided
880
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
881
+ html = retried.html;
882
+ cookies = retried.cookies;
883
+ userID = retried.userID;
884
+ }
885
+ if (html.includes("/checkpoint/block/?next")) {
886
+ logger("Appstate die, vui lòng thay cái mới!", "error");
887
+ throw new Error("Checkpoint");
888
+ }
889
+ let mqttEndpoint;
890
+ let region = "PRN";
891
+ let fb_dtsg;
892
+ let irisSeqID;
893
+ try {
894
+ const m1 = html.match(/"endpoint":"([^"]+)"/);
895
+ const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
896
+ const raw = (m1 && m1[1]) || (m2 && m2[1]);
897
+ if (raw) mqttEndpoint = raw.replace(/\\\//g, "/");
898
+ region = parseRegion(html);
899
+ const rinfo = REGION_MAP.get(region);
900
+ if (rinfo) logger(`Server region ${region} - ${rinfo.name}`, "info");
901
+ else logger(`Server region ${region}`, "info");
902
+ } catch {
903
+ logger("Not MQTT endpoint", "warn");
904
+ }
905
+ try {
906
+ const userDataMatch = String(html).match(/\["CurrentUserInitialData",\[\],({.*?}),\d+\]/);
907
+ if (userDataMatch) {
908
+ const info = JSON.parse(userDataMatch[1]);
909
+ logger(`Đăng nhập tài khoản: ${info.NAME} (${info.USER_ID})`, "info");
910
+ } else if (userID) {
911
+ logger(`ID người dùng: ${userID}`, "info");
912
+ }
913
+ } catch { }
914
+ const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
915
+ if (tokenMatch) fb_dtsg = tokenMatch[1];
916
+ try {
917
+ if (userID) await backupAppStateSQL(jar, userID);
918
+ } catch { }
919
+ Promise.resolve()
920
+ .then(function () {
921
+ if (models && models.sequelize && typeof models.sequelize.authenticate === "function") {
922
+ return models.sequelize.authenticate();
923
+ }
924
+ })
925
+ .then(function () {
926
+ if (models && typeof models.syncAll === "function") {
927
+ return models.syncAll();
928
+ }
929
+ })
930
+ .catch(function (error) {
931
+ // Silently handle database errors - they're not critical for login
932
+ const errorMsg = error && error.message ? error.message : String(error);
933
+ if (!errorMsg.includes("No Sequelize instance passed")) {
934
+ // Only log non-Sequelize instance errors
935
+ logger(`Database connection failed: ${errorMsg}`, "warn");
936
+ }
937
+ });
938
+ logger("FCA fix/update by DongDev (Donix-VN)", "info");
939
+ const ctxMain = {
940
+ userID,
941
+ jar,
942
+ globalOptions,
943
+ loggedIn: true,
944
+ access_token: "NONE",
945
+ clientMutationId: 0,
946
+ mqttClient: undefined,
947
+ lastSeqId: irisSeqID,
948
+ syncToken: undefined,
949
+ mqttEndpoint,
950
+ region,
951
+ firstListen: true,
952
+ fb_dtsg,
953
+ clientID: ((Math.random() * 2147483648) | 0).toString(16),
954
+ clientId: getFrom(html, '["MqttWebDeviceID",[],{"clientID":"', '"}') || "",
955
+ wsReqNumber: 0,
956
+ wsTaskNumber: 0,
957
+ tasks: new Map()
958
+ };
959
+ ctxMain.options = globalOptions;
960
+ ctxMain.bypassAutomation = ctx.bypassAutomation.bind(ctxMain);
961
+ ctxMain.performAutoLogin = async () => {
962
+ try {
963
+ const u = config.credentials?.email || email;
964
+ const p = config.credentials?.password || password;
965
+ const tf = config.credentials?.twofactor || null;
966
+ if (!u || !p) return false;
967
+ const r = await tokens(u, p, tf);
968
+ if (!(r && r.status && Array.isArray(r.cookies))) return false;
969
+ const pairs = r.cookies.map(c => `${c.key || c.name}=${c.value}`);
970
+ setJarFromPairs(jar, pairs, ".facebook.com");
971
+ await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
972
+ return true;
973
+ } catch {
974
+ return false;
975
+ }
976
+ };
977
+ const api = {
978
+ setOptions: require("./options").setOptions.bind(null, globalOptions),
979
+ getCookies: function () {
980
+ return cookieHeaderFromJar(jar);
981
+ },
982
+ getAppState: function () {
983
+ return getAppState(jar);
984
+ },
985
+ getLatestAppStateFromDB: async function (uid = userID) {
986
+ const data = await getLatestBackup(uid, "appstate");
987
+ return data ? JSON.parse(data) : null;
988
+ },
989
+ getLatestCookieFromDB: async function (uid = userID) {
990
+ return await getLatestBackup(uid, "cookie");
991
+ }
992
+ };
993
+ const defaultFuncs = makeDefaults(html, userID, ctxMain);
994
+ const srcRoot = path.join(__dirname, "../src/api");
995
+ let loaded = 0;
996
+ let skipped = 0;
997
+ fs.readdirSync(srcRoot, { withFileTypes: true }).forEach((sub) => {
998
+ if (!sub.isDirectory()) return;
999
+ const subDir = path.join(srcRoot, sub.name);
1000
+ fs.readdirSync(subDir, { withFileTypes: true }).forEach((entry) => {
1001
+ if (!entry.isFile() || !entry.name.endsWith(".js")) return;
1002
+ const p = path.join(subDir, entry.name);
1003
+ const key = path.basename(entry.name, ".js");
1004
+ if (api[key]) {
1005
+ skipped++;
1006
+ return;
1007
+ }
1008
+ api[key] = require(p)(defaultFuncs, api, ctxMain);
1009
+ loaded++;
1010
+ });
1011
+ });
1012
+ logger(`Loaded ${loaded} FCA API methods${skipped ? `, skipped ${skipped} duplicates` : ""}`);
1013
+ if (api.listenMqtt) api.listen = api.listenMqtt;
1014
+ if (api.refreshFb_dtsg) {
1015
+ setInterval(function () {
1016
+ api.refreshFb_dtsg().then(function () {
1017
+ logger("Successfully refreshed fb_dtsg");
1018
+ }).catch(function () {
1019
+ logger("An error occurred while refreshing fb_dtsg", "error");
1020
+ });
1021
+ }, 86400000);
1022
+ }
1023
+ logger("Login successful!");
1024
+ callback(null, api);
1025
+ })
1026
+ .catch(function (e) {
1027
+ callback(e);
1028
+ });
1029
+ } catch (e) {
1030
+ callback(e);
1031
+ }
1032
+ }
1033
+
1034
+ module.exports = loginHelper;