@dongdev/fca-unofficial 1.0.20 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.gitattributes +1 -0
  2. package/CHANGELOG.md +32 -29
  3. package/DOCS.md +727 -592
  4. package/README.md +94 -97
  5. package/func/logger.js +112 -0
  6. package/html.html +474 -0
  7. package/index.js +1 -379
  8. package/module/config.js +26 -0
  9. package/module/login.js +45 -0
  10. package/module/loginHelper.js +634 -0
  11. package/module/options.js +49 -0
  12. package/package.json +6 -39
  13. package/src/api/action/addExternalModule.js +25 -0
  14. package/src/api/action/changeAvatar.js +136 -0
  15. package/src/api/action/changeBio.js +76 -0
  16. package/src/api/action/getCurrentUserID.js +7 -0
  17. package/src/api/action/handleFriendRequest.js +57 -0
  18. package/src/api/action/logout.js +75 -0
  19. package/src/{refreshFb_dtsg.js → api/action/refreshFb_dtsg.js} +8 -8
  20. package/src/api/action/setPostReaction.js +107 -0
  21. package/src/api/action/unfriend.js +55 -0
  22. package/src/api/http/httpGet.js +65 -0
  23. package/src/api/http/httpPost.js +65 -0
  24. package/src/{postFormData.js → api/http/postFormData.js} +10 -10
  25. package/src/api/messaging/addUserToGroup.js +69 -0
  26. package/src/api/messaging/changeAdminStatus.js +103 -0
  27. package/src/api/messaging/changeArchivedStatus.js +55 -0
  28. package/src/api/messaging/changeBlockedStatus.js +49 -0
  29. package/src/api/messaging/changeGroupImage.js +135 -0
  30. package/src/api/messaging/changeNickname.js +59 -0
  31. package/src/api/messaging/changeThreadColor.js +65 -0
  32. package/src/api/messaging/createNewGroup.js +88 -0
  33. package/src/api/messaging/createPoll.js +70 -0
  34. package/src/api/messaging/deleteMessage.js +56 -0
  35. package/src/api/messaging/deleteThread.js +56 -0
  36. package/src/api/messaging/forwardAttachment.js +60 -0
  37. package/src/api/messaging/getEmojiUrl.js +29 -0
  38. package/src/api/messaging/getFriendsList.js +83 -0
  39. package/src/api/messaging/getMessage.js +834 -0
  40. package/src/api/messaging/getThreadHistory.js +681 -0
  41. package/src/api/messaging/handleMessageRequest.js +65 -0
  42. package/src/api/messaging/markAsDelivered.js +57 -0
  43. package/src/api/messaging/markAsRead.js +88 -0
  44. package/src/api/messaging/markAsReadAll.js +50 -0
  45. package/src/api/messaging/markAsSeen.js +61 -0
  46. package/src/api/messaging/muteThread.js +51 -0
  47. package/src/api/messaging/removeUserFromGroup.js +79 -0
  48. package/src/api/messaging/resolvePhotoUrl.js +44 -0
  49. package/src/api/messaging/searchForThread.js +53 -0
  50. package/src/api/messaging/sendMessage.js +306 -0
  51. package/src/api/messaging/sendMessageMqtt.js +321 -0
  52. package/src/api/messaging/sendTypingIndicator.js +110 -0
  53. package/src/{setMessageReaction.js → api/messaging/setMessageReaction.js} +20 -20
  54. package/src/api/messaging/setTitle.js +90 -0
  55. package/src/api/messaging/shareContact.js +51 -0
  56. package/src/api/messaging/threadColors.js +131 -0
  57. package/src/api/messaging/unsendMessage.js +44 -0
  58. package/src/api/messaging/uploadAttachment.js +93 -0
  59. package/src/api/socket/detail/buildStream.js +100 -0
  60. package/src/{listenMqtt.js → api/socket/listenMqtt.js} +122 -206
  61. package/src/api/threads/changeThreadEmoji.js +55 -0
  62. package/src/api/threads/getThreadInfo.js +572 -0
  63. package/src/{getThreadList.js → api/threads/getThreadList.js} +110 -54
  64. package/src/api/threads/getThreadPictures.js +79 -0
  65. package/src/api/users/getUserID.js +66 -0
  66. package/src/api/users/getUserInfo.js +88 -0
  67. package/src/core/sendReqMqtt.js +63 -0
  68. package/{lib → src}/database/models/index.js +12 -10
  69. package/{lib → src}/database/models/thread.js +5 -5
  70. package/{lib → src}/database/threadData.js +19 -14
  71. package/src/utils/client.js +159 -0
  72. package/src/utils/constants.js +13 -0
  73. package/src/utils/format.js +60 -0
  74. package/src/utils/headers.js +41 -0
  75. package/src/utils/index.js +1497 -0
  76. package/src/utils/request.js +147 -0
  77. package/lib/logger.js +0 -96
  78. package/src/addExternalModule.js +0 -19
  79. package/src/addUserToGroup.js +0 -113
  80. package/src/changeAdminStatus.js +0 -79
  81. package/src/changeArchivedStatus.js +0 -55
  82. package/src/changeAvatar.js +0 -126
  83. package/src/changeBio.js +0 -77
  84. package/src/changeBlockedStatus.js +0 -47
  85. package/src/changeGroupImage.js +0 -132
  86. package/src/changeNickname.js +0 -59
  87. package/src/changeThreadColor.js +0 -65
  88. package/src/changeThreadEmoji.js +0 -55
  89. package/src/createNewGroup.js +0 -86
  90. package/src/createPoll.js +0 -71
  91. package/src/deleteMessage.js +0 -56
  92. package/src/deleteThread.js +0 -56
  93. package/src/forwardAttachment.js +0 -60
  94. package/src/getCurrentUserID.js +0 -7
  95. package/src/getEmojiUrl.js +0 -29
  96. package/src/getFriendsList.js +0 -83
  97. package/src/getMessage.js +0 -796
  98. package/src/getThreadHistory.js +0 -666
  99. package/src/getThreadInfo.js +0 -535
  100. package/src/getThreadPictures.js +0 -79
  101. package/src/getUserID.js +0 -66
  102. package/src/getUserInfo.js +0 -80
  103. package/src/handleFriendRequest.js +0 -61
  104. package/src/handleMessageRequest.js +0 -65
  105. package/src/httpGet.js +0 -57
  106. package/src/httpPost.js +0 -57
  107. package/src/httpPostFormData.js +0 -63
  108. package/src/logout.js +0 -75
  109. package/src/markAsDelivered.js +0 -58
  110. package/src/markAsRead.js +0 -80
  111. package/src/markAsReadAll.js +0 -50
  112. package/src/markAsSeen.js +0 -59
  113. package/src/muteThread.js +0 -52
  114. package/src/removeUserFromGroup.js +0 -79
  115. package/src/resolvePhotoUrl.js +0 -45
  116. package/src/searchForThread.js +0 -53
  117. package/src/sendMessage.js +0 -328
  118. package/src/sendMessageMqtt.js +0 -316
  119. package/src/sendTypingIndicator.js +0 -103
  120. package/src/setPostReaction.js +0 -109
  121. package/src/setTitle.js +0 -86
  122. package/src/shareContact.js +0 -49
  123. package/src/threadColors.js +0 -131
  124. package/src/unfriend.js +0 -52
  125. package/src/unsendMessage.js +0 -49
  126. package/src/uploadAttachment.js +0 -95
  127. package/utils.js +0 -1387
  128. /package/{lib → func}/login.js +0 -0
