@lazyneoaz/nkxchat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +199 -0
  3. package/examples/login-with-cookies.js +102 -0
  4. package/examples/verify.js +70 -0
  5. package/index.js +2 -0
  6. package/package.json +84 -0
  7. package/src/apis/addExternalModule.js +24 -0
  8. package/src/apis/addUserToGroup.js +108 -0
  9. package/src/apis/changeAdminStatus.js +148 -0
  10. package/src/apis/changeArchivedStatus.js +61 -0
  11. package/src/apis/changeAvatar.js +103 -0
  12. package/src/apis/changeBio.js +69 -0
  13. package/src/apis/changeBlockedStatus.js +54 -0
  14. package/src/apis/changeGroupImage.js +136 -0
  15. package/src/apis/changeThreadColor.js +116 -0
  16. package/src/apis/changeThreadEmoji.js +53 -0
  17. package/src/apis/comment.js +207 -0
  18. package/src/apis/createAITheme.js +129 -0
  19. package/src/apis/createNewGroup.js +79 -0
  20. package/src/apis/createPoll.js +73 -0
  21. package/src/apis/deleteMessage.js +44 -0
  22. package/src/apis/deleteThread.js +52 -0
  23. package/src/apis/editMessage.js +70 -0
  24. package/src/apis/emoji.js +124 -0
  25. package/src/apis/enableAutoSaveAppState.js +69 -0
  26. package/src/apis/fetchThemeData.js +113 -0
  27. package/src/apis/follow.js +81 -0
  28. package/src/apis/forwardAttachment.js +178 -0
  29. package/src/apis/forwardMessage.js +52 -0
  30. package/src/apis/friend.js +243 -0
  31. package/src/apis/gcmember.js +122 -0
  32. package/src/apis/gcname.js +123 -0
  33. package/src/apis/gcrule.js +119 -0
  34. package/src/apis/getAccess.js +111 -0
  35. package/src/apis/getBotInfo.js +88 -0
  36. package/src/apis/getBotInitialData.js +43 -0
  37. package/src/apis/getEmojiUrl.js +40 -0
  38. package/src/apis/getFriendsList.js +79 -0
  39. package/src/apis/getMessage.js +423 -0
  40. package/src/apis/getTheme.js +123 -0
  41. package/src/apis/getThemeInfo.js +116 -0
  42. package/src/apis/getThemePictures.js +87 -0
  43. package/src/apis/getThreadColors.js +119 -0
  44. package/src/apis/getThreadHistory.js +239 -0
  45. package/src/apis/getThreadInfo.js +267 -0
  46. package/src/apis/getThreadList.js +232 -0
  47. package/src/apis/getThreadPictures.js +58 -0
  48. package/src/apis/getUserID.js +117 -0
  49. package/src/apis/getUserInfo.js +513 -0
  50. package/src/apis/getUserInfoV2.js +146 -0
  51. package/src/apis/handleFriendRequest.js +66 -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 +924 -0
  57. package/src/apis/listenSpeed.js +178 -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 +40 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +252 -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 +150 -0
  68. package/src/apis/produceMetaTheme.js +160 -0
  69. package/src/apis/realtime.js +182 -0
  70. package/src/apis/refreshFb_dtsg.js +94 -0
  71. package/src/apis/removeUserFromGroup.js +117 -0
  72. package/src/apis/resolvePhotoUrl.js +58 -0
  73. package/src/apis/searchForThread.js +154 -0
  74. package/src/apis/sendEffect.js +306 -0
  75. package/src/apis/sendMessage.js +353 -0
  76. package/src/apis/sendMessageMqtt.js +255 -0
  77. package/src/apis/sendTypingIndicator.js +40 -0
  78. package/src/apis/setMessageReaction.js +27 -0
  79. package/src/apis/setMessageReactionMqtt.js +61 -0
  80. package/src/apis/setPostReaction.js +118 -0
  81. package/src/apis/setThreadTheme.js +210 -0
  82. package/src/apis/setThreadThemeMqtt.js +94 -0
  83. package/src/apis/setTitle.js +26 -0
  84. package/src/apis/share.js +106 -0
  85. package/src/apis/shareContact.js +66 -0
  86. package/src/apis/stickers.js +257 -0
  87. package/src/apis/story.js +181 -0
  88. package/src/apis/theme.js +233 -0
  89. package/src/apis/unfriend.js +47 -0
  90. package/src/apis/unsendMessage.js +17 -0
  91. package/src/apis/uploadAttachment.js +87 -0
  92. package/src/database/appStateBackup.js +189 -0
  93. package/src/database/models/index.js +56 -0
  94. package/src/database/models/thread.js +31 -0
  95. package/src/database/models/user.js +32 -0
  96. package/src/database/threadData.js +101 -0
  97. package/src/database/userData.js +90 -0
  98. package/src/engine/client.js +92 -0
  99. package/src/engine/models/buildAPI.js +118 -0
  100. package/src/engine/models/loginHelper.js +492 -0
  101. package/src/engine/models/setOptions.js +88 -0
  102. package/src/types/index.d.ts +498 -0
  103. package/src/utils/antiSuspension.js +516 -0
  104. package/src/utils/auth-helpers.js +149 -0
  105. package/src/utils/autoReLogin.js +237 -0
  106. package/src/utils/axios.js +368 -0
  107. package/src/utils/cache.js +54 -0
  108. package/src/utils/clients.js +279 -0
  109. package/src/utils/constants.js +525 -0
  110. package/src/utils/formatters/data/formatAttachment.js +370 -0
  111. package/src/utils/formatters/data/formatDelta.js +109 -0
  112. package/src/utils/formatters/index.js +159 -0
  113. package/src/utils/formatters/value/formatCookie.js +91 -0
  114. package/src/utils/formatters/value/formatDate.js +36 -0
  115. package/src/utils/formatters/value/formatID.js +16 -0
  116. package/src/utils/formatters.js +1369 -0
  117. package/src/utils/headers.js +235 -0
  118. package/src/utils/index.js +152 -0
  119. package/src/utils/monitoring.js +333 -0
  120. package/src/utils/rateLimiter.js +251 -0
  121. package/src/utils/tokenRefresh.js +285 -0
  122. package/src/utils/user-agents.js +238 -0
  123. package/src/utils/validation.js +157 -0
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ /**
7
+ * Made by Choru Official
8
+ * Mqtt
9
+ * Sets the custom emoji for a specific Facebook thread via MQTT.
10
+ *
11
+ * @param {string} emoji The emoji character to set as the custom emoji (e.g., "👍", "❤️").
12
+ * @param {string} threadID The ID of the thread where the emoji will be set.
13
+ * @param {Function} [callback] Optional callback function to be invoked upon completion.
14
+ * @param {string} [initiatorID] The ID of the user who initiated the emoji change (e.g., from event.senderID).
15
+ * @returns {Promise<object>} A promise that resolves with a structured event object on success or rejects on error.
16
+ */
17
+ return function emoji(emoji, threadID, callback, initiatorID) {
18
+ let _callback;
19
+ let _initiatorID;
20
+
21
+ let _resolvePromise;
22
+ let _rejectPromise;
23
+ const returnPromise = new Promise((resolve, reject) => {
24
+ _resolvePromise = resolve;
25
+ _rejectPromise = reject;
26
+ });
27
+
28
+ if (utils.getType(callback) === "Function" || utils.getType(callback) === "AsyncFunction") {
29
+ _callback = callback;
30
+ _initiatorID = initiatorID;
31
+ } else if (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction") {
32
+ _callback = threadID;
33
+ threadID = null;
34
+ _initiatorID = callback;
35
+ } else if (utils.getType(callback) === "string") {
36
+ _initiatorID = callback;
37
+ _callback = undefined;
38
+ } else {
39
+ _callback = undefined;
40
+ _initiatorID = undefined;
41
+ }
42
+
43
+ if (!_callback) {
44
+ _callback = function (__err, __data) {
45
+ if (__err) _rejectPromise(__err);
46
+ else _resolvePromise(__data);
47
+ };
48
+ } else {
49
+ const originalCallback = _callback;
50
+ _callback = function(__err, __data) {
51
+ if (__err) {
52
+ originalCallback(__err);
53
+ _rejectPromise(__err);
54
+ } else {
55
+ originalCallback(null, __data);
56
+ _resolvePromise(__data);
57
+ }
58
+ };
59
+ }
60
+
61
+ _initiatorID = _initiatorID || ctx.userID;
62
+
63
+ threadID = threadID || ctx.threadID;
64
+
65
+ if (!threadID) {
66
+ return _callback(new Error("threadID is required to set an emoji."));
67
+ }
68
+ if (!emoji) {
69
+ return _callback(new Error("An emoji character is required."));
70
+ }
71
+
72
+ if (!ctx.mqttClient) {
73
+ return _callback(new Error("Not connected to MQTT"));
74
+ }
75
+
76
+ ctx.wsReqNumber += 1;
77
+ ctx.wsTaskNumber += 1;
78
+
79
+ const queryPayload = {
80
+ thread_key: threadID.toString(),
81
+ custom_emoji: emoji,
82
+ avatar_sticker_instruction_key_id: null,
83
+ sync_group: 1,
84
+ };
85
+
86
+ const query = {
87
+ failure_count: null,
88
+ label: '100003',
89
+ payload: JSON.stringify(queryPayload),
90
+ queue_name: 'thread_quick_reaction',
91
+ task_id: ctx.wsTaskNumber,
92
+ };
93
+
94
+ const context = {
95
+ app_id: ctx.appID,
96
+ payload: {
97
+ epoch_id: parseInt(utils.generateOfflineThreadingID()),
98
+ tasks: [query],
99
+ version_id: '24631415369801570',
100
+ },
101
+ request_id: ctx.wsReqNumber,
102
+ type: 3,
103
+ };
104
+ context.payload = JSON.stringify(context.payload);
105
+
106
+ ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
107
+ if (err) {
108
+ return _callback(new Error(`MQTT publish failed for emoji: ${err.message || err}`));
109
+ }
110
+
111
+ const emojiChangeEvent = {
112
+ type: "thread_emoji_update",
113
+ threadID: threadID,
114
+ newEmoji: emoji,
115
+ senderID: _initiatorID,
116
+ BotID: ctx.userID,
117
+ timestamp: Date.now(),
118
+ };
119
+ _callback(null, emojiChangeEvent);
120
+ });
121
+
122
+ return returnPromise;
123
+ };
124
+ };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ /**
7
+ * Enables automatic periodic saving of the current appState (session cookies)
8
+ * to a JSON file. Returns a `disable()` function to cancel auto-saving.
9
+ *
10
+ * @example
11
+ * const disable = api.enableAutoSaveAppState({ filePath: "./appstate.json" });
12
+ * // Later:
13
+ * disable();
14
+ *
15
+ * @param {object} [options]
16
+ * @param {string} [options.filePath="./appstate.json"] Path to save the appState JSON.
17
+ * @param {number} [options.interval=600000] Save interval in ms (default: 10 min).
18
+ * @param {boolean} [options.saveOnLogin=true] Save once immediately on call.
19
+ * @returns {Function} `disable()` — stops the auto-save timer.
20
+ */
21
+ module.exports = function (defaultFuncs, api, ctx) {
22
+ return function enableAutoSaveAppState(options) {
23
+ options = options || {};
24
+ const filePath = options.filePath || path.join(process.cwd(), "appstate.json");
25
+ const interval = options.interval || 10 * 60 * 1000;
26
+ const saveOnLogin = options.saveOnLogin !== false;
27
+
28
+ function saveAppState() {
29
+ try {
30
+ const appState = api.getAppState ? api.getAppState() : null;
31
+ if (!appState || (Array.isArray(appState) && appState.length === 0)) {
32
+ return;
33
+ }
34
+ const payload = Array.isArray(appState) ? appState : (appState.appState || appState);
35
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
36
+ } catch (err) {
37
+ // Silently ignore write errors (e.g. read-only FS in some environments)
38
+ }
39
+ }
40
+
41
+ let immediateTimer = null;
42
+ if (saveOnLogin) {
43
+ immediateTimer = setTimeout(() => {
44
+ saveAppState();
45
+ immediateTimer = null;
46
+ }, 2000);
47
+ }
48
+
49
+ const timerId = setInterval(saveAppState, interval);
50
+
51
+ // Track timers on ctx so they can be cleaned up on logout
52
+ if (!ctx._autoSaveIntervals) ctx._autoSaveIntervals = [];
53
+ ctx._autoSaveIntervals.push(timerId);
54
+
55
+ function disable() {
56
+ if (immediateTimer) {
57
+ clearTimeout(immediateTimer);
58
+ immediateTimer = null;
59
+ }
60
+ clearInterval(timerId);
61
+ if (ctx._autoSaveIntervals) {
62
+ const i = ctx._autoSaveIntervals.indexOf(timerId);
63
+ if (i !== -1) ctx._autoSaveIntervals.splice(i, 1);
64
+ }
65
+ }
66
+
67
+ return disable;
68
+ };
69
+ };
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+
3
+ const utils = require("../utils");
4
+
5
+ /**
6
+ * Fetches detailed color data for a single theme by its numeric ID.
7
+ * Uses the MWPThreadThemeProviderQuery GraphQL endpoint.
8
+ *
9
+ * @param {string} themeID Numeric theme ID (e.g. "196241301102133")
10
+ * @param {Function} [callback]
11
+ * @returns {Promise<ThemeData>}
12
+ */
13
+ module.exports = function (defaultFuncs, api, ctx) {
14
+ return function fetchThemeData(themeID, callback) {
15
+ let resolveFunc, rejectFunc;
16
+ const promise = new Promise((resolve, reject) => {
17
+ resolveFunc = resolve;
18
+ rejectFunc = reject;
19
+ });
20
+
21
+ const done = callback || function (err, data) {
22
+ if (err) return rejectFunc(err);
23
+ resolveFunc(data);
24
+ };
25
+
26
+ if (!themeID) {
27
+ done(new Error("Theme ID is a required parameter"));
28
+ return promise;
29
+ }
30
+
31
+ const payload = {
32
+ av: ctx.userID,
33
+ __user: ctx.userID,
34
+ __a: 1,
35
+ __req: utils.getSignatureID(),
36
+ fb_dtsg: ctx.fb_dtsg,
37
+ lsd: ctx.lsd || ctx.fb_dtsg,
38
+ jazoest: ctx.jazoest,
39
+ fb_api_caller_class: "RelayModern",
40
+ fb_api_req_friendly_name: "MWPThreadThemeProviderQuery",
41
+ variables: JSON.stringify({ id: String(themeID) }),
42
+ server_timestamps: true,
43
+ doc_id: "9734829906576883",
44
+ };
45
+
46
+ defaultFuncs
47
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, payload)
48
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
49
+ .then(async (res) => {
50
+ if (res.errors) throw new Error(JSON.stringify(res.errors));
51
+
52
+ const data = res && res.data && res.data.messenger_thread_theme;
53
+ if (!data) throw new Error("Theme data could not be located in the response");
54
+
55
+ const gradientColors = data.gradient_colors || (data.fallback_color ? [data.fallback_color] : []);
56
+ const bgGradient = data.background_gradient_colors || gradientColors;
57
+ const inboundGradient = data.inbound_message_gradient_colors || gradientColors;
58
+
59
+ const output = {
60
+ id: data.id,
61
+ name: data.accessibility_label || data.name || '',
62
+ description: data.description || '',
63
+
64
+ // ── Colors ─────────────────────────────────────────────
65
+ primary_color: gradientColors[0] || data.fallback_color || null,
66
+ fallback_color: data.fallback_color || null,
67
+ gradient_colors: gradientColors,
68
+ background_gradient_colors: bgGradient,
69
+ inbound_message_gradient_colors: inboundGradient,
70
+
71
+ message_text_color: data.message_text_color || null,
72
+ inbound_message_text_color: data.inbound_message_text_color || null,
73
+ composer_background_color: data.composer_background_color || null,
74
+ composer_input_background_color:data.composer_input_background_color|| null,
75
+ composer_tint_color: data.composer_tint_color || null,
76
+ title_bar_background_color: data.title_bar_background_color || null,
77
+ title_bar_text_color: data.title_bar_text_color || null,
78
+ title_bar_button_tint_color: data.title_bar_button_tint_color || null,
79
+ title_bar_attribution_color: data.title_bar_attribution_color || null,
80
+ primary_button_background_color:data.primary_button_background_color|| null,
81
+ hot_like_color: data.hot_like_color || null,
82
+ reaction_pill_background_color: data.reaction_pill_background_color || null,
83
+ secondary_text_color: data.secondary_text_color || null,
84
+ tertiary_text_color: data.tertiary_text_color || null,
85
+ app_color_mode: data.app_color_mode || null,
86
+
87
+ // ── Assets ─────────────────────────────────────────────
88
+ backgroundImage: data.background_asset ? (data.background_asset.image || {}).uri || null : null,
89
+ iconImage: data.icon_asset ? (data.icon_asset.image || {}).uri || null : null,
90
+
91
+ // ── Alternatives (dark mode, etc.) ──────────────────────
92
+ alternative_themes: Array.isArray(data.alternative_themes)
93
+ ? data.alternative_themes.map(a => ({
94
+ id: a.id,
95
+ name: a.accessibility_label || a.name || '',
96
+ backgroundImage: a.background_asset ? (a.background_asset.image || {}).uri || null : null,
97
+ iconImage: a.icon_asset ? (a.icon_asset.image || {}).uri || null : null,
98
+ gradient_colors: a.gradient_colors || [],
99
+ fallback_color: a.fallback_color || null,
100
+ }))
101
+ : [],
102
+ };
103
+
104
+ done(null, output);
105
+ })
106
+ .catch((err) => {
107
+ utils.error("fetchThemeData", err.message || err);
108
+ done(err instanceof Error ? err : new Error(String(err.message || err)));
109
+ });
110
+
111
+ return promise;
112
+ };
113
+ };
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Author @YanMaglinte
5
+ * https://github.com/YANDEVA
6
+ * * Example:
7
+ * api.follow("100090794779367", true);
8
+ * jsdocs @ChoruOfficial
9
+ */
10
+
11
+ /**
12
+ * @param {object} defaultFuncs The default functions for making API requests.
13
+ * @param {object} api The full API object.
14
+ * @param {object} ctx The context object.
15
+ * @returns {function(senderID: string, boolean: boolean, callback?: (err: any, data?: any) => void): void} The follow function.
16
+ */
17
+ module.exports = function (defaultFuncs, api, ctx) {
18
+ /**
19
+ * Follows or unfollows a user on Facebook.
20
+ * @param {string} senderID The ID of the user to follow or unfollow.
21
+ * @param {boolean} boolean Set to `true` to follow the user, `false` to unfollow.
22
+ * @param {(err: any, data?: any) => void} [callback] An optional callback function.
23
+ */
24
+ return function follow(senderID, boolean, callback) {
25
+ let form;
26
+ if (boolean) {
27
+ form = {
28
+ av: ctx.userID,
29
+ fb_api_req_friendly_name: "CometUserFollowMutation",
30
+ fb_api_caller_class: "RelayModern",
31
+ doc_id: "25472099855769847",
32
+ variables: JSON.stringify({
33
+ input: {
34
+ attribution_id_v2:
35
+ "ProfileCometTimelineListViewRoot.react,comet.profile.timeline.list,via_cold_start,1717249218695,723451,250100865708545,,",
36
+ is_tracking_encrypted: true,
37
+ subscribe_location: "PROFILE",
38
+ subscribee_id: senderID,
39
+ tracking: null,
40
+ actor_id: ctx.userID,
41
+ client_mutation_id: "1",
42
+ },
43
+ scale: 1,
44
+ }),
45
+ };
46
+ } else {
47
+ form = {
48
+ av: ctx.userID,
49
+ fb_api_req_friendly_name: "CometUserUnfollowMutation",
50
+ fb_api_caller_class: "RelayModern",
51
+ doc_id: "25472099855769847",
52
+ variables: JSON.stringify({
53
+ action_render_location: "WWW_COMET_FRIEND_MENU",
54
+ input: {
55
+ attribution_id_v2:
56
+ "ProfileCometTimelineListViewRoot.react,comet.profile.timeline.list,tap_search_bar,1717294006136,602597,250100865708545,,",
57
+ is_tracking_encrypted: true,
58
+ subscribe_location: "PROFILE",
59
+ tracking: null,
60
+ unsubscribee_id: senderID,
61
+ actor_id: ctx.userID,
62
+ client_mutation_id: "10",
63
+ },
64
+ scale: 1,
65
+ }),
66
+ };
67
+ }
68
+
69
+ api.httpPost("https://www.facebook.com/api/graphql/", form, (err, data) => {
70
+ if (err) {
71
+ if (typeof callback === "function") {
72
+ callback(err);
73
+ }
74
+ } else {
75
+ if (typeof callback === "function") {
76
+ callback(null, data);
77
+ }
78
+ }
79
+ });
80
+ };
81
+ };
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+
3
+ const utils = require("../utils");
4
+
5
+ /**
6
+ * Forwards one or more attachment IDs to a thread via MQTT.
7
+ *
8
+ * Attachment IDs can be obtained from api.uploadAttachment() or from the
9
+ * attachments field of a received message event.
10
+ *
11
+ * @example
12
+ * // Forward a photo attachment to another thread
13
+ * await api.forwardAttachment(["1234567890"], targetThreadID);
14
+ *
15
+ * @param {string|string[]} attachmentIDs One or more Facebook attachment IDs.
16
+ * @param {string} threadID Target thread to send the attachment(s) to.
17
+ * @param {string} [body] Optional text body to accompany the attachment.
18
+ * @param {Function} [callback]
19
+ * @returns {Promise<{ messageID, threadID }>}
20
+ */
21
+ module.exports = function (defaultFuncs, api, ctx) {
22
+ return function forwardAttachment(attachmentIDs, threadID, body, callback) {
23
+ // Allow omitting body
24
+ if (typeof body === "function") {
25
+ callback = body;
26
+ body = "";
27
+ }
28
+ body = body || "";
29
+
30
+ let resolveFunc, rejectFunc;
31
+ const promise = new Promise((resolve, reject) => {
32
+ resolveFunc = resolve;
33
+ rejectFunc = reject;
34
+ });
35
+ if (!callback) {
36
+ callback = (err, data) => { if (err) return rejectFunc(err); resolveFunc(data); };
37
+ }
38
+
39
+ if (!attachmentIDs) return callback(new Error("attachmentIDs is required"));
40
+ if (!threadID) return callback(new Error("threadID is required"));
41
+
42
+ const ids = Array.isArray(attachmentIDs) ? attachmentIDs : [attachmentIDs];
43
+
44
+ // ── Try MQTT first (preferred) ────────────────────────────────────────
45
+ if (ctx.mqttClient) {
46
+ const otid = utils.generateOfflineThreadingID();
47
+ const epoch_id = utils.generateOfflineThreadingID();
48
+ const timestamp = Date.now();
49
+ const tid = String(threadID);
50
+ const reqID = ++ctx.wsReqNumber;
51
+
52
+ const sendPayload = {
53
+ thread_id: tid,
54
+ otid: otid.toString(),
55
+ source: 2097153,
56
+ send_type: 3, // attachment
57
+ sync_group: 1,
58
+ mark_thread_read: 1,
59
+ text: body || null,
60
+ initiating_source: 0,
61
+ skip_url_preview_gen: 0,
62
+ attachment_fbids: ids,
63
+ };
64
+
65
+ const content = {
66
+ app_id: "2220391788200892",
67
+ payload: JSON.stringify({
68
+ tasks: [{
69
+ label: "46",
70
+ payload: JSON.stringify(sendPayload),
71
+ queue_name: tid,
72
+ task_id: ++ctx.wsTaskNumber,
73
+ failure_count: null,
74
+ }],
75
+ epoch_id,
76
+ version_id: "6120284488008082",
77
+ data_trace_id: null,
78
+ }),
79
+ request_id: reqID,
80
+ type: 3,
81
+ };
82
+
83
+ let done = false;
84
+ const timer = setTimeout(() => {
85
+ if (done) return;
86
+ done = true;
87
+ ctx.mqttClient.removeListener("message", onMsg);
88
+ callback({ error: "Timeout waiting for MQTT ACK" });
89
+ }, 15000);
90
+
91
+ const onMsg = (topic, raw) => {
92
+ if (topic !== "/ls_resp") return;
93
+ let parsed;
94
+ try { parsed = JSON.parse(raw.toString()); parsed.payload = JSON.parse(parsed.payload); } catch { return; }
95
+ if (parsed.request_id !== reqID || done) return;
96
+ done = true;
97
+ clearTimeout(timer);
98
+ ctx.mqttClient.removeListener("message", onMsg);
99
+ callback(null, { threadID, messageID: otid.toString(), timestamp, method: "mqtt" });
100
+ };
101
+
102
+ ctx.mqttClient.on("message", onMsg);
103
+ ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, (err) => {
104
+ if (err && !done) {
105
+ done = true;
106
+ clearTimeout(timer);
107
+ ctx.mqttClient.removeListener("message", onMsg);
108
+ callback(err);
109
+ }
110
+ });
111
+
112
+ return promise;
113
+ }
114
+
115
+ // ── HTTP fallback ─────────────────────────────────────────────────────
116
+ const messageAndOTID = utils.generateOfflineThreadingID();
117
+ const form = {
118
+ client: "mercury",
119
+ action_type: "ma-type:user-generated-message",
120
+ author: "fbid:" + ctx.userID,
121
+ timestamp: Date.now(),
122
+ timestamp_absolute: "Today",
123
+ timestamp_relative: utils.generateTimestampRelative(),
124
+ timestamp_time_passed:"0",
125
+ is_unread: false,
126
+ is_cleared: false,
127
+ is_forward: true,
128
+ is_filtered_content: false,
129
+ is_filtered_content_bh: false,
130
+ is_filtered_content_account: false,
131
+ is_filtered_content_quasar: false,
132
+ is_filtered_content_invalid_app: false,
133
+ is_spoof_warning: false,
134
+ source: "source:chat:web",
135
+ "source_tags[0]": "source:chat",
136
+ body: body,
137
+ html_body: false,
138
+ ui_push_phase: "V3",
139
+ status: "0",
140
+ offline_threading_id: messageAndOTID,
141
+ message_id: messageAndOTID,
142
+ threading_id: utils.generateThreadingID(ctx.clientID),
143
+ "ephemeral_ttl_mode:":"0",
144
+ manual_retry_cnt: "0",
145
+ has_attachment: true,
146
+ signatureID: utils.getSignatureID(),
147
+ thread_fbid: String(threadID),
148
+ };
149
+
150
+ // Add attachment IDs to form
151
+ ids.forEach((id, i) => {
152
+ form[`image_ids[${i}]`] = id;
153
+ });
154
+
155
+ defaultFuncs
156
+ .post("https://www.facebook.com/messaging/send/", ctx.jar, form, { ...ctx, requestThreadID: String(threadID) })
157
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
158
+ .then((resData) => {
159
+ if (!resData) throw new Error("Empty response from messaging/send");
160
+ if (resData.error) throw new Error(JSON.stringify(resData));
161
+
162
+ const actions = (resData.payload && resData.payload.actions) || [];
163
+ const msgInfo = actions.reduce((p, v) => ({
164
+ threadID: v.thread_fbid || p.threadID,
165
+ messageID: v.message_id || p.messageID,
166
+ timestamp: v.timestamp || p.timestamp,
167
+ }), { threadID, messageID: messageAndOTID, timestamp: Date.now() });
168
+
169
+ callback(null, { ...msgInfo, method: "http" });
170
+ })
171
+ .catch((err) => {
172
+ utils.error("forwardAttachment", err.message || err);
173
+ callback(err instanceof Error ? err : new Error(String(err.message || err)));
174
+ });
175
+
176
+ return promise;
177
+ };
178
+ };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ module.exports = (defaultFuncs, api, ctx) => {
6
+ return async function forwardMessage(messageID, threadIDs, 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 (!Array.isArray(threadIDs)) {
23
+ threadIDs = [threadIDs];
24
+ }
25
+
26
+ const form = {
27
+ message_id: messageID
28
+ };
29
+
30
+ threadIDs.forEach(id => {
31
+ form[`recipient_ids[${id}]`] = id;
32
+ });
33
+
34
+ const res = await defaultFuncs.post(
35
+ "https://www.facebook.com/ajax/mercury/forward_message.php",
36
+ ctx.jar,
37
+ form
38
+ ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
39
+
40
+ if (res && res.error) {
41
+ throw res;
42
+ }
43
+
44
+ callback(null, { success: true, forwardedTo: threadIDs });
45
+ } catch (err) {
46
+ utils.error("forwardMessage", err);
47
+ callback(err);
48
+ }
49
+
50
+ return returnPromise;
51
+ };
52
+ };