shadowx-fca 8.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.
- package/README.md +1066 -0
- package/build/messagix.dll +0 -0
- package/build/messagix.so +0 -0
- package/checkUpdate.js +393 -0
- package/config.json +17 -0
- package/e2ee.js +563 -0
- package/e2eetest.js +356 -0
- package/index.js +611 -0
- package/lib/index.mjs +1412 -0
- package/logger.js +500 -0
- package/package.json +65 -0
- package/src/GetBotInfo.js +66 -0
- package/src/OldMessage.js +182 -0
- package/src/Screenshot.js +83 -0
- package/src/addExternalModule.js +13 -0
- package/src/addUserToGroup.js +33 -0
- package/src/approveGroupJoinRequests.js +18 -0
- package/src/changeAdminStatus.js +16 -0
- package/src/changeArchivedStatus.js +17 -0
- package/src/changeAvatar.js +136 -0
- package/src/changeAvatarV2.js +86 -0
- package/src/changeAvt.js +85 -0
- package/src/changeBio.js +76 -0
- package/src/changeBlockedStatus.js +20 -0
- package/src/changeBlockedStatusMqtt.js +80 -0
- package/src/changeCover.js +72 -0
- package/src/changeGroupImage.js +16 -0
- package/src/changeName.js +79 -0
- package/src/changeNickname.js +16 -0
- package/src/changeThreadColor.js +15 -0
- package/src/changeThreadEmoji.js +15 -0
- package/src/changeThreadMemberNickname.js +6 -0
- package/src/changeUsername.js +59 -0
- package/src/createCommentPost.js +230 -0
- package/src/createNewGroup.js +38 -0
- package/src/createNote.js +35 -0
- package/src/createPoll.js +27 -0
- package/src/createPost.js +276 -0
- package/src/createThemeAI.js +129 -0
- package/src/data/cache/system/data.json +4 -0
- package/src/data/cache/system/datahandle.js +21 -0
- package/src/data/getThreadInfo.json +1 -0
- package/src/deleteComment.js +23 -0
- package/src/deleteMessage.js +15 -0
- package/src/deleteThread.js +15 -0
- package/src/denyGroupJoinRequests.js +18 -0
- package/src/e2ee/crypto.js +173 -0
- package/src/e2ee/index.js +144 -0
- package/src/e2ee/proto/ArmadilloApplication.proto +281 -0
- package/src/e2ee/proto/ArmadilloICDC.proto +14 -0
- package/src/e2ee/proto/ConsumerApplication.proto +232 -0
- package/src/e2ee/proto/MessageApplication.proto +82 -0
- package/src/e2ee/proto/MessageTransport.proto +77 -0
- package/src/e2ee/proto/WACommon.proto +66 -0
- package/src/e2ee/proto/WAMediaTransport.proto +176 -0
- package/src/e2ee/proto/proto-writer.ts +76 -0
- package/src/e2ee/protocol.js +196 -0
- package/src/e2ee/ratchet.js +219 -0
- package/src/e2ee/store.js +182 -0
- package/src/e2ee.js +8 -0
- package/src/editMessage.js +56 -0
- package/src/editMessageOld.js +67 -0
- package/src/enableReactions.js +24 -0
- package/src/follow.js +74 -0
- package/src/followUser.js +23 -0
- package/src/forwardAttachment.js +16 -0
- package/src/friendList.js +98 -0
- package/src/getAccess.js +112 -0
- package/src/getAppState.js +13 -0
- package/src/getAvatarUser.js +11 -0
- package/src/getBio.js +24 -0
- package/src/getBotInitialData.js +42 -0
- package/src/getCtx.js +5 -0
- package/src/getCurrentUserID.js +6 -0
- package/src/getEmojiUrl.js +29 -0
- package/src/getFriendsList.js +36 -0
- package/src/getMessage.js +37 -0
- package/src/getNotes.js +17 -0
- package/src/getOptions.js +5 -0
- package/src/getPinnedMessages.js +33 -0
- package/src/getPostInfo.js +17 -0
- package/src/getProfileInfo.js +17 -0
- package/src/getPublicData.js +25 -0
- package/src/getRegion.js +7 -0
- package/src/getRepInfo.js +17 -0
- package/src/getStickerPacks.js +25 -0
- package/src/getStickers.js +39 -0
- package/src/getStoryReactions.js +18 -0
- package/src/getThreadHistory.js +45 -0
- package/src/getThreadHistoryDeprecated.js +71 -0
- package/src/getThreadInfo.js +73 -0
- package/src/getThreadInfoDeprecated.js +56 -0
- package/src/getThreadList.js +76 -0
- package/src/getThreadListDeprecated.js +46 -0
- package/src/getThreadPictures.js +59 -0
- package/src/getThreadTheme.js +77 -0
- package/src/getUID.js +17 -0
- package/src/getUserID.js +17 -0
- package/src/getUserInfo.js +28 -0
- package/src/handleFriendRequest.js +21 -0
- package/src/handleMessageRequest.js +15 -0
- package/src/httpGet.js +13 -0
- package/src/httpPost.js +12 -0
- package/src/httpPostFormData.js +12 -0
- package/src/listenE2EE.js +75 -0
- package/src/listenMqtt.js +802 -0
- package/src/listenNotification.js +85 -0
- package/src/logout.js +22 -0
- package/src/markAsDelivered.js +17 -0
- package/src/markAsRead.js +14 -0
- package/src/markAsReadAll.js +15 -0
- package/src/markAsSeen.js +15 -0
- package/src/metaTheme.js +185 -0
- package/src/muteThread.js +52 -0
- package/src/note.js +228 -0
- package/src/pin.js +53 -0
- package/src/pinMessage.js +6 -0
- package/src/postComment.js +29 -0
- package/src/postFormData.js +46 -0
- package/src/reactToComment.js +31 -0
- package/src/reactToPost.js +32 -0
- package/src/refreshFb_dtsg.js +31 -0
- package/src/removeSuspiciousAccount.js +74 -0
- package/src/removeUserFromGroup.js +15 -0
- package/src/reply.js +442 -0
- package/src/resolvePhotoUrl.js +15 -0
- package/src/searchForThread.js +20 -0
- package/src/searchFriends.js +28 -0
- package/src/searchStickers.js +53 -0
- package/src/send.js +46 -0
- package/src/sendAudio.js +8 -0
- package/src/sendBroadcast.js +93 -0
- package/src/sendButtons.js +161 -0
- package/src/sendComment.js +159 -0
- package/src/sendEmoji.js +10 -0
- package/src/sendFile.js +9 -0
- package/src/sendFriendRequest.js +33 -0
- package/src/sendGif.js +24 -0
- package/src/sendImage.js +9 -0
- package/src/sendLocation.js +9 -0
- package/src/sendMessage.js +487 -0
- package/src/sendMessage1.js +309 -0
- package/src/sendMessageMqtt.js +68 -0
- package/src/sendSticker.js +8 -0
- package/src/sendTypingIndicator.js +36 -0
- package/src/sendTypingIndicatorV2.js +28 -0
- package/src/sendVideo.js +9 -0
- package/src/sessionGuard.js +130 -0
- package/src/setActiveStatus.js +16 -0
- package/src/setMessageReaction.js +61 -0
- package/src/setMessageReactionMqtt.js +62 -0
- package/src/setOptions.js +22 -0
- package/src/setPollVote.js +17 -0
- package/src/setPostReaction.js +112 -0
- package/src/setProfileGuard.js +44 -0
- package/src/setProfileLock.js +93 -0
- package/src/setStoryReaction.js +129 -0
- package/src/setStorySeen.js +99 -0
- package/src/setThreadTheme.js +17 -0
- package/src/setTitle.js +15 -0
- package/src/shareContact.js +33 -0
- package/src/shareLink.js +8 -0
- package/src/sharePost.js +31 -0
- package/src/stopListenMqtt.js +23 -0
- package/src/storyManager.js +353 -0
- package/src/suggestFriend.js +128 -0
- package/src/threadColors.js +131 -0
- package/src/unfollowUser.js +23 -0
- package/src/unfriend.js +15 -0
- package/src/unpinMessage.js +6 -0
- package/src/unsendMessage.js +14 -0
- package/src/uploadAttachment.js +58 -0
- package/src/uploadImageToImgbb.js +29 -0
- package/utils.js +2945 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var utils = require("../utils");
|
|
4
|
+
var logger = require("../logger");
|
|
5
|
+
|
|
6
|
+
var ALLOWED = {
|
|
7
|
+
attachment: true, url: true, sticker: true, emoji: true,
|
|
8
|
+
emojiSize: true, body: true, mentions: true, location: true,
|
|
9
|
+
replyToMessage: true, forwardAttachmentIds: true
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
var EMOJI_SIZES = { small: 1, medium: 2, large: 3 };
|
|
13
|
+
|
|
14
|
+
function toEmojiSize(size) {
|
|
15
|
+
if (typeof size === "number" && !isNaN(size)) return Math.min(3, Math.max(1, size));
|
|
16
|
+
if (typeof size === "string" && size in EMOJI_SIZES) return EMOJI_SIZES[size];
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function hasLinks(text) {
|
|
21
|
+
return /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(text);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildMentionData(msg, baseBody) {
|
|
25
|
+
if (!Array.isArray(msg.mentions) || !msg.mentions.length) return null;
|
|
26
|
+
var ids = [], offsets = [], lengths = [], types = [];
|
|
27
|
+
for (var i = 0; i < msg.mentions.length; i++) {
|
|
28
|
+
var mention = msg.mentions[i];
|
|
29
|
+
// Ensure tag always has @ prefix — Facebook counts @ in both offset and length
|
|
30
|
+
var tag = String(mention.tag || "");
|
|
31
|
+
if (tag && !tag.startsWith("@")) tag = "@" + tag;
|
|
32
|
+
var fromIndex = Number.isInteger(mention.fromIndex) ? mention.fromIndex : 0;
|
|
33
|
+
var offset = baseBody.indexOf(tag, fromIndex);
|
|
34
|
+
if (offset === -1) {
|
|
35
|
+
// Fallback: search for bare name without @
|
|
36
|
+
offset = baseBody.indexOf(tag.slice(1), fromIndex);
|
|
37
|
+
}
|
|
38
|
+
if (offset < 0) offset = 0;
|
|
39
|
+
ids.push(String(mention.id || 0));
|
|
40
|
+
offsets.push(offset);
|
|
41
|
+
lengths.push(tag.length); // includes @ — matches Facebook's expectation
|
|
42
|
+
types.push("p");
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
mention_ids: ids.join(","),
|
|
46
|
+
mention_offsets: offsets.join(","),
|
|
47
|
+
mention_lengths: lengths.join(","),
|
|
48
|
+
mention_types: types.join(",")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractIdsFromPayload(payload) {
|
|
53
|
+
var messageID = null, threadID = null;
|
|
54
|
+
function walk(node) {
|
|
55
|
+
if (!Array.isArray(node)) return;
|
|
56
|
+
if (node[0] === 5 && (node[1] === "replaceOptimsiticMessage" || node[1] === "replaceOptimisticMessage")) {
|
|
57
|
+
messageID = String(node[3]);
|
|
58
|
+
}
|
|
59
|
+
if (node[0] === 5 && node[1] === "writeCTAIdToThreadsTable") {
|
|
60
|
+
var candidate = node[2];
|
|
61
|
+
if (Array.isArray(candidate) && candidate[0] === 19) threadID = String(candidate[1]);
|
|
62
|
+
}
|
|
63
|
+
for (var i = 0; i < node.length; i++) walk(node[i]);
|
|
64
|
+
}
|
|
65
|
+
try { walk(payload && payload.step); } catch (_) { }
|
|
66
|
+
return { threadID, messageID };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function publishLsRequestWithAck(mqttClient, content, requestId, timeout) {
|
|
70
|
+
timeout = timeout || 15000;
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
var timer = setTimeout(() => {
|
|
73
|
+
mqttClient.removeListener('message', onMessage);
|
|
74
|
+
reject(new Error('MQTT sendMessage timed out after ' + timeout + 'ms'));
|
|
75
|
+
}, timeout);
|
|
76
|
+
|
|
77
|
+
function onMessage(topic, message) {
|
|
78
|
+
if (topic !== '/ls_resp') return;
|
|
79
|
+
try {
|
|
80
|
+
var data = JSON.parse(message.toString());
|
|
81
|
+
if (String(data.request_id) === String(requestId)) {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
mqttClient.removeListener('message', onMessage);
|
|
84
|
+
var extracted = extractIdsFromPayload(data.payload ? JSON.parse(data.payload) : {});
|
|
85
|
+
resolve({ threadID: extracted.threadID, messageID: extracted.messageID });
|
|
86
|
+
}
|
|
87
|
+
} catch (_) { }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
mqttClient.on('message', onMessage);
|
|
91
|
+
mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 }, err => {
|
|
92
|
+
if (err) {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
mqttClient.removeListener('message', onMessage);
|
|
95
|
+
reject(err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
102
|
+
var uploadAttachmentFn = require('./uploadAttachment')(defaultFuncs, api, ctx);
|
|
103
|
+
|
|
104
|
+
async function uploadAttachments(attachments) {
|
|
105
|
+
if (!Array.isArray(attachments)) attachments = [attachments];
|
|
106
|
+
return uploadAttachmentFn(attachments);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function sendViaMqtt(msg, threadID, replyToMessage) {
|
|
110
|
+
var mqttClient = ctx.mqttClient || global.mqttClient;
|
|
111
|
+
if (!mqttClient) throw new Error('MQTT client not available');
|
|
112
|
+
|
|
113
|
+
var baseBody = msg.body != null ? String(msg.body) : "";
|
|
114
|
+
var requestId = Math.floor(100 + Math.random() * 900);
|
|
115
|
+
var epoch = (BigInt(Date.now()) << 22n).toString();
|
|
116
|
+
|
|
117
|
+
var payload0 = {
|
|
118
|
+
thread_id: String(threadID),
|
|
119
|
+
otid: utils.generateOfflineThreadingID(),
|
|
120
|
+
source: 2097153,
|
|
121
|
+
send_type: 1,
|
|
122
|
+
sync_group: 1,
|
|
123
|
+
mark_thread_read: 1,
|
|
124
|
+
text: baseBody === "" ? null : baseBody,
|
|
125
|
+
initiating_source: 0,
|
|
126
|
+
skip_url_preview_gen: 0,
|
|
127
|
+
text_has_links: hasLinks(baseBody) ? 1 : 0,
|
|
128
|
+
multitab_env: 0,
|
|
129
|
+
metadata_dataclass: JSON.stringify({ media_accessibility_metadata: { alt_text: null } })
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
var mentionData = buildMentionData(msg, baseBody);
|
|
133
|
+
if (mentionData) payload0.mention_data = mentionData;
|
|
134
|
+
|
|
135
|
+
if (msg.sticker) { payload0.send_type = 2; payload0.sticker_id = msg.sticker; }
|
|
136
|
+
if (msg.emoji) { payload0.send_type = 1; payload0.text = msg.emoji; payload0.hot_emoji_size = toEmojiSize(msg.emojiSize); }
|
|
137
|
+
|
|
138
|
+
if (msg.location && msg.location.latitude != null && msg.location.longitude != null) {
|
|
139
|
+
payload0.send_type = 1;
|
|
140
|
+
payload0.location_data = {
|
|
141
|
+
coordinates: { latitude: msg.location.latitude, longitude: msg.location.longitude },
|
|
142
|
+
is_current_location: Boolean(msg.location.current),
|
|
143
|
+
is_live_location: Boolean(msg.location.live)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var effectiveReplyTo = replyToMessage || msg.replyToMessage;
|
|
148
|
+
if (effectiveReplyTo) {
|
|
149
|
+
payload0.reply_metadata = {
|
|
150
|
+
reply_source_id: effectiveReplyTo,
|
|
151
|
+
reply_source_type: 1,
|
|
152
|
+
reply_type: 0
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (msg.attachment) {
|
|
157
|
+
payload0.send_type = 3;
|
|
158
|
+
if (payload0.text === "") payload0.text = null;
|
|
159
|
+
payload0.attachment_fbids = [];
|
|
160
|
+
var list = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
|
|
161
|
+
var preuploaded = [], toUpload = [];
|
|
162
|
+
for (var item of list) {
|
|
163
|
+
if (Array.isArray(item) && item.length >= 2 && typeof item[0] === "string") {
|
|
164
|
+
preuploaded.push(String(item[1]));
|
|
165
|
+
} else if (utils.isReadableStream(item)) {
|
|
166
|
+
toUpload.push(item);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (preuploaded.length) payload0.attachment_fbids = payload0.attachment_fbids.concat(preuploaded);
|
|
170
|
+
if (msg.forwardAttachmentIds && msg.forwardAttachmentIds.length) {
|
|
171
|
+
payload0.attachment_fbids = payload0.attachment_fbids.concat(msg.forwardAttachmentIds.map(String));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (toUpload.length) {
|
|
175
|
+
var uploaded = await uploadAttachments(toUpload);
|
|
176
|
+
for (var u of uploaded) {
|
|
177
|
+
if (!u) continue;
|
|
178
|
+
var fbid = u.image_id || u.video_id || u.audio_id || u.file_id || u.sticker_id || u.gif_id;
|
|
179
|
+
if (!fbid) {
|
|
180
|
+
var firstKey = Object.keys(u).find(function(k) { return u[k] && /^\d+$/.test(String(u[k])); });
|
|
181
|
+
if (firstKey) fbid = u[firstKey];
|
|
182
|
+
}
|
|
183
|
+
if (fbid) payload0.attachment_fbids.push(String(fbid));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!payload0.attachment_fbids.length) delete payload0.attachment_fbids;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var tasks = [
|
|
190
|
+
{
|
|
191
|
+
label: '46',
|
|
192
|
+
payload: JSON.stringify(payload0),
|
|
193
|
+
queue_name: String(threadID),
|
|
194
|
+
task_id: 400,
|
|
195
|
+
failure_count: null
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
label: '21',
|
|
199
|
+
payload: JSON.stringify({
|
|
200
|
+
thread_id: String(threadID),
|
|
201
|
+
last_read_watermark_ts: Date.now(),
|
|
202
|
+
sync_group: 1
|
|
203
|
+
}),
|
|
204
|
+
queue_name: String(threadID),
|
|
205
|
+
task_id: 401,
|
|
206
|
+
failure_count: null
|
|
207
|
+
}
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
var content = {
|
|
211
|
+
app_id: '2220391788200892',
|
|
212
|
+
payload: JSON.stringify({
|
|
213
|
+
tasks: tasks,
|
|
214
|
+
epoch_id: epoch,
|
|
215
|
+
version_id: '24804310205905615',
|
|
216
|
+
data_trace_id: '#' + Buffer.from(String(Math.random())).toString('base64').replace(/=+$/, '')
|
|
217
|
+
}),
|
|
218
|
+
request_id: requestId,
|
|
219
|
+
type: 3
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return publishLsRequestWithAck(mqttClient, content, requestId, 15000);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return async function sendMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
|
|
226
|
+
if (typeof msg === "string") msg = { body: msg };
|
|
227
|
+
if (typeof callback !== "function") { callback = null; }
|
|
228
|
+
|
|
229
|
+
var resolve, reject;
|
|
230
|
+
var promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
231
|
+
|
|
232
|
+
for (var key in msg) {
|
|
233
|
+
if (!ALLOWED[key]) {
|
|
234
|
+
var err = { error: "sendMessage: Unknown property '" + key + "'" };
|
|
235
|
+
if (callback) callback(err);
|
|
236
|
+
else reject(err);
|
|
237
|
+
return promise;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Auto-detect isSingleUser from ctx.threadTypes if not explicitly provided.
|
|
242
|
+
// parseDelta in listenMqtt.js populates ctx.threadTypes[senderID] = 'dm' | 'group'
|
|
243
|
+
if (isSingleUser === undefined && ctx.threadTypes) {
|
|
244
|
+
isSingleUser = ctx.threadTypes[String(threadID)] === 'dm';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// DM attachment sends — skip MQTT entirely.
|
|
248
|
+
// For E2EE DMs: route through the E2EE bridge (Noise WebSocket, Signal Protocol).
|
|
249
|
+
// Facebook strips attachment_fbids from MQTT messages in E2EE threads
|
|
250
|
+
// (can't re-encrypt CDN attachments on the fly), so MQTT silently drops them.
|
|
251
|
+
// The vendor's client.sendImage/sendVideo/sendAudio encrypts the file data
|
|
252
|
+
// and sends via the Noise WebSocket — the only path that actually delivers
|
|
253
|
+
// attachments in E2EE threads.
|
|
254
|
+
// For non-E2EE DMs: use OldMessage.
|
|
255
|
+
// /messaging/send/ with other_user_fbid routing works for plain DMs.
|
|
256
|
+
// (For E2EE DMs it returns 404 because the endpoint is deprecated for those.)
|
|
257
|
+
if (isSingleUser && msg.attachment) {
|
|
258
|
+
var useE2EE = api.e2ee && typeof api.e2ee.isConnected === "function" && api.e2ee.isConnected();
|
|
259
|
+
if (useE2EE) {
|
|
260
|
+
try {
|
|
261
|
+
var e2eeResult = await api.e2ee.sendMessage(String(threadID), msg, replyToMessage);
|
|
262
|
+
var wrapped = e2eeResult && e2eeResult.messageId
|
|
263
|
+
? { threadID: String(threadID), messageID: String(e2eeResult.messageId) }
|
|
264
|
+
: e2eeResult;
|
|
265
|
+
if (callback) callback(null, wrapped);
|
|
266
|
+
else resolve(wrapped);
|
|
267
|
+
} catch (e2eeErr) {
|
|
268
|
+
logger.error("sendMessage", "E2EE DM attachment send failed: " + (e2eeErr.message || e2eeErr));
|
|
269
|
+
if (callback) callback(e2eeErr);
|
|
270
|
+
else reject(e2eeErr);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
try {
|
|
274
|
+
var omResult = await new Promise((res2, rej2) => {
|
|
275
|
+
api.OldMessage(msg, threadID, (err2, data2) => err2 ? rej2(err2) : res2(data2), replyToMessage, true);
|
|
276
|
+
});
|
|
277
|
+
if (callback) callback(null, omResult);
|
|
278
|
+
else resolve(omResult);
|
|
279
|
+
} catch (omErr) {
|
|
280
|
+
logger.error("sendMessage", "DM attachment via OldMessage failed: " + (omErr.error || omErr.message || omErr));
|
|
281
|
+
if (callback) callback(omErr);
|
|
282
|
+
else reject(omErr);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return promise;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
var result = await sendViaMqtt(msg, threadID, replyToMessage);
|
|
290
|
+
if (callback) callback(null, result);
|
|
291
|
+
else resolve(result);
|
|
292
|
+
} catch (mqttErr) {
|
|
293
|
+
logger.warn("sendMessage", "MQTT failed, falling back to OldMessage: " + (mqttErr.message || mqttErr));
|
|
294
|
+
try {
|
|
295
|
+
var fbResult = await new Promise((res2, rej2) => {
|
|
296
|
+
api.OldMessage(msg, threadID, (err2, data2) => err2 ? rej2(err2) : res2(data2), replyToMessage, isSingleUser);
|
|
297
|
+
});
|
|
298
|
+
if (callback) callback(null, fbResult);
|
|
299
|
+
else resolve(fbResult);
|
|
300
|
+
} catch (fbErr) {
|
|
301
|
+
logger.error("sendMessage", fbErr.error || fbErr.message || fbErr);
|
|
302
|
+
if (callback) callback(fbErr);
|
|
303
|
+
else reject(fbErr);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return promise;
|
|
308
|
+
};
|
|
309
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
6
|
+
return function sendMessageMqtt(msg, threadID, callback, replyToMessage) {
|
|
7
|
+
if (typeof msg === "string") msg = { body: msg };
|
|
8
|
+
var mqttClient = ctx.mqttClient || global.mqttClient;
|
|
9
|
+
if (!mqttClient) {
|
|
10
|
+
var err = { error: "MQTT not connected. Call listenMqtt first." };
|
|
11
|
+
if (typeof callback === "function") return callback(err);
|
|
12
|
+
return Promise.reject(err);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
|
|
16
|
+
ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
|
|
17
|
+
|
|
18
|
+
var baseBody = msg.body != null ? String(msg.body) : "";
|
|
19
|
+
var requestId = ctx.wsReqNumber;
|
|
20
|
+
|
|
21
|
+
var payload0 = {
|
|
22
|
+
thread_id: String(threadID),
|
|
23
|
+
otid: utils.generateOfflineThreadingID(),
|
|
24
|
+
source: 2097153,
|
|
25
|
+
send_type: 1,
|
|
26
|
+
sync_group: 1,
|
|
27
|
+
mark_thread_read: 1,
|
|
28
|
+
text: baseBody === "" ? null : baseBody,
|
|
29
|
+
initiating_source: 0,
|
|
30
|
+
skip_url_preview_gen: 0,
|
|
31
|
+
text_has_links: 0,
|
|
32
|
+
multitab_env: 0
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
if (msg.sticker) { payload0.send_type = 2; payload0.sticker_id = msg.sticker; }
|
|
36
|
+
if (replyToMessage || msg.replyToMessage) {
|
|
37
|
+
payload0.reply_metadata = {
|
|
38
|
+
reply_source_id: replyToMessage || msg.replyToMessage,
|
|
39
|
+
reply_source_type: 1,
|
|
40
|
+
reply_type: 0
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var content = {
|
|
45
|
+
app_id: '2220391788200892',
|
|
46
|
+
payload: JSON.stringify({
|
|
47
|
+
tasks: [{
|
|
48
|
+
failure_count: null,
|
|
49
|
+
label: '46',
|
|
50
|
+
payload: JSON.stringify(payload0),
|
|
51
|
+
queue_name: String(threadID),
|
|
52
|
+
task_id: ctx.wsTaskNumber
|
|
53
|
+
}],
|
|
54
|
+
epoch_id: utils.generateOfflineThreadingID(),
|
|
55
|
+
version_id: '7214102258676893'
|
|
56
|
+
}),
|
|
57
|
+
request_id: requestId,
|
|
58
|
+
type: 3
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (typeof callback === "function") {
|
|
62
|
+
ctx.reqCallbacks[requestId] = callback;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 });
|
|
66
|
+
return Promise.resolve({ requestId });
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var utils = require("../utils");
|
|
3
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
4
|
+
return function sendSticker(stickerID, threadID, replyToMessage, callback) {
|
|
5
|
+
if (typeof replyToMessage === "function") { callback = replyToMessage; replyToMessage = null; }
|
|
6
|
+
return api.sendMessage({ sticker: stickerID, replyToMessage }, threadID, callback);
|
|
7
|
+
};
|
|
8
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var utils = require("../utils");
|
|
3
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
4
|
+
return function sendTypingIndicator(threadID, isTyping, callback) {
|
|
5
|
+
var { promise, callback: cb } = utils.wrapCallback(callback);
|
|
6
|
+
var mqttClient = ctx.mqttClient || global.mqttClient;
|
|
7
|
+
if (!mqttClient) { cb({ error: "MQTT not connected" }); return promise; }
|
|
8
|
+
|
|
9
|
+
ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
|
|
10
|
+
var payload = {
|
|
11
|
+
thread_key: String(threadID),
|
|
12
|
+
is_group_thread: (ctx.threadTypes && ctx.threadTypes[threadID] === 'group') ? 1 : 0,
|
|
13
|
+
is_typing: isTyping ? 1 : 0,
|
|
14
|
+
attribution: 0
|
|
15
|
+
};
|
|
16
|
+
var content = {
|
|
17
|
+
app_id: '2220391788200892',
|
|
18
|
+
payload: JSON.stringify({
|
|
19
|
+
tasks: [{
|
|
20
|
+
failure_count: null,
|
|
21
|
+
label: '3',
|
|
22
|
+
payload: JSON.stringify(payload),
|
|
23
|
+
queue_name: 'typing_state_notify',
|
|
24
|
+
task_id: ctx.wsReqNumber
|
|
25
|
+
}],
|
|
26
|
+
epoch_id: utils.generateOfflineThreadingID(),
|
|
27
|
+
version_id: '7214102258676893'
|
|
28
|
+
}),
|
|
29
|
+
request_id: ctx.wsReqNumber,
|
|
30
|
+
type: 3
|
|
31
|
+
};
|
|
32
|
+
mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 });
|
|
33
|
+
cb(null, true);
|
|
34
|
+
return promise;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var utils = require("../utils");
|
|
6
|
+
// @NethWs3Dev
|
|
7
|
+
|
|
8
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
9
|
+
return async function sendTypingIndicatorV2(sendTyping,threadID, callback) {
|
|
10
|
+
let count_req = 0
|
|
11
|
+
var wsContent = {
|
|
12
|
+
app_id: 2220391788200892,
|
|
13
|
+
payload: JSON.stringify({
|
|
14
|
+
label: 3,
|
|
15
|
+
payload: JSON.stringify({
|
|
16
|
+
thread_key: threadID.toString(),
|
|
17
|
+
is_group_thread: +(threadID.toString().length >= 16),
|
|
18
|
+
is_typing: +sendTyping,
|
|
19
|
+
attribution: 0
|
|
20
|
+
}),
|
|
21
|
+
version: 5849951561777440
|
|
22
|
+
}),
|
|
23
|
+
request_id: ++count_req,
|
|
24
|
+
type: 4
|
|
25
|
+
};
|
|
26
|
+
await new Promise((resolve, reject) => mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => err ? reject(err) : resolve()));
|
|
27
|
+
};
|
|
28
|
+
};
|
package/src/sendVideo.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var fs = require("fs");
|
|
3
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
4
|
+
return function sendVideo(videoPath, threadID, caption, callback) {
|
|
5
|
+
if (typeof caption === "function") { callback = caption; caption = ""; }
|
|
6
|
+
var stream = typeof videoPath === "string" ? fs.createReadStream(videoPath) : videoPath;
|
|
7
|
+
return api.sendMessage({ attachment: stream, body: caption || "" }, threadID, callback);
|
|
8
|
+
};
|
|
9
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SessionGuard — Protects the appstate (session) from corruption and silent logouts.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* • Auto-saves appstate to disk periodically (default every 5 min)
|
|
8
|
+
* • Saves on every successful message send (debounced, 30s cooldown)
|
|
9
|
+
* • Backs up the previous appstate before overwriting (.bak file)
|
|
10
|
+
* • Detects logout/checkpoint events from listen stream and alerts
|
|
11
|
+
* • Provides api.saveSession([path]) for manual save at any time
|
|
12
|
+
* • Never overwrites with a shorter/smaller appstate (corruption guard)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
var fs = require("fs");
|
|
16
|
+
var path = require("path");
|
|
17
|
+
var logger = require("../logger");
|
|
18
|
+
|
|
19
|
+
var SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
20
|
+
var DEBOUNCE_MS = 30 * 1000; // 30 seconds cooldown between auto-saves
|
|
21
|
+
var MIN_COOKIES = 5; // minimum cookie count we consider "valid"
|
|
22
|
+
|
|
23
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
24
|
+
|
|
25
|
+
function getState() {
|
|
26
|
+
try { return api.getAppState(); } catch (_) { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function saveToDisk(filePath) {
|
|
30
|
+
var state = getState();
|
|
31
|
+
if (!state || !Array.isArray(state) || state.length < MIN_COOKIES) {
|
|
32
|
+
logger.warn("SessionGuard", "Skipped save — state looks empty or invalid (" + (state ? state.length : 0) + " cookies).");
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Corruption guard: never write a smaller appstate than what's already on disk
|
|
37
|
+
if (fs.existsSync(filePath)) {
|
|
38
|
+
try {
|
|
39
|
+
var existing = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
40
|
+
if (Array.isArray(existing) && state.length < existing.length * 0.8) {
|
|
41
|
+
logger.warn("SessionGuard", "Skipped save — new state has " + state.length + " cookies vs " + existing.length + " on disk (possible truncation).");
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Backup the current valid state before overwriting
|
|
45
|
+
fs.writeFileSync(filePath + ".bak", JSON.stringify(existing, null, 2), "utf8");
|
|
46
|
+
} catch (_) {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf8");
|
|
50
|
+
logger.success("SessionGuard", "Session saved → " + filePath + " (" + state.length + " cookies)");
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return function sessionGuard(appStatePath, options) {
|
|
55
|
+
options = options || {};
|
|
56
|
+
var interval = options.interval !== undefined ? options.interval : SAVE_INTERVAL_MS;
|
|
57
|
+
var debounce = options.debounce !== undefined ? options.debounce : DEBOUNCE_MS;
|
|
58
|
+
|
|
59
|
+
appStatePath = appStatePath || path.join(process.cwd(), "appstate.json");
|
|
60
|
+
|
|
61
|
+
var lastSave = 0;
|
|
62
|
+
var intervalRef = null;
|
|
63
|
+
|
|
64
|
+
// Periodic save
|
|
65
|
+
if (interval > 0) {
|
|
66
|
+
intervalRef = setInterval(function () {
|
|
67
|
+
logger.info("SessionGuard", "Periodic save...");
|
|
68
|
+
saveToDisk(appStatePath);
|
|
69
|
+
lastSave = Date.now();
|
|
70
|
+
}, interval);
|
|
71
|
+
if (intervalRef.unref) intervalRef.unref(); // don't block process exit
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Debounced on-send save
|
|
75
|
+
// Patch sendMessage to auto-save appstate after a successful send
|
|
76
|
+
var originalSendMessage = api.sendMessage;
|
|
77
|
+
api.sendMessage = function () {
|
|
78
|
+
var result = originalSendMessage.apply(this, arguments);
|
|
79
|
+
// After send, debounce-save
|
|
80
|
+
Promise.resolve(result).then(function () {
|
|
81
|
+
if (Date.now() - lastSave > debounce) {
|
|
82
|
+
saveToDisk(appStatePath);
|
|
83
|
+
lastSave = Date.now();
|
|
84
|
+
}
|
|
85
|
+
}).catch(function () {});
|
|
86
|
+
return result;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Expose manual save
|
|
90
|
+
api.saveSession = function (customPath) {
|
|
91
|
+
return saveToDisk(customPath || appStatePath);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Expose restore from backup
|
|
95
|
+
api.restoreSessionBackup = function (customPath) {
|
|
96
|
+
var bakPath = (customPath || appStatePath) + ".bak";
|
|
97
|
+
if (!fs.existsSync(bakPath)) {
|
|
98
|
+
logger.warn("SessionGuard", "No backup file found at " + bakPath);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
var bak = fs.readFileSync(bakPath, "utf8");
|
|
103
|
+
fs.writeFileSync(customPath || appStatePath, bak, "utf8");
|
|
104
|
+
logger.success("SessionGuard", "Backup restored from " + bakPath);
|
|
105
|
+
return true;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
logger.error("SessionGuard", "Restore failed: " + e.message);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Expose stop
|
|
113
|
+
api.stopSessionGuard = function () {
|
|
114
|
+
if (intervalRef) clearInterval(intervalRef);
|
|
115
|
+
api.sendMessage = originalSendMessage;
|
|
116
|
+
logger.info("SessionGuard", "Stopped.");
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
logger.success(
|
|
120
|
+
"SessionGuard",
|
|
121
|
+
"Active — auto-save every " + Math.round(interval / 60000) + " min → " + appStatePath
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
save: function (p) { return saveToDisk(p || appStatePath); },
|
|
126
|
+
stop: api.stopSessionGuard,
|
|
127
|
+
restore: api.restoreSessionBackup
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var utils = require("../utils");
|
|
3
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
4
|
+
return function setActiveStatus(enable, callback) {
|
|
5
|
+
var { promise, callback: cb } = utils.wrapCallback(callback);
|
|
6
|
+
defaultFuncs.post("https://www.facebook.com/ajax/presence/update.php", ctx.jar, {
|
|
7
|
+
fb_api_req_friendly_name: "updatePresence",
|
|
8
|
+
presence_state: enable ? "ACTIVE" : "OFFLINE",
|
|
9
|
+
__user: ctx.userID
|
|
10
|
+
})
|
|
11
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
12
|
+
.then(resData => { if (resData.error) throw resData; cb(null, true); })
|
|
13
|
+
.catch(err => cb(err));
|
|
14
|
+
return promise;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var utils = require("../utils");
|
|
3
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
4
|
+
return function setMessageReaction(reaction, messageID, threadID, callback) {
|
|
5
|
+
var { promise, callback: cb } = utils.wrapCallback(callback);
|
|
6
|
+
var mqttClient = ctx.mqttClient || global.mqttClient;
|
|
7
|
+
|
|
8
|
+
if (mqttClient) {
|
|
9
|
+
ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
|
|
10
|
+
ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
|
|
11
|
+
var payload = {
|
|
12
|
+
thread_key: String(threadID),
|
|
13
|
+
timestamp_ms: Date.now(),
|
|
14
|
+
message_id: String(messageID),
|
|
15
|
+
reaction: reaction || "",
|
|
16
|
+
actor_id: ctx.userID,
|
|
17
|
+
reaction_style: null,
|
|
18
|
+
sync_group: 1,
|
|
19
|
+
send_attribution: 65537
|
|
20
|
+
};
|
|
21
|
+
var content = {
|
|
22
|
+
app_id: '2220391788200892',
|
|
23
|
+
payload: JSON.stringify({
|
|
24
|
+
data_trace_id: null,
|
|
25
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
26
|
+
tasks: [{
|
|
27
|
+
failure_count: null,
|
|
28
|
+
label: '29',
|
|
29
|
+
payload: JSON.stringify(payload),
|
|
30
|
+
queue_name: JSON.stringify(['reaction', String(messageID)]),
|
|
31
|
+
task_id: ctx.wsTaskNumber
|
|
32
|
+
}],
|
|
33
|
+
version_id: '7158486590867448'
|
|
34
|
+
}),
|
|
35
|
+
request_id: ctx.wsReqNumber,
|
|
36
|
+
type: 3
|
|
37
|
+
};
|
|
38
|
+
mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false });
|
|
39
|
+
cb(null, true);
|
|
40
|
+
return promise;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defaultFuncs.postFormData("https://www.facebook.com/webgraphql/mutation/", ctx.jar, {}, {
|
|
44
|
+
doc_id: "1491398900900362",
|
|
45
|
+
variables: JSON.stringify({
|
|
46
|
+
data: {
|
|
47
|
+
client_mutation_id: String(ctx.clientMutationId++),
|
|
48
|
+
actor_id: ctx.userID,
|
|
49
|
+
action: reaction ? "ADD_REACTION" : "REMOVE_REACTION",
|
|
50
|
+
message_id: String(messageID),
|
|
51
|
+
reaction: reaction || null
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
dpr: 1
|
|
55
|
+
})
|
|
56
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
57
|
+
.then(r => { if (r && r.error) throw r; cb(null, true); })
|
|
58
|
+
.catch(err => cb(err));
|
|
59
|
+
return promise;
|
|
60
|
+
};
|
|
61
|
+
};
|