@@ -0,0 +1,634 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const models = require("../src/database/models");
4
+ const logger = require("../func/logger");
5
+ const { get, post, jar, makeDefaults } = require("../src/utils/request");
6
+ const { saveCookies, getAppState } = require("../src/utils/client");
7
+ const { getFrom } = require("../src/utils/constants");
8
+ const config = require(process.cwd() + "/fca-config.json");
9
+ const { v4: uuidv4 } = require("uuid");
10
+ const { CookieJar } = require("tough-cookie");
11
+ const { wrapper } = require("axios-cookiejar-support");
12
+ const axiosBase = require("axios");
13
+ const qs = require("querystring");
14
+ const crypto = require("crypto");
15
+ const { TOTP } = require("totp-generator");
16
+
17
+ const regions = [
18
+ { code: "PRN", name: "Pacific Northwest Region", location: "Khu vực Tây Bắc Thái Bình Dương" },
19
+ { code: "VLL", name: "Valley Region", location: "Valley" },
20
+ { code: "ASH", name: "Ashburn Region", location: "Ashburn" },
21
+ { code: "DFW", name: "Dallas/Fort Worth Region", location: "Dallas/Fort Worth" },
22
+ { code: "LLA", name: "Los Angeles Region", location: "Los Angeles" },
23
+ { code: "FRA", name: "Frankfurt", location: "Frankfurt" },
24
+ { code: "SIN", name: "Singapore", location: "Singapore" },
25
+ { code: "NRT", name: "Tokyo", location: "Japan" },
26
+ { code: "HKG", name: "Hong Kong", location: "Hong Kong" },
27
+ { code: "SYD", name: "Sydney", location: "Sydney" },
28
+ { code: "PNB", name: "Pacific Northwest - Beta", location: "Pacific Northwest " }
29
+ ];
30
+
31
+ const REGION_MAP = new Map(regions.map(r => [r.code, r]));
32
+
33
+ function parseRegion(html) {
34
+ try {
35
+ const m1 = html.match(/"endpoint":"([^"]+)"/);
36
+ const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
37
+ const raw = (m1 && m1[1]) || (m2 && m2[1]);
38
+ if (!raw) return "PRN";
39
+ const endpoint = raw.replace(/\\\//g, "/");
40
+ const url = new URL(endpoint);
41
+ const rp = url.searchParams ? url.searchParams.get("region") : null;
42
+ return rp ? rp.toUpperCase() : "PRN";
43
+ } catch {
44
+ return "PRN";
45
+ }
46
+ }
47
+
48
+ function mask(s, keep = 3) {
49
+ if (!s) return "";
50
+ const n = s.length;
51
+ return n <= keep ? "*".repeat(n) : s.slice(0, keep) + "*".repeat(Math.max(0, n - keep));
52
+ }
53
+
54
+ function md5(s) {
55
+ return crypto.createHash("md5").update(s).digest("hex");
56
+ }
57
+
58
+ function randomString(length = 24) {
59
+ let s = "abcdefghijklmnopqrstuvwxyz";
60
+ let out = s.charAt(Math.floor(Math.random() * s.length));
61
+ for (let i = 1; i < length; i++) out += "abcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(36 * Math.random()));
62
+ return out;
63
+ }
64
+
65
+ function sortObject(o) {
66
+ const keys = Object.keys(o).sort();
67
+ const x = {};
68
+ for (const k of keys) x[k] = o[k];
69
+ return x;
70
+ }
71
+
72
+ function encodeSig(obj) {
73
+ let data = "";
74
+ for (const k of Object.keys(obj)) data += `${k}=${obj[k]}`;
75
+ return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
76
+ }
77
+
78
+ function rand(min, max) {
79
+ return Math.floor(Math.random() * (max - min + 1)) + min;
80
+ }
81
+
82
+ function choice(arr) {
83
+ return arr[Math.floor(Math.random() * arr.length)];
84
+ }
85
+
86
+ function randomBuildId() {
87
+ const prefixes = ["QP1A", "RP1A", "SP1A", "TP1A", "UP1A", "AP4A"];
88
+ return `${choice(prefixes)}.${rand(180000, 250000)}.${rand(10, 99)}`;
89
+ }
90
+
91
+ function randomResolution() {
92
+ 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 }];
93
+ return choice(presets);
94
+ }
95
+
96
+ function randomFbav() {
97
+ return `${rand(390, 499)}.${rand(0, 3)}.${rand(0, 2)}.${rand(10, 60)}.${rand(100, 999)}`;
98
+ }
99
+
100
+ function randomOrcaUA() {
101
+ const androidVersions = ["8.1.0", "9", "10", "11", "12", "13", "14"];
102
+ 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" }];
103
+ const carriers = ["Viettel Telecom", "Mobifone", "Vinaphone", "T-Mobile", "Verizon", "AT&T", "Telkomsel", "Jio", "NTT DOCOMO", "Vodafone", "Orange"];
104
+ const locales = ["vi_VN", "en_US", "en_GB", "id_ID", "th_TH", "fr_FR", "de_DE", "es_ES", "pt_BR"];
105
+ const archs = ["arm64-v8a", "armeabi-v7a"];
106
+ const a = choice(androidVersions);
107
+ const d = choice(devices);
108
+ const b = randomBuildId();
109
+ const r = randomResolution();
110
+ const fbav = randomFbav();
111
+ const fbbv = rand(320000000, 520000000);
112
+ const arch = `${choice(archs)}:${choice(archs)}`;
113
+ 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;]`;
114
+ return ua;
115
+ }
116
+
117
+ const MOBILE_UA = randomOrcaUA();
118
+
119
+ function buildHeaders(url, extra = {}) {
120
+ const u = new URL(url);
121
+ 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 };
122
+ }
123
+
124
+ const genTotp = async secret => {
125
+ const cleaned = String(secret || "").replace(/\s+/g, "").toUpperCase();
126
+ const r = await TOTP.generate(cleaned);
127
+ return typeof r === "object" ? r.otp : r;
128
+ };
129
+
130
+ function cookieHeaderFromJar(j) {
131
+ const urls = ["https://www.facebook.com", "https://www.messenger.com"];
132
+ const seen = new Set();
133
+ const parts = [];
134
+ for (const u of urls) {
135
+ let s = "";
136
+ try {
137
+ s = typeof j.getCookieStringSync === "function" ? j.getCookieStringSync(u) : "";
138
+ } catch {}
139
+ if (!s) continue;
140
+ for (const kv of s.split(";")) {
141
+ const t = kv.trim();
142
+ const name = t.split("=")[0];
143
+ if (!name || seen.has(name)) continue;
144
+ seen.add(name);
145
+ parts.push(t);
146
+ }
147
+ }
148
+ return parts.join("; ");
149
+ }
150
+
151
+ let uniqueIndexEnsured = false;
152
+
153
+ function getBackupModel() {
154
+ if (!models || !models.sequelize || !models.Sequelize) return null;
155
+ const sequelize = models.sequelize;
156
+ const { DataTypes } = models.Sequelize;
157
+ if (sequelize.models && sequelize.models.AppStateBackup) return sequelize.models.AppStateBackup;
158
+ const dialect = typeof sequelize.getDialect === "function" ? sequelize.getDialect() : "sqlite";
159
+ const LongText = (dialect === "mysql" || dialect === "mariadb") ? DataTypes.TEXT("long") : DataTypes.TEXT;
160
+ const AppStateBackup = sequelize.define(
161
+ "AppStateBackup",
162
+ {
163
+ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
164
+ userID: { type: DataTypes.STRING, allowNull: false },
165
+ type: { type: DataTypes.STRING, allowNull: false },
166
+ data: { type: LongText }
167
+ },
168
+ { tableName: "app_state_backups", timestamps: true, indexes: [{ unique: true, fields: ["userID", "type"] }] }
169
+ );
170
+ return AppStateBackup;
171
+ }
172
+
173
+ async function ensureUniqueIndex(sequelize) {
174
+ if (uniqueIndexEnsured) return;
175
+ try {
176
+ await sequelize.getQueryInterface().addIndex("app_state_backups", ["userID", "type"], { unique: true, name: "app_state_user_type_unique" });
177
+ } catch {}
178
+ uniqueIndexEnsured = true;
179
+ }
180
+
181
+ async function upsertBackup(Model, userID, type, data) {
182
+ const where = { userID: String(userID || ""), type };
183
+ const row = await Model.findOne({ where });
184
+ if (row) {
185
+ await row.update({ data });
186
+ logger(`Overwrote existing ${type} backup for user ${where.userID}`, "info");
187
+ return;
188
+ }
189
+ await Model.create({ ...where, data });
190
+ logger(`Created new ${type} backup for user ${where.userID}`, "info");
191
+ }
192
+
193
+ async function backupAppStateSQL(j, userID) {
194
+ try {
195
+ const Model = getBackupModel();
196
+ if (!Model) return;
197
+ await Model.sync();
198
+ await ensureUniqueIndex(models.sequelize);
199
+ const appJson = getAppState(j);
200
+ const ck = cookieHeaderFromJar(j);
201
+ await upsertBackup(Model, userID, "appstate", JSON.stringify(appJson));
202
+ await upsertBackup(Model, userID, "cookie", ck);
203
+ logger("Backup stored (overwrite mode)", "info");
204
+ } catch (e) {
205
+ logger(`Failed to save appstate backup ${e && e.message ? e.message : String(e)}`, "warn");
206
+ }
207
+ }
208
+
209
+ async function getLatestBackup(userID, type) {
210
+ try {
211
+ const Model = getBackupModel();
212
+ if (!Model) return null;
213
+ const row = await Model.findOne({ where: { userID: String(userID || ""), type } });
214
+ return row ? row.data : null;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
220
+ async function getLatestBackupAny(type) {
221
+ try {
222
+ const Model = getBackupModel();
223
+ if (!Model) return null;
224
+ const row = await Model.findOne({ where: { type }, order: [["updatedAt", "DESC"]] });
225
+ return row ? row.data : null;
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+
231
+ async function tokens(username, password, twofactor = null) {
232
+ const t0 = process.hrtime.bigint();
233
+ if (!username || !password) {
234
+ logger("Missing email or password", {}, "error");
235
+ return { status: false, message: "Please provide email and password" };
236
+ }
237
+ logger(`AUTO-LOGIN: Initialize login ${mask(username, 2)}`, "info");
238
+ const cj = new CookieJar();
239
+ const axios = wrapper(axiosBase.create({ jar: cj, withCredentials: true, validateStatus: () => true, timeout: 30000 }));
240
+ const loginUrl = "https://b-graph.facebook.com/auth/login";
241
+ const baseForm = { adid: uuidv4(), email: username, password: password, format: "json", device_id: uuidv4(), cpl: "true", family_device_id: uuidv4(), locale: "en_US", client_country_code: "US", credentials_type: "device_based_login_password", generate_session_cookies: "1", generate_analytics_claim: "1", generate_machine_id: "1", currently_logged_in_userid: "0", irisSeqID: 1, try_num: "1", enroll_misauth: "false", meta_inf_fbmeta: "", source: "login", machine_id: randomString(24), fb_api_req_friendly_name: "authenticate", fb_api_caller_class: "com.facebook.account.login.protocol.Fb4aAuthHandler", api_key: "882a8490361da98702bf97a021ddc14d", access_token: "350685531728%7C62f8ce9f74b12f84c123cc23437a4a32" };
242
+ try {
243
+ const form1 = { ...baseForm };
244
+ form1.sig = encodeSig(sortObject(form1));
245
+ logger("AUTO-LOGIN: Send login request", "info");
246
+ const r0 = process.hrtime.bigint();
247
+ const res1 = await axios.post(loginUrl, qs.stringify(form1), { headers: buildHeaders(loginUrl, { "x-fb-friendly-name": form1.fb_api_req_friendly_name }) });
248
+ const dt1 = Number(process.hrtime.bigint() - r0) / 1e6;
249
+ logger(`AUTO-LOGIN: Received response ${res1.status} ${Math.round(dt1)}ms`, "info");
250
+ if (res1.status >= 400) throw { response: res1 };
251
+ if (res1.data && res1.data.session_cookies) {
252
+ const cookies = res1.data.session_cookies.map(e => ({ key: e.name, value: e.value, domain: "facebook.com", path: e.path, hostOnly: false }));
253
+ logger(`AUTO-LOGIN: Login success (first attempt) ${cookies.length} cookies`, "info");
254
+ const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
255
+ logger(`Done success login ${Math.round(t1)}ms`, "info");
256
+ return { status: true, cookies };
257
+ }
258
+ throw { response: res1 };
259
+ } catch (err) {
260
+ const e = err && err.response ? err.response : null;
261
+ const code = e && e.data && e.data.error ? e.data.error.code : null;
262
+ const message = e && e.data && e.data.error ? e.data.error.message : "";
263
+ if (code) logger(`AUTO-LOGIN: Error on request #1 ${code} ${message}`, "warn");
264
+ logger("AUTO-LOGIN: Processing twofactor...", "info");
265
+ if (code === 401) return { status: false, message: message || "Unauthorized" };
266
+ if (twofactor === "0" || !twofactor) {
267
+ logger("AUTO-LOGIN: 2FA required but secret missing", {}, "warn");
268
+ return { status: false, message: "Please provide the 2FA secret!" };
269
+ }
270
+ try {
271
+ const dataErr = e && e.data && e.data.error && e.data.error.error_data ? e.data.error.error_data : {};
272
+ const codeTotp = await genTotp(twofactor);
273
+ logger(`AUTO-LOGIN: Performing 2FA ${mask(codeTotp, 2)}`, "info");
274
+ const form2 = { ...baseForm, twofactor_code: codeTotp, encrypted_msisdn: "", userid: dataErr.uid || "", machine_id: dataErr.machine_id || baseForm.machine_id, first_factor: dataErr.login_first_factor || "", credentials_type: "two_factor" };
275
+ form2.sig = encodeSig(sortObject(form2));
276
+ const r1 = process.hrtime.bigint();
277
+ const res2 = await axios.post(loginUrl, qs.stringify(form2), { headers: buildHeaders(loginUrl, { "x-fb-friendly-name": form2.fb_api_req_friendly_name }) });
278
+ const dt2 = Number(process.hrtime.bigint() - r1) / 1e6;
279
+ logger(`AUTO-LOGIN: Received 2FA response ${res2.status} ${Math.round(dt2)}ms`, "info");
280
+ if (res2.status >= 400 || !(res2.data && res2.data.session_cookies)) throw new Error("2FA failed");
281
+ const cookies = res2.data.session_cookies.map(e => ({ key: e.name, value: e.value, domain: "facebook.com", path: e.path, hostOnly: false }));
282
+ logger(`AUTO-LOGIN: Login success with 2FA ${cookies.length} cookies`, "info");
283
+ const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
284
+ logger(`AUTO-LOGIN: Done success login with 2FA ${Math.round(t1)}ms`, "info");
285
+ return { status: true, cookies };
286
+ } catch {
287
+ logger("AUTO-LOGIN: 2FA failed", {}, "error");
288
+ return { status: false, message: "Invalid two-factor code!" };
289
+ }
290
+ }
291
+ }
292
+
293
+ function normalizeCookieHeaderString(s) {
294
+ let str = String(s || "").trim();
295
+ if (!str) return [];
296
+ if (/^cookie\s*:/i.test(str)) str = str.replace(/^cookie\s*:/i, "").trim();
297
+ str = str.replace(/\r?\n/g, " ").replace(/\s*;\s*/g, ";");
298
+ const parts = str.split(";").map(v => v.trim()).filter(Boolean);
299
+ const out = [];
300
+ for (const p of parts) {
301
+ const eq = p.indexOf("=");
302
+ if (eq <= 0) continue;
303
+ const k = p.slice(0, eq).trim();
304
+ const v = p.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1");
305
+ if (!k) continue;
306
+ out.push(`${k}=${v}`);
307
+ }
308
+ return out;
309
+ }
310
+
311
+ function setJarFromPairs(j, pairs, domain) {
312
+ const expires = new Date(Date.now() + 31536e6).toUTCString();
313
+ for (const kv of pairs) {
314
+ const cookieStr = `${kv}; expires=${expires}; domain=${domain}; path=/;`;
315
+ try {
316
+ if (typeof j.setCookieSync === "function") j.setCookieSync(cookieStr, "https://www.facebook.com");
317
+ else j.setCookie(cookieStr, "https://www.facebook.com");
318
+ } catch {}
319
+ }
320
+ }
321
+
322
+ function makeLogin(j, email, password, globalOptions, callback, prCallback) {
323
+ return async function () {
324
+ const u = email || config.email;
325
+ const p = password || config.password;
326
+ const tf = config.twofactor || null;
327
+ if (!u || !p) return;
328
+ const r = await tokens(u, p, tf);
329
+ if (r && r.status && Array.isArray(r.cookies)) {
330
+ const pairs = r.cookies.map(c => `${c.key || c.name}=${c.value}`);
331
+ setJarFromPairs(j, pairs, ".facebook.com");
332
+ await get("https://www.facebook.com/", j, null, globalOptions).then(saveCookies(j));
333
+ } else {
334
+ throw new Error(r && r.message ? r.message : "Login failed");
335
+ }
336
+ };
337
+ }
338
+
339
+ async function hydrateJarFromDB(userID) {
340
+ try {
341
+ let ck = null;
342
+ let app = null;
343
+ if (userID) {
344
+ ck = await getLatestBackup(userID, "cookie");
345
+ app = await getLatestBackup(userID, "appstate");
346
+ } else {
347
+ ck = await getLatestBackupAny("cookie");
348
+ app = await getLatestBackupAny("appstate");
349
+ }
350
+ if (ck) {
351
+ const pairs = normalizeCookieHeaderString(ck);
352
+ if (pairs.length) {
353
+ setJarFromPairs(jar, pairs, ".facebook.com");
354
+ return true;
355
+ }
356
+ }
357
+ if (app) {
358
+ let parsed = null;
359
+ try {
360
+ parsed = JSON.parse(app);
361
+ } catch {}
362
+ if (Array.isArray(parsed)) {
363
+ const pairs = parsed.map(c => [c.name || c.key, c.value].join("="));
364
+ setJarFromPairs(jar, pairs, ".facebook.com");
365
+ return true;
366
+ }
367
+ }
368
+ return false;
369
+ } catch {
370
+ return false;
371
+ }
372
+ }
373
+
374
+ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions) {
375
+ const getUID = cs =>
376
+ cs.find(c => c.key === "i_user")?.value ||
377
+ cs.find(c => c.key === "c_user")?.value ||
378
+ cs.find(c => c.name === "i_user")?.value ||
379
+ cs.find(c => c.name === "c_user")?.value;
380
+
381
+ let userID = getUID(currentCookies);
382
+ if (userID) return { html: currentHtml, cookies: currentCookies, userID };
383
+
384
+ const hydrated = await hydrateJarFromDB(null);
385
+ if (hydrated) {
386
+ logger("AppState backup live — proceeding to login", "info");
387
+ const resB = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
388
+ const htmlB = resB && resB.data ? resB.data : "";
389
+ if (htmlB.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
390
+ const cookiesB = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
391
+ const uidB = getUID(cookiesB);
392
+ if (uidB) return { html: htmlB, cookies: cookiesB, userID: uidB };
393
+ }
394
+
395
+ logger("AppState backup die — proceeding to email/password login", "warn");
396
+ const u = config.email;
397
+ const p = config.password;
398
+ const tf = config.twofactor || null;
399
+ if (!u || !p) throw new Error("Missing user cookie");
400
+ const r = await tokens(u, p, tf);
401
+ if (!(r && r.status && Array.isArray(r.cookies))) throw new Error(r && r.message ? r.message : "Login failed");
402
+ const pairs = r.cookies.map(c => `${c.key || c.name}=${c.value}`);
403
+ setJarFromPairs(jar, pairs, ".facebook.com");
404
+ const res2 = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
405
+ const html2 = res2 && res2.data ? res2.data : "";
406
+ if (html2.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
407
+ const cookies2 = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
408
+ const uid2 = getUID(cookies2);
409
+ if (!uid2) throw new Error("Login failed");
410
+ return { html: html2, cookies: cookies2, userID: uid2 };
411
+ }
412
+
413
+ function loginHelper(appState, Cookie, email, password, globalOptions, callback, prCallback) {
414
+ try {
415
+ let main;
416
+ const domain = ".facebook.com";
417
+ try {
418
+ if (appState) {
419
+ if (typeof appState === "string") {
420
+ let parsed = appState;
421
+ try {
422
+ parsed = JSON.parse(appState);
423
+ } catch {}
424
+ if (Array.isArray(parsed)) {
425
+ const pairs = parsed.map(c => [c.name || c.key, c.value].join("="));
426
+ setJarFromPairs(jar, pairs, domain);
427
+ } else if (typeof parsed === "string") {
428
+ const pairs = normalizeCookieHeaderString(parsed);
429
+ if (!pairs.length) throw new Error("Empty appState cookie header");
430
+ setJarFromPairs(jar, pairs, domain);
431
+ } else {
432
+ throw new Error("Invalid appState format");
433
+ }
434
+ } else if (Array.isArray(appState)) {
435
+ const pairs = appState.map(c => [c.name || c.key, c.value].join("="));
436
+ setJarFromPairs(jar, pairs, domain);
437
+ } else {
438
+ throw new Error("Invalid appState format");
439
+ }
440
+ }
441
+ if (Cookie) {
442
+ let cookiePairs = [];
443
+ if (typeof Cookie === "string") cookiePairs = normalizeCookieHeaderString(Cookie);
444
+ else if (Array.isArray(Cookie)) cookiePairs = Cookie.map(String).filter(Boolean);
445
+ else if (Cookie && typeof Cookie === "object") cookiePairs = Object.entries(Cookie).map(([k, v]) => `${k}=${v}`);
446
+ if (cookiePairs.length) setJarFromPairs(jar, cookiePairs, domain);
447
+ }
448
+ } catch (e) {
449
+ return callback(e);
450
+ }
451
+
452
+ (async () => {
453
+ if (appState || Cookie) {
454
+ return get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
455
+ }
456
+ const hydrated = await hydrateJarFromDB(null);
457
+ if (hydrated) {
458
+ logger("AppState backup live — proceeding to login", "info");
459
+ return get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
460
+ }
461
+ logger("AppState backup die — proceeding to email/password login", "warn");
462
+ return get("https://www.facebook.com/", null, null, globalOptions)
463
+ .then(saveCookies(jar))
464
+ .then(makeLogin(jar, email, password, globalOptions, callback, prCallback))
465
+ .then(function () {
466
+ return get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
467
+ });
468
+ })()
469
+ .then(async function (res) {
470
+ let html = res && res.data ? res.data : "";
471
+ let cookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
472
+ let userID =
473
+ cookies.find(c => c.key === "i_user")?.value ||
474
+ cookies.find(c => c.key === "c_user")?.value ||
475
+ cookies.find(c => c.name === "i_user")?.value ||
476
+ cookies.find(c => c.name === "c_user")?.value;
477
+
478
+ try {
479
+ const userDataMatch = String(html).match(/\["CurrentUserInitialData",\[\],({.*?}),\d+\]/);
480
+ if (userDataMatch) {
481
+ const info = JSON.parse(userDataMatch[1]);
482
+ logger(`Đăng nhập tài khoản: ${info.NAME} (${info.USER_ID})`, "info");
483
+ } else if (userID) {
484
+ logger(`ID người dùng: ${userID}`, "info");
485
+ }
486
+ } catch {}
487
+
488
+ if (!userID) {
489
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions);
490
+ html = retried.html;
491
+ cookies = retried.cookies;
492
+ userID = retried.userID;
493
+ }
494
+
495
+ if (html.includes("/checkpoint/block/?next")) {
496
+ logger("Appstate die, vui lòng thay cái mới!", "error");
497
+ throw new Error("Checkpoint");
498
+ }
499
+
500
+ let mqttEndpoint;
501
+ let region = "PRN";
502
+ let fb_dtsg;
503
+ let irisSeqID;
504
+
505
+ try {
506
+ const m1 = html.match(/"endpoint":"([^"]+)"/);
507
+ const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
508
+ const raw = (m1 && m1[1]) || (m2 && m2[1]);
509
+ if (raw) mqttEndpoint = raw.replace(/\\\//g, "/");
510
+ region = parseRegion(html);
511
+ const rinfo = REGION_MAP.get(region);
512
+ if (rinfo) logger(`Server region ${region} - ${rinfo.name}`, "info");
513
+ else logger(`Server region ${region}`, "info");
514
+ } catch {
515
+ logger("Not MQTT endpoint", "warn");
516
+ }
517
+ try {
518
+ const userDataMatch = String(html).match(/\["CurrentUserInitialData",\[\],({.*?}),\d+\]/);
519
+ if (userDataMatch) {
520
+ const info = JSON.parse(userDataMatch[1]);
521
+ logger(`Đăng nhập tài khoản: ${info.NAME} (${info.USER_ID})`, "info");
522
+ } else if (userID) {
523
+ logger(`ID người dùng: ${userID}`, "info");
524
+ }
525
+ } catch { }
526
+ const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
527
+ if (tokenMatch) fb_dtsg = tokenMatch[1];
528
+
529
+ try {
530
+ if (userID) await backupAppStateSQL(jar, userID);
531
+ } catch {}
532
+
533
+ Promise.resolve()
534
+ .then(function () {
535
+ if (models && models.sequelize && typeof models.sequelize.authenticate === "function") {
536
+ return models.sequelize.authenticate();
537
+ }
538
+ })
539
+ .then(function () {
540
+ if (models && typeof models.syncAll === "function") {
541
+ return models.syncAll();
542
+ }
543
+ })
544
+ .catch(function (error) {
545
+ console.error(error);
546
+ console.error("Database connection failed:", error && error.message ? error.message : String(error));
547
+ });
548
+
549
+ logger("FCA fix/update by DongDev", "info");
550
+
551
+ const ctx = {
552
+ userID,
553
+ jar,
554
+ globalOptions,
555
+ loggedIn: true,
556
+ access_token: "NONE",
557
+ clientMutationId: 0,
558
+ mqttClient: undefined,
559
+ lastSeqId: irisSeqID,
560
+ syncToken: undefined,
561
+ mqttEndpoint,
562
+ region,
563
+ firstListen: true,
564
+ fb_dtsg,
565
+ clientID: ((Math.random() * 2147483648) | 0).toString(16),
566
+ clientId: getFrom(html, '["MqttWebDeviceID",[],{"clientID":"', '"}') || "",
567
+ wsReqNumber: 0,
568
+ wsTaskNumber: 0
569
+ };
570
+
571
+ const api = {
572
+ setOptions: require("./options").setOptions.bind(null, globalOptions),
573
+ getCookies: function () {
574
+ return cookieHeaderFromJar(jar);
575
+ },
576
+ getAppState: function () {
577
+ return getAppState(jar);
578
+ },
579
+ getLatestAppStateFromDB: async function (uid = userID) {
580
+ const data = await getLatestBackup(uid, "appstate");
581
+ return data ? JSON.parse(data) : null;
582
+ },
583
+ getLatestCookieFromDB: async function (uid = userID) {
584
+ return await getLatestBackup(uid, "cookie");
585
+ }
586
+ };
587
+
588
+ const defaultFuncs = makeDefaults(html, userID, ctx);
589
+ const srcRoot = path.join(__dirname, "../src/api");
590
+ let loaded = 0;
591
+ let skipped = 0;
592
+ fs.readdirSync(srcRoot, { withFileTypes: true }).forEach((sub) => {
593
+ if (!sub.isDirectory()) return;
594
+ const subDir = path.join(srcRoot, sub.name);
595
+ fs.readdirSync(subDir, { withFileTypes: true }).forEach((entry) => {
596
+ if (!entry.isFile() || !entry.name.endsWith(".js")) return;
597
+ const p = path.join(subDir, entry.name);
598
+ const key = path.basename(entry.name, ".js");
599
+ if (api[key]) {
600
+ skipped++;
601
+ return;
602
+ }
603
+ api[key] = require(p)(defaultFuncs, api, ctx);
604
+ loaded++;
605
+ });
606
+ });
607
+
608
+ logger(`Loaded ${loaded} FCA API methods${skipped ? `, skipped ${skipped} duplicates` : ""}`, "[ FCA-UNO ] >");
609
+
610
+ api.listen = api.listenMqtt;
611
+
612
+ setInterval(function () {
613
+ api
614
+ .refreshFb_dtsg()
615
+ .then(function () {
616
+ logger("Successfully refreshed fb_dtsg", "[ FCA-UNO ] >");
617
+ })
618
+ .catch(function () {
619
+ logger("An error occurred while refreshing fb_dtsg", "error");
620
+ });
621
+ }, 86400000);
622
+
623
+ logger("Login successful!", "[ FCA-UNO ] >");
624
+ callback(null, api);
625
+ })
626
+ .catch(function (e) {
627
+ callback(e);
628
+ });
629
+ } catch (e) {
630
+ callback(e);
631
+ }
632
+ }
633
+
634
+ module.exports = loginHelper;
@@ -0,0 +1,49 @@
1
+ const utils = require("../src/utils");
2
+ const logger = require("../func/logger");
3
+ const Boolean_Option = [
4
+ "online",
5
+ "selfListen",
6
+ "listenEvents",
7
+ "updatePresence",
8
+ "forceLogin",
9
+ "autoMarkDelivery",
10
+ "autoMarkRead",
11
+ "listenTyping",
12
+ "autoReconnect",
13
+ "emitReady",
14
+ "selfListenEvent"
15
+ ];
16
+ function setOptions(globalOptions, options) {
17
+ for (const key of Object.keys(options || {})) {
18
+ if (Boolean_Option.includes(key)) {
19
+ globalOptions[key] = Boolean(options[key]);
20
+ continue;
21
+ }
22
+ switch (key) {
23
+ case "pauseLog": {
24
+ if (options.pauseLog) log.pause();
25
+ else log.resume();
26
+ break;
27
+ }
28
+ case "userAgent": {
29
+ 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";
30
+ break;
31
+ }
32
+ case "proxy": {
33
+ if (typeof options.proxy !== "string") {
34
+ delete globalOptions.proxy;
35
+ utils.setProxy();
36
+ } else {
37
+ globalOptions.proxy = options.proxy;
38
+ utils.setProxy(globalOptions.proxy);
39
+ }
40
+ break;
41
+ }
42
+ default: {
43
+ logger("setOptions Unrecognized option given to setOptions: " + key, "warn");
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ module.exports = { setOptions, Boolean_Option };