shadowx-fca 2.4.0 → 2.6.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/checkUpdate.js +1 -1
- package/index.js +663 -418
- package/package.json +2 -2
- package/src/GetBotInfo.js +66 -0
- package/src/OldMessage.js +96 -76
- package/src/comment.js +226 -0
- package/src/emoji.js +124 -0
- package/src/friend.js +243 -0
- package/src/gcmember.js +122 -0
- package/src/listenMqtt.js +4 -4
- package/src/nickname.js +140 -0
- package/src/sendMessage.js +398 -234
- package/src/sendMessage2.js +243 -0
- package/src/share.js +62 -0
- package/src/shareContact.js +73 -69
- package/src/shareContact.js.bak +110 -0
- package/src/stickers.js +117 -0
- package/src/story.js +181 -0
- package/src/theme.js +233 -0
- package/utils.js +24 -1311
package/src/sendMessage.js
CHANGED
|
@@ -1,243 +1,407 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// shadowx-fca — sendMessage (MQTT Version)
|
|
3
|
+
// Supports: BOTH DM/E2EE Inbox AND Group Threads
|
|
4
|
+
// Supports: Both callback style and Promise/await style
|
|
2
5
|
|
|
3
6
|
const utils = require('../utils');
|
|
4
|
-
|
|
7
|
+
const log = require('npmlog');
|
|
5
8
|
|
|
6
9
|
const allowedProperties = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
attachment : true,
|
|
11
|
+
url : true,
|
|
12
|
+
sticker : true,
|
|
13
|
+
emoji : true,
|
|
14
|
+
emojiSize : true,
|
|
15
|
+
body : true,
|
|
16
|
+
mentions : true,
|
|
17
|
+
location : true,
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
module.exports = (defaultFuncs, api, ctx) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
21
|
+
|
|
22
|
+
let variance = 0;
|
|
23
|
+
const epoch_id = () => Math.floor(Date.now() * (4194304 + (variance = (variance + 0.1) % 5)));
|
|
24
|
+
|
|
25
|
+
const emojiSizes = {
|
|
26
|
+
small: 1,
|
|
27
|
+
medium: 2,
|
|
28
|
+
large: 3,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Upload attachments
|
|
32
|
+
async function uploadAttachment(attachments) {
|
|
33
|
+
const uploads = [];
|
|
34
|
+
for (const att of attachments) {
|
|
35
|
+
if (!utils.isReadableStream(att)) {
|
|
36
|
+
throw new Error("Attachment must be a readable stream, got: " + utils.getType(att));
|
|
37
|
+
}
|
|
38
|
+
const res = await defaultFuncs.postFormData(
|
|
39
|
+
"https://upload.facebook.com/ajax/mercury/upload.php",
|
|
40
|
+
ctx.jar,
|
|
41
|
+
{ upload_1024: att, voice_clip: "true" },
|
|
42
|
+
{}
|
|
43
|
+
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
44
|
+
|
|
45
|
+
if (res.error) throw new Error("Upload failed: " + JSON.stringify(res));
|
|
46
|
+
uploads.push(res.payload.metadata[0]);
|
|
47
|
+
}
|
|
48
|
+
return uploads;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get URL share params
|
|
52
|
+
async function getUrl(url) {
|
|
53
|
+
const res = await defaultFuncs.post(
|
|
54
|
+
"https://www.facebook.com/message_share_attachment/fromURI/",
|
|
55
|
+
ctx.jar,
|
|
56
|
+
{ image_height: 960, image_width: 960, uri: url }
|
|
57
|
+
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
58
|
+
|
|
59
|
+
if (!res || res.error || !res.payload) {
|
|
60
|
+
throw new Error("URL attachment failed: " + JSON.stringify(res));
|
|
61
|
+
}
|
|
62
|
+
return res.payload.share_data.share_params;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Send via HTTP Mercury (Fallback for Groups only)
|
|
66
|
+
async function sendHttp(form, threadID, messageAndOTID, isSingleUser) {
|
|
67
|
+
const tid = String(threadID);
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(threadID)) {
|
|
70
|
+
threadID.forEach((id, idx) => { form[`specific_to_list[${idx}]`] = "fbid:" + id; });
|
|
71
|
+
form[`specific_to_list[${threadID.length}]`] = "fbid:" + ctx.userID;
|
|
72
|
+
form["client_thread_id"] = "root:" + messageAndOTID;
|
|
73
|
+
} else {
|
|
74
|
+
if (isSingleUser) {
|
|
75
|
+
form["specific_to_list[0]"] = "fbid:" + threadID;
|
|
76
|
+
form["specific_to_list[1]"] = "fbid:" + ctx.userID;
|
|
77
|
+
form["other_user_fbid"] = threadID;
|
|
78
|
+
} else {
|
|
79
|
+
form["thread_fbid"] = tid;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (ctx.globalOptions.pageID) {
|
|
84
|
+
form["author"] = "fbid:" + ctx.globalOptions.pageID;
|
|
85
|
+
form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
|
|
86
|
+
form["creator_info[creatorID]"] = ctx.userID;
|
|
87
|
+
form["creator_info[creatorType]"] = "direct_admin";
|
|
88
|
+
form["creator_info[labelType]"] = "sent_message";
|
|
89
|
+
form["creator_info[pageID]"] = ctx.globalOptions.pageID;
|
|
90
|
+
form["request_user_id"] = ctx.globalOptions.pageID;
|
|
91
|
+
form["creator_info[profileURI]"] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const resData = await defaultFuncs
|
|
95
|
+
.post("https://www.facebook.com/messaging/send/", ctx.jar, form)
|
|
96
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
97
|
+
|
|
98
|
+
if (!resData) throw new Error("Send message failed — no response.");
|
|
99
|
+
if (resData.error) {
|
|
100
|
+
if (resData.error === 1545012) {
|
|
101
|
+
throw new Error(`Cannot send message to thread ${tid}: Bot is not part of this conversation`);
|
|
102
|
+
}
|
|
103
|
+
throw new Error("Send message error: " + JSON.stringify(resData));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return resData.payload.actions.reduce((p, v) => ({
|
|
107
|
+
threadID: v.thread_fbid,
|
|
108
|
+
messageID: v.message_id,
|
|
109
|
+
timestamp: v.timestamp,
|
|
110
|
+
}), null);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Send via MQTT (Works for BOTH DM and Group!)
|
|
114
|
+
function sendMqtt(payload, threadID, replyToMessage, callback) {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
if (!ctx.mqttClient) {
|
|
117
|
+
return reject(new Error('Not connected to MQTT. Call listenMqtt first.'));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const timestamp = Date.now();
|
|
121
|
+
const epoch = timestamp << 22;
|
|
122
|
+
const otid = epoch + Math.floor(Math.random() * 4194304);
|
|
123
|
+
|
|
124
|
+
const tasks = [
|
|
125
|
+
{
|
|
126
|
+
label: "46",
|
|
127
|
+
payload: {
|
|
128
|
+
thread_id: String(threadID),
|
|
129
|
+
otid: String(otid),
|
|
130
|
+
source: 0,
|
|
131
|
+
send_type: 1,
|
|
132
|
+
sync_group: 1,
|
|
133
|
+
text: payload.text || "",
|
|
134
|
+
initiating_source: 1,
|
|
135
|
+
skip_url_preview_gen: 0
|
|
136
|
+
},
|
|
137
|
+
queue_name: String(threadID),
|
|
138
|
+
task_id: 0,
|
|
139
|
+
failure_count: null
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: "21",
|
|
143
|
+
payload: {
|
|
144
|
+
thread_id: String(threadID),
|
|
145
|
+
last_read_watermark_ts: Date.now(),
|
|
146
|
+
sync_group: 1
|
|
147
|
+
},
|
|
148
|
+
queue_name: String(threadID),
|
|
149
|
+
task_id: 1,
|
|
150
|
+
failure_count: null
|
|
151
|
+
}
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
// Add reply metadata if replying
|
|
155
|
+
if (replyToMessage) {
|
|
156
|
+
tasks[0].payload.reply_metadata = {
|
|
157
|
+
reply_source_id: String(replyToMessage),
|
|
158
|
+
reply_source_type: 1,
|
|
159
|
+
reply_type: 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add emoji size if present
|
|
164
|
+
if (payload.hot_emoji_size) {
|
|
165
|
+
tasks[0].payload.hot_emoji_size = payload.hot_emoji_size;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Add sticker if present
|
|
169
|
+
if (payload.sticker_id) {
|
|
170
|
+
tasks[0].payload.send_type = 2;
|
|
171
|
+
tasks[0].payload.sticker_id = payload.sticker_id;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Add attachments if present
|
|
175
|
+
if (payload.attachment_fbids && payload.attachment_fbids.length > 0) {
|
|
176
|
+
tasks[0].payload.send_type = 3;
|
|
177
|
+
tasks[0].payload.attachment_fbids = payload.attachment_fbids;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add mentions if present
|
|
181
|
+
if (payload.mention_data) {
|
|
182
|
+
tasks[0].payload.mention_data = payload.mention_data;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Add location if present
|
|
186
|
+
if (payload.location_data) {
|
|
187
|
+
tasks[0].payload.location_data = payload.location_data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const mqttForm = {
|
|
191
|
+
app_id: ctx.appID || "2220391788200892",
|
|
192
|
+
payload: {
|
|
193
|
+
tasks: tasks,
|
|
194
|
+
epoch_id: epoch_id(),
|
|
195
|
+
version_id: "6120284488008082",
|
|
196
|
+
data_trace_id: null
|
|
197
|
+
},
|
|
198
|
+
request_id: 1,
|
|
199
|
+
type: 3
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Stringify all task payloads
|
|
203
|
+
mqttForm.payload.tasks.forEach(task => {
|
|
204
|
+
task.payload = JSON.stringify(task.payload);
|
|
205
|
+
});
|
|
206
|
+
mqttForm.payload = JSON.stringify(mqttForm.payload);
|
|
207
|
+
|
|
208
|
+
log.info("sendMessage", `Sending via MQTT to ${threadID}`);
|
|
209
|
+
|
|
210
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(mqttForm), { qos: 1, retain: false }, (err) => {
|
|
211
|
+
if (err) {
|
|
212
|
+
log.error("sendMessage", `MQTT publish failed: ${err.message || err}`);
|
|
213
|
+
reject(new Error(`MQTT publish failed: ${err.message || err}`));
|
|
214
|
+
} else {
|
|
215
|
+
const result = {
|
|
216
|
+
threadID: String(threadID),
|
|
217
|
+
messageID: String(otid),
|
|
218
|
+
timestamp: timestamp
|
|
219
|
+
};
|
|
220
|
+
if (callback) callback(null, result);
|
|
221
|
+
resolve(result);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Main send function
|
|
228
|
+
return async function sendMessage(msg, threadID, replyOrCallback, callbackArg) {
|
|
229
|
+
let replyToMessage = null;
|
|
230
|
+
let callback = null;
|
|
231
|
+
|
|
232
|
+
// Parse arguments
|
|
233
|
+
if (typeof replyOrCallback === 'function') {
|
|
234
|
+
callback = replyOrCallback;
|
|
235
|
+
} else if (typeof replyOrCallback === 'string' || typeof replyOrCallback === 'number') {
|
|
236
|
+
replyToMessage = String(replyOrCallback);
|
|
237
|
+
if (typeof callbackArg === 'function') callback = callbackArg;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const doSend = async () => {
|
|
241
|
+
const msgType = utils.getType(msg);
|
|
242
|
+
if (msgType !== "String" && msgType !== "Object") {
|
|
243
|
+
throw new Error("Message must be a string or object, got: " + msgType);
|
|
244
|
+
}
|
|
245
|
+
if (msgType === "String") msg = { body: msg };
|
|
246
|
+
|
|
247
|
+
const disallowed = Object.keys(msg).filter(p => !allowedProperties[p]);
|
|
248
|
+
if (disallowed.length > 0) {
|
|
249
|
+
throw new Error("Disallowed message properties: " + disallowed.join(", "));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Detect if DM or Group
|
|
253
|
+
const threadIDStr = String(threadID);
|
|
254
|
+
const isSingleUser = !Array.isArray(threadID) &&
|
|
255
|
+
(threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/));
|
|
256
|
+
|
|
257
|
+
// Build MQTT payload
|
|
258
|
+
const mqttPayload = {
|
|
259
|
+
text: msg.body || ""
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Handle mentions
|
|
263
|
+
if (msg.mentions && msg.mentions.length > 0) {
|
|
264
|
+
const arrayIds = [];
|
|
265
|
+
const arrayOffsets = [];
|
|
266
|
+
const arrayLengths = [];
|
|
267
|
+
const mention_types = [];
|
|
268
|
+
|
|
269
|
+
for (const mention of msg.mentions) {
|
|
270
|
+
const { tag, id, fromIndex } = mention;
|
|
271
|
+
if (typeof tag !== "string") throw new Error("Mention tags must be strings.");
|
|
272
|
+
|
|
273
|
+
const offset = msg.body.indexOf(tag, fromIndex || 0);
|
|
274
|
+
if (offset < 0) log.warn("sendMessage", `Mention "${tag}" not found in body.`);
|
|
275
|
+
|
|
276
|
+
arrayIds.push(id || 0);
|
|
277
|
+
arrayOffsets.push(offset);
|
|
278
|
+
arrayLengths.push(tag.length);
|
|
279
|
+
mention_types.push("p");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
mqttPayload.mention_data = {
|
|
283
|
+
mention_ids: arrayIds.join(","),
|
|
284
|
+
mention_offsets: arrayOffsets.join(","),
|
|
285
|
+
mention_lengths: arrayLengths.join(","),
|
|
286
|
+
mention_types: mention_types.join(",")
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Handle emoji
|
|
291
|
+
if (msg.emoji) {
|
|
292
|
+
let emojiSize = msg.emojiSize || "medium";
|
|
293
|
+
if (!["small", "medium", "large"].includes(emojiSize)) {
|
|
294
|
+
throw new Error("emojiSize must be small, medium, or large.");
|
|
295
|
+
}
|
|
296
|
+
mqttPayload.text = msg.emoji;
|
|
297
|
+
mqttPayload.hot_emoji_size = emojiSizes[emojiSize];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Handle sticker
|
|
301
|
+
if (msg.sticker) {
|
|
302
|
+
mqttPayload.sticker_id = String(msg.sticker);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Handle location
|
|
306
|
+
if (msg.location) {
|
|
307
|
+
if (!msg.location.latitude || !msg.location.longitude) {
|
|
308
|
+
throw new Error("location needs both latitude and longitude.");
|
|
309
|
+
}
|
|
310
|
+
mqttPayload.location_data = {
|
|
311
|
+
coordinates: {
|
|
312
|
+
latitude: msg.location.latitude,
|
|
313
|
+
longitude: msg.location.longitude
|
|
314
|
+
},
|
|
315
|
+
is_current_location: !!msg.location.current,
|
|
316
|
+
is_live_location: !!msg.location.live
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Handle attachments
|
|
321
|
+
if (msg.attachment) {
|
|
322
|
+
if (utils.getType(msg.attachment) !== "Array") {
|
|
323
|
+
msg.attachment = [msg.attachment];
|
|
324
|
+
}
|
|
325
|
+
const files = await uploadAttachment(msg.attachment);
|
|
326
|
+
mqttPayload.attachment_fbids = files.map(file => {
|
|
327
|
+
const key = Object.keys(file)[0];
|
|
328
|
+
return file[key];
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Handle URL
|
|
333
|
+
if (msg.url) {
|
|
334
|
+
try {
|
|
335
|
+
const params = await getUrl(msg.url);
|
|
336
|
+
// For MQTT, we just append URL to text
|
|
337
|
+
mqttPayload.text = mqttPayload.text ? mqttPayload.text + " " + msg.url : msg.url;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
log.warn("sendMessage", "URL attachment failed, sending as text link");
|
|
340
|
+
mqttPayload.text = mqttPayload.text ? mqttPayload.text + " " + msg.url : msg.url;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Try MQTT first (works for both DM and Group)
|
|
345
|
+
try {
|
|
346
|
+
return await sendMqtt(mqttPayload, threadID, replyToMessage, callback);
|
|
347
|
+
} catch (mqttErr) {
|
|
348
|
+
log.warn("sendMessage", "MQTT send failed: " + mqttErr.message);
|
|
349
|
+
|
|
350
|
+
// Fallback to HTTP for groups only
|
|
351
|
+
if (!isSingleUser && !Array.isArray(threadID)) {
|
|
352
|
+
log.info("sendMessage", "Falling back to HTTP for group message");
|
|
353
|
+
|
|
354
|
+
const messageAndOTID = utils.generateOfflineThreadingID();
|
|
355
|
+
const httpForm = {
|
|
356
|
+
client: "mercury",
|
|
357
|
+
action_type: "ma-type:user-generated-message",
|
|
358
|
+
author: "fbid:" + ctx.userID,
|
|
359
|
+
timestamp: Date.now(),
|
|
360
|
+
timestamp_absolute: "Today",
|
|
361
|
+
timestamp_relative: utils.generateTimestampRelative(),
|
|
362
|
+
timestamp_time_passed: "0",
|
|
363
|
+
is_unread: false,
|
|
364
|
+
is_cleared: false,
|
|
365
|
+
is_forward: false,
|
|
366
|
+
is_filtered_content: false,
|
|
367
|
+
is_filtered_content_bh: false,
|
|
368
|
+
is_filtered_content_account: false,
|
|
369
|
+
is_filtered_content_quasar: false,
|
|
370
|
+
is_filtered_content_invalid_app: false,
|
|
371
|
+
is_spoof_warning: false,
|
|
372
|
+
source: "source:chat:web",
|
|
373
|
+
"source_tags[0]": "source:chat",
|
|
374
|
+
body: msg.body || "",
|
|
375
|
+
html_body: false,
|
|
376
|
+
ui_push_phase: "V3",
|
|
377
|
+
status: "0",
|
|
378
|
+
offline_threading_id: messageAndOTID,
|
|
379
|
+
message_id: messageAndOTID,
|
|
380
|
+
threading_id: utils.generateThreadingID(ctx.clientID),
|
|
381
|
+
"ephemeral_ttl_mode:": "0",
|
|
382
|
+
manual_retry_cnt: "0",
|
|
383
|
+
has_attachment: !!(msg.attachment || msg.url || msg.sticker),
|
|
384
|
+
signatureID: utils.getSignatureID(),
|
|
385
|
+
...(replyToMessage && { replied_to_message_id: replyToMessage }),
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (msg.sticker) httpForm["sticker_id"] = msg.sticker;
|
|
389
|
+
|
|
390
|
+
return await sendHttp(httpForm, threadID, messageAndOTID, isSingleUser);
|
|
391
|
+
} else {
|
|
392
|
+
throw new Error("DM/E2EE messages require MQTT. Make sure listenMqtt is called first.");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Execute and handle callback/promise
|
|
398
|
+
if (callback) {
|
|
399
|
+
doSend()
|
|
400
|
+
.then(info => callback(null, info))
|
|
401
|
+
.catch(err => callback(err));
|
|
402
|
+
return undefined;
|
|
403
|
+
} else {
|
|
404
|
+
return doSend();
|
|
405
|
+
}
|
|
406
|
+
};
|
|
243
407
|
};
|