fca-sahu 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 (92) hide show
  1. package/index.js +552 -0
  2. package/package.json +61 -0
  3. package/src/OldMessage.js +357 -0
  4. package/src/addExternalModule.js +25 -0
  5. package/src/addUserToGroup.js +115 -0
  6. package/src/changeAdminStatus.js +103 -0
  7. package/src/changeArchivedStatus.js +55 -0
  8. package/src/changeAvatar.js +136 -0
  9. package/src/changeAvatarV2.js +86 -0
  10. package/src/changeAvt.js +85 -0
  11. package/src/changeBio.js +76 -0
  12. package/src/changeBlockedStatus.js +49 -0
  13. package/src/changeBlockedStatusMqtt.js +80 -0
  14. package/src/changeCover.js +72 -0
  15. package/src/changeGroupImage.js +135 -0
  16. package/src/changeName.js +79 -0
  17. package/src/changeNickname.js +59 -0
  18. package/src/changeThreadColor.js +65 -0
  19. package/src/changeThreadEmoji.js +55 -0
  20. package/src/changeUsername.js +59 -0
  21. package/src/createCommentPost.js +230 -0
  22. package/src/createNewGroup.js +88 -0
  23. package/src/createPoll.js +71 -0
  24. package/src/createPost.js +276 -0
  25. package/src/deleteMessage.js +56 -0
  26. package/src/deleteThread.js +56 -0
  27. package/src/editMessage.js +68 -0
  28. package/src/editMessageOld.js +67 -0
  29. package/src/follow.js +74 -0
  30. package/src/forwardAttachment.js +60 -0
  31. package/src/friendList.js +103 -0
  32. package/src/getAccess.js +112 -0
  33. package/src/getAvatarUser.js +78 -0
  34. package/src/getCurrentUserID.js +7 -0
  35. package/src/getEmojiUrl.js +29 -0
  36. package/src/getFriendsList.js +83 -0
  37. package/src/getMessage.js +847 -0
  38. package/src/getRegion.js +7 -0
  39. package/src/getThreadHistory.js +680 -0
  40. package/src/getThreadHistoryDeprecated.js +71 -0
  41. package/src/getThreadInfo.js +232 -0
  42. package/src/getThreadInfoDeprecated.js +56 -0
  43. package/src/getThreadList.js +213 -0
  44. package/src/getThreadListDeprecated.js +46 -0
  45. package/src/getThreadPictures.js +59 -0
  46. package/src/getThreadTheme.js +82 -0
  47. package/src/getUID.js +119 -0
  48. package/src/getUserID.js +61 -0
  49. package/src/getUserInfo.js +66 -0
  50. package/src/handleFriendRequest.js +46 -0
  51. package/src/handleMessageRequest.js +47 -0
  52. package/src/httpGet.js +49 -0
  53. package/src/httpPost.js +48 -0
  54. package/src/listenMqtt.js +719 -0
  55. package/src/logout.js +75 -0
  56. package/src/markAsDelivered.js +47 -0
  57. package/src/markAsRead.js +70 -0
  58. package/src/markAsReadAll.js +40 -0
  59. package/src/markAsSeen.js +48 -0
  60. package/src/metaTheme.js +190 -0
  61. package/src/muteThread.js +45 -0
  62. package/src/note.js +228 -0
  63. package/src/refreshFb_dtsg.js +89 -0
  64. package/src/removeSuspiciousAccount.js +79 -0
  65. package/src/removeUserFromGroup.js +79 -0
  66. package/src/resolvePhotoUrl.js +45 -0
  67. package/src/searchForThread.js +53 -0
  68. package/src/searchFriends.js +139 -0
  69. package/src/searchStickers.js +53 -0
  70. package/src/sendFriendRequest.js +113 -0
  71. package/src/sendMessage.js +258 -0
  72. package/src/sendMessageMqtt.js +322 -0
  73. package/src/sendTypingIndicator.js +45 -0
  74. package/src/setActiveStatus.js +93 -0
  75. package/src/setMessageReaction.js +122 -0
  76. package/src/setMessageReactionMqtt.js +62 -0
  77. package/src/setPostReaction.js +112 -0
  78. package/src/setProfileLock.js +98 -0
  79. package/src/setStoryReaction.js +134 -0
  80. package/src/setStorySeen.js +109 -0
  81. package/src/setThreadTheme.js +103 -0
  82. package/src/setTitle.js +90 -0
  83. package/src/shareContact.js +110 -0
  84. package/src/shareLink.js +59 -0
  85. package/src/stopListenMqtt.js +23 -0
  86. package/src/storyManager.js +358 -0
  87. package/src/suggestFriend.js +133 -0
  88. package/src/threadColors.js +131 -0
  89. package/src/unfriend.js +52 -0
  90. package/src/unsendMessage.js +45 -0
  91. package/src/uploadAttachment.js +93 -0
  92. package/utils.js +2962 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * ===========================================================
