toru-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.
Files changed (113) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +319 -0
  3. package/advancedSystem.js +95 -0
  4. package/build/messagix.dll +0 -0
  5. package/build/messagix.so +0 -0
  6. package/checkUpdate.js +37 -0
  7. package/config.json +45 -0
  8. package/e2ee.js +719 -0
  9. package/index.js +715 -0
  10. package/lib/index.mjs +1412 -0
  11. package/package.json +69 -0
  12. package/src/OldMessage.js +357 -0
  13. package/src/addExternalModule.js +25 -0
  14. package/src/addUserToGroup.js +117 -0
  15. package/src/changeAdminStatus.js +105 -0
  16. package/src/changeArchivedStatus.js +57 -0
  17. package/src/changeAvatar.js +138 -0
  18. package/src/changeAvatarV2.js +86 -0
  19. package/src/changeAvt.js +85 -0
  20. package/src/changeBio.js +78 -0
  21. package/src/changeBlockedStatus.js +51 -0
  22. package/src/changeBlockedStatusMqtt.js +80 -0
  23. package/src/changeCover.js +72 -0
  24. package/src/changeGroupImage.js +137 -0
  25. package/src/changeName.js +79 -0
  26. package/src/changeNickname.js +61 -0
  27. package/src/changeThreadColor.js +67 -0
  28. package/src/changeThreadEmoji.js +57 -0
  29. package/src/changeUsername.js +59 -0
  30. package/src/createCommentPost.js +230 -0
  31. package/src/createNewGroup.js +90 -0
  32. package/src/createPoll.js +73 -0
  33. package/src/createPost.js +276 -0
  34. package/src/data/getThreadInfo.json +1 -0
  35. package/src/deleteMessage.js +58 -0
  36. package/src/deleteThread.js +58 -0
  37. package/src/editMessage.js +81 -0
  38. package/src/editMessageOld.js +67 -0
  39. package/src/follow.js +74 -0
  40. package/src/forwardAttachment.js +62 -0
  41. package/src/friendList.js +104 -0
  42. package/src/getAccess.js +112 -0
  43. package/src/getAvatarUser.js +78 -0
  44. package/src/getBotInitialData.js +42 -0
  45. package/src/getCtx.js +5 -0
  46. package/src/getCurrentUserID.js +7 -0
  47. package/src/getEmojiUrl.js +29 -0
  48. package/src/getFriendsList.js +85 -0
  49. package/src/getMessage.js +849 -0
  50. package/src/getOptions.js +5 -0
  51. package/src/getRegion.js +7 -0
  52. package/src/getThreadHistory.js +682 -0
  53. package/src/getThreadHistoryDeprecated.js +71 -0
  54. package/src/getThreadInfo.js +232 -0
  55. package/src/getThreadInfoDeprecated.js +56 -0
  56. package/src/getThreadList.js +213 -0
  57. package/src/getThreadListDeprecated.js +46 -0
  58. package/src/getThreadPictures.js +59 -0
  59. package/src/getThreadTheme.js +81 -0
  60. package/src/getUID.js +119 -0
  61. package/src/getUserID.js +61 -0
  62. package/src/getUserInfo.js +66 -0
  63. package/src/handleFriendRequest.js +46 -0
  64. package/src/handleMessageRequest.js +47 -0
  65. package/src/httpGet.js +49 -0
  66. package/src/httpPost.js +48 -0
  67. package/src/httpPostFormData.js +70 -0
  68. package/src/listenMqtt.js +955 -0
  69. package/src/listenNotification.js +85 -0
  70. package/src/logout.js +19 -0
  71. package/src/markAsDelivered.js +47 -0
  72. package/src/markAsRead.js +72 -0
  73. package/src/markAsReadAll.js +40 -0
  74. package/src/markAsSeen.js +48 -0
  75. package/src/metaTheme.js +189 -0
  76. package/src/muteThread.js +45 -0
  77. package/src/note.js +228 -0
  78. package/src/pinMessage.js +59 -0
  79. package/src/refreshFb_dtsg.js +91 -0
  80. package/src/removeSuspiciousAccount.js +78 -0
  81. package/src/removeUserFromGroup.js +81 -0
  82. package/src/reply.js +442 -0
  83. package/src/resolvePhotoUrl.js +47 -0
  84. package/src/searchForThread.js +53 -0
  85. package/src/searchFriends.js +140 -0
  86. package/src/searchStickers.js +53 -0
  87. package/src/send.js +46 -0
  88. package/src/sendButtons.js +163 -0
  89. package/src/sendComment.js +159 -0
  90. package/src/sendFriendRequest.js +112 -0
  91. package/src/sendMessage.js +440 -0
  92. package/src/sendMessageMqtt.js +323 -0
  93. package/src/sendTypingIndicator.js +54 -0
  94. package/src/setActiveStatus.js +92 -0
  95. package/src/setMessageReaction.js +149 -0
  96. package/src/setMessageReactionMqtt.js +62 -0
  97. package/src/setPostReaction.js +114 -0
  98. package/src/setProfileGuard.js +44 -0
  99. package/src/setProfileLock.js +97 -0
  100. package/src/setStoryReaction.js +133 -0
  101. package/src/setStorySeen.js +108 -0
  102. package/src/setThreadTheme.js +102 -0
  103. package/src/setTitle.js +92 -0
  104. package/src/shareContact.js +110 -0
  105. package/src/shareLink.js +59 -0
  106. package/src/stopListenMqtt.js +37 -0
  107. package/src/storyManager.js +357 -0
  108. package/src/suggestFriend.js +132 -0
  109. package/src/threadColors.js +131 -0
  110. package/src/unfriend.js +54 -0
  111. package/src/unsendMessage.js +68 -0
  112. package/src/uploadAttachment.js +115 -0
  113. package/utils.js +3044 -0
