fca-neokex-fix 1.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 (114) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/LICENSE +26 -0
  3. package/README.md +346 -0
  4. package/THEME_FEATURES.md +137 -0
  5. package/examples/README.md +131 -0
  6. package/examples/apply-ai-theme.js +127 -0
  7. package/examples/check-current-theme.js +74 -0
  8. package/examples/simple-bot.js +114 -0
  9. package/examples/test-bot.js +752 -0
  10. package/examples/test-logging.js +85 -0
  11. package/examples/theme-usage-example.js +53 -0
  12. package/index.js +2 -0
  13. package/package.json +105 -0
  14. package/src/apis/addExternalModule.js +24 -0
  15. package/src/apis/addUserToGroup.js +108 -0
  16. package/src/apis/changeAdminStatus.js +148 -0
  17. package/src/apis/changeArchivedStatus.js +61 -0
  18. package/src/apis/changeAvatar.js +103 -0
  19. package/src/apis/changeBio.js +69 -0
  20. package/src/apis/changeBlockedStatus.js +54 -0
  21. package/src/apis/changeGroupImage.js +136 -0
  22. package/src/apis/changeThreadColor.js +116 -0
  23. package/src/apis/comment.js +207 -0
  24. package/src/apis/createAITheme.js +129 -0
  25. package/src/apis/createNewGroup.js +79 -0
  26. package/src/apis/createPoll.js +73 -0
  27. package/src/apis/deleteMessage.js +44 -0
  28. package/src/apis/deleteThread.js +52 -0
  29. package/src/apis/editMessage.js +70 -0
  30. package/src/apis/emoji.js +124 -0
  31. package/src/apis/fetchThemeData.js +65 -0
  32. package/src/apis/follow.js +81 -0
  33. package/src/apis/forwardMessage.js +52 -0
  34. package/src/apis/friend.js +243 -0
  35. package/src/apis/gcmember.js +122 -0
  36. package/src/apis/gcname.js +123 -0
  37. package/src/apis/gcrule.js +119 -0
  38. package/src/apis/getAccess.js +111 -0
  39. package/src/apis/getBotInfo.js +88 -0
  40. package/src/apis/getBotInitialData.js +43 -0
  41. package/src/apis/getFriendsList.js +79 -0
  42. package/src/apis/getMessage.js +423 -0
  43. package/src/apis/getTheme.js +104 -0
  44. package/src/apis/getThemeInfo.js +96 -0
  45. package/src/apis/getThreadHistory.js +239 -0
  46. package/src/apis/getThreadInfo.js +257 -0
  47. package/src/apis/getThreadList.js +222 -0
  48. package/src/apis/getThreadPictures.js +58 -0
  49. package/src/apis/getUserID.js +83 -0
  50. package/src/apis/getUserInfo.js +495 -0
  51. package/src/apis/getUserInfoV2.js +146 -0
  52. package/src/apis/handleMessageRequest.js +50 -0
  53. package/src/apis/httpGet.js +63 -0
  54. package/src/apis/httpPost.js +89 -0
  55. package/src/apis/httpPostFormData.js +69 -0
  56. package/src/apis/listenMqtt.js +796 -0
  57. package/src/apis/listenSpeed.js +170 -0
  58. package/src/apis/logout.js +63 -0
  59. package/src/apis/markAsDelivered.js +47 -0
  60. package/src/apis/markAsRead.js +95 -0
  61. package/src/apis/markAsReadAll.js +41 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +330 -0
  64. package/src/apis/muteThread.js +45 -0
  65. package/src/apis/nickname.js +132 -0
  66. package/src/apis/notes.js +163 -0
  67. package/src/apis/pinMessage.js +141 -0
  68. package/src/apis/produceMetaTheme.js +180 -0
  69. package/src/apis/realtime.js +161 -0
  70. package/src/apis/removeUserFromGroup.js +117 -0
  71. package/src/apis/resolvePhotoUrl.js +58 -0
  72. package/src/apis/searchForThread.js +154 -0
  73. package/src/apis/sendMessage.js +281 -0
  74. package/src/apis/sendMessageMqtt.js +188 -0
  75. package/src/apis/sendTypingIndicator.js +41 -0
  76. package/src/apis/setMessageReaction.js +27 -0
  77. package/src/apis/setMessageReactionMqtt.js +61 -0
  78. package/src/apis/setThreadTheme.js +260 -0
  79. package/src/apis/setThreadThemeMqtt.js +94 -0
  80. package/src/apis/share.js +107 -0
  81. package/src/apis/shareContact.js +66 -0
  82. package/src/apis/stickers.js +257 -0
  83. package/src/apis/story.js +181 -0
  84. package/src/apis/theme.js +233 -0
  85. package/src/apis/unfriend.js +47 -0
  86. package/src/apis/unsendMessage.js +17 -0
  87. package/src/database/appStateBackup.js +189 -0
  88. package/src/database/models/index.js +56 -0
  89. package/src/database/models/thread.js +31 -0
  90. package/src/database/models/user.js +32 -0
  91. package/src/database/threadData.js +101 -0
  92. package/src/database/userData.js +90 -0
  93. package/src/engine/client.js +91 -0
  94. package/src/engine/models/buildAPI.js +109 -0
  95. package/src/engine/models/loginHelper.js +326 -0
  96. package/src/engine/models/setOptions.js +53 -0
  97. package/src/utils/auth-helpers.js +149 -0
  98. package/src/utils/autoReLogin.js +169 -0
  99. package/src/utils/axios.js +290 -0
  100. package/src/utils/clients.js +270 -0
  101. package/src/utils/constants.js +396 -0
  102. package/src/utils/formatters/data/formatAttachment.js +370 -0
  103. package/src/utils/formatters/data/formatDelta.js +153 -0
  104. package/src/utils/formatters/index.js +159 -0
  105. package/src/utils/formatters/value/formatCookie.js +91 -0
  106. package/src/utils/formatters/value/formatDate.js +36 -0
  107. package/src/utils/formatters/value/formatID.js +16 -0
  108. package/src/utils/formatters.js +1067 -0
  109. package/src/utils/headers.js +199 -0
  110. package/src/utils/index.js +151 -0
  111. package/src/utils/monitoring.js +358 -0
  112. package/src/utils/rateLimiter.js +380 -0
  113. package/src/utils/tokenRefresh.js +311 -0
  114. package/src/utils/user-agents.js +238 -0
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+
3
+ const utils = require('../../utils');
4
+
5
+ /**
6
+ * Builds the core API context and default functions after successful login.
7
+ *
8
+ * @param {string} html The HTML body from the initial Facebook page.
9
+ * @param {object} jar The cookie jar.
10
+ * @param {Array<object>} netData Network data extracted from the HTML.
11
+ * @param {object} globalOptions The global options object.
12
+ * @param {function} fbLinkFunc A function to generate Facebook links.
13
+ * @param {string} errorRetrievingMsg The error message for retrieving user ID.
14
+ * @returns {Array<object>} An array containing [ctx, defaultFuncs, {}].
15
+ */
16
+ async function buildAPI(html, jar, netData, globalOptions, fbLinkFunc, errorRetrievingMsg) {
17
+ let userID;
18
+ const cookies = jar.getCookiesSync(fbLinkFunc());
19
+ const primaryProfile = cookies.find((val) => val.cookieString().startsWith("c_user="));
20
+ const secondaryProfile = cookies.find((val) => val.cookieString().startsWith("i_user="));
21
+ if (!primaryProfile && !secondaryProfile) {
22
+ throw new Error(errorRetrievingMsg);
23
+ }
24
+ userID = secondaryProfile?.cookieString().split("=")[1] || primaryProfile.cookieString().split("=")[1];
25
+
26
+ const findConfig = (key) => {
27
+ for (const scriptData of netData) {
28
+ if (scriptData.require) {
29
+ for (const req of scriptData.require) {
30
+ if (Array.isArray(req) && req[0] === key && req[2]) {
31
+ return req[2];
32
+ }
33
+ if (Array.isArray(req) && req[3] && req[3][0] && req[3][0].__bbox && req[3][0].__bbox.define) {
34
+ for (const def of req[3][0].__bbox.define) {
35
+ if (Array.isArray(def) && def[0].endsWith(key) && def[2]) {
36
+ return def[2];
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return null;
44
+ };
45
+
46
+ const dtsgData = findConfig("DTSGInitialData");
47
+ const dtsg = dtsgData ? dtsgData.token : utils.getFrom(html, '"token":"', '"');
48
+
49
+ const lsdData = findConfig("LSD");
50
+ const lsd = lsdData ? lsdData.token : utils.getFrom(html, '"LSD",[],{"token":"', '"');
51
+
52
+ const dtsgResult = {
53
+ fb_dtsg: dtsg,
54
+ jazoest: `2${Array.from(dtsg).reduce((a, b) => a + b.charCodeAt(0), '')}`,
55
+ lsd: lsd
56
+ };
57
+
58
+ const clientIDData = findConfig("MqttWebDeviceID");
59
+ const clientID = clientIDData ? clientIDData.clientID : undefined;
60
+
61
+ const mqttConfigData = findConfig("MqttWebConfig");
62
+ const mqttAppID = mqttConfigData ? mqttConfigData.appID : undefined;
63
+
64
+ const currentUserData = findConfig("CurrentUserInitialData");
65
+ const userAppID = currentUserData ? currentUserData.APP_ID : undefined;
66
+
67
+ let primaryAppID = userAppID || mqttAppID;
68
+
69
+ let mqttEndpoint = mqttConfigData ? mqttConfigData.endpoint : undefined;
70
+
71
+ let region = mqttEndpoint ? new URL(mqttEndpoint).searchParams.get("region")?.toUpperCase() : undefined;
72
+ const irisSeqIDMatch = html.match(/irisSeqID:"(.+?)"/);
73
+ const irisSeqID = irisSeqIDMatch ? irisSeqIDMatch[1] : null;
74
+ if (globalOptions.bypassRegion && mqttEndpoint) {
75
+ const currentEndpoint = new URL(mqttEndpoint);
76
+ currentEndpoint.searchParams.set('region', globalOptions.bypassRegion.toLowerCase());
77
+ mqttEndpoint = currentEndpoint.toString();
78
+ region = globalOptions.bypassRegion.toUpperCase();
79
+ }
80
+
81
+ const ctx = {
82
+ userID,
83
+ jar,
84
+ clientID,
85
+ appID: primaryAppID,
86
+ mqttAppID: mqttAppID,
87
+ userAppID: userAppID,
88
+ globalOptions,
89
+ loggedIn: true,
90
+ access_token: "NONE",
91
+ clientMutationId: 0,
92
+ mqttClient: undefined,
93
+ lastSeqId: irisSeqID,
94
+ syncToken: undefined,
95
+ mqttEndpoint,
96
+ wsReqNumber: 0,
97
+ wsTaskNumber: 0,
98
+ reqCallbacks: {},
99
+ callback_Task: {},
100
+ region,
101
+ firstListen: true,
102
+ ...dtsgResult,
103
+ };
104
+ const defaultFuncs = utils.makeDefaults(html, userID, ctx);
105
+
106
+ return [ctx, defaultFuncs, {}];
107
+ }
108
+
109
+ module.exports = buildAPI;
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+
3
+ const utils = require('../../utils');
4
+ const axios = require("axios");
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const qs = require("querystring");
8
+ const { normalizeCookieHeaderString, setJarFromPairs } = require('../../utils/formatters/value/formatCookie');
9
+ const { parseRegion, genTotp } = require('../../utils/auth-helpers');
10
+ const { generateUserAgentByPersona, cachePersonaData } = require('../../utils/user-agents');
11
+
12
+ /**
13
+ * The main login helper function, orchestrating the login process.
14
+ *
15
+ * @param {object} credentials User credentials or appState.
16
+ * @param {object} globalOptions Global options for the API.
17
+ * @param {function} callback The final callback function.
18
+ * @param {function} setOptionsFunc Reference to the setOptions function from models.
19
+ * @param {function} buildAPIFunc Reference to the buildAPI function from models.
20
+ * @param {object} initialApi The initial API object to extend.
21
+ * @param {function} fbLinkFunc A function to generate Facebook links.
22
+ * @param {string} errorRetrievingMsg The error message for retrieving user ID.
23
+ * @returns {Promise<void>}
24
+ */
25
+ async function loginHelper(credentials, globalOptions, callback, setOptionsFunc, buildAPIFunc, initialApi, fbLinkFunc, errorRetrievingMsg) {
26
+ let ctx = null;
27
+ let defaultFuncs = null;
28
+ let api = initialApi;
29
+
30
+ try {
31
+ const jar = utils.getJar();
32
+ utils.log("Logging in...");
33
+
34
+ const persona = globalOptions.persona || 'desktop';
35
+ const personaSwitched = globalOptions.cachedPersona && globalOptions.cachedPersona !== persona;
36
+
37
+ if (personaSwitched) {
38
+ const oldPersona = globalOptions.cachedPersona;
39
+ utils.log(`Persona switched from ${oldPersona} to ${persona}, clearing ALL cached fingerprints`);
40
+
41
+ delete globalOptions.cachedUserAgent;
42
+ delete globalOptions.cachedSecChUa;
43
+ delete globalOptions.cachedSecChUaFullVersionList;
44
+ delete globalOptions.cachedSecChUaPlatform;
45
+ delete globalOptions.cachedSecChUaPlatformVersion;
46
+ delete globalOptions.cachedBrowser;
47
+
48
+ delete globalOptions.cachedAndroidUA;
49
+ delete globalOptions.cachedAndroidVersion;
50
+ delete globalOptions.cachedAndroidDevice;
51
+ delete globalOptions.cachedAndroidBuildId;
52
+ delete globalOptions.cachedAndroidResolution;
53
+ delete globalOptions.cachedAndroidFbav;
54
+ delete globalOptions.cachedAndroidFbbv;
55
+ delete globalOptions.cachedAndroidLocale;
56
+ delete globalOptions.cachedAndroidCarrier;
57
+
58
+ delete globalOptions.cachedLocale;
59
+ delete globalOptions.cachedTimezone;
60
+ }
61
+
62
+ const needsDesktopCache = (persona === 'desktop') && !globalOptions.cachedUserAgent;
63
+ const needsAndroidCache = (persona === 'android' || persona === 'mobile') && !globalOptions.cachedAndroidUA;
64
+
65
+ if (needsDesktopCache || needsAndroidCache) {
66
+ const personaData = generateUserAgentByPersona(persona, globalOptions);
67
+ cachePersonaData(globalOptions, personaData);
68
+ globalOptions.cachedPersona = persona;
69
+
70
+ if (persona === 'desktop') {
71
+ utils.log("Using desktop persona with browser:", personaData.browser);
72
+ } else {
73
+ utils.log("Using Android/Orca mobile persona");
74
+ }
75
+
76
+ const { getRandomLocale, getRandomTimezone } = require('../../utils/headers');
77
+ if (!globalOptions.cachedLocale) {
78
+ globalOptions.cachedLocale = getRandomLocale();
79
+ }
80
+ if (!globalOptions.cachedTimezone) {
81
+ globalOptions.cachedTimezone = getRandomTimezone();
82
+ }
83
+ } else {
84
+ if (persona === 'desktop' && globalOptions.cachedUserAgent) {
85
+ utils.log("Using cached desktop persona");
86
+ } else if ((persona === 'android' || persona === 'mobile') && globalOptions.cachedAndroidUA) {
87
+ utils.log("Using cached Android/Orca mobile persona");
88
+ }
89
+ }
90
+
91
+ let appState = credentials.appState;
92
+
93
+ if (!appState && !credentials.email && !credentials.password) {
94
+ try {
95
+ const { hydrateJarFromDB } = require('../../database/appStateBackup');
96
+ const restored = await hydrateJarFromDB(jar, null);
97
+ if (restored) {
98
+ utils.log("Restored AppState from database backup");
99
+ }
100
+ } catch (dbErr) {
101
+ utils.warn("Failed to restore AppState from database:", dbErr.message);
102
+ }
103
+ }
104
+
105
+ if (appState) {
106
+ let cookieStrings = [];
107
+ if (Array.isArray(appState)) {
108
+ cookieStrings = appState.map(c => [c.name || c.key, c.value].join('='));
109
+ } else if (typeof appState === 'string') {
110
+ cookieStrings = normalizeCookieHeaderString(appState);
111
+
112
+ if (cookieStrings.length === 0) {
113
+ cookieStrings = appState.split(';').map(s => s.trim()).filter(Boolean);
114
+ }
115
+ } else {
116
+ throw new Error("Invalid appState format. Please provide an array of cookie objects or a cookie string.");
117
+ }
118
+
119
+ setJarFromPairs(jar, cookieStrings);
120
+ utils.log("Cookies set for facebook.com and messenger.com domains");
121
+
122
+ } else if (credentials.email && credentials.password) {
123
+
124
+ if (credentials.totpSecret) {
125
+ utils.log("TOTP secret detected, will generate 2FA code if needed");
126
+ }
127
+
128
+ const url = "https://api.facebook.com/method/auth.login";
129
+ const params = {
130
+ access_token: "350685531728|62f8ce9f74b12f84c123cc23437a4a32",
131
+ format: "json",
132
+ sdk_version: 2,
133
+ email: credentials.email,
134
+ locale: "en_US",
135
+ password: credentials.password,
136
+ generate_session_cookies: 1,
137
+ sig: "c1c640010993db92e5afd11634ced864",
138
+ }
139
+
140
+ if (credentials.totpSecret) {
141
+ try {
142
+ const totpCode = await genTotp(credentials.totpSecret);
143
+ params.credentials_type = "two_factor";
144
+ params.twofactor_code = totpCode;
145
+ utils.log("TOTP code generated successfully");
146
+ } catch (totpError) {
147
+ utils.warn("Failed to generate TOTP code:", totpError.message);
148
+ }
149
+ }
150
+
151
+ const query = qs.stringify(params);
152
+ const xurl = `${url}?${query}`;
153
+ try {
154
+ const resp = await axios.get(xurl);
155
+ if (resp.status !== 200) {
156
+ throw new Error("Wrong password / email");
157
+ }
158
+ let cstrs = resp.data["session_cookies"].map(c => `${c.name}=${c.value}`);
159
+ setJarFromPairs(jar, cstrs);
160
+ utils.log("Login successful with email/password");
161
+ } catch (e) {
162
+ if (credentials.totpSecret && !params.twofactor_code) {
163
+ throw new Error("2FA required but TOTP code generation failed");
164
+ }
165
+ throw new Error("Wrong password / email");
166
+ }
167
+ } else {
168
+ throw new Error("No cookie or credentials found. Please provide cookies or credentials.");
169
+ }
170
+
171
+ if (!api) {
172
+ api = {
173
+ setOptions: setOptionsFunc.bind(null, globalOptions),
174
+ getAppState() {
175
+ const appState = utils.getAppState(jar);
176
+ if (!Array.isArray(appState)) return [];
177
+ const uniqueAppState = appState.filter((item, index, self) => self.findIndex((t) => t.key === item.key) === index);
178
+ return uniqueAppState.length > 0 ? uniqueAppState : appState;
179
+ },
180
+ };
181
+ }
182
+
183
+ const resp = await utils.get(fbLinkFunc(), jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
184
+ const extractNetData = (html) => {
185
+ const allScriptsData = [];
186
+ const scriptRegex = /<script type="application\/json"[^>]*>(.*?)<\/script>/g;
187
+ let match;
188
+ while ((match = scriptRegex.exec(html)) !== null) {
189
+ try {
190
+ allScriptsData.push(JSON.parse(match[1]));
191
+ } catch (e) {
192
+ utils.error(`Failed to parse a JSON blob from HTML`, e.message);
193
+ }
194
+ }
195
+ return allScriptsData;
196
+ };
197
+
198
+ const netData = extractNetData(resp.body);
199
+
200
+ const [newCtx, newDefaultFuncs] = await buildAPIFunc(resp.body, jar, netData, globalOptions, fbLinkFunc, errorRetrievingMsg);
201
+ ctx = newCtx;
202
+ defaultFuncs = newDefaultFuncs;
203
+
204
+ const region = parseRegion(resp.body);
205
+ ctx.region = region;
206
+ utils.log("Detected Facebook region:", region);
207
+
208
+ try {
209
+ const { backupAppStateSQL } = require('../../database/appStateBackup');
210
+ await backupAppStateSQL(jar, ctx.userID);
211
+ } catch (backupErr) {
212
+ utils.warn("Failed to backup AppState to database:", backupErr.message);
213
+ }
214
+ api.message = new Map();
215
+ api.timestamp = {};
216
+
217
+ /**
218
+ * Loads API modules from the apis directory.
219
+ *
220
+ * @returns {void}
221
+ */
222
+ const loadApiModules = () => {
223
+ const apiPath = path.join(__dirname, '..', '..', 'apis');
224
+
225
+ if (!fs.existsSync(apiPath)) {
226
+ utils.error('API directory not found:', apiPath);
227
+ return;
228
+ }
229
+
230
+ const helperModules = ['mqttDeltaValue'];
231
+
232
+ fs.readdirSync(apiPath)
233
+ .filter(file => file.endsWith('.js'))
234
+ .forEach(file => {
235
+ const moduleName = path.basename(file, '.js');
236
+
237
+ if (helperModules.includes(moduleName)) {
238
+ return;
239
+ }
240
+
241
+ const fullPath = path.join(apiPath, file);
242
+ try {
243
+ const moduleExport = require(fullPath);
244
+ if (typeof moduleExport === 'function') {
245
+ api[moduleName] = moduleExport(defaultFuncs, api, ctx);
246
+ }
247
+ } catch (e) {
248
+ utils.error(`Failed to load API module ${moduleName}:`, e);
249
+ }
250
+ });
251
+ };
252
+
253
+ api.getCurrentUserID = () => ctx.userID;
254
+ api.getOptions = (key) => key ? globalOptions[key] : globalOptions;
255
+ loadApiModules();
256
+
257
+ if (api.nickname && typeof api.nickname === 'function') {
258
+ api.changeNickname = api.nickname;
259
+ }
260
+
261
+ try {
262
+ const models = require('../../database/models');
263
+ const threadDataModule = require('../../database/threadData');
264
+ const userDataModule = require('../../database/userData');
265
+
266
+ models.syncAll().then(() => {
267
+ utils.log("Database synchronized successfully");
268
+ }).catch(err => {
269
+ utils.warn("Failed to sync database:", err.message);
270
+ });
271
+
272
+ api.threadData = threadDataModule(api);
273
+ api.userData = userDataModule(api);
274
+ utils.log("Database methods initialized");
275
+ } catch (dbError) {
276
+ utils.warn("Database initialization failed (optional feature):", dbError.message);
277
+ }
278
+
279
+ api.ctx = ctx;
280
+ api.defaultFuncs = defaultFuncs;
281
+ api.globalOptions = globalOptions;
282
+
283
+ const { TokenRefreshManager } = require('../../utils/tokenRefresh');
284
+ if (api.tokenRefreshManager) {
285
+ api.tokenRefreshManager.stopAutoRefresh();
286
+ } else {
287
+ api.tokenRefreshManager = new TokenRefreshManager();
288
+ }
289
+
290
+ const { globalAutoReLoginManager } = require('../../utils/autoReLogin');
291
+
292
+ if (globalOptions.autoReLogin !== false) {
293
+ globalAutoReLoginManager.setCredentials(credentials, globalOptions, callback);
294
+ utils.log("AutoReLogin", "Auto re-login enabled with stored credentials");
295
+
296
+ api.tokenRefreshManager.setSessionExpiryCallback((error) => {
297
+ utils.warn("TokenRefresh", "Session expiry detected. Triggering auto re-login...");
298
+ globalAutoReLoginManager.handleSessionExpiry(api, fbLinkFunc(), errorRetrievingMsg);
299
+ });
300
+ }
301
+
302
+ api.tokenRefreshManager.startAutoRefresh(ctx, defaultFuncs, fbLinkFunc());
303
+
304
+ api.refreshTokens = () => api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, fbLinkFunc());
305
+ api.getTokenRefreshStatus = () => ({
306
+ lastRefresh: api.tokenRefreshManager.lastRefresh,
307
+ nextRefresh: api.tokenRefreshManager.getTimeUntilNextRefresh(),
308
+ failureCount: api.tokenRefreshManager.getFailureCount()
309
+ });
310
+ api.enableAutoReLogin = (enable = true) => {
311
+ if (enable) {
312
+ globalAutoReLoginManager.setCredentials(credentials, globalOptions, callback);
313
+ } else {
314
+ globalAutoReLoginManager.disable();
315
+ }
316
+ };
317
+ api.isAutoReLoginEnabled = () => globalAutoReLoginManager.isEnabled();
318
+
319
+ return callback(null, api);
320
+ } catch (error) {
321
+ utils.error("loginHelper", error.error || error);
322
+ return callback(error);
323
+ }
324
+ }
325
+
326
+ module.exports = loginHelper;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ const utils = require('../../utils');
4
+
5
+ /**
6
+ * Sets global options for the API.
7
+ *
8
+ * @param {object} globalOptions The global options object to modify.
9
+ * @param {object} [options={}] New options to apply.
10
+ * @returns {Promise<void>}
11
+ */
12
+ async function setOptions(globalOptions, options = {}) {
13
+ const optionHandlers = {
14
+ online: (value) => (globalOptions.online = Boolean(value)),
15
+ selfListen: (value) => (globalOptions.selfListen = Boolean(value)),
16
+ selfListenEvent: (value) => (globalOptions.selfListenEvent = value),
17
+ listenEvents: (value) => (globalOptions.listenEvents = Boolean(value)),
18
+ updatePresence: (value) => (globalOptions.updatePresence = Boolean(value)),
19
+ forceLogin: (value) => (globalOptions.forceLogin = Boolean(value)),
20
+ userAgent: (value) => (globalOptions.userAgent = value),
21
+ autoMarkDelivery: (value) => (globalOptions.autoMarkDelivery = Boolean(value)),
22
+ autoMarkRead: (value) => (globalOptions.autoMarkRead = Boolean(value)),
23
+ listenTyping: (value) => (globalOptions.listenTyping = Boolean(value)),
24
+ proxy(value) {
25
+ if (typeof value !== "string") {
26
+ delete globalOptions.proxy;
27
+ utils.setProxy();
28
+ } else {
29
+ globalOptions.proxy = value;
30
+ utils.setProxy(value);
31
+ }
32
+ },
33
+ autoReconnect: (value) => (globalOptions.autoReconnect = Boolean(value)),
34
+ emitReady: (value) => (globalOptions.emitReady = Boolean(value)),
35
+ randomUserAgent(value) {
36
+ globalOptions.randomUserAgent = Boolean(value);
37
+ if (value) {
38
+ globalOptions.userAgent = utils.randomUserAgent();
39
+ }
40
+ },
41
+ bypassRegion(value) {
42
+ if (value){
43
+ value = value.toUpperCase();
44
+ }
45
+ globalOptions.bypassRegion = value;
46
+ }
47
+ };
48
+ Object.entries(options).forEach(([key, value]) => {
49
+ if (optionHandlers[key]) optionHandlers[key](value);
50
+ });
51
+ }
52
+
53
+ module.exports = setOptions;
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+
3
+ const REGION_MAP = new Map([
4
+ ["PRN", { code: "PRN", name: "Pacific Northwest Region", location: "Khu vực Tây Bắc Thái Bình Dương" }],
5
+ ["VLL", { code: "VLL", name: "Valley Region", location: "Valley" }],
6
+ ["ASH", { code: "ASH", name: "Ashburn Region", location: "Ashburn" }],
7
+ ["DFW", { code: "DFW", name: "Dallas/Fort Worth Region", location: "Dallas/Fort Worth" }],
8
+ ["LLA", { code: "LLA", name: "Los Angeles Region", location: "Los Angeles" }],
9
+ ["FRA", { code: "FRA", name: "Frankfurt", location: "Frankfurt" }],
10
+ ["SIN", { code: "SIN", name: "Singapore", location: "Singapore" }],
11
+ ["NRT", { code: "NRT", name: "Tokyo", location: "Japan" }],
12
+ ["HKG", { code: "HKG", name: "Hong Kong", location: "Hong Kong" }],
13
+ ["SYD", { code: "SYD", name: "Sydney", location: "Sydney" }],
14
+ ["PNB", { code: "PNB", name: "Pacific Northwest - Beta", location: "Pacific Northwest" }]
15
+ ]);
16
+
17
+ /**
18
+ * Parses the region from HTML response content with fallback to default.
19
+ * @param {string} html - The HTML response from Facebook.
20
+ * @returns {string} The region code (e.g., "PRN", "VLL", etc.).
21
+ */
22
+ function parseRegion(html) {
23
+ try {
24
+ const match1 = html.match(/"endpoint":"([^"]+)"/);
25
+ const match2 = match1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
26
+ const rawEndpoint = (match1 && match1[1]) || (match2 && match2[1]);
27
+
28
+ if (!rawEndpoint) {
29
+ return "PRN";
30
+ }
31
+
32
+ const endpoint = rawEndpoint.replace(/\\\//g, "/");
33
+
34
+ try {
35
+ const url = new URL(endpoint);
36
+ const regionParam = url.searchParams ? url.searchParams.get("region") : null;
37
+
38
+ if (regionParam) {
39
+ const regionCode = regionParam.toUpperCase();
40
+ if (REGION_MAP.has(regionCode)) {
41
+ return regionCode;
42
+ }
43
+ }
44
+ } catch (urlError) {
45
+ }
46
+
47
+ return "PRN";
48
+ } catch (error) {
49
+ return "PRN";
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Generates a TOTP code from a secret using a basic TOTP algorithm.
55
+ * This is a fallback implementation that doesn't require external dependencies.
56
+ * @param {string} secret - The TOTP secret (base32 encoded).
57
+ * @returns {Promise<string>} The generated 6-digit TOTP code.
58
+ */
59
+ async function genTotp(secret) {
60
+ try {
61
+ const cleaned = String(secret || "")
62
+ .replace(/\s+/g, "")
63
+ .toUpperCase();
64
+
65
+ if (!cleaned) {
66
+ throw new Error("TOTP secret is empty");
67
+ }
68
+
69
+ try {
70
+ const totpGenerator = require('totp-generator');
71
+ if (typeof totpGenerator.TOTP !== 'undefined' && typeof totpGenerator.TOTP.generate === 'function') {
72
+ const result = await totpGenerator.TOTP.generate(cleaned);
73
+ return typeof result === 'object' ? result.otp : result;
74
+ } else if (typeof totpGenerator === 'function') {
75
+ return totpGenerator(cleaned);
76
+ } else {
77
+ return totpGenerator(cleaned);
78
+ }
79
+ } catch (requireError) {
80
+ const crypto = require('crypto');
81
+
82
+ function base32Decode(base32) {
83
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
84
+ let bits = '';
85
+
86
+ for (let i = 0; i < base32.length; i++) {
87
+ const val = alphabet.indexOf(base32.charAt(i).toUpperCase());
88
+ if (val === -1) continue;
89
+ bits += val.toString(2).padStart(5, '0');
90
+ }
91
+
92
+ const bytes = [];
93
+ for (let i = 0; i + 8 <= bits.length; i += 8) {
94
+ bytes.push(parseInt(bits.substr(i, 8), 2));
95
+ }
96
+
97
+ return Buffer.from(bytes);
98
+ }
99
+
100
+ const key = base32Decode(cleaned);
101
+ const epoch = Math.floor(Date.now() / 1000);
102
+ const timeCounter = Math.floor(epoch / 30);
103
+
104
+ const buffer = Buffer.alloc(8);
105
+ buffer.writeBigUInt64BE(BigInt(timeCounter));
106
+
107
+ const hmac = crypto.createHmac('sha1', key);
108
+ hmac.update(buffer);
109
+ const digest = hmac.digest();
110
+
111
+ const offset = digest[digest.length - 1] & 0x0f;
112
+ const code = (
113
+ ((digest[offset] & 0x7f) << 24) |
114
+ ((digest[offset + 1] & 0xff) << 16) |
115
+ ((digest[offset + 2] & 0xff) << 8) |
116
+ (digest[offset + 3] & 0xff)
117
+ ) % 1000000;
118
+
119
+ return code.toString().padStart(6, '0');
120
+ }
121
+ } catch (error) {
122
+ throw new Error(`Failed to generate TOTP code: ${error.message}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Gets region information by code.
128
+ * @param {string} code - The region code.
129
+ * @returns {object|null} The region information or null if not found.
130
+ */
131
+ function getRegionInfo(code) {
132
+ return REGION_MAP.get(code.toUpperCase()) || null;
133
+ }
134
+
135
+ /**
136
+ * Gets all available regions.
137
+ * @returns {Array<object>} Array of all region objects.
138
+ */
139
+ function getAllRegions() {
140
+ return Array.from(REGION_MAP.values());
141
+ }
142
+
143
+ module.exports = {
144
+ REGION_MAP,
145
+ parseRegion,
146
+ genTotp,
147
+ getRegionInfo,
148
+ getAllRegions
149
+ };