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,61 @@
1
+
2
+ 'use strict';
3
+
4
+ const utils = require('../utils');
5
+
6
+ function isCallable(func) {
7
+   try {
8
+     Reflect.apply(func, null, []);
9
+     return true;
10
+   } catch (error) {
11
+     return false;
12
+   }
13
+ }
14
+
15
+ module.exports = function (defaultFuncs, api, ctx) {
16
+   return function setMessageReactionMqtt(reaction, messageID, threadID) {
17
+     if (!ctx.mqttClient) {
18
+       throw new Error('Not connected to MQTT');
19
+     }
20
+
21
+     ctx.wsReqNumber += 1;
22
+     ctx.wsTaskNumber += 1;
23
+
24
+     const taskPayload = {
25
+       thread_key: threadID,
26
+       timestamp_ms: Date.now(),
27
+       message_id: messageID,
28
+       reaction,
29
+       actor_id: ctx.userID,
30
+       reaction_style: null,
31
+       sync_group: 1,
32
+       send_attribution: Math.random() < 0.5 ? 65537 : 524289
33
+     };
34
+
35
+     const task = {
36
+       failure_count: null,
37
+       label: '29',
38
+       payload: JSON.stringify(taskPayload),
39
+       queue_name: JSON.stringify(['reaction', messageID]),
40
+       task_id: ctx.wsTaskNumber
41
+     };
42
+
43
+     const content = {
44
+       app_id: '2220391788200892',
45
+       payload: JSON.stringify({
46
+         data_trace_id: null,
47
+         epoch_id: parseInt(utils.generateOfflineThreadingID()),
48
+         tasks: [task],
49
+         version_id: '7158486590867448',
50
+       }),
51
+       request_id: ctx.wsReqNumber,
52
+       type: 3,
53
+     };
54
+
55
+     /*if (isCallable(callback)) {
56
+       ctx.reqCallbacks[ctx.wsReqNumber] = callback;
57
+     }*/
58
+
59
+     ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false });
60
+   };
61
+ };
@@ -0,0 +1,260 @@
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 setThreadTheme(threadID, themeData, callback) {
8
+ let resolveFunc, rejectFunc;
9
+ const promise = new Promise((resolve, reject) => {
10
+ resolveFunc = resolve;
11
+ rejectFunc = reject;
12
+ });
13
+
14
+ if (!callback) {
15
+ callback = function (err, data) {
16
+ if (err) return rejectFunc(err);
17
+ resolveFunc(data);
18
+ };
19
+ }
20
+
21
+ if (!threadID) {
22
+ return callback({ error: "threadID is required" });
23
+ }
24
+
25
+ (async function worker() {
26
+ try {
27
+ const now = Date.now();
28
+
29
+ // Try to fetch bootloader
30
+ try {
31
+ const bootParams = new URLSearchParams({
32
+ modules: "LSUpdateThreadTheme,LSUpdateThreadCustomEmoji,LSUpdateThreadThemePayloadCacheKey",
33
+ __aaid: 0,
34
+ __user: ctx.userID,
35
+ __a: 1,
36
+ __req: utils.getSignatureID(),
37
+ __hs: "20352.HYP:comet_pkg.2.1...0",
38
+ dpr: 1,
39
+ __ccg: "EXCELLENT",
40
+ __rev: "1027396270",
41
+ __s: utils.getSignatureID(),
42
+ __hsi: "7552524636527201016",
43
+ __comet_req: 15,
44
+ fb_dtsg_ag: ctx.fb_dtsg,
45
+ jazoest: ctx.jazoest,
46
+ __spin_r: "1027396270",
47
+ __spin_b: "trunk",
48
+ __spin_t: now,
49
+ __crn: "comet.fbweb.MWInboxHomeRoute"
50
+ });
51
+
52
+ await defaultFuncs.get(
53
+ "https://www.facebook.com/ajax/bootloader-endpoint/?" + bootParams.toString(),
54
+ ctx.jar
55
+ ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
56
+ } catch (bootErr) {
57
+ log.warn("setThreadTheme", "bootloader fetch failed, continuing");
58
+ }
59
+
60
+ let availableThemes = [];
61
+ try {
62
+ const themeQueryForm = {
63
+ av: ctx.userID,
64
+ __aaid: 0,
65
+ __user: ctx.userID,
66
+ __a: 1,
67
+ __req: utils.getSignatureID(),
68
+ __hs: "20352.HYP:comet_pkg.2.1...0",
69
+ dpr: 1,
70
+ __ccg: "EXCELLENT",
71
+ __rev: "1027396270",
72
+ __s: utils.getSignatureID(),
73
+ __hsi: "7552524636527201016",
74
+ __comet_req: 15,
75
+ fb_dtsg: ctx.fb_dtsg,
76
+ jazoest: ctx.jazoest,
77
+ lsd: ctx.fb_dtsg,
78
+ __spin_r: "1027396270",
79
+ __spin_b: "trunk",
80
+ __spin_t: now,
81
+ __crn: "comet.fbweb.MWInboxHomeRoute",
82
+ qpl_active_flow_ids: "25308101",
83
+ fb_api_caller_class: "RelayModern",
84
+ fb_api_req_friendly_name: "MWPThreadThemeQuery_AllThemesQuery",
85
+ variables: JSON.stringify({ version: "default" }),
86
+ server_timestamps: true,
87
+ doc_id: "24474714052117636"
88
+ };
89
+
90
+ const themeRes = await defaultFuncs
91
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, themeQueryForm)
92
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
93
+
94
+ if (themeRes && themeRes.data && themeRes.data.messenger_thread_themes) {
95
+ availableThemes = themeRes.data.messenger_thread_themes;
96
+ }
97
+ } catch (fetchErr) {
98
+ log.warn("setThreadTheme", "Could not fetch available themes, proceeding without list");
99
+ }
100
+
101
+ let chosenThemeId = null;
102
+ let chosenEmoji = "👍";
103
+
104
+ if (typeof themeData === "string") {
105
+ const s = themeData.trim();
106
+
107
+ if (/^[0-9]+$/.test(s)) {
108
+ chosenThemeId = s;
109
+ } else {
110
+ const found = (availableThemes || []).find(function (t) {
111
+ return (
112
+ t.accessibility_label &&
113
+ t.accessibility_label.toLowerCase().includes(s.toLowerCase())
114
+ );
115
+ });
116
+ if (found) {
117
+ chosenThemeId = found.id;
118
+ } else {
119
+ const palette = {
120
+ blue: "196241301102133",
121
+ purple: "370940413392601",
122
+ green: "169463077092846",
123
+ pink: "230032715012014",
124
+ orange: "175615189761153",
125
+ red: "2136751179887052",
126
+ yellow: "2058653964378557",
127
+ teal: "417639218648241",
128
+ black: "539927563794799",
129
+ white: "2873642392710980",
130
+ default: "196241301102133"
131
+ };
132
+ chosenThemeId = palette[s.toLowerCase()] || palette.default;
133
+ }
134
+ }
135
+ } else if (typeof themeData === "object" && themeData !== null) {
136
+ chosenThemeId = themeData.themeId || themeData.theme_id || themeData.id || null;
137
+ chosenEmoji = themeData.emoji || themeData.customEmoji || chosenEmoji;
138
+ }
139
+
140
+ if (!chosenThemeId) {
141
+ chosenThemeId = "196241301102133";
142
+ }
143
+
144
+ // Try legacy approach first
145
+ try {
146
+ const legacyBody = {
147
+ dpr: 1,
148
+ queries: JSON.stringify({
149
+ o0: {
150
+ doc_id: "1727493033983591",
151
+ query_params: {
152
+ data: {
153
+ actor_id: ctx.userID,
154
+ client_mutation_id: "0",
155
+ source: "SETTINGS",
156
+ theme_id: chosenThemeId,
157
+ thread_id: threadID
158
+ }
159
+ }
160
+ }
161
+ })
162
+ };
163
+
164
+ const legacyResp = await defaultFuncs
165
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, legacyBody)
166
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
167
+
168
+ if (legacyResp && !legacyResp[0]?.o0?.errors) {
169
+ return callback(null, {
170
+ threadID: threadID,
171
+ themeId: chosenThemeId,
172
+ customEmoji: chosenEmoji,
173
+ timestamp: now,
174
+ success: true,
175
+ method: "legacy",
176
+ availableThemes: availableThemes.length > 0
177
+ ? availableThemes.map(function (t) {
178
+ return { id: t.id, name: t.accessibility_label, description: t.description };
179
+ })
180
+ : null
181
+ });
182
+ }
183
+ } catch (legacyErr) {
184
+ log.warn("setThreadTheme", "Legacy approach failed; falling back to GraphQL mutation");
185
+ }
186
+
187
+ // Fall back to GraphQL mutation
188
+ const mutationBody = {
189
+ av: ctx.userID,
190
+ __aaid: 0,
191
+ __user: ctx.userID,
192
+ __a: 1,
193
+ __req: utils.getSignatureID(),
194
+ __hs: "20352.HYP:comet_pkg.2.1...0",
195
+ dpr: 1,
196
+ __ccg: "EXCELLENT",
197
+ __rev: "1027396270",
198
+ __s: utils.getSignatureID(),
199
+ __hsi: "7552524636527201016",
200
+ __comet_req: 15,
201
+ fb_dtsg: ctx.fb_dtsg,
202
+ jazoest: ctx.jazoest,
203
+ lsd: ctx.fb_dtsg,
204
+ __spin_r: "1027396270",
205
+ __spin_b: "trunk",
206
+ __spin_t: now,
207
+ __crn: "comet.fbweb.MWInboxHomeRoute",
208
+ fb_api_caller_class: "RelayModern",
209
+ fb_api_req_friendly_name: "MessengerThreadThemeUpdateMutation",
210
+ variables: JSON.stringify({
211
+ input: {
212
+ actor_id: ctx.userID,
213
+ client_mutation_id: Math.floor(Math.random() * 10000).toString(),
214
+ source: "SETTINGS",
215
+ thread_id: threadID.toString(),
216
+ theme_id: chosenThemeId.toString(),
217
+ custom_emoji: chosenEmoji
218
+ }
219
+ }),
220
+ server_timestamps: true,
221
+ doc_id: "9734829906576883"
222
+ };
223
+
224
+ const gqlResult = await defaultFuncs
225
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, mutationBody)
226
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
227
+
228
+ if (gqlResult && gqlResult.errors && gqlResult.errors.length > 0) {
229
+ throw new Error("GraphQL Error: " + JSON.stringify(gqlResult.errors));
230
+ }
231
+
232
+ if (gqlResult && gqlResult.data && gqlResult.data.messenger_thread_theme_update) {
233
+ const updatePayload = gqlResult.data.messenger_thread_theme_update;
234
+ if (updatePayload.errors && updatePayload.errors.length > 0) {
235
+ throw new Error("Theme Update Error: " + JSON.stringify(updatePayload.errors));
236
+ }
237
+ }
238
+
239
+ return callback(null, {
240
+ threadID: threadID,
241
+ themeId: chosenThemeId,
242
+ customEmoji: chosenEmoji,
243
+ timestamp: now,
244
+ success: true,
245
+ method: "graphql",
246
+ availableThemes: availableThemes.length > 0
247
+ ? availableThemes.map(function (t) {
248
+ return { id: t.id, name: t.accessibility_label, description: t.description };
249
+ })
250
+ : null
251
+ });
252
+ } catch (err) {
253
+ log.error("setThreadTheme", err);
254
+ return callback(err);
255
+ }
256
+ })();
257
+
258
+ return promise;
259
+ };
260
+ };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../utils');
4
+
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ return function setThreadThemeMqtt(threadID, themeFBID, callback) {
7
+ if (!ctx.mqttClient) {
8
+ throw new Error('Not connected to MQTT');
9
+ }
10
+
11
+ ctx.wsReqNumber += 1;
12
+ let baseTaskNumber = ++ctx.wsTaskNumber;
13
+
14
+ const makeTask = (label, queueName, extraPayload = {}) => ({
15
+ failure_count: null,
16
+ label: String(label),
17
+ payload: JSON.stringify({
18
+ thread_key: threadID,
19
+ theme_fbid: themeFBID,
20
+ sync_group: 1,
21
+ ...extraPayload,
22
+ }),
23
+ queue_name: typeof queueName === 'string' ? queueName : JSON.stringify(queueName),
24
+ task_id: baseTaskNumber++,
25
+ });
26
+
27
+ const messages = [
28
+ {
29
+ label: 1013,
30
+ queue: ['ai_generated_theme', String(threadID)],
31
+ },
32
+ {
33
+ label: 1037,
34
+ queue: ['msgr_custom_thread_theme', String(threadID)],
35
+ },
36
+ {
37
+ label: 1028,
38
+ queue: ['thread_theme_writer', String(threadID)],
39
+ },
40
+ {
41
+ label: 43,
42
+ queue: 'thread_theme',
43
+ extra: { source: null, payload: null },
44
+ },
45
+ ].map(({ label, queue, extra }) => {
46
+ ctx.wsReqNumber += 1;
47
+ return {
48
+ app_id: '772021112871879',
49
+ payload: JSON.stringify({
50
+ epoch_id: parseInt(utils.generateOfflineThreadingID()),
51
+ tasks: [makeTask(label, queue, extra)],
52
+ version_id: '24227364673632991',
53
+ }),
54
+ request_id: ctx.wsReqNumber,
55
+ type: 3,
56
+ };
57
+ });
58
+
59
+ // Return promise if no callback provided
60
+ if (!callback) {
61
+ return new Promise((resolve, reject) => {
62
+ try {
63
+ messages.forEach((msg, idx) => {
64
+ ctx.mqttClient.publish(
65
+ '/ls_req',
66
+ JSON.stringify(msg),
67
+ { qos: 1, retain: false },
68
+ idx === messages.length - 1 ? (err) => {
69
+ if (err) reject(err);
70
+ else resolve();
71
+ } : undefined
72
+ );
73
+ });
74
+ } catch (err) {
75
+ reject(err);
76
+ }
77
+ });
78
+ }
79
+
80
+ // Callback mode
81
+ try {
82
+ messages.forEach((msg, idx) => {
83
+ ctx.mqttClient.publish(
84
+ '/ls_req',
85
+ JSON.stringify(msg),
86
+ { qos: 1, retain: false },
87
+ idx === messages.length - 1 ? callback : undefined
88
+ );
89
+ });
90
+ } catch (err) {
91
+ callback(err);
92
+ }
93
+ };
94
+ };
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ function formatPreviewResult(data) {
6
+ if (data.errors) {
7
+ throw data.errors[0];
8
+ }
9
+ const previewData = data.data?.xma_preview_data;
10
+ if (!previewData) {
11
+ throw { error: "Could not generate a preview for this post." };
12
+ }
13
+ return {
14
+ postID: previewData.post_id,
15
+ header: previewData.header_title,
16
+ subtitle: previewData.subtitle_text,
17
+ title: previewData.title_text,
18
+ previewImage: previewData.preview_url,
19
+ favicon: previewData.favicon_url,
20
+ headerImage: previewData.header_image_url
21
+ };
22
+ }
23
+
24
+ module.exports = function(defaultFuncs, api, ctx) {
25
+ return async function getPostPreview(postID, callback) {
26
+ let resolveFunc, rejectFunc;
27
+ const returnPromise = new Promise((resolve, reject) => {
28
+ resolveFunc = resolve;
29
+ rejectFunc = reject;
30
+ });
31
+
32
+ const cb = (err, data) => {
33
+ if (callback) callback(err, data);
34
+ if (err) return rejectFunc(err);
35
+ resolveFunc(data);
36
+ };
37
+ if (!postID) {
38
+ cb({ error: "A postID is required to generate a preview." });
39
+ return returnPromise;
40
+ }
41
+
42
+ const variables = {
43
+ shareable_id: postID.toString(),
44
+ scale: 3,
45
+ };
46
+
47
+ // Use configurable doc_id or default (may be outdated)
48
+ // To update:
49
+ // 1. Open Facebook Messenger in browser
50
+ // 2. Open DevTools (F12) → Network tab → Filter by "graphql"
51
+ // 3. Trigger a share/preview action
52
+ // 4. Look for CometXMAProxyShareablePreviewQuery request
53
+ // 5. Copy the doc_id value from the request payload
54
+ // 6. Set ctx.options.sharePreviewDocId = 'NEW_DOC_ID' when logging in
55
+ //
56
+ // Known doc_ids (may expire):
57
+ // - 28939050904374351 (expired as of Nov 2024)
58
+ // - Check ws3-fca or @dongdev packages for potential updates
59
+ const docId = ctx.options?.sharePreviewDocId || '28939050904374351';
60
+
61
+ const form = {
62
+ fb_api_caller_class: 'RelayModern',
63
+ fb_api_req_friendly_name: 'CometXMAProxyShareablePreviewQuery',
64
+ variables: JSON.stringify(variables),
65
+ doc_id: docId
66
+ };
67
+
68
+ try {
69
+ const resData = await defaultFuncs
70
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, form)
71
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
72
+
73
+ // Check for persisted query not found error (case-insensitive)
74
+ if (resData?.errors) {
75
+ const persistedQueryError = resData.errors.find(e => {
76
+ const msg = (e.message || '').toLowerCase();
77
+ return msg.includes('persistedquerynotfound') ||
78
+ msg.includes('document') && msg.includes('not found') ||
79
+ msg.includes('persisted query');
80
+ });
81
+ if (persistedQueryError) {
82
+ const error = {
83
+ error: "Facebook GraphQL doc_id expired. Please update ctx.options.sharePreviewDocId",
84
+ details: "Capture the current doc_id from Messenger web traffic (inspect CometXMAProxyShareablePreviewQuery request)",
85
+ currentDocId: docId,
86
+ fbError: persistedQueryError.message
87
+ };
88
+ utils.error("getPostPreview", error);
89
+ cb(error);
90
+ return returnPromise;
91
+ }
92
+ }
93
+
94
+ const result = formatPreviewResult(resData);
95
+ cb(null, result);
96
+ return returnPromise;
97
+ } catch (err) {
98
+ // Add helpful context for common failure modes
99
+ if (err.message?.includes('UNHANDLED_REJECTION') || err.message?.includes('not found')) {
100
+ err.hint = "The GraphQL doc_id may be outdated. Set ctx.options.sharePreviewDocId with current value from Messenger traffic.";
101
+ }
102
+ utils.error("getPostPreview", err);
103
+ cb(err);
104
+ return returnPromise;
105
+ }
106
+ };
107
+ };
@@ -0,0 +1,66 @@
1
+ /* eslint-disable linebreak-style */
2
+ "use strict";
3
+
4
+ const utils = require('../utils');
5
+
6
+ /**
7
+ * @module shareContact
8
+ * @param {Object} defaultFuncs - The default functions provided by the API.
9
+ * @param {Object} api - The full API object.
10
+ * @param {Object} ctx - The context object.
11
+ * @returns {function(text: string, senderID: string, threadID: string, callback: Function): void} - A function to share a contact.
12
+ */
13
+ module.exports = function(defaultFuncs, api, ctx) {
14
+ /**
15
+ * Shares a user's contact information into a specific thread via MQTT.
16
+ * @param {string} [text] - An optional message to send along with the contact card.
17
+ * @param {string} senderID - The Facebook user ID of the contact you want to share.
18
+ * @param {string} threadID - The ID of the thread where the contact will be shared.
19
+ * @param {Function} [callback] - An optional callback function to be executed.
20
+ */
21
+ return function shareContact(text, senderID, threadID, callback) {
22
+ if (!ctx.mqttClient) {
23
+ throw new Error('Not connected to MQTT');
24
+ }
25
+
26
+ ctx.wsReqNumber ??= 0;
27
+ ctx.wsTaskNumber ??= 0;
28
+
29
+ ctx.wsReqNumber += 1;
30
+ ctx.wsTaskNumber += 1;
31
+
32
+ const queryPayload = {
33
+ contact_id: senderID,
34
+ sync_group: 1,
35
+ text: text || "",
36
+ thread_id: threadID
37
+ };
38
+
39
+ const query = {
40
+ failure_count: null,
41
+ label: '359',
42
+ payload: JSON.stringify(queryPayload),
43
+ queue_name: 'messenger_contact_sharing',
44
+ task_id: Math.random() * 1001 << 0,
45
+ };
46
+
47
+ const context = {
48
+ app_id: '2220391788200892',
49
+ payload: {
50
+ tasks: [query],
51
+ epoch_id: utils.generateOfflineThreadingID(),
52
+ version_id: '7214102258676893',
53
+ },
54
+ request_id: ctx.wsReqNumber,
55
+ type: 3,
56
+ };
57
+
58
+ context.payload = JSON.stringify(context.payload);
59
+
60
+ if (typeof callback === 'function') {
61
+ ctx.callback_Task[ctx.wsReqNumber] = { callback, type: "shareContact" };
62
+ }
63
+
64
+ ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false });
65
+ };
66
+ };