package/index.js ADDED
@@ -0,0 +1,715 @@
1
+ "use strict";
2
+ /* ═══════════════════════════════════════════════════════════════════════════
3
+ * alpha-fca v1.0.0 — index.js
4
+ * Ultimate merged Facebook Chat API
5
+ * Best-of: hridoy-custom · hridoy-akash · ST-FCA · fca-azad · mahmud-fca
6
+ * ═══════════════════════════════════════════════════════════════════════════
7
+ */
8
+
9
+ var utils = require("./utils");
10
+ var cheerio = require("cheerio");
11
+ var log = require("npmlog");
12
+ var fs = require("fs");
13
+ var path = require("path");
14
+ var request = require("request");
15
+ var AdvancedSystem = require("./advancedSystem");
16
+ var { checkForFCAUpdate } = require("./checkUpdate");
17
+
18
+ log.maxRecordSize = 100;
19
+
20
+ // ─── Global state guards ─────────────────────────────────────────────────────
21
+ global._alphaFcaUpdateChecked = global._alphaFcaUpdateChecked || false;
22
+ global._alphaFcaErrorHandlers = global._alphaFcaErrorHandlers || false;
23
+
24
+ // Load config.json once per process
25
+ global._alphaFcaConfig = global._alphaFcaConfig || (() => {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(path.join(__dirname, "config.json"), "utf8"));
28
+ } catch (_) {
29
+ return {
30
+ version : "1.0.0",
31
+ automation: { enabled: false },
32
+ inbox : { enabled: true },
33
+ smooth : { enabled: true },
34
+ e2ee : { enable: true }
35
+ };
36
+ }
37
+ })();
38
+
39
+ // ─── Global error handlers (install exactly once) ────────────────────────────
40
+ if (!global._alphaFcaErrorHandlers) {
41
+ global._alphaFcaErrorHandlers = true;
42
+
43
+ process.on("unhandledRejection", (reason) => {
44
+ try {
45
+ if (reason && typeof reason === "object") {
46
+ const code = reason.code || (reason.cause && reason.cause.code);
47
+ const msg = reason.message || String(reason);
48
+
49
+ if (/No Sequelize instance passed/i.test(msg)) return; // silent
50
+
51
+ if (code === "UND_ERR_CONNECT_TIMEOUT" || code === "ETIMEDOUT" ||
52
+ /Connect Timeout|fetch failed/i.test(msg)) {
53
+ log.warn("alpha-fca", "Network timeout (non-fatal): " + msg);
54
+ return;
55
+ }
56
+ if (/ECONNREFUSED|ENOTFOUND|ECONNRESET/.test(code || msg)) {
57
+ log.warn("alpha-fca", "Network error (non-fatal): " + msg);
58
+ return;
59
+ }
60
+ }
61
+ log.warn("alpha-fca", "Unhandled rejection (non-fatal): " +
62
+ (reason && reason.message ? reason.message : String(reason)));
63
+ } catch (_) {}
64
+ });
65
+
66
+ process.on("uncaughtException", (error) => {
67
+ try {
68
+ const msg = error.message || String(error);
69
+ const code = error.code;
70
+ if (/No Sequelize instance passed/i.test(msg)) return;
71
+ if (code === "UND_ERR_CONNECT_TIMEOUT" || code === "ETIMEDOUT" ||
72
+ /Connect Timeout|fetch failed/i.test(msg)) {
73
+ log.warn("alpha-fca", "Uncaught network timeout (non-fatal): " + msg);
74
+ return;
75
+ }
76
+ log.warn("alpha-fca", "Uncaught exception (continuing): " + msg);
77
+ } catch (_) {}
78
+ });
79
+ }
80
+
81
+ // ─── Non-blocking update check (once per process) ────────────────────────────
82
+ if (!global._alphaFcaUpdateChecked) {
83
+ global._alphaFcaUpdateChecked = true;
84
+ setImmediate(() => checkForFCAUpdate().catch(() => {}));
85
+ }
86
+
87
+ // ─── Boolean options (cast to bool) ──────────────────────────────────────────
88
+ const BOOL_OPTIONS = [
89
+ "online", "selfListen", "selfListenEvent", "listenEvents", "updatePresence",
90
+ "forceLogin", "autoMarkDelivery", "autoMarkRead", "listenTyping",
91
+ "autoReconnect", "emitReady"
92
+ ];
93
+
94
+ function setOptions(globalOptions, options) {
95
+ if (!options || typeof options !== "object") return;
96
+ Object.keys(options).forEach(function(key) {
97
+ if (BOOL_OPTIONS.includes(key)) {
98
+ globalOptions[key] = Boolean(options[key]);
99
+ } else {
100
+ switch (key) {
101
+ case "pauseLog":
102
+ options.pauseLog ? log.pause() : log.resume();
103
+ break;
104
+ case "logLevel":
105
+ log.level = options.logLevel;
106
+ globalOptions.logLevel = options.logLevel;
107
+ break;
108
+ case "logRecordSize":
109
+ log.maxRecordSize = options.logRecordSize;
110
+ globalOptions.logRecordSize = options.logRecordSize;
111
+ break;
112
+ case "pageID":
113
+ globalOptions.pageID = options.pageID.toString();
114
+ break;
115
+ case "userAgent":
116
+ globalOptions.userAgent = options.userAgent ||
117
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
118
+ break;
119
+ case "proxy":
120
+ if (typeof options.proxy !== "string") {
121
+ delete globalOptions.proxy;
122
+ utils.setProxy();
123
+ } else {
124
+ globalOptions.proxy = options.proxy;
125
+ utils.setProxy(globalOptions.proxy);
126
+ }
127
+ break;
128
+ default:
129
+ // Pass through unknown options (e.g., reconnect delays, E2EE opts)
130
+ globalOptions[key] = options[key];
131
+ }
132
+ }
133
+ });
134
+ }
135
+
136
+ // ─────────────────────────────────────────────────────────────────────────────
137
+ // buildAPI — core context + api object construction
138
+ // ─────────────────────────────────────────────────────────────────────────────
139
+ function buildAPI(globalOptions, html, jar) {
140
+ const fcaCfg = global._alphaFcaConfig || {};
141
+
142
+ const advancedSystem = new AdvancedSystem({
143
+ automationEnabled: Boolean(fcaCfg.automation && fcaCfg.automation.enabled),
144
+ minDelayMs : Number(fcaCfg.connection && fcaCfg.connection.minDelayMs) || 1000,
145
+ maxDelayMs : Number(fcaCfg.connection && fcaCfg.connection.maxDelayMs) || 30000
146
+ });
147
+
148
+ // ── Extract fb_dtsg and irisSeqID from HTML ──────────────────────────────
149
+ let fb_dtsg = null;
150
+ let irisSeqID = null;
151
+
152
+ ;(function extractFromHTML() {
153
+ try {
154
+ const $ = cheerio.load(html);
155
+ const patterns = [
156
+ /\["DTSGInitialData",\[\],{"token":"([^"]+)"}\]/,
157
+ /\["DTSGInitData",\[\],{"token":"([^"]+)"/,
158
+ /,"token":"([^"]+)"/,
159
+ /{"token":"([^"]+)"/,
160
+ /name="fb_dtsg" value="([^"]+)"/,
161
+ /"async_get_token":"([^"]+)"/,
162
+ /"dtsg":\{"token":"([^"]+)"/
163
+ ];
164
+ $("script").each((_, script) => {
165
+ if (fb_dtsg) return;
166
+ const text = $(script).html() || "";
167
+ for (const p of patterns) {
168
+ const m = text.match(p);
169
+ if (m && m[1]) { fb_dtsg = m[1]; break; }
170
+ }
171
+ });
172
+ if (!fb_dtsg) {
173
+ const inp = $("input[name=\"fb_dtsg\"]").val();
174
+ if (inp) fb_dtsg = inp;
175
+ }
176
+ const seqM = html.match(/irisSeqID":"([^"]+)"/);
177
+ if (seqM && seqM[1]) irisSeqID = seqM[1];
178
+ } catch (e) {
179
+ log.warn("buildAPI", "Error extracting fb_dtsg: " + (e && e.message ? e.message : e));
180
+ }
181
+ })();
182
+
183
+ // ── Identify the logged-in user ───────────────────────────────────────────
184
+ const cookies = jar.getCookies("https://www.facebook.com");
185
+ const userCookie = cookies.find(c => c.cookieString().startsWith("c_user="));
186
+ const iUserCookie = cookies.find(c => c.cookieString().startsWith("i_user="));
187
+
188
+ if (!userCookie && !iUserCookie) {
189
+ log.error("buildAPI", "Cookie state is invalid — no c_user or i_user cookie found.");
190
+ return null;
191
+ }
192
+ if (html.includes("/checkpoint/block/?next")) {
193
+ log.error("buildAPI", "Account is checkpointed. Please resolve on Facebook before retrying.");
194
+ return null;
195
+ }
196
+
197
+ const userID = (iUserCookie || userCookie).cookieString().split("=")[1];
198
+ const clientID = (Math.random() * 2147483648 | 0).toString(16);
199
+
200
+ // ── MQTT endpoint / region extraction ────────────────────────────────────
201
+ let mqttEndpoint = "wss://edge-chat.facebook.com/chat?region=pnb";
202
+ let region = "PNB";
203
+ try {
204
+ const epMatch = html.match(/"endpoint":"([^"]+)"/);
205
+ if (epMatch) {
206
+ let ep = epMatch[1].replace(/\\\//g, "/");
207
+ try {
208
+ const epUrl = new URL(ep);
209
+ epUrl.searchParams.delete("sid");
210
+ epUrl.searchParams.delete("cid");
211
+ region = (epUrl.searchParams.get("region") || "PNB").toUpperCase();
212
+ mqttEndpoint = epUrl.toString();
213
+ } catch (_) {
214
+ mqttEndpoint = ep.replace(/[?&]sid=[^&]*/g, "").replace(/[?&]cid=[^&]*/g, "");
215
+ region = ((mqttEndpoint.match(/region=([^&]+)/) || [])[1] || "PNB").toUpperCase();
216
+ }
217
+ }
218
+ } catch (_) {}
219
+
220
+ log.info("alpha-fca", "Logged in as " + userID + " [region: " + region + "]");
221
+
222
+ // ── Core context object ───────────────────────────────────────────────────
223
+ const ctx = {
224
+ userID,
225
+ jar,
226
+ clientID,
227
+ globalOptions,
228
+ loggedIn : true,
229
+ access_token : "NONE",
230
+ clientMutationId: 0,
231
+ mqttClient : undefined,
232
+ lastSeqId : irisSeqID,
233
+ syncToken : undefined,
234
+ mqttEndpoint,
235
+ region,
236
+ firstListen : true,
237
+ fb_dtsg,
238
+ req_ID : 0,
239
+ callback_Task : {},
240
+ wsReqNumber : 0,
241
+ wsTaskNumber : 0,
242
+ reqCallbacks : {},
243
+ threadTypes : {} // cache thread type (dm/group) per thread
244
+ };
245
+
246
+ // ── Load runtime config ───────────────────────────────────────────────────
247
+ let runtimeCfg = { enableTypingIndicator: false, typingDuration: 4000 };
248
+ try {
249
+ // Bot CWD config.json takes priority over FCA package config
250
+ const rootCfgPath = path.join(process.cwd(), "config.json");
251
+ const fcaCfgPath = path.join(__dirname, "config.json");
252
+ for (const cfgPath of [fcaCfgPath, rootCfgPath]) {
253
+ if (fs.existsSync(cfgPath)) {
254
+ try {
255
+ const c = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
256
+ if (c && typeof c === "object") {
257
+ if (c.enableTypingIndicator != null) runtimeCfg.enableTypingIndicator = c.enableTypingIndicator;
258
+ if (c.typingDuration != null) runtimeCfg.typingDuration = c.typingDuration;
259
+ }
260
+ } catch (_) {}
261
+ }
262
+ }
263
+ // GoatBot config override
264
+ if (global.GoatBot && global.GoatBot.config) {
265
+ const gc = global.GoatBot.config;
266
+ if (gc.enableTypingIndicator != null) runtimeCfg.enableTypingIndicator = gc.enableTypingIndicator;
267
+ if (gc.typingDuration != null) runtimeCfg.typingDuration = gc.typingDuration;
268
+ }
269
+ } catch (_) {}
270
+ ctx.config = runtimeCfg;
271
+
272
+ // ── E2EE options: layered config ──────────────────────────────────────────
273
+ try {
274
+ var _e2eeCfg = {};
275
+ const fcaCfgPath = path.join(__dirname, "config.json");
276
+ const rootCfgPath = path.join(process.cwd(), "config.json");
277
+ for (const cfgPath of [fcaCfgPath, rootCfgPath]) {
278
+ if (fs.existsSync(cfgPath)) {
279
+ try {
280
+ const c = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
281
+ if (c && c.e2ee && typeof c.e2ee === "object") Object.assign(_e2eeCfg, c.e2ee);
282
+ } catch (_) {}
283
+ }
284
+ }
285
+ if (_e2eeCfg.enable === true) globalOptions.enableE2EE = true;
286
+ globalOptions.e2eeLogLevel = _e2eeCfg.logLevel || "none";
287
+ globalOptions.e2eeAutoReconnect = _e2eeCfg.autoReconnect !== false;
288
+ const saveType = _e2eeCfg.saveType || (_e2eeCfg.memoryOnly === false ? "path" : "memory");
289
+ globalOptions.e2eeMemoryOnly = saveType !== "path";
290
+ if (saveType === "path") {
291
+ var devicePath = _e2eeCfg.devicePath || "./data/e2ee-device.json";
292
+ devicePath = path.resolve(process.cwd(), devicePath);
293
+ try { fs.mkdirSync(path.dirname(devicePath), { recursive: true }); } catch (_) {}
294
+ globalOptions.e2eeDevicePath = devicePath;
295
+ }
296
+ if (_e2eeCfg.deviceData) globalOptions.e2eeDeviceData = _e2eeCfg.deviceData;
297
+ } catch (_) {}
298
+
299
+ // ── Build default functions ───────────────────────────────────────────────
300
+ const defaultFuncs = utils.makeDefaults(html, userID, ctx);
301
+
302
+ // ── Core api object ───────────────────────────────────────────────────────
303
+ const api = {
304
+ ctx,
305
+ setOptions : setOptions.bind(null, globalOptions),
306
+ getAppState : () => utils.getAppState(jar),
307
+ postFormData: (url, body) => defaultFuncs.postFormData(url, ctx.jar, body)
308
+ };
309
+
310
+ // ── Load all src/*.js modules ─────────────────────────────────────────────
311
+ try {
312
+ fs.readdirSync(path.join(__dirname, "src"))
313
+ .filter(v => v.endsWith(".js"))
314
+ .forEach(v => {
315
+ try {
316
+ api[v.replace(".js", "")] = require(path.join(__dirname, "src", v))(defaultFuncs, api, ctx);
317
+ } catch (err) {
318
+ log.warn("buildAPI", "Failed to load src/" + v + ": " + (err && err.message ? err.message : err));
319
+ }
320
+ });
321
+ } catch (err) {
322
+ log.error("buildAPI", "Failed to read src directory: " + (err && err.message ? err.message : err));
323
+ }
324
+
325
+ // ── Convenience aliases ───────────────────────────────────────────────────
326
+ api.listen = api.listenMqtt;
327
+ api.send = api.sendMessage;
328
+ api.getInbox = api.getThreadList;
329
+ api.getInboxThreads = api.getThreadList;
330
+ api.getInboxHistory = api.getThreadHistory;
331
+ api.getCurrentUserID = api.getCurrentUserID || function() { return ctx.userID; };
332
+
333
+ // Inbox guard — ensure always accessible
334
+ api.inbox = {
335
+ enabled : true,
336
+ getThreads: api.getThreadList ? api.getThreadList.bind(api) : null,
337
+ getHistory: api.getThreadHistory ? api.getThreadHistory.bind(api) : null
338
+ };
339
+
340
+ // ── Logout: safe no-op (session stays alive) ─────────────────────────────
341
+ // Overrides src/logout.js — explicit decision to never destroy FB session.
342
+ api.logout = function(callback) {
343
+ log.warn("logout", "[alpha-fca] Logout is disabled — session kept alive.");
344
+ var cb = typeof callback === "function" ? callback : function() {};
345
+ cb(null);
346
+ return Promise.resolve();
347
+ };
348
+
349
+ // ── Session guard: auto-save appstate ────────────────────────────────────
350
+ api.sessionGuard = function(accountPath, opts) {
351
+ if (!accountPath || typeof accountPath !== "string") return null;
352
+ const _interval = (opts && opts.interval > 0) ? Number(opts.interval) : 3 * 60 * 1000;
353
+ const _debounce = (opts && opts.debounce > 0) ? Number(opts.debounce) : 30 * 1000;
354
+ let _lastSave = 0;
355
+ let _debTimer = null;
356
+
357
+ function _save() {
358
+ _debTimer = null;
359
+ if (Date.now() - _lastSave < _debounce) return;
360
+ try {
361
+ const appState = utils.getAppState(jar);
362
+ if (!appState || !appState.length) return;
363
+ fs.writeFileSync(accountPath, JSON.stringify(appState, null, 2), "utf8");
364
+ _lastSave = Date.now();
365
+ } catch (e) {
366
+ log.warn("sessionGuard", "Failed to save appstate: " + (e && e.message ? e.message : e));
367
+ }
368
+ }
369
+
370
+ // Periodic save
371
+ const _guardTimer = setInterval(function() {
372
+ if (!ctx.loggedIn) { clearInterval(_guardTimer); return; }
373
+ if (_debTimer || Date.now() - _lastSave < _debounce) return;
374
+ _save();
375
+ }, _interval);
376
+
377
+ // Hook into cookie updates for debounced save
378
+ if (jar._jar && typeof jar._jar.setCookie === "function") {
379
+ const _orig = jar._jar.setCookie.bind(jar._jar);
380
+ jar._jar.setCookie = function() {
381
+ const result = _orig.apply(this, arguments);
382
+ if (!_debTimer) _debTimer = setTimeout(_save, _debounce);
383
+ return result;
384
+ };
385
+ }
386
+
387
+ return { stop: function() { clearInterval(_guardTimer); if (_debTimer) { clearTimeout(_debTimer); _debTimer = null; } } };
388
+ };
389
+
390
+ // ── Automation guard (off by default) ────────────────────────────────────
391
+ if (!(fcaCfg.automation && fcaCfg.automation.enabled)) {
392
+ advancedSystem.setAutomationEnabled(false);
393
+ }
394
+ api.advanced = advancedSystem;
395
+ api.getHealth = function() { return advancedSystem.health(api); };
396
+ api.isAutomationEnabled = function() { return advancedSystem.isAutomationEnabled(); };
397
+ api.setAutomationEnabled = function(enabled) { return advancedSystem.setAutomationEnabled(enabled); };
398
+ ctx.advancedSystem = advancedSystem;
399
+
400
+ // ── Smooth mode (optional typing-indicator before send) ──────────────────
401
+ ;(function patchSmooth() {
402
+ const smooth = (fcaCfg.smooth) || {};
403
+ if (smooth.enabled !== true) return; // off by default
404
+ const msgDelay = Number(smooth.messageDelay) || 400;
405
+ const _origSend = api.sendMessage;
406
+ if (typeof _origSend !== "function") return;
407
+ api.sendMessage = function(msg, threadID, callback, replyToMessage, isSingleUser) {
408
+ try {
409
+ if (typeof api.sendTypingIndicator === "function") {
410
+ api.sendTypingIndicator(threadID, function() {});
411
+ }
412
+ } catch (_) {}
413
+ return new Promise(function(resolve, reject) {
414
+ setTimeout(function() {
415
+ try {
416
+ const r = _origSend(msg, threadID, callback, replyToMessage, isSingleUser);
417
+ if (r && typeof r.then === "function") r.then(resolve).catch(reject);
418
+ else resolve(r);
419
+ } catch (e) { reject(e); }
420
+ }, msgDelay);
421
+ });
422
+ };
423
+ api.send = api.sendMessage;
424
+ })();
425
+
426
+ // ── E2EE: patch api + expose helpers ─────────────────────────────────────
427
+ if (globalOptions.enableE2EE) {
428
+ try {
429
+ const _e2ee = require("./e2ee");
430
+ _e2ee.patchApiForE2EE(api, ctx);
431
+
432
+ api.connectE2EE = function(callback) {
433
+ const bridge = _e2ee.createBridge(ctx);
434
+ api._e2eeBridge = bridge;
435
+ return bridge.connect(callback);
436
+ };
437
+
438
+ api.getE2EEBridge = function() {
439
+ return ctx._e2eeBridge || null;
440
+ };
441
+
442
+ api.getE2EEDeviceData = function(callback) {
443
+ let resolve, reject;
444
+ const promise = new Promise(function(res, rej) { resolve = res; reject = rej; });
445
+ if (ctx._e2eeDeviceData) {
446
+ if (typeof callback === "function") callback(null, ctx._e2eeDeviceData);
447
+ resolve(ctx._e2eeDeviceData);
448
+ return promise;
449
+ }
450
+ _e2ee.createBridge(ctx).getDeviceData()
451
+ .then(function(d) {
452
+ ctx._e2eeDeviceData = d;
453
+ if (typeof callback === "function") callback(null, d);
454
+ resolve(d);
455
+ })
456
+ .catch(function(e) {
457
+ if (typeof callback === "function") callback(e);
458
+ reject(e);
459
+ });
460
+ return promise;
461
+ };
462
+ } catch (patchErr) {
463
+ log.warn("E2EE", "Failed to init E2EE: " + (patchErr && patchErr.message ? patchErr.message : patchErr));
464
+ }
465
+ }
466
+
467
+ return { ctx, defaultFuncs, api };
468
+ }
469
+
470
+ // ─────────────────────────────────────────────────────────────────────────────
471
+ // loginHelper — cookie-based login flow
472
+ // ─────────────────────────────────────────────────────────────────────────────
473
+ function makeLoginForm(jar, email, password, loginOptions, callback) {
474
+ return async function(res) {
475
+ try {
476
+ const html = res.body;
477
+ const $ = cheerio.load(html);
478
+ let arr = [];
479
+ $("#login_form input").each((_, v) => arr.push({ val: $(v).val(), name: $(v).attr("name") }));
480
+ arr = arr.filter(v => v.val && v.val.length);
481
+ let form = utils.arrToForm(arr);
482
+ form.lsd = utils.getFrom(html, "[\"LSD\",[],{\"token\":\"", "\"}");
483
+ form.lgndim = Buffer.from(JSON.stringify({ w: 1440, h: 900, aw: 1440, ah: 834, c: 24 })).toString("base64");
484
+ form.email = email;
485
+ form.pass = password;
486
+ form.default_persistent = "0";
487
+ form.lgnrnd = utils.getFrom(html, "name=\"lgnrnd\" value=\"", "\"");
488
+ form.locale = "en_US";
489
+ form.timezone = "240";
490
+ form.lgnjs = Math.floor(Date.now() / 1000);
491
+
492
+ // Inject pre-cookies from HTML
493
+ html.split("\"_js_").slice(1).forEach(val => {
494
+ const cd = JSON.parse("[\"" + utils.getFrom(val, "", "]") + "\"]");
495
+ jar.setCookie(utils.formatCookie(cd, "facebook"), "https://www.facebook.com");
496
+ });
497
+
498
+ log.info("alpha-fca", "Logging in with email/password...");
499
+ const loginRes = await utils.post(
500
+ "https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110",
501
+ jar, form, loginOptions
502
+ );
503
+ await utils.saveCookies(jar)(loginRes);
504
+ const headers = loginRes.headers;
505
+ if (!headers.location) throw new Error("Wrong username/password.");
506
+
507
+ if (headers.location.includes("https://www.facebook.com/checkpoint/")) {
508
+ log.info("alpha-fca", "Login approval required (2FA / checkpoint).");
509
+ const cpRes = await utils.get(headers.location, jar, null, loginOptions);
510
+ await utils.saveCookies(jar)(cpRes);
511
+ const cpHtml = cpRes.body;
512
+ const $_cp = cheerio.load(cpHtml);
513
+ let cpForm = [];
514
+ $_cp("form input").each((_, v) => cpForm.push({ val: $_cp(v).val(), name: $_cp(v).attr("name") }));
515
+ cpForm = cpForm.filter(v => v.val && v.val.length);
516
+ const form2 = utils.arrToForm(cpForm);
517
+
518
+ if (cpHtml.includes("checkpoint/?next")) {
519
+ return new Promise((resolve, reject) => {
520
+ const submit2FA = async (code) => {
521
+ try {
522
+ form2.approvals_code = code;
523
+ form2["submit[Continue]"] = $_cp("#checkpointSubmitButton").html();
524
+ const r1 = await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form2, loginOptions);
525
+ await utils.saveCookies(jar)(r1);
526
+ const approvalError = $_cp("#approvals_code").parent().attr("data-xui-error");
527
+ if (approvalError) throw new Error("Invalid 2FA code.");
528
+ form2.name_action_selected = "dont_save";
529
+ const r2 = await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form2, loginOptions);
530
+ await utils.saveCookies(jar)(r2);
531
+ const appState = utils.getAppState(jar);
532
+ resolve(await loginHelper(appState, email, password, loginOptions, callback));
533
+ } catch (err) { reject(err); }
534
+ };
535
+ callback({ error: "login-approval", continue: submit2FA });
536
+ });
537
+ }
538
+
539
+ if (!loginOptions.forceLogin) throw new Error("Facebook blocked this login attempt.");
540
+ form2["submit[This was me]"] = cpHtml.includes("Suspicious Login") ? "This was me" : "This Is Okay";
541
+ await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form2, loginOptions);
542
+ form2.name_action_selected = "save_device";
543
+ await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form2, loginOptions);
544
+ const appState = utils.getAppState(jar);
545
+ return loginHelper(appState, email, password, loginOptions, callback);
546
+ }
547
+
548
+ await utils.get("https://www.facebook.com/", jar, null, loginOptions);
549
+ return utils.saveCookies(jar);
550
+ } catch (err) {
551
+ callback(err);
552
+ }
553
+ };
554
+ }
555
+
556
+ function loginHelper(appState, email, password, globalOptions, callback) {
557
+ const jar = utils.getJar();
558
+ let mainPromise;
559
+
560
+ if (appState) {
561
+ try {
562
+ if (typeof appState === "string") {
563
+ const trimmed = appState.trim();
564
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
565
+ appState = JSON.parse(trimmed);
566
+ } else {
567
+ // Support raw Cookie header string: "c_user=xxx; xs=yyy; ..."
568
+ appState = trimmed.split(/;\s*/).map(part => {
569
+ part = part.trim();
570
+ if (!part) return null;
571
+ const i = part.indexOf("=");
572
+ if (i <= 0) return null;
573
+ return { name: part.slice(0, i).trim(), value: part.slice(i + 1), domain: ".facebook.com", path: "/" };
574
+ }).filter(Boolean);
575
+ }
576
+ }
577
+ } catch (e) {
578
+ return callback(new Error("Failed to parse appState: " + e.message));
579
+ }
580
+
581
+ try {
582
+ if (!Array.isArray(appState)) throw new Error("appState must be an array or cookie string");
583
+ appState.forEach(c => {
584
+ const name = c && (c.key || c.name);
585
+ if (!name || typeof c.value === "undefined") return;
586
+ const domain = c.domain || ".facebook.com";
587
+ const expires = c.expirationDate
588
+ ? new Date(c.expirationDate * 1000).toUTCString()
589
+ : (c.expires ? new Date(c.expires).toUTCString() : "");
590
+ const str = `${name}=${c.value};${expires ? " expires=" + expires + ";" : ""} domain=${domain}; path=${c.path || "/"};`;
591
+ try { jar.setCookie(str, "https://www.facebook.com/"); } catch (_) {}
592
+ });
593
+ mainPromise = utils.get("https://www.facebook.com/", jar, null, globalOptions, { noRef: true })
594
+ .then(utils.saveCookies(jar));
595
+ } catch (e) {
596
+ return callback(new Error("Failed to load appState: " + e.message));
597
+ }
598
+ } else {
599
+ mainPromise = utils.get("https://www.facebook.com/", null, null, globalOptions, { noRef: true })
600
+ .then(utils.saveCookies(jar))
601
+ .then(makeLoginForm(jar, email, password, globalOptions, callback))
602
+ .then(() => utils.get("https://www.facebook.com/", jar, null, globalOptions).then(utils.saveCookies(jar)));
603
+ }
604
+
605
+ function handleRedirect(res) {
606
+ const redir = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/.exec(res.body);
607
+ if (redir && redir[1]) {
608
+ return utils.get(redir[1], jar, null, globalOptions).then(utils.saveCookies(jar));
609
+ }
610
+ return res;
611
+ }
612
+
613
+ let ctx, api;
614
+ mainPromise = mainPromise
615
+ .then(handleRedirect)
616
+ .then(res => {
617
+ // Ensure we get a desktop HTML response (needed to extract fb_dtsg properly)
618
+ if (!/MPageLoadClientMetrics/gs.test(res.body)) {
619
+ globalOptions.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
620
+ return utils.get("https://www.facebook.com/", jar, null, globalOptions, { noRef: true })
621
+ .then(utils.saveCookies(jar));
622
+ }
623
+ return res;
624
+ })
625
+ .then(handleRedirect)
626
+ .then(res => {
627
+ const result = buildAPI(globalOptions, res.body, jar);
628
+ if (!result) throw new Error("buildAPI returned null — cookie state invalid.");
629
+ ctx = result.ctx;
630
+ api = result.api;
631
+ return res;
632
+ });
633
+
634
+ if (globalOptions.pageID) {
635
+ mainPromise = mainPromise
636
+ .then(() => utils.get(
637
+ "https://www.facebook.com/" + globalOptions.pageID + "/messages/?section=messages&subsection=inbox",
638
+ jar, null, globalOptions
639
+ ))
640
+ .then(resData => {
641
+ let url = utils.getFrom(resData.body, "window.location.replace(\"https:\\/\\/www.facebook.com\\", "\");").split("\\").join("");
642
+ url = url.substring(0, url.length - 1);
643
+ return utils.get("https://www.facebook.com" + url, jar, null, globalOptions);
644
+ });
645
+ }
646
+
647
+ mainPromise
648
+ .then(() => {
649
+ log.info("alpha-fca", "Login successful. UserID: " + (ctx && ctx.userID));
650
+ callback(null, api);
651
+ })
652
+ .catch(e => callback(e));
653
+ }
654
+
655
+ // ─────────────────────────────────────────────────────────────────────────────
656
+ // login — public entry point
657
+ // ─────────────────────────────────────────────────────────────────────────────
658
+ function login(loginData, options, callback) {
659
+ if (utils.getType(options) === "Function" || utils.getType(options) === "AsyncFunction") {
660
+ callback = options;
661
+ options = {};
662
+ }
663
+
664
+ // Default global options
665
+ const globalOptions = {
666
+ selfListen : true,
667
+ selfListenEvent : false,
668
+ listenEvents : true,
669
+ listenTyping : false,
670
+ updatePresence : false,
671
+ forceLogin : true,
672
+ autoMarkDelivery : false,
673
+ autoMarkRead : false,
674
+ autoReconnect : true,
675
+ // Reconnect backoff (used by listenMqtt)
676
+ reconnectMinDelayMs : 3000,
677
+ reconnectMaxDelayMs : 60000,
678
+ reconnectStableResetMs: 30000,
679
+ logRecordSize : 100,
680
+ online : false, // false = lower automation / presence footprint
681
+ emitReady : false,
682
+ userAgent : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
683
+ };
684
+
685
+ // Promisify when no callback provided
686
+ let resolveFunc, rejectFunc;
687
+ const returnPromise = new Promise(function(resolve, reject) {
688
+ resolveFunc = resolve;
689
+ rejectFunc = reject;
690
+ });
691
+ if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
692
+ callback = function(err, api) {
693
+ if (err) return rejectFunc(err);
694
+ resolveFunc(api);
695
+ };
696
+ }
697
+
698
+ // Apply user-supplied options
699
+ setOptions(globalOptions, options || {});
700
+
701
+ if (loginData.email && loginData.password) {
702
+ setOptions(globalOptions, { logLevel: "silent", forceLogin: true });
703
+ loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback);
704
+ } else if (loginData.appState) {
705
+ loginHelper(loginData.appState, null, null, globalOptions, callback);
706
+ } else {
707
+ callback(new Error("loginData must have either appState or email+password."));
708
+ }
709
+
710
+ return returnPromise;
711
+ }
712
+
713
+ module.exports = login;
714
+ module.exports.login = login;
715
+ module.exports.default = login;