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,141 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const _ = require("lodash");
7
+ const deepdash = require("deepdash");
8
+ const { JSONPath } = require("jsonpath-plus");
9
+
10
+ deepdash(_);
11
+
12
+ /**
13
+ * Extract and save the full lightspeed_web_request object from thread data.
14
+ * Also search for specific pinned message commands.
15
+ * @param {Object} allJsonData - Raw JSON response from Facebook.
16
+ * @returns {Object|null} The full lightspeed_web_request object or null.
17
+ */
18
+ function extractAndSearchLightspeedRequest(allJsonData) {
19
+ const outputFile = path.join(__dirname, "lightspeed_web_request.json");
20
+
21
+ const lightReq = _.get(allJsonData, "__bbox.result.data.viewer.lightspeed_web_request");
22
+ if (!lightReq) {
23
+ utils.warn("pin.js: lightspeed_web_request not found.");
24
+ return null;
25
+ }
26
+
27
+
28
+ try {
29
+ fs.writeFileSync(outputFile, JSON.stringify(lightReq, null, 2), "utf8");
30
+ utils.log(`pin.js: Saved lightspeed_web_request to ${outputFile}`);
31
+ } catch (err) {
32
+ utils.error("pin.js: Failed to write lightspeed_web_request.json", err);
33
+ }
34
+
35
+
36
+ try {
37
+ const matches = JSONPath({
38
+ path: `$..[?(@ === "setPinnedMessage" || @ === "deleteThenInsertMessage")]`,
39
+ json: lightReq
40
+ });
41
+
42
+ utils.log(`pin.js: Found ${matches.length} matching command(s).`);
43
+ matches.forEach((match, idx) => {
44
+ console.log(`📌 Match ${idx + 1}:`, match);
45
+ });
46
+ } catch (err) {
47
+ utils.error("pin.js: JSONPath search failed.", err);
48
+ }
49
+
50
+ return lightReq;
51
+ }
52
+
53
+ module.exports = function (defaultFuncs, api, ctx) {
54
+ return async function pin(action, threadID, messageID) {
55
+ if (action === "list") {
56
+ if (!threadID) throw new Error('Action "list" requires threadID.');
57
+
58
+ try {
59
+ const url = `https://www.facebook.com/messages/t/${threadID}/`;
60
+ const allJsonData = await utils.json(url, ctx.jar, null, ctx.globalOptions, ctx);
61
+
62
+
63
+ const lightReq = extractAndSearchLightspeedRequest(allJsonData);
64
+ if (!lightReq || !lightReq.pin_status) {
65
+ utils.warn("pin.js: No pinned messages found or pin_status missing.");
66
+ return [];
67
+ }
68
+ return lightReq;
69
+ } catch (err) {
70
+ utils.error(`pin.js: Failed to process "list" for thread ${threadID}`, err);
71
+ throw err;
72
+ }
73
+ }
74
+
75
+ if (!ctx.mqttClient) throw new Error("MQTT not connected.");
76
+ if (!threadID || !messageID) throw new Error(`"${action}" requires threadID and messageID.`);
77
+
78
+ const epoch_id = parseInt(utils.generateOfflineThreadingID());
79
+ const version_id = "9523201934447612";
80
+ const app_id = "2220391788200892";
81
+
82
+ const createMqttRequest = (tasks, increment = 0) => ({
83
+ app_id,
84
+ payload: JSON.stringify({ epoch_id: epoch_id + increment, tasks, version_id }),
85
+ request_id: (ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1),
86
+ type: 3
87
+ });
88
+
89
+ const publishMqtt = (content) =>
90
+ new Promise((resolve, reject) => {
91
+ ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, (err) => {
92
+ if (err) reject(err);
93
+ else resolve({ success: true, request_id: content.request_id });
94
+ });
95
+ });
96
+
97
+ if (action === "pin") {
98
+ const pinTask = {
99
+ label: "430",
100
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
101
+ queue_name: `pin_msg_v2_${threadID}`,
102
+ task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
103
+ };
104
+ const setSearchTask = {
105
+ label: "751",
106
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 1 }),
107
+ queue_name: "set_pinned_message_search",
108
+ task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
109
+ };
110
+ const req1 = createMqttRequest([pinTask], 0);
111
+ const req2 = createMqttRequest([setSearchTask], 1);
112
+ return Promise.all([publishMqtt(req1), publishMqtt(req2)]);
113
+ }
114
+
115
+ if (action === "unpin") {
116
+ const setSearchTask1 = {
117
+ label: "751",
118
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
119
+ queue_name: "set_pinned_message_search",
120
+ task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
121
+ };
122
+ const unpinTask = {
123
+ label: "431",
124
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
125
+ queue_name: `unpin_msg_v2_${threadID}`,
126
+ task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
127
+ };
128
+ const setSearchTask2 = {
129
+ label: "751",
130
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
131
+ queue_name: "set_pinned_message_search",
132
+ task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
133
+ };
134
+ await publishMqtt(createMqttRequest([setSearchTask1], 0));
135
+ await publishMqtt(createMqttRequest([unpinTask], 1));
136
+ return publishMqtt(createMqttRequest([setSearchTask2], 2));
137
+ }
138
+
139
+ throw new Error(`Invalid action: "${action}". Use "pin", "unpin", or "list".`);
140
+ };
141
+ };
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+
3
+ const utils = require("../utils");
4
+ const log = require("npmlog");
5
+
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return function produceMetaTheme(prompt, opts, callback) {
8
+ let resolveFunc, rejectFunc;
9
+ const promise = new Promise((resolve, reject) => {
10
+ resolveFunc = resolve;
11
+ rejectFunc = reject;
12
+ });
13
+
14
+ if (typeof opts === "function") {
15
+ callback = opts;
16
+ opts = {};
17
+ }
18
+ opts = opts || {};
19
+ if (typeof callback !== "function") {
20
+ callback = (err, data) => {
21
+ if (err) return rejectFunc(err);
22
+ resolveFunc(data);
23
+ };
24
+ }
25
+
26
+ if (!prompt || typeof prompt !== "string") {
27
+ callback({ error: "Prompt is required and must be a string" });
28
+ return promise;
29
+ }
30
+
31
+ const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
32
+ const randId = () => Math.floor(Math.random() * 10).toString();
33
+
34
+ const makeInput = () => {
35
+ const desired = ("numThemes" in opts) ? Number(opts.numThemes) : (opts.numThemes || 1);
36
+ const safeCount = clamp(Number.isFinite(desired) ? desired : 1, 1, 5);
37
+ const body = {
38
+ client_mutation_id: randId(),
39
+ actor_id: ctx.userID,
40
+ bypass_cache: true,
41
+ caller: "MESSENGER",
42
+ num_themes: safeCount,
43
+ prompt: prompt
44
+ };
45
+ if (opts.imageUrl) body.image_url = opts.imageUrl;
46
+ return body;
47
+ };
48
+
49
+ const constructForm = (input) => ({
50
+ av: ctx.userID,
51
+ __aaid: 0,
52
+ __user: ctx.userID,
53
+ __a: 1,
54
+ __req: utils.getSignatureID(),
55
+ __hs: "20358.HYP:comet_pkg.2.1...0",
56
+ dpr: 1,
57
+ __ccg: "EXCELLENT",
58
+ __rev: "1027673511",
59
+ __s: utils.getSignatureID(),
60
+ __hsi: "7554561631547849479",
61
+ __comet_req: 15,
62
+ fb_dtsg: ctx.fb_dtsg,
63
+ jazoest: ctx.jazoest,
64
+ lsd: ctx.fb_dtsg,
65
+ __spin_r: "1027673511",
66
+ __spin_b: "trunk",
67
+ __spin_t: Date.now(),
68
+ __crn: "comet.fbweb.MWInboxHomeRoute",
69
+ qpl_active_flow_ids: "25309433,521485406",
70
+ fb_api_caller_class: "RelayModern",
71
+ fb_api_req_friendly_name: "useGenerateAIThemeMutation",
72
+ variables: JSON.stringify({ input }),
73
+ server_timestamps: true,
74
+ doc_id: "23873748445608673",
75
+ fb_api_analytics_tags: JSON.stringify(["qpl_active_flow_ids=25309433,521485406"])
76
+ });
77
+
78
+ const themeNormal = (t, idx) => ({
79
+ success: true,
80
+ themeId: t.id,
81
+ name: t.accessibility_label,
82
+ description: t.description,
83
+ serialNumber: idx + 1,
84
+ colors: {
85
+ composerBackground: t.composer_background_color,
86
+ backgroundGradient: t.background_gradient_colors,
87
+ titleBarButton: t.title_bar_button_tint_color,
88
+ inboundMessageGradient: t.inbound_message_gradient_colors,
89
+ titleBarText: t.title_bar_text_color,
90
+ composerTint: t.composer_tint_color,
91
+ messageText: t.message_text_color,
92
+ primaryButton: t.primary_button_background_color,
93
+ titleBarBackground: t.title_bar_background_color,
94
+ fallback: t.fallback_color,
95
+ gradient: t.gradient_colors
96
+ },
97
+ backgroundImage: t.background_asset ? t.background_asset.image.uri : null,
98
+ iconImage: t.icon_asset ? t.icon_asset.image.uri : null,
99
+ images: {
100
+ background: t.background_asset ? t.background_asset.image.uri : null,
101
+ icon: t.icon_asset ? t.icon_asset.image.uri : null
102
+ },
103
+ preview_image_urls: t.preview_image_urls || null,
104
+ alternativeThemes: Array.isArray(t.alternative_themes)
105
+ ? t.alternative_themes.map(a => ({
106
+ id: a.id,
107
+ name: a.accessibility_label,
108
+ backgroundImage: a.background_asset ? a.background_asset.image.uri : null,
109
+ iconImage: a.icon_asset ? a.icon_asset.image.uri : null
110
+ }))
111
+ : []
112
+ });
113
+
114
+ const errorMatchers = [
115
+ {
116
+ test: (e) => !!(e && e.message && e.message.includes("not authorized")),
117
+ msg: "This account doesn't have permission to create AI themes. The feature may be restricted for your profile."
118
+ },
119
+ {
120
+ test: (e) => !!(e && e.message && e.message.includes("rate limit")),
121
+ msg: "You're sending requests too quickly. Please slow down and try again shortly."
122
+ },
123
+ {
124
+ test: (e) => !!(e && e.message && e.message.includes("Invalid")),
125
+ msg: "Your request contained invalid parameters. Please review your input and retry."
126
+ },
127
+ {
128
+ test: (e) => !!(e && e.statusCode === 403),
129
+ msg: "Access forbidden. Your account might not support generating Meta AI chat themes."
130
+ },
131
+ {
132
+ test: (e) => !!(e && e.statusCode === 429),
133
+ msg: "Request limit hit. Take a brief pause before sending another request."
134
+ }
135
+ ];
136
+
137
+ const friendlyError = (err) => {
138
+ for (let i = 0; i < errorMatchers.length; i++) {
139
+ if (errorMatchers[i].test(err)) return errorMatchers[i].msg;
140
+ }
141
+ return "Something went wrong while producing your theme.";
142
+ };
143
+
144
+ (async function run() {
145
+ try {
146
+ const formData = constructForm(makeInput());
147
+ const raw = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, formData);
148
+ const checked = await utils.parseAndCheckLogin(ctx, defaultFuncs)(raw);
149
+
150
+ if (checked.errors) throw checked.errors;
151
+
152
+ const payload = checked && checked.data && checked.data.xfb_generate_ai_themes_from_prompt;
153
+ if (!payload) throw new Error("Invalid response from AI theme generation");
154
+
155
+ if (!payload.success || !Array.isArray(payload.themes) || payload.themes.length === 0) {
156
+ throw new Error("No themes generated for the given prompt");
157
+ }
158
+
159
+ const normalized = payload.themes.map(themeNormal);
160
+ const out = {
161
+ success: true,
162
+ count: normalized.length,
163
+ themes: normalized,
164
+ ...normalized[0]
165
+ };
166
+
167
+ callback(null, out);
168
+ } catch (err) {
169
+ log.error("produceMetaTheme", err);
170
+ callback({
171
+ error: friendlyError(err),
172
+ originalError: (err && (err.message || err)) || err,
173
+ statusCode: err && err.statusCode ? err.statusCode : null
174
+ });
175
+ }
176
+ })();
177
+
178
+ return promise;
179
+ };
180
+ };
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ const { WebSocket } = require("undici");
4
+ const EventEmitter = require("events");
5
+ const utils = require('../utils');
6
+ const HttpsProxyAgent = require("https-proxy-agent");
7
+
8
+ function formatNotification(data) {
9
+ if (!data.data || !data.data.viewer) return null;
10
+ const notifEdge = data.data.viewer.notifications_page?.edges?.[1]?.node?.notif;
11
+ if (!notifEdge) return null;
12
+
13
+ return {
14
+ type: "notification",
15
+ notifID: notifEdge.notif_id,
16
+ body: notifEdge.body?.text,
17
+ senderID: Object.keys(notifEdge.tracking.from_uids || {})[0],
18
+ url: notifEdge.url,
19
+ timestamp: notifEdge.creation_time.timestamp,
20
+ seenState: notifEdge.seen_state,
21
+ };
22
+ }
23
+
24
+ module.exports = function (defaultFuncs, api, ctx) {
25
+ return function listenRealtime() {
26
+ const emitter = new EventEmitter();
27
+ let ws;
28
+ let reconnectTimeout;
29
+ let keepAliveInterval;
30
+
31
+ const subscriptions = [
32
+ '{"x-dgw-app-XRSS-method":"Falco","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
33
+ '{"x-dgw-app-XRSS-method":"FBGQLS:USER_ACTIVITY_UPDATE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"9525970914181809","x-dgw-app-XRSS-routing_hint":"UserActivitySubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
34
+ '{"x-dgw-app-XRSS-method":"FBGQLS:ACTOR_GATEWAY_EXPERIENCE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"24191710730466150","x-dgw-app-XRSS-routing_hint":"CometActorGatewayExperienceSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
35
+ `{"x-dgw-app-XRSS-method":"FBLQ:comet_notifications_live_query_experimental","x-dgw-app-XRSS-doc_id":"9784489068321501","x-dgw-app-XRSS-actor_id":"${ctx.userID}","x-dgw-app-XRSS-page_id":"${ctx.userID}","x-dgw-app-XRSS-request_stream_retry":"false","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}`,
36
+ '{"x-dgw-app-XRSS-method":"FBGQLS:FRIEND_REQUEST_CONFIRM_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"9687616244672204","x-dgw-app-XRSS-routing_hint":"FriendingCometFriendRequestConfirmSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
37
+ '{"x-dgw-app-XRSS-method":"FBGQLS:FRIEND_REQUEST_RECEIVE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"24047008371656912","x-dgw-app-XRSS-routing_hint":"FriendingCometFriendRequestReceiveSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
38
+ '{"x-dgw-app-XRSS-method":"FBGQLS:RTWEB_CALL_BLOCKED_SETTING_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"24429620016626810","x-dgw-app-XRSS-routing_hint":"RTWebCallBlockedSettingSubscription_CallBlockSettingSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
39
+ '{"x-dgw-app-XRSS-method":"PresenceUnifiedJSON","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
40
+ '{"x-dgw-app-XRSS-method":"FBGQLS:MESSENGER_CHAT_TABS_NOTIFICATION_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"23885219097739619","x-dgw-app-XRSS-routing_hint":"MWChatTabsNotificationSubscription_MessengerChatTabsNotificationSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
41
+ '{"x-dgw-app-XRSS-method":"FBGQLS:BATCH_NOTIFICATION_STATE_CHANGE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"30300156509571373","x-dgw-app-XRSS-routing_hint":"CometBatchNotificationsStateChangeSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
42
+ '{"x-dgw-app-XRSS-method":"FBGQLS:NOTIFICATION_STATE_CHANGE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"23864641996495578","x-dgw-app-XRSS-routing_hint":"CometNotificationsStateChangeSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
43
+ '{"x-dgw-app-XRSS-method":"FBGQLS:NOTIFICATION_STATE_CHANGE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"9754477301332178","x-dgw-app-XRSS-routing_hint":"CometFriendNotificationsStateChangeSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}'
44
+ ];
45
+
46
+ async function handleMessage(data) {
47
+ try {
48
+ const text = await data.text();
49
+ const jsonStart = text.indexOf("{");
50
+ if (jsonStart !== -1) {
51
+ const jsonData = JSON.parse(text.substring(jsonStart));
52
+ if (jsonData.code === 200) {
53
+ utils.log("✅ Subscription success received.");
54
+ emitter.emit("success", jsonData);
55
+ return;
56
+ }
57
+
58
+ const formattedNotif = formatNotification(jsonData);
59
+ if (formattedNotif) {
60
+ emitter.emit("notification", formattedNotif);
61
+ } else {
62
+ emitter.emit("payload", jsonData);
63
+ }
64
+ }
65
+ } catch (err) {
66
+ utils.error("❌ Error parsing WebSocket message:", err);
67
+ emitter.emit("error", err);
68
+ }
69
+ }
70
+
71
+ async function connect() {
72
+ try {
73
+ const queryParams = new URLSearchParams({
74
+ "x-dgw-appid": "2220391788200892",
75
+ "x-dgw-appversion": "0",
76
+ "x-dgw-authtype": "1:0",
77
+ "x-dgw-version": "5",
78
+ "x-dgw-uuid": ctx.userID,
79
+ "x-dgw-tier": "prod",
80
+ "x-dgw-deviceid": ctx.clientID,
81
+ "x-dgw-app-stream-group": "group1"
82
+ });
83
+
84
+ const url = `wss://gateway.facebook.com/ws/realtime?${queryParams.toString()}`;
85
+ const cookies = ctx.jar.getCookiesSync("https://www.facebook.com").join("; ");
86
+
87
+ const baseHeaders = {
88
+ "Cookie": cookies,
89
+ "Origin": "https://www.facebook.com",
90
+ "User-Agent": ctx.globalOptions.userAgent,
91
+ "Referer": "https://www.facebook.com",
92
+ "Host": new URL(url).hostname,
93
+ "Accept-Encoding": "gzip, deflate, br",
94
+ "Accept-Language": "en-US,en;q=0.9"
95
+ };
96
+
97
+ utils.log(`📤 Headers for WebSocket handshake:\n${Object.entries(baseHeaders).map(([k, v]) => `${k}: ${v}`).join("\n")}`);
98
+
99
+ const wsOptions = { headers: baseHeaders };
100
+ if (ctx.globalOptions.proxy) {
101
+ wsOptions.agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
102
+ }
103
+
104
+ ws = new WebSocket(url, wsOptions);
105
+
106
+ ws.onopen = () => {
107
+ utils.log("✅ Connected via undici.WebSocket");
108
+ subscriptions.forEach((payload, index) => {
109
+ const prefix = Buffer.from([14, index, 0, payload.length]);
110
+ const suffix = Buffer.from([0, 0]);
111
+ const fullMessage = Buffer.concat([prefix, Buffer.from(payload), suffix]);
112
+ ws.send(fullMessage);
113
+ });
114
+
115
+ keepAliveInterval = setInterval(() => {
116
+ if (ws.readyState === ws.OPEN) {
117
+ ws.send("ping");
118
+ utils.log("🔁 Sent keep-alive ping.");
119
+ }
120
+ }, 10000);
121
+ };
122
+
123
+ ws.onmessage = (event) => {
124
+ if (event.data instanceof Blob) {
125
+ handleMessage(event.data);
126
+ } else {
127
+ utils.warn("Unknown message type:", typeof event.data);
128
+ }
129
+ };
130
+
131
+ ws.onerror = (err) => {
132
+ utils.error("WebSocket error:", err.message || err);
133
+ emitter.emit("error", err);
134
+ };
135
+
136
+ ws.onclose = () => {
137
+ utils.warn("🔌 WebSocket closed. Reconnecting...");
138
+ clearInterval(keepAliveInterval);
139
+ reconnectTimeout = setTimeout(connect, 1000);
140
+ };
141
+
142
+ } catch (err) {
143
+ utils.error("💥 Connection error:", err.message);
144
+ emitter.emit("error", err);
145
+ clearInterval(keepAliveInterval);
146
+ clearTimeout(reconnectTimeout);
147
+ reconnectTimeout = setTimeout(connect, 1000);
148
+ }
149
+ }
150
+
151
+ connect();
152
+
153
+ emitter.stop = () => {
154
+ clearInterval(keepAliveInterval);
155
+ clearTimeout(reconnectTimeout);
156
+ if (ws) ws.close();
157
+ };
158
+
159
+ return emitter;
160
+ };
161
+ };
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ module.exports = (defaultFuncs, api, ctx) => {
6
+ return async function removeUserFromGroup(userID, threadID, callback) {
7
+ let resolveFunc = () => {};
8
+ let rejectFunc = () => {};
9
+ const returnPromise = new Promise((resolve, reject) => {
10
+ resolveFunc = resolve;
11
+ rejectFunc = reject;
12
+ });
13
+
14
+ if (!callback) {
15
+ callback = (err, result) => {
16
+ if (err) return rejectFunc(err);
17
+ resolveFunc(result);
18
+ };
19
+ }
20
+
21
+ try {
22
+ if (utils.getType(threadID) !== "Number" && utils.getType(threadID) !== "String") {
23
+ throw new Error("threadID should be of type Number or String");
24
+ }
25
+ if (utils.getType(userID) !== "Number" && utils.getType(userID) !== "String") {
26
+ throw new Error("userID should be of type Number or String");
27
+ }
28
+
29
+ if (ctx.mqttClient) {
30
+ const reqID = ++ctx.wsReqNumber;
31
+ const taskID = ++ctx.wsTaskNumber;
32
+
33
+ const payload = {
34
+ epoch_id: utils.generateOfflineThreadingID(),
35
+ tasks: [
36
+ {
37
+ failure_count: null,
38
+ label: '140',
39
+ payload: JSON.stringify({
40
+ thread_id: threadID,
41
+ contact_id: userID,
42
+ sync_group: 1
43
+ }),
44
+ queue_name: 'remove_participant_v2',
45
+ task_id: taskID
46
+ }
47
+ ],
48
+ version_id: '8798795233522156'
49
+ };
50
+
51
+ const form = JSON.stringify({
52
+ app_id: "2220391788200892",
53
+ payload: JSON.stringify(payload),
54
+ request_id: reqID,
55
+ type: 3
56
+ });
57
+
58
+ let responseHandled = false;
59
+ const handleRes = (topic, message) => {
60
+ if (topic !== "/ls_resp" || responseHandled) return;
61
+ let jsonMsg;
62
+ try {
63
+ jsonMsg = JSON.parse(message.toString());
64
+ jsonMsg.payload = JSON.parse(jsonMsg.payload);
65
+ } catch {
66
+ return;
67
+ }
68
+ if (jsonMsg.request_id !== reqID) return;
69
+ responseHandled = true;
70
+ ctx.mqttClient.removeListener("message", handleRes);
71
+ callback(null, { success: true });
72
+ resolveFunc({ success: true });
73
+ };
74
+
75
+ const timeout = setTimeout(() => {
76
+ if (!responseHandled) {
77
+ responseHandled = true;
78
+ ctx.mqttClient.removeListener("message", handleRes);
79
+ const err = new Error("MQTT request timeout");
80
+ callback(err);
81
+ rejectFunc(err);
82
+ }
83
+ }, 30000);
84
+
85
+ ctx.mqttClient.on("message", handleRes);
86
+ ctx.mqttClient.publish("/ls_req", form, { qos: 1, retain: false }, (err) => {
87
+ if (err && !responseHandled) {
88
+ responseHandled = true;
89
+ clearTimeout(timeout);
90
+ ctx.mqttClient.removeListener("message", handleRes);
91
+ callback(err);
92
+ rejectFunc(err);
93
+ }
94
+ });
95
+ } else {
96
+ const form = {
97
+ uid: userID,
98
+ tid: threadID
99
+ };
100
+
101
+ const res = await defaultFuncs.post("https://www.facebook.com/chat/remove_participants", ctx.jar, form)
102
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
103
+
104
+ if (!res || res.error) {
105
+ throw res || new Error("Remove from group failed");
106
+ }
107
+
108
+ callback(null, { success: true });
109
+ }
110
+ } catch (err) {
111
+ utils.error("removeUserFromGroup", err);
112
+ callback(err);
113
+ }
114
+
115
+ return returnPromise;
116
+ };
117
+ };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ /**
6
+ * @module resolvePhotoUrl
7
+ * @description Fetches the direct URL of a Facebook photo using its photo ID.
8
+ * @param {Object} defaultFuncs - An object containing default request functions.
9
+ * @param {Object} api - Facebook API object (unused here but kept for compatibility).
10
+ * @param {Object} ctx - Context object containing cookies (jar) and other session info.
11
+ * @returns {Function} resolvePhotoUrl - A function that takes a photo ID and optional callback, and returns a Promise resolving to the photo URL.
12
+ */
13
+ module.exports = function (defaultFuncs, api, ctx) {
14
+ /**
15
+ * @function resolvePhotoUrl
16
+ * @param {string} photoID - The ID of the Facebook photo to resolve.
17
+ * @param {Function} [callback] - Optional Node-style callback function `(err, photoUrl)`.
18
+ * @returns {Promise<string>} A Promise that resolves to the direct photo URL.
19
+ */
20
+ return function resolvePhotoUrl(photoID, callback) {
21
+ let resolveFunc = function () {};
22
+ let rejectFunc = function () {};
23
+ const returnPromise = new Promise(function (resolve, reject) {
24
+ resolveFunc = resolve;
25
+ rejectFunc = reject;
26
+ });
27
+
28
+ if (!callback) {
29
+ callback = function (err, photoUrl) {
30
+ if (err) {
31
+ return rejectFunc(err);
32
+ }
33
+ resolveFunc(photoUrl);
34
+ };
35
+ }
36
+
37
+ defaultFuncs
38
+ .get("https://www.facebook.com/mercury/attachments/photo", ctx.jar, {
39
+ photo_id: photoID,
40
+ })
41
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
42
+ .then((resData) => {
43
+ if (resData.error) {
44
+ throw resData;
45
+ }
46
+
47
+ const photoUrl = resData.jsmods.require[0][3][0];
48
+
49
+ return callback(null, photoUrl);
50
+ })
51
+ .catch((err) => {
52
+ utils.error("resolvePhotoUrl", err);
53
+ return callback(err);
54
+ });
55
+
56
+ return returnPromise;
57
+ };
58
+ };