@dongdev/fca-unofficial 3.0.30 → 4.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 (104) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +224 -406
  3. package/dist/index.d.mts +1241 -0
  4. package/dist/index.d.ts +1241 -0
  5. package/dist/index.js +27749 -0
  6. package/dist/index.mjs +27713 -0
  7. package/docs/ARCHITECTURE.md +467 -0
  8. package/docs/DOCS.md +686 -0
  9. package/fca-config.example.json +33 -0
  10. package/package.json +33 -22
  11. package/test/fca.test.cjs +533 -0
  12. package/CHANGELOG.md +0 -293
  13. package/DOCS.md +0 -2712
  14. package/func/checkUpdate.js +0 -222
  15. package/func/logAdapter.js +0 -33
  16. package/func/logger.js +0 -48
  17. package/index.d.ts +0 -751
  18. package/index.js +0 -8
  19. package/module/config.js +0 -40
  20. package/module/login.js +0 -133
  21. package/module/loginHelper.js +0 -1296
  22. package/module/options.js +0 -44
  23. package/src/api/action/addExternalModule.js +0 -25
  24. package/src/api/action/changeAvatar.js +0 -137
  25. package/src/api/action/changeBio.js +0 -75
  26. package/src/api/action/enableAutoSaveAppState.js +0 -73
  27. package/src/api/action/getCurrentUserID.js +0 -7
  28. package/src/api/action/handleFriendRequest.js +0 -57
  29. package/src/api/action/logout.js +0 -76
  30. package/src/api/action/refreshFb_dtsg.js +0 -48
  31. package/src/api/action/setPostReaction.js +0 -106
  32. package/src/api/action/unfriend.js +0 -54
  33. package/src/api/http/httpGet.js +0 -46
  34. package/src/api/http/httpPost.js +0 -52
  35. package/src/api/http/postFormData.js +0 -47
  36. package/src/api/messaging/addUserToGroup.js +0 -68
  37. package/src/api/messaging/changeAdminStatus.js +0 -126
  38. package/src/api/messaging/changeArchivedStatus.js +0 -55
  39. package/src/api/messaging/changeBlockedStatus.js +0 -48
  40. package/src/api/messaging/changeGroupImage.js +0 -91
  41. package/src/api/messaging/changeNickname.js +0 -70
  42. package/src/api/messaging/changeThreadColor.js +0 -79
  43. package/src/api/messaging/changeThreadEmoji.js +0 -111
  44. package/src/api/messaging/createNewGroup.js +0 -88
  45. package/src/api/messaging/createPoll.js +0 -46
  46. package/src/api/messaging/createThemeAI.js +0 -98
  47. package/src/api/messaging/deleteMessage.js +0 -136
  48. package/src/api/messaging/deleteThread.js +0 -56
  49. package/src/api/messaging/editMessage.js +0 -68
  50. package/src/api/messaging/forwardAttachment.js +0 -57
  51. package/src/api/messaging/getEmojiUrl.js +0 -29
  52. package/src/api/messaging/getFriendsList.js +0 -82
  53. package/src/api/messaging/getMessage.js +0 -829
  54. package/src/api/messaging/getThemePictures.js +0 -62
  55. package/src/api/messaging/handleMessageRequest.js +0 -65
  56. package/src/api/messaging/markAsDelivered.js +0 -57
  57. package/src/api/messaging/markAsRead.js +0 -88
  58. package/src/api/messaging/markAsReadAll.js +0 -49
  59. package/src/api/messaging/markAsSeen.js +0 -61
  60. package/src/api/messaging/muteThread.js +0 -50
  61. package/src/api/messaging/removeUserFromGroup.js +0 -62
  62. package/src/api/messaging/resolvePhotoUrl.js +0 -43
  63. package/src/api/messaging/scheduler.js +0 -264
  64. package/src/api/messaging/searchForThread.js +0 -52
  65. package/src/api/messaging/sendMessage.js +0 -270
  66. package/src/api/messaging/sendTypingIndicator.js +0 -74
  67. package/src/api/messaging/setMessageReaction.js +0 -91
  68. package/src/api/messaging/setTitle.js +0 -124
  69. package/src/api/messaging/shareContact.js +0 -49
  70. package/src/api/messaging/threadColors.js +0 -128
  71. package/src/api/messaging/unsendMessage.js +0 -81
  72. package/src/api/messaging/uploadAttachment.js +0 -492
  73. package/src/api/socket/core/connectMqtt.js +0 -258
  74. package/src/api/socket/core/emitAuth.js +0 -103
  75. package/src/api/socket/core/getSeqID.js +0 -320
  76. package/src/api/socket/core/getTaskResponseData.js +0 -25
  77. package/src/api/socket/core/parseDelta.js +0 -377
  78. package/src/api/socket/detail/buildStream.js +0 -215
  79. package/src/api/socket/detail/constants.js +0 -28
  80. package/src/api/socket/listenMqtt.js +0 -377
  81. package/src/api/socket/middleware/index.js +0 -216
  82. package/src/api/threads/getThreadHistory.js +0 -664
  83. package/src/api/threads/getThreadInfo.js +0 -295
  84. package/src/api/threads/getThreadList.js +0 -293
  85. package/src/api/threads/getThreadPictures.js +0 -78
  86. package/src/api/users/getUserID.js +0 -65
  87. package/src/api/users/getUserInfo.js +0 -399
  88. package/src/api/users/getUserInfoV2.js +0 -134
  89. package/src/core/sendReqMqtt.js +0 -96
  90. package/src/database/models/index.js +0 -87
  91. package/src/database/models/thread.js +0 -50
  92. package/src/database/models/user.js +0 -46
  93. package/src/database/threadData.js +0 -98
  94. package/src/database/userData.js +0 -89
  95. package/src/remote/remoteClient.js +0 -123
  96. package/src/utils/broadcast.js +0 -51
  97. package/src/utils/client.js +0 -10
  98. package/src/utils/constants.js +0 -23
  99. package/src/utils/cookies.js +0 -68
  100. package/src/utils/format.js +0 -1174
  101. package/src/utils/headers.js +0 -115
  102. package/src/utils/loginParser.js +0 -365
  103. package/src/utils/messageFormat.js +0 -1173
  104. package/src/utils/request.js +0 -332