3
+ * 🧑‍💻 Author: Sheikh Tamim (ST | Sheikh Tamim)
4
+ * 🔰 Owner & Developer
5
+ * 🌐 GitHub: https://github.com/sheikhtamimlover
6
+ * 📸 Instagram: https://instagram.com/sheikh.tamim_lover
7
+ * -----------------------------------------------------------
8
+ * 🕊️ Respect the creator & give proper credits if reused.
9
+ * ===========================================================
10
+ */
11
+ "use strict";
12
+
13
+ const utils = require("../utils");
14
+
15
+ module.exports = function (defaultFuncs, api, ctx) {
16
+ /** Developed by Sheikh Tamim | GitHub: sheikhtamimlover | Instagram: @sheikh.tamim_lover */
17
+ return function searchFriends(searchQuery, callback) {
18
+ let resolveFunc = function () {};
19
+ let rejectFunc = function () {};
20
+ const returnPromise = new Promise(function (resolve, reject) {
21
+ resolveFunc = resolve;
22
+ rejectFunc = reject;
23
+ });
24
+
25
+ if (!callback) {
26
+ callback = function (err, result) {
27
+ if (err) {
28
+ return rejectFunc(err);
29
+ }
30
+ resolveFunc(result);
31
+ };
32
+ }
33
+
34
+ if (!searchQuery || searchQuery.trim().length === 0) {
35
+ return callback(new Error("Search query cannot be empty"));
36
+ }
37
+
38
+ // Enhanced form data based on captured API
39
+ const form = {
40
+ av: ctx.userID,
41
+ __aaid: 0,
42
+ __user: ctx.userID,
43
+ __a: 1,
44
+ __req: utils.getSignatureID(),
45
+ __hs: "20358.HYP:comet_pkg.2.1...0",
46
+ dpr: 1,
47
+ __ccg: "EXCELLENT",
48
+ __rev: "1027694919",
49
+ __s: utils.getSignatureID(),
50
+ __hsi: "7554748243252799467",
51
+ __comet_req: 15,
52
+ fb_dtsg: ctx.fb_dtsg,
53
+ jazoest: ctx.ttstamp,
54
+ lsd: ctx.fb_dtsg,
55
+ __spin_r: "1027694919",
56
+ __spin_b: "trunk",
57
+ __spin_t: Date.now(),
58
+ fb_api_caller_class: "RelayModern",
59
+ fb_api_req_friendly_name: "ProfileCometAppCollectionSelfFriendsListRendererPaginationQuery",
60
+ variables: JSON.stringify({
61
+ count: 20, // Increased count for better results
62
+ cursor: null,
63
+ scale: 1,
64
+ search: searchQuery.trim(),
65
+ id: "YXBwX2NvbGxlY3Rpb246cGZiaWQwMkJSM3NDeXRjNkJIeVVXem9OeUxNcjNoYnVDclRFZkdCcVlEaXZuSlZYOUNLR2pXVmRyYTQ4U29FalJTVzduMm03NlhDa0xEQXAybVVUenF6RXZraGc3ZHkyaGw="
66
+ }),
67
+ server_timestamps: true,
68
+ doc_id: "31767020089578751"
69
+ };
70
+
71
+ defaultFuncs
72
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, form)
73
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
74
+ .then(function (resData) {
75
+ if (!resData || !resData.data) {
76
+ throw { error: "searchFriends returned empty object." };
77
+ }
78
+ if (resData.error) {
79
+ throw resData;
80
+ }
81
+
82
+ const friendsData = resData.data.node?.pageItems?.edges || [];
83
+ const formattedFriends = friendsData.map(edge => {
84
+ const friend = edge.node;
85
+ const friendUser = friend.node || friend;
86
+
87
+ // Extract mutual friends count from subtitle
88
+ let mutualFriends = 0;
89
+ if (friend.subtitle_text?.text) {
90
+ const mutualMatch = friend.subtitle_text.text.match(/(\d+)\s+mutual\s+friend/i);
91
+ if (mutualMatch) {
92
+ mutualFriends = parseInt(mutualMatch[1]);
93
+ }
94
+ }
95
+
96
+ return {
97
+ userID: friendUser.id || friend.id,
98
+ name: friend.title?.text || friendUser.name || friend.name,
99
+ profilePicture: friend.image?.uri || null,
100
+ profileUrl: friend.url || friendUser.url,
101
+ subtitle: friend.subtitle_text?.text || "",
102
+ mutualFriends: mutualFriends,
103
+ // Additional fields from the captured API
104
+ cursor: edge.cursor,
105
+ friendshipStatus: friendUser.friendship_status || "UNKNOWN",
106
+ gender: friendUser.gender || null,
107
+ shortName: friendUser.short_name || null
108
+ };
109
+ }).filter(friend => friend.userID && friend.name);
110
+
111
+ // Sort by relevance (exact matches first, then by mutual friends)
112
+ formattedFriends.sort((a, b) => {
113
+ const queryLower = searchQuery.toLowerCase();
114
+ const aNameLower = a.name.toLowerCase();
115
+ const bNameLower = b.name.toLowerCase();
116
+
117
+ // Exact matches first
118
+ if (aNameLower === queryLower && bNameLower !== queryLower) return -1;
119
+ if (bNameLower === queryLower && aNameLower !== queryLower) return 1;
120
+
121
+ // Then by starts with
122
+ if (aNameLower.startsWith(queryLower) && !bNameLower.startsWith(queryLower)) return -1;
123
+ if (bNameLower.startsWith(queryLower) && !aNameLower.startsWith(queryLower)) return 1;
124
+
125
+ // Then by mutual friends count
126
+ return b.mutualFriends - a.mutualFriends;
127
+ });
128
+
129
+ callback(null, formattedFriends);
130
+ })
131
+ .catch(function (err) {
132
+ console.error("searchFriends error:", err);
133
+ return callback(err);
134
+ });
135
+
136
+ return returnPromise;
137
+ };
138
+ };
139
+ /** Developed by Sheikh Tamim | GitHub: sheikhtamimlover | Please give credits if reused. */
@@ -0,0 +1,53 @@
1
+
2
+ 'use strict';
3
+
4
+ var utils = require('../utils.js');
5
+ var log = require('npmlog');
6
+
7
+ module.exports = function (http, api, ctx) {
8
+ function formatData(res) {
9
+ return {
10
+ id: res.node.id,
11
+ image: res.node.image,
12
+ package: res.node.pack != null ? {
13
+ name: res.node.pack.name,
14
+ id: res.node.pack.id
15
+ } : {},
16
+ label: res.node.label
17
+ };
18
+ }
19
+
20
+ return function searchStickers(query = '', callback) {
21
+ var cb;
22
+ var returnPromise = new Promise(function (resolve, reject) {
23
+ cb = function (error, data) {
24
+ data ? resolve(data) : reject(error);
25
+ }
26
+ });
27
+
28
+ if (typeof callback == 'function') cb = callback;
29
+
30
+ var form = {
31
+ fb_api_req_friendly_name: 'StickersFlyoutTagSelectorQuery',
32
+ variables: JSON.stringify({
33
+ stickerWidth: 64,
34
+ stickerHeight: 64,
35
+ stickerInterface: 'messages',
36
+ query
37
+ }),
38
+ doc_id: '4642836929159953'
39
+ }
40
+ http
41
+ .post('https://www.facebook.com/api/graphql/', ctx.jar, form)
42
+ .then(utils.parseAndCheckLogin(ctx, http))
43
+ .then(function (res) {
44
+ return cb(null, res.data.sticker_search.sticker_results.edges.map(formatData));
45
+ })
46
+ .catch(function (err) {
47
+ console.error('searchStickers', err);
48
+ return cb(err);
49
+ });
50
+ return returnPromise;
51
+ }
52
+ }
53
+
@@ -0,0 +1,113 @@
1
+ /**
2
+ * ===========================================================
3
+ * 🧑‍💻 Author: Sheikh Tamim (ST | Sheikh Tamim)
4
+ * 🔰 Owner & Developer
5
+ * 🌐 GitHub: https://github.com/sheikhtamimlover
6
+ * 📸 Instagram: https://instagram.com/sheikh.tamim_lover
7
+ * -----------------------------------------------------------
8
+ * 🕊️ Respect the creator & give proper credits if reused.
9
+ * ===========================================================
10
+ */
11
+ "use strict";
12
+
13
+ var utils = require("../utils");
14
+ var log = require("npmlog");
15
+
16
+ module.exports = function (defaultFuncs, api, ctx) {
17
+ /** Developed by Sheikh Tamim | GitHub: sheikhtamimlover | Instagram: @sheikh.tamim_lover */
18
+ return function sendFriendRequest(userID, callback) {
19
+ var resolveFunc = function () { };
20
+ var rejectFunc = function () { };
21
+ var returnPromise = new Promise(function (resolve, reject) {
22
+ resolveFunc = resolve;
23
+ rejectFunc = reject;
24
+ });
25
+
26
+ if (!callback) {
27
+ callback = function (err, data) {
28
+ if (err) return rejectFunc(err);
29
+ resolveFunc(data);
30
+ };
31
+ }
32
+
33
+ if (!userID) {
34
+ return callback({ error: "User ID is required" });
35
+ }
36
+
37
+ var form = {
38
+ av: ctx.userID,
39
+ __aaid: 0,
40
+ __user: ctx.userID,
41
+ __a: 1,
42
+ __req: utils.getSignatureID(),
43
+ __hs: "20353.HYP:comet_pkg.2.1...0",
44
+ dpr: 1,
45
+ __ccg: "EXCELLENT",
46
+ __rev: "1027405870",
47
+ __s: utils.getSignatureID(),
48
+ __hsi: "7552782279085106329",
49
+ __comet_req: 15,
50
+ fb_dtsg: ctx.fb_dtsg,
51
+ jazoest: ctx.ttstamp,
52
+ lsd: ctx.fb_dtsg,
53
+ __spin_r: "1027405870",
54
+ __spin_b: "trunk",
55
+ __spin_t: Date.now(),
56
+ __crn: "comet.fbweb.CometFriendingRoute",
57
+ fb_api_caller_class: "RelayModern",
58
+ fb_api_req_friendly_name: "FriendingCometFriendRequestSendMutation",
59
+ variables: JSON.stringify({
60
+ input: {
61
+ click_correlation_id: Date.now().toString(),
62
+ click_proof_validation_result: '{"validated":true}',
63
+ friend_requestee_ids: [userID.toString()],
64
+ friending_channel: "FRIENDS_HOME_MAIN",
65
+ warn_ack_for_ids: [],
66
+ actor_id: ctx.userID,
67
+ client_mutation_id: Math.floor(Math.random() * 10).toString()
68
+ },
69
+ scale: 1
70
+ }),
71
+ server_timestamps: true,
72
+ doc_id: "24614631718227645"
73
+ };
74
+
75
+ defaultFuncs
76
+ .post("https://www.facebook.com/api/graphql/", ctx.jar, form)
77
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
78
+ .then(function (resData) {
79
+ if (resData.error) {
80
+ throw resData;
81
+ }
82
+
83
+ if (resData.data && resData.data.friend_request_send) {
84
+ var responseData = resData.data.friend_request_send;
85
+ if (responseData.friend_requestees && responseData.friend_requestees.length > 0) {
86
+ var requestee = responseData.friend_requestees[0];
87
+ var result = {
88
+ userID: requestee.id,
89
+ friendshipStatus: requestee.friendship_status,
90
+ success: requestee.friendship_status === "OUTGOING_REQUEST"
91
+ };
92
+
93
+ if (requestee.profile_action) {
94
+ result.actionTitle = requestee.profile_action.title ? requestee.profile_action.title.text : "";
95
+ }
96
+
97
+ return callback(null, result);
98
+ } else {
99
+ return callback({ error: "No friend request data received" });
100
+ }
101
+ } else {
102
+ return callback({ error: "Invalid response format" });
103
+ }
104
+ })
105
+ .catch(function (err) {
106
+ log.error("sendFriendRequest", err);
107
+ return callback(err);
108
+ });
109
+
110
+ return returnPromise;
111
+ };
112
+ };
113
+ /** Developed by Sheikh Tamim | GitHub: sheikhtamimlover | Please give credits if reused. */
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+ // @NethWs3Dev
5
+
6
+ const allowedProperties = {
7
+ attachment: true,
8
+ url: true,
9
+ sticker: true,
10
+ emoji: true,
11
+ emojiSize: true,
12
+ body: true,
13
+ mentions: true,
14
+ location: true,
15
+ };
16
+
17
+ module.exports = (defaultFuncs, api, ctx) => {
18
+ async function uploadAttachment(attachments) {
19
+ var uploads = [];
20
+ for (var i = 0; i < attachments.length; i++) {
21
+ if (!utils.isReadableStream(attachments[i])) {
22
+ throw new Error("Attachment should be a readable stream and not " + utils.getType(attachments[i]) + ".");
23
+ }
24
+ const oksir = await defaultFuncs.postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar,{
25
+ upload_1024: attachments[i],
26
+ voice_clip: "true"
27
+ }, {}).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
28
+ if (oksir.error) {
29
+ throw new Error(resData);
30
+ }
31
+ uploads.push(oksir.payload.metadata[0]);
32
+ }
33
+ return uploads;
34
+ }
35
+
36
+ async function getUrl(url) {
37
+ const resData = await defaultFuncs.post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, {
38
+ image_height: 960,
39
+ image_width: 960,
40
+ uri: url
41
+ }).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
42
+ if (!resData || resData.error || !resData.payload){
43
+ throw new Error(resData);
44
+ }
45
+ }
46
+
47
+ async function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
48
+ // There are three cases here:
49
+ // 1. threadID is of type array, where we're starting a new group chat with users
50
+ // specified in the array.
51
+ // 2. User is sending a message to a specific user.
52
+ // 3. No additional form params and the message goes to an existing group chat.
53
+ if (utils.getType(threadID) === "Array") {
54
+ for (var i = 0; i < threadID.length; i++) {
55
+ form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
56
+ }
57
+ form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
58
+ form["client_thread_id"] = "root:" + messageAndOTID;
59
+ utils.log("sendMessage", "Sending message to multiple users: " + threadID);
60
+ } else {
61
+ const threadIDStr = threadID.toString();
62
+ // Check if it's a DM: doesn't start with numeric group ID pattern or explicitly marked as single user
63
+ const isDM = !threadIDStr.match(/^\d{15,}$/) || isSingleUser === true;
64
+
65
+ // This means that threadID is the id of a user, and the chat
66
+ // is a single person chat
67
+ if (isDM) {
68
+ form["specific_to_list[0]"] = "fbid:" + threadID;
69
+ form["specific_to_list[1]"] = "fbid:" + ctx.userID;
70
+ form["other_user_fbid"] = threadID;
71
+ } else {
72
+ form["thread_fbid"] = threadID;
73
+ }
74
+ }
75
+
76
+ if (ctx.globalOptions.pageID) {
77
+ form["author"] = "fbid:" + ctx.globalOptions.pageID;
78
+ form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
79
+ form["creator_info[creatorID]"] = ctx.userID;
80
+ form["creator_info[creatorType]"] = "direct_admin";
81
+ form["creator_info[labelType]"] = "sent_message";
82
+ form["creator_info[pageID]"] = ctx.globalOptions.pageID;
83
+ form["request_user_id"] = ctx.globalOptions.pageID;
84
+ form["creator_info[profileURI]"] =
85
+ "https://www.facebook.com/profile.php?id=" + ctx.userID;
86
+ }
87
+
88
+ const resData = await defaultFuncs.post("https://www.facebook.com/messaging/send/", ctx.jar, form).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
89
+ if (!resData) {
90
+ throw new Error("Send message failed.");
91
+ }
92
+ if (resData.error) {
93
+ if (resData.error === 1545012) {
94
+ utils.warn("sendMessage", "Got error 1545012. This might mean that you're not part of the conversation " + threadID);
95
+ throw new Error(`Cannot send message to thread ${threadID}: Bot is not part of this conversation (Error 1545012)`);
96
+ }
97
+ throw new Error(resData);
98
+ }
99
+ const messageInfo = resData.payload.actions.reduce((p, v) => {
100
+ return { threadID: v.thread_fbid, messageID: v.message_id, timestamp: v.timestamp } || p;
101
+ }, null);
102
+ return messageInfo;
103
+ }
104
+
105
+ return async (msg, threadID, callback, replyToMessage, isSingleUser = null) => {
106
+ // Handle different parameter patterns for backward compatibility
107
+ if (typeof callback === "string" || (callback && typeof callback === "object")) {
108
+ // callback is actually replyToMessage, shift parameters
109
+ isSingleUser = replyToMessage;
110
+ replyToMessage = callback;
111
+ callback = function() {};
112
+ } else if (typeof callback !== "function") {
113
+ callback = function() {};
114
+ }
115
+
116
+ let msgType = utils.getType(msg);
117
+ let threadIDType = utils.getType(threadID);
118
+ let messageIDType = utils.getType(replyToMessage);
119
+ if (msgType !== "String" && msgType !== "Object") throw new Error("Message should be of type string or object and not " + msgType + ".");
120
+ if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") throw new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
121
+ if (replyToMessage && messageIDType !== 'String' && messageIDType !== 'string') throw new Error("MessageID should be of type string and not " + messageIDType + ".");
122
+ if (msgType === "String") {
123
+ msg = { body: msg };
124
+ }
125
+
126
+ // Auto-detect if this is a DM if not explicitly specified
127
+ if (isSingleUser === null && ctx.threadTypes && ctx.threadTypes[threadID]) {
128
+ isSingleUser = ctx.threadTypes[threadID] === 'dm';
129
+ } else if (isSingleUser === null) {
130
+ // Fallback: check if threadID looks like a user ID (15 digits) vs group ID (longer)
131
+ const threadIDStr = threadID.toString();
132
+ isSingleUser = threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/);
133
+ }
134
+ let disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
135
+ if (disallowedProperties.length > 0) {
136
+ throw new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`");
137
+ }
138
+ let messageAndOTID = utils.generateOfflineThreadingID();
139
+ let form = {
140
+ client: "mercury",
141
+ action_type: "ma-type:user-generated-message",
142
+ author: "fbid:" + ctx.userID,
143
+ timestamp: Date.now(),
144
+ timestamp_absolute: "Today",
145
+ timestamp_relative: utils.generateTimestampRelative(),
146
+ timestamp_time_passed: "0",
147
+ is_unread: false,
148
+ is_cleared: false,
149
+ is_forward: false,
150
+ is_filtered_content: false,
151
+ is_filtered_content_bh: false,
152
+ is_filtered_content_account: false,
153
+ is_filtered_content_quasar: false,
154
+ is_filtered_content_invalid_app: false,
155
+ is_spoof_warning: false,
156
+ source: "source:chat:web",
157
+ "source_tags[0]": "source:chat",
158
+ ...(msg.body && {
159
+ body: msg.body
160
+ }),
161
+ html_body: false,
162
+ ui_push_phase: "V3",
163
+ status: "0",
164
+ offline_threading_id: messageAndOTID,
165
+ message_id: messageAndOTID,
166
+ threading_id: utils.generateThreadingID(ctx.clientID),
167
+ "ephemeral_ttl_mode:": "0",
168
+ manual_retry_cnt: "0",
169
+ has_attachment: !!(msg.attachment || msg.url || msg.sticker),
170
+ signatureID: utils.getSignatureID(),
171
+ ...(replyToMessage && {
172
+ replied_to_message_id: replyToMessage
173
+ })
174
+ };
175
+
176
+ if (msg.location) {
177
+ if (!msg.location.latitude || !msg.location.longitude) throw new Error("location property needs both latitude and longitude");
178
+ form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
179
+ form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
180
+ form["location_attachment[is_current_location]"] = !!msg.location.current;
181
+ }
182
+ if (msg.sticker) {
183
+ form["sticker_id"] = msg.sticker;
184
+ }
185
+ if (msg.attachment) {
186
+ form.image_ids = [];
187
+ form.gif_ids = [];
188
+ form.file_ids = [];
189
+ form.video_ids = [];
190
+ form.audio_ids = [];
191
+ if (utils.getType(msg.attachment) !== "Array") {
192
+ msg.attachment = [msg.attachment];
193
+ }
194
+ const files = await uploadAttachment(msg.attachment);
195
+ files.forEach(file => {
196
+ const type = Object.keys(file)[0];
197
+ form["" + type + "s"].push(file[type]);
198
+ });
199
+ }
200
+ if (msg.url) {
201
+ form["shareable_attachment[share_type]"] = "100";
202
+ const params = await getUrl(msg.url);
203
+ form["shareable_attachment[share_params]"] = params;
204
+ }
205
+ if (msg.emoji) {
206
+ if (!msg.emojiSize) {
207
+ msg.emojiSize = "medium";
208
+ }
209
+ if (msg.emojiSize !== "small" && msg.emojiSize !== "medium" && msg.emojiSize !== "large") {
210
+ throw new Error("emojiSize property is invalid");
211
+ }
212
+ if (!form.body) {
213
+ throw new Error("body is not empty");
214
+ }
215
+ form.body = msg.emoji;
216
+ form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
217
+ }
218
+ if (msg.mentions) {
219
+ for (let i = 0; i < msg.mentions.length; i++) {
220
+ const mention = msg.mentions[i];
221
+ const tag = mention.tag;
222
+ if (typeof tag !== "string") {
223
+ throw new Error("Mention tags must be strings.");
224
+ }
225
+ const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
226
+ if (offset < 0) utils.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
227
+ if (!mention.id) utils.warn("handleMention", "Mention id should be non-null.");
228
+ const id = mention.id || 0;
229
+ const emptyChar = '\u200E';
230
+ form["body"] = emptyChar + msg.body;
231
+ form["profile_xmd[" + i + "][offset]"] = offset + 1;
232
+ form["profile_xmd[" + i + "][length]"] = tag.length;
233
+ form["profile_xmd[" + i + "][id]"] = id;
234
+ form["profile_xmd[" + i + "][type]"] = "p";
235
+ }
236
+ }
237
+ const configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : ctx.config || {};
238
+ const enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : ctx.config?.enableTypingIndicator;
239
+ const typingDuration = Number(configSource.typingDuration || ctx.config?.typingDuration || 4000);
240
+
241
+ if (enableTypingIndicator) {
242
+ await api.sendTypingIndicator(true, threadID, () => {});
243
+ await utils.delay(typingDuration);
244
+ const result = await sendContent(form, threadID, isSingleUser, messageAndOTID);
245
+ await api.sendTypingIndicator(false, threadID, () => {});
246
+ if (callback && typeof callback === "function") {
247
+ callback(null, result);
248
+ }
249
+ return result;
250
+ } else {
251
+ const result = await sendContent(form, threadID, isSingleUser, messageAndOTID);
252
+ if (callback && typeof callback === "function") {
253
+ callback(null, result);
254
+ }
255
+ return result;
256
+ }
257
+ };
258
+ };