@@ -1,258 +0,0 @@
1
- "use strict";
2
- /**
3
- * MQTT/WebSocket listener for Facebook Messenger real-time events.
4
- * Connects to edge-chat.facebook.com, subscribes to topics, parses deltas and typing/presence.
5
- */
6
- const { formatID } = require("../../../utils/format");
7
-
8
- const DEFAULT_RECONNECT_DELAY_MS = 2000;
9
- const T_MS_WAIT_TIMEOUT_MS = 5000;
10
-
11
- module.exports = function createListenMqtt(deps) {
12
- const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
13
- topics, parseDelta, getTaskResponseData, logger, emitAuth
14
- } = deps;
15
-
16
- return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
17
-
18
- function scheduleReconnect(delayMs) {
19
- const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
20
- const ms = typeof delayMs === "number" ? delayMs : d;
21
- if (ctx._reconnectTimer) {
22
- logger("mqtt reconnect already scheduled", "warn");
23
- return; // debounce
24
- }
25
- if (ctx._ending) {
26
- logger("mqtt reconnect skipped - ending", "warn");
27
- return;
28
- }
29
- logger(`mqtt will reconnect in ${ms}ms`, "warn");
30
- ctx._reconnectTimer = setTimeout(() => {
31
- ctx._reconnectTimer = null;
32
- if (!ctx._ending) {
33
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
34
- }
35
- }, ms);
36
- }
37
- function isEndingLikeError(msg) {
38
- return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
39
- }
40
-
41
- const chatOn = ctx.globalOptions.online;
42
- const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
43
- const username = {
44
- u: ctx.userID, s: sessionID, chat_on: chatOn, fg: false, d: ctx.clientId,
45
- ct: "websocket", aid: 219994525426954, aids: null, mqtt_sid: "",
46
- cp: 3, ecp: 10, st: [], pm: [], dc: "", no_auto_fg: true, gas: null, pack: [], p: null, php_override: ""
47
- };
48
-
49
- const cookies = api.getCookies();
50
- let host;
51
- if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${ctx.clientId}`;
52
- else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${ctx.clientId}`;
53
- else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${ctx.clientId}`;
54
-
55
- const options = {
56
- clientId: "mqttwsclient",
57
- protocolId: "MQIsdp",
58
- protocolVersion: 3,
59
- username: JSON.stringify(username),
60
- clean: true,
61
- wsOptions: {
62
- headers: {
63
- Cookie: cookies,
64
- Origin: "https://www.facebook.com",
65
- "User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0",
66
- Referer: "https://www.facebook.com/",
67
- Host: "edge-chat.facebook.com",
68
- Connection: "Upgrade",
69
- Pragma: "no-cache",
70
- "Cache-Control": "no-cache",
71
- Upgrade: "websocket",
72
- "Sec-WebSocket-Version": "13",
73
- "Accept-Encoding": "gzip, deflate, br",
74
- "Accept-Language": "vi,en;q=0.9",
75
- "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits"
76
- },
77
- origin: "https://www.facebook.com",
78
- protocolVersion: 13,
79
- binaryType: "arraybuffer"
80
- },
81
- keepalive: 30,
82
- reschedulePings: true,
83
- reconnectPeriod: 0,
84
- connectTimeout: 5000
85
- };
86
- if (ctx.globalOptions.proxy !== undefined) {
87
- const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
88
- options.wsOptions.agent = agent;
89
- }
90
-
91
- ctx.mqttClient = new mqtt.Client(
92
- () => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()),
93
- options
94
- );
95
- const mqttClient = ctx.mqttClient;
96
-
97
- mqttClient.on("error", function (err) {
98
- const msg = String(err && err.message ? err.message : err || "");
99
- if ((ctx._ending || ctx._cycling) && /No subscription existed|client disconnecting/i.test(msg)) {
100
- logger(`mqtt expected during shutdown: ${msg}`, "info");
101
- return;
102
- }
103
-
104
- if (/Not logged in|Not logged in.|blocked the login|401|403/i.test(msg)) {
105
- try {
106
- if (mqttClient && mqttClient.connected) {
107
- mqttClient.end(true);
108
- }
109
- } catch (_) { }
110
- return emitAuth(ctx, api, globalCallback,
111
- /blocked/i.test(msg) ? "login_blocked" : "not_logged_in",
112
- msg
113
- );
114
- }
115
- logger(`mqtt error: ${msg}`, "error");
116
- try {
117
- if (mqttClient && mqttClient.connected) {
118
- mqttClient.end(true);
119
- }
120
- } catch (_) { }
121
- if (ctx._ending || ctx._cycling) return;
122
-
123
- if (ctx.globalOptions.autoReconnect && !ctx._ending) {
124
- const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
125
- logger(`mqtt autoReconnect listenMqtt() in ${d}ms`, "warn");
126
- scheduleReconnect(d);
127
- } else {
128
- globalCallback({ type: "stop_listen", error: msg || "Connection refused" }, null);
129
- }
130
- });
131
-
132
- mqttClient.on("connect", function () {
133
- if (process.env.OnStatus === undefined) {
134
- logger("fca-unofficial", "info");
135
- process.env.OnStatus = true;
136
- }
137
- ctx._cycling = false;
138
-
139
- topics.forEach(t => mqttClient.subscribe(t));
140
-
141
-
142
- const queue = {
143
- sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
144
- encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
145
- };
146
- const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
147
- if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
148
- mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
149
- mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
150
- mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
151
- const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
152
- let rTimeout = setTimeout(function () {
153
- rTimeout = null;
154
- if (ctx._ending) {
155
- logger("mqtt t_ms timeout skipped - ending", "warn");
156
- return;
157
- }
158
- logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
159
- try {
160
- if (mqttClient && mqttClient.connected) {
161
- mqttClient.end(true);
162
- }
163
- } catch (_) { }
164
- scheduleReconnect(d);
165
- }, T_MS_WAIT_TIMEOUT_MS);
166
-
167
- // Store timeout reference for cleanup
168
- ctx._rTimeout = rTimeout;
169
-
170
- ctx.tmsWait = function () {
171
- if (rTimeout) {
172
- clearTimeout(rTimeout);
173
- rTimeout = null;
174
- }
175
- if (ctx._rTimeout) {
176
- delete ctx._rTimeout;
177
- }
178
- if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
179
- delete ctx.tmsWait;
180
- };
181
- });
182
-
183
- mqttClient.on("message", function (topic, message) {
184
- if (ctx._ending) return; // Ignore messages if ending
185
- try {
186
- let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
187
- try {
188
- jsonMessage = JSON.parse(jsonMessage);
189
- } catch (parseErr) {
190
- logger(`mqtt message parse error for topic ${topic}: ${parseErr && parseErr.message ? parseErr.message : String(parseErr)}`, "warn");
191
- jsonMessage = {};
192
- }
193
-
194
- if (jsonMessage.type === "jewel_requests_add") {
195
- globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
196
- } else if (jsonMessage.type === "jewel_requests_remove_old") {
197
- globalCallback(null, { type: "friend_request_cancel", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
198
- } else if (topic === "/t_ms") {
199
- if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();
200
- if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
201
- ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
202
- ctx.syncToken = jsonMessage.syncToken;
203
- }
204
- if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
205
- for (const dlt of (jsonMessage.deltas || [])) {
206
- parseDelta(defaultFuncs, api, ctx, globalCallback, { delta: dlt });
207
- }
208
- } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
209
- const typ = {
210
- type: "typ",
211
- isTyping: !!jsonMessage.state,
212
- from: jsonMessage.sender_fbid.toString(),
213
- threadID: formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
214
- };
215
- globalCallback(null, typ);
216
- } else if (topic === "/orca_presence") {
217
- if (!ctx.globalOptions.updatePresence) {
218
- for (const data of (jsonMessage.list || [])) {
219
- const presence = { type: "presence", userID: String(data.u), timestamp: data.l * 1000, statuses: data.p };
220
- globalCallback(null, presence);
221
- }
222
- }
223
- } else if (topic === "/ls_resp") {
224
- const parsedPayload = JSON.parse(jsonMessage.payload);
225
- const reqID = jsonMessage.request_id;
226
- const tasks = ctx.tasks;
227
- if (tasks && tasks instanceof Map && tasks.has(reqID)) {
228
- const taskData = tasks.get(reqID);
229
- const { type: taskType, callback: taskCallback } = taskData;
230
- const taskRespData = getTaskResponseData(taskType, parsedPayload);
231
- if (taskRespData == null) taskCallback("error", null);
232
- else taskCallback(null, Object.assign({ type: taskType, reqID }, taskRespData));
233
- }
234
- }
235
- } catch (ex) {
236
- const errMsg = ex && ex.message ? ex.message : String(ex || "Unknown error");
237
- logger(`mqtt message handler error: ${errMsg}`, "error");
238
- // Don't crash on message parsing errors, just log and continue
239
- }
240
- });
241
-
242
- mqttClient.on("close", function () {
243
- if (ctx._ending || ctx._cycling) {
244
- logger("mqtt close expected", "info");
245
- return;
246
- }
247
- logger("mqtt connection closed", "warn");
248
- });
249
-
250
- mqttClient.on("disconnect", () => {
251
- if (ctx._ending || ctx._cycling) {
252
- logger("mqtt disconnect expected", "info");
253
- return;
254
- }
255
- logger("mqtt disconnected", "warn");
256
- });
257
- };
258
- };
@@ -1,103 +0,0 @@
1
- "use strict";
2
- /**
3
- * Emits account_inactive to the global callback and cleans up MQTT/timers/scheduler.
4
- * Used when login is invalid, blocked, or session is lost.
5
- */
6
- module.exports = function createEmitAuth({ logger }) {
7
- return function emitAuth(ctx, api, globalCallback, reason, detail) {
8
- // Clean up all timers
9
- try {
10
- if (ctx._autoCycleTimer) {
11
- clearInterval(ctx._autoCycleTimer);
12
- ctx._autoCycleTimer = null;
13
- }
14
- } catch (_) { }
15
- try {
16
- if (ctx._reconnectTimer) {
17
- clearTimeout(ctx._reconnectTimer);
18
- ctx._reconnectTimer = null;
19
- }
20
- } catch (_) { }
21
-
22
- try {
23
- ctx._ending = true;
24
- ctx._cycling = false;
25
- } catch (_) { }
26
-
27
- // Clean up MQTT client
28
- try {
29
- if (ctx.mqttClient) {
30
- ctx.mqttClient.removeAllListeners();
31
- if (ctx.mqttClient.connected) {
32
- ctx.mqttClient.end(true);
33
- }
34
- }
35
- } catch (_) { }
36
-
37
- ctx.mqttClient = undefined;
38
- ctx.loggedIn = false;
39
-
40
- // Clean up timeout references
41
- try {
42
- if (ctx._rTimeout) {
43
- clearTimeout(ctx._rTimeout);
44
- ctx._rTimeout = null;
45
- }
46
- } catch (_) { }
47
-
48
- // Clean up tasks Map to prevent memory leak
49
- try {
50
- if (ctx.tasks && ctx.tasks instanceof Map) {
51
- ctx.tasks.clear();
52
- }
53
- } catch (_) { }
54
-
55
- // Clean up userInfo intervals
56
- try {
57
- if (ctx._userInfoIntervals && Array.isArray(ctx._userInfoIntervals)) {
58
- ctx._userInfoIntervals.forEach(interval => {
59
- try {
60
- clearInterval(interval);
61
- } catch (_) { }
62
- });
63
- ctx._userInfoIntervals = [];
64
- }
65
- } catch (_) { }
66
-
67
- // Clean up autoSave intervals
68
- try {
69
- if (ctx._autoSaveInterval && Array.isArray(ctx._autoSaveInterval)) {
70
- ctx._autoSaveInterval.forEach(interval => {
71
- try {
72
- clearInterval(interval);
73
- } catch (_) { }
74
- });
75
- ctx._autoSaveInterval = [];
76
- }
77
- } catch (_) { }
78
-
79
- // Clean up scheduler
80
- try {
81
- if (ctx._scheduler && typeof ctx._scheduler.destroy === "function") {
82
- ctx._scheduler.destroy();
83
- ctx._scheduler = undefined;
84
- }
85
- } catch (_) { }
86
-
87
- const msg = detail || reason;
88
- logger(`auth change -> ${reason}: ${msg}`, "error");
89
-
90
- if (typeof globalCallback === "function") {
91
- try {
92
- globalCallback({
93
- type: "account_inactive",
94
- reason,
95
- error: msg,
96
- timestamp: Date.now()
97
- }, null);
98
- } catch (cbErr) {
99
- logger(`emitAuth callback error: ${cbErr && cbErr.message ? cbErr.message : String(cbErr)}`, "error");
100
- }
101
- }
102
- };
103
- };
@@ -1,320 +0,0 @@
1
- "use strict";
2
- /**
3
- * Fetches MQTT sync sequence ID from GraphQL and starts listenMqtt.
4
- * Handles retries and auto re-login via fca-config.json when session expires.
5
- */
6
- const { getType } = require("../../../utils/format");
7
- const { parseAndCheckLogin, saveCookies } = require("../../../utils/client");
8
- const path = require("path");
9
- const loginHelper = require("../../../../module/loginHelper");
10
-
11
- function getConfig() {
12
- try {
13
- const configPath = path.join(process.cwd(), "fca-config.json");
14
- const fs = require("fs");
15
- if (fs.existsSync(configPath)) {
16
- return JSON.parse(fs.readFileSync(configPath, "utf8"));
17
- }
18
- } catch { }
19
- return {};
20
- }
21
-
22
- // Parse cookie string to array format
23
- function parseCookieString(cookieStr) {
24
- if (!cookieStr || typeof cookieStr !== "string") return [];
25
- const cookies = [];
26
- const pairs = cookieStr.split(";").map(p => p.trim()).filter(Boolean);
27
- for (const pair of pairs) {
28
- const eq = pair.indexOf("=");
29
- if (eq > 0) {
30
- const key = pair.slice(0, eq).trim();
31
- const value = pair.slice(eq + 1).trim();
32
- if (key && value) {
33
- cookies.push({
34
- key,
35
- value,
36
- domain: ".facebook.com",
37
- path: "/"
38
- });
39
- }
40
- }
41
- }
42
- return cookies;
43
- }
44
-
45
- // Try to auto-login using API and refresh web session
46
- async function tryAutoLogin(logger, config, ctx, defaultFuncs) {
47
- const email = config.credentials?.email || config.email;
48
- const password = config.credentials?.password || config.password;
49
- const twofactor = config.credentials?.twofactor || config.twofactor || null;
50
-
51
- if (config.autoLogin === false || !email || !password) {
52
- return null;
53
- }
54
-
55
- logger("getSeqID: attempting auto re-login via API...", "warn");
56
-
57
- try {
58
- const result = await loginHelper.tokensViaAPI(
59
- email,
60
- password,
61
- twofactor,
62
- config.apiServer || null
63
- );
64
-
65
- if (result && result.status) {
66
- const normalizeCookieHeaderString = loginHelper.normalizeCookieHeaderString;
67
- let cookiePairs = [];
68
-
69
- if (typeof result.cookies === "string") {
70
- cookiePairs = normalizeCookieHeaderString(result.cookies);
71
- }
72
- // If cookies is an array, convert to pairs
73
- else if (Array.isArray(result.cookies)) {
74
- cookiePairs = result.cookies.map(c => {
75
- if (typeof c === "string") {
76
- // Already in "key=value" format
77
- return c;
78
- } else if (c && typeof c === "object") {
79
- // Object format {key, value} or {name, value}
80
- return `${c.key || c.name}=${c.value}`;
81
- }
82
- return null;
83
- }).filter(Boolean);
84
- }
85
-
86
- // Also check for cookie field (alternative field name)
87
- if (cookiePairs.length === 0 && result.cookie) {
88
- if (typeof result.cookie === "string") {
89
- cookiePairs = normalizeCookieHeaderString(result.cookie);
90
- } else if (Array.isArray(result.cookie)) {
91
- cookiePairs = result.cookie.map(c => {
92
- if (typeof c === "string") return c;
93
- if (c && typeof c === "object") return `${c.key || c.name}=${c.value}`;
94
- return null;
95
- }).filter(Boolean);
96
- }
97
- }
98
-
99
- if (cookiePairs.length > 0 || result.uid) {
100
- logger(`getSeqID: auto re-login successful! UID: ${result.uid}, Cookies: ${cookiePairs.length}`, "info");
101
-
102
- // Apply cookies to ctx.jar using setJarFromPairs approach
103
- if (ctx.jar && cookiePairs.length > 0) {
104
- const expires = new Date(Date.now() + 31536e6).toUTCString();
105
- for (const kv of cookiePairs) {
106
- const cookieStr = `${kv}; expires=${expires}; domain=.facebook.com; path=/;`;
107
- try {
108
- if (typeof ctx.jar.setCookieSync === "function") {
109
- ctx.jar.setCookieSync(cookieStr, "https://www.facebook.com");
110
- } else if (typeof ctx.jar.setCookie === "function") {
111
- await ctx.jar.setCookie(cookieStr, "https://www.facebook.com");
112
- }
113
- } catch (err) {
114
- logger(`getSeqID: Failed to set cookie ${kv.substring(0, 50)}: ${err && err.message ? err.message : String(err)}`, "warn");
115
- }
116
- }
117
- logger(`getSeqID: applied ${cookiePairs.length} API cookies to jar`, "info");
118
- }
119
-
120
- // Now refresh web session to get proper web cookies
121
- logger("getSeqID: refreshing web session after API login...", "info");
122
- try {
123
- const { get, jar: globalJar } = require("../../../utils/request");
124
- const { saveCookies: saveWebCookies } = require("../../../utils/client");
125
-
126
- // Apply cookies to global jar as well using cookie pairs
127
- const expires = new Date(Date.now() + 31536e6).toUTCString();
128
- for (const kv of cookiePairs) {
129
- const cookieStr = `${kv}; expires=${expires}; domain=.facebook.com; path=/;`;
130
- try {
131
- if (typeof globalJar.setCookieSync === "function") {
132
- globalJar.setCookieSync(cookieStr, "https://www.facebook.com");
133
- } else if (typeof globalJar.setCookie === "function") {
134
- await globalJar.setCookie(cookieStr, "https://www.facebook.com");
135
- }
136
- } catch (err) {
137
- logger(`getSeqID: Failed to set cookie in global jar ${kv.substring(0, 50)}: ${err && err.message ? err.message : String(err)}`, "warn");
138
- }
139
- }
140
-
141
- // Fetch Facebook to refresh and get web session cookies
142
- // Do this multiple times to ensure session is fully established
143
- // Try both m.facebook.com and www.facebook.com
144
- let webResponse = null;
145
- let htmlContent = "";
146
- const htmlUID = body => {
147
- const s = typeof body === "string" ? body : String(body ?? "");
148
- return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
149
- };
150
- const isValidUID = uid => uid && uid !== "0" && /^\d+$/.test(uid) && parseInt(uid, 10) > 0;
151
- const urlsToTry = ["https://m.facebook.com/", "https://www.facebook.com/"];
152
-
153
- // Try refreshing up to 3 times to get valid session
154
- for (let attempt = 0; attempt < 3; attempt++) {
155
- try {
156
- // Try m.facebook.com first (mobile version often works better for API login)
157
- const urlToUse = attempt === 0 ? urlsToTry[0] : urlsToTry[attempt % urlsToTry.length];
158
- logger(`getSeqID: Refreshing ${urlToUse} (attempt ${attempt + 1}/3)...`, "info");
159
-
160
- webResponse = await get(urlToUse, ctx.jar, null, ctx.globalOptions, ctx);
161
- if (webResponse && webResponse.data) {
162
- await saveWebCookies(ctx.jar)(webResponse);
163
- htmlContent = typeof webResponse.data === "string" ? webResponse.data : String(webResponse.data || "");
164
-
165
- // Check if HTML contains valid USER_ID
166
- const htmlUserID = htmlUID(htmlContent);
167
- if (isValidUID(htmlUserID)) {
168
- logger(`getSeqID: Found valid USER_ID in HTML from ${urlToUse}: ${htmlUserID}`, "info");
169
- break;
170
- } else if (attempt < 2) {
171
- logger(`getSeqID: No valid USER_ID in HTML from ${urlToUse} (attempt ${attempt + 1}/3), retrying...`, "warn");
172
- await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
173
- }
174
- }
175
- } catch (refreshErr) {
176
- logger(`getSeqID: Error refreshing session (attempt ${attempt + 1}/3): ${refreshErr && refreshErr.message ? refreshErr.message : String(refreshErr)}`, "warn");
177
- if (attempt < 2) {
178
- await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
179
- }
180
- }
181
- }
182
-
183
- if (webResponse && webResponse.data) {
184
- // Get updated cookies from jar
185
- const updatedCookies = await ctx.jar.getCookies("https://www.facebook.com");
186
- logger(`getSeqID: refreshed session, now have ${updatedCookies.length} web cookies`, "info");
187
-
188
- // Check HTML for USER_ID
189
- const htmlUserID = htmlUID(htmlContent);
190
- if (!isValidUID(htmlUserID)) {
191
- logger("getSeqID: WARNING - HTML does not show valid USER_ID after refresh. Session may not be fully established.", "warn");
192
- }
193
-
194
- // Update ctx state
195
- if (ctx) {
196
- ctx.loggedIn = true;
197
- // Use USER_ID from HTML if available, otherwise from API response
198
- if (isValidUID(htmlUserID)) {
199
- ctx.userID = htmlUserID;
200
- logger(`getSeqID: Updated ctx.userID from HTML: ${htmlUserID}`, "info");
201
- } else if (result.uid && isValidUID(result.uid)) {
202
- ctx.userID = result.uid;
203
- logger(`getSeqID: Updated ctx.userID from API: ${result.uid}`, "info");
204
- }
205
- }
206
- } else {
207
- logger("getSeqID: Failed to refresh web session after API login", "error");
208
- }
209
- } catch (refreshErr) {
210
- logger(`getSeqID: web session refresh failed - ${refreshErr && refreshErr.message ? refreshErr.message : String(refreshErr)}`, "warn");
211
- }
212
-
213
- return { ...result, cookies };
214
- }
215
- }
216
-
217
- logger(`getSeqID: auto re-login failed - ${result && result.message ? result.message : "unknown error"}`, "error");
218
- } catch (loginErr) {
219
- logger(`getSeqID: auto re-login error - ${loginErr && loginErr.message ? loginErr.message : String(loginErr)}`, "error");
220
- }
221
-
222
- return null;
223
- }
224
-
225
- module.exports = function createGetSeqID(deps) {
226
- const { listenMqtt, logger, emitAuth } = deps;
227
-
228
- return function getSeqID(defaultFuncs, api, ctx, globalCallback, form, retryCount = 0) {
229
- const MAX_RETRIES = 3;
230
- const RETRY_DELAY = 2000;
231
- ctx.t_mqttCalled = false;
232
-
233
- return defaultFuncs
234
- .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
235
- .then(parseAndCheckLogin(ctx, defaultFuncs))
236
- .then(async resData => {
237
- // Better error handling: check what we actually got
238
- if (getType(resData) !== "Array") {
239
- // Log what we actually received for debugging
240
- logger(`getSeqID: Unexpected response type: ${getType(resData)}, value: ${JSON.stringify(resData).substring(0, 200)}`, "warn");
241
-
242
- // If parseAndCheckLogin returned an error object, check if it's an auth error
243
- if (resData && typeof resData === "object") {
244
- const errorMsg = resData.error || resData.message || "";
245
- if (/Not logged in|login|blocked|401|403|checkpoint/i.test(errorMsg)) {
246
- throw { error: "Not logged in", originalResponse: resData };
247
- }
248
- }
249
-
250
- throw { error: "Not logged in", originalResponse: resData };
251
- }
252
- if (!Array.isArray(resData) || !resData.length) return;
253
- const lastRes = resData[resData.length - 1];
254
- if (lastRes && lastRes.successful_results === 0) return;
255
-
256
- const syncSeqId = resData[0]?.o0?.data?.viewer?.message_threads?.sync_sequence_id;
257
- if (syncSeqId) {
258
- ctx.lastSeqId = syncSeqId;
259
- logger("mqtt getSeqID ok -> listenMqtt()", "info");
260
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
261
- } else {
262
- throw { error: "getSeqId: no sync_sequence_id found." };
263
- }
264
- })
265
- .catch(async err => {
266
- const detail = (err && err.detail && err.detail.message) ? ` | detail=${err.detail.message}` : "";
267
- const msg = ((err && err.error) || (err && err.message) || String(err || "")) + detail;
268
-
269
- // Check if this is an auth-related error
270
- const isAuthError = /Not logged in|no sync_sequence_id found|blocked the login|401|403/i.test(msg);
271
-
272
- // For auth errors, try retry + auto-login
273
- if (isAuthError) {
274
- // Retry logic with increasing delay
275
- if (retryCount < MAX_RETRIES) {
276
- const delay = RETRY_DELAY * (retryCount + 1); // Increasing delay: 2s, 4s, 6s
277
- logger(`getSeqID: retry ${retryCount + 1}/${MAX_RETRIES} after ${delay}ms... (error: ${msg})`, "warn");
278
- await new Promise(resolve => setTimeout(resolve, delay));
279
-
280
- // Before retrying, try to refresh the session if this is the first retry
281
- if (retryCount === 0 && ctx.loggedIn) {
282
- try {
283
- logger("getSeqID: refreshing session before retry...", "info");
284
- const { get } = require("../../../utils/request");
285
- const { saveCookies } = require("../../../utils/client");
286
- await get("https://www.facebook.com/", ctx.jar, null, ctx.globalOptions, ctx).then(saveCookies(ctx.jar));
287
- } catch (refreshErr) {
288
- logger(`getSeqID: session refresh failed: ${refreshErr && refreshErr.message ? refreshErr.message : String(refreshErr)}`, "warn");
289
- }
290
- }
291
-
292
- return getSeqID(defaultFuncs, api, ctx, globalCallback, form, retryCount + 1);
293
- }
294
-
295
- // All retries failed, try auto-login
296
- logger("getSeqID: all retries failed, attempting auto re-login...", "warn");
297
- const config = getConfig();
298
- const loginResult = await tryAutoLogin(logger, config, ctx, defaultFuncs);
299
-
300
- if (loginResult) {
301
- // Wait longer after auto-login to ensure session is ready
302
- logger("getSeqID: retrying with new session...", "info");
303
- await new Promise(resolve => setTimeout(resolve, 3000)); // Increased delay to 3s
304
- return getSeqID(defaultFuncs, api, ctx, globalCallback, form, 0);
305
- }
306
-
307
- // Determine the auth error type
308
- if (/blocked/i.test(msg)) {
309
- return emitAuth(ctx, api, globalCallback, "login_blocked", msg);
310
- }
311
- if (/Not logged in/i.test(msg)) {
312
- return emitAuth(ctx, api, globalCallback, "not_logged_in", msg);
313
- }
314
- }
315
-
316
- logger(`getSeqID error: ${msg}`, "error");
317
- return emitAuth(ctx, api, globalCallback, "auth_error", msg);
318
- });
319
- };
320
- };