stfca 1.0.6 → 1.0.7
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/index.js +22 -1
- package/package.json +1 -1
- package/src/OldMessage.js +239 -144
- package/src/listenMqtt.js +8 -2
- package/src/sendMessage.js +10 -1
- package/utils.js +15 -3
package/index.js
CHANGED
|
@@ -174,7 +174,8 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
174
174
|
callback_Task: {},
|
|
175
175
|
wsReqNumber: 0,
|
|
176
176
|
wsTaskNumber: 0,
|
|
177
|
-
reqCallbacks: {}
|
|
177
|
+
reqCallbacks: {},
|
|
178
|
+
threadTypes: {} // Store thread type (dm/group) for each thread
|
|
178
179
|
};
|
|
179
180
|
var api = {
|
|
180
181
|
setOptions: setOptions.bind(null, globalOptions),
|
|
@@ -222,6 +223,26 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
222
223
|
};
|
|
223
224
|
//if (noMqttData) api.htmlData = noMqttData;
|
|
224
225
|
require('fs').readdirSync(__dirname + '/src/').filter(v => v.endsWith('.js')).forEach(v => { api[v.replace('.js', '')] = require(`./src/${v}`)(utils.makeDefaults(html, userID, ctx), api, ctx); });
|
|
226
|
+
|
|
227
|
+
// Store original sendMessage as the primary method
|
|
228
|
+
const originalSendMessage = api.sendMessage;
|
|
229
|
+
|
|
230
|
+
// Wrap sendMessage to use OldMessage as fallback on error
|
|
231
|
+
api.sendMessage = async function(msg, threadID, callback, replyToMessage, isSingleUser) {
|
|
232
|
+
try {
|
|
233
|
+
return await originalSendMessage(msg, threadID, callback, replyToMessage, isSingleUser);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
// If modern method fails, fallback to OldMessage
|
|
236
|
+
console.log('sendMessage failed, using OldMessage fallback:', error.message);
|
|
237
|
+
return api.OldMessage(msg, threadID, callback, replyToMessage, isSingleUser);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Provide explicit method for DM sending using OldMessage
|
|
242
|
+
api.sendMessageDM = function(msg, threadID, callback, replyToMessage) {
|
|
243
|
+
return api.OldMessage(msg, threadID, callback, replyToMessage, true);
|
|
244
|
+
};
|
|
245
|
+
|
|
225
246
|
api.listen = api.listenMqtt;
|
|
226
247
|
return {
|
|
227
248
|
ctx,
|
package/package.json
CHANGED
package/src/OldMessage.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
var utils = require("../utils");
|
|
4
|
+
var log = require("npmlog");
|
|
5
|
+
var bluebird = require("bluebird");
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
var allowedProperties = {
|
|
7
8
|
attachment: true,
|
|
8
9
|
url: true,
|
|
9
10
|
sticker: true,
|
|
@@ -14,63 +15,83 @@ const allowedProperties = {
|
|
|
14
15
|
location: true,
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
module.exports = (defaultFuncs, api, ctx)
|
|
18
|
-
|
|
18
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
19
|
+
function uploadAttachment(attachments, callback) {
|
|
19
20
|
var uploads = [];
|
|
21
|
+
|
|
22
|
+
// create an array of promises
|
|
20
23
|
for (var i = 0; i < attachments.length; i++) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
if (!utils.isReadableStream(attachments[i])) throw { error: "Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "." };
|
|
25
|
+
var form = {
|
|
26
|
+
upload_1024: attachments[i],
|
|
27
|
+
voice_clip: "true"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
uploads.push(
|
|
31
|
+
defaultFuncs
|
|
32
|
+
.postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form, {}, {})
|
|
33
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
34
|
+
.then(function (resData) {
|
|
35
|
+
if (resData.error) throw resData;
|
|
36
|
+
// We have to return the data unformatted unless we want to change it
|
|
37
|
+
// back in sendMessage.
|
|
38
|
+
return resData.payload.metadata[0];
|
|
39
|
+
})
|
|
40
|
+
);
|
|
32
41
|
}
|
|
33
|
-
|
|
42
|
+
|
|
43
|
+
// resolve all promises
|
|
44
|
+
bluebird
|
|
45
|
+
.all(uploads)
|
|
46
|
+
.then(resData => callback(null, resData))
|
|
47
|
+
.catch(function (err) {
|
|
48
|
+
log.error("uploadAttachment", err);
|
|
49
|
+
return callback(err);
|
|
50
|
+
});
|
|
34
51
|
}
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
function getUrl(url, callback) {
|
|
54
|
+
var form = {
|
|
38
55
|
image_height: 960,
|
|
39
56
|
image_width: 960,
|
|
40
57
|
uri: url
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
defaultFuncs
|
|
61
|
+
.post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, form)
|
|
62
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
63
|
+
.then(function (resData) {
|
|
64
|
+
if (resData.error) return callback(resData);
|
|
65
|
+
if (!resData.payload) return callback({ error: "Invalid url" });
|
|
66
|
+
callback(null, resData.payload.share_data.share_params);
|
|
67
|
+
})
|
|
68
|
+
.catch(function (err) {
|
|
69
|
+
log.error("getUrl", err);
|
|
70
|
+
return callback(err);
|
|
71
|
+
});
|
|
45
72
|
}
|
|
46
73
|
|
|
47
|
-
|
|
74
|
+
function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
|
|
48
75
|
// There are three cases here:
|
|
49
76
|
// 1. threadID is of type array, where we're starting a new group chat with users
|
|
50
77
|
// specified in the array.
|
|
51
78
|
// 2. User is sending a message to a specific user.
|
|
52
79
|
// 3. No additional form params and the message goes to an existing group chat.
|
|
53
80
|
if (utils.getType(threadID) === "Array") {
|
|
54
|
-
for (var i = 0; i < threadID.length; i++)
|
|
55
|
-
form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
|
|
56
|
-
}
|
|
81
|
+
for (var i = 0; i < threadID.length; i++) form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
|
|
57
82
|
form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
|
|
58
83
|
form["client_thread_id"] = "root:" + messageAndOTID;
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
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
|
-
|
|
84
|
+
log.info("sendMessage", "Sending message to multiple users: " + threadID);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
65
87
|
// This means that threadID is the id of a user, and the chat
|
|
66
88
|
// is a single person chat
|
|
67
|
-
if (
|
|
89
|
+
if (isSingleUser) {
|
|
68
90
|
form["specific_to_list[0]"] = "fbid:" + threadID;
|
|
69
91
|
form["specific_to_list[1]"] = "fbid:" + ctx.userID;
|
|
70
92
|
form["other_user_fbid"] = threadID;
|
|
71
|
-
} else {
|
|
72
|
-
form["thread_fbid"] = threadID;
|
|
73
93
|
}
|
|
94
|
+
else form["thread_fbid"] = threadID;
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
if (ctx.globalOptions.pageID) {
|
|
@@ -81,53 +102,182 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
81
102
|
form["creator_info[labelType]"] = "sent_message";
|
|
82
103
|
form["creator_info[pageID]"] = ctx.globalOptions.pageID;
|
|
83
104
|
form["request_user_id"] = ctx.globalOptions.pageID;
|
|
84
|
-
form["creator_info[profileURI]"] =
|
|
85
|
-
"https://www.facebook.com/profile.php?id=" + ctx.userID;
|
|
105
|
+
form["creator_info[profileURI]"] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
defaultFuncs
|
|
109
|
+
.post("https://www.facebook.com/messaging/send/", ctx.jar, form)
|
|
110
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
111
|
+
.then(function (resData) {
|
|
112
|
+
if (!resData) return callback({ error: "Send message failed." });
|
|
113
|
+
if (resData.error) {
|
|
114
|
+
if (resData.error === 1545012) {
|
|
115
|
+
log.warn("sendMessage", "Got error 1545012. This might mean that you're not part of the conversation " + threadID);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
log.error("sendMessage", resData);
|
|
119
|
+
}
|
|
120
|
+
return callback(resData);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var messageInfo = resData.payload.actions.reduce(function (p, v) {
|
|
124
|
+
return (
|
|
125
|
+
{
|
|
126
|
+
threadID: v.thread_fbid,
|
|
127
|
+
messageID: v.message_id,
|
|
128
|
+
timestamp: v.timestamp
|
|
129
|
+
} || p
|
|
130
|
+
);
|
|
131
|
+
}, null);
|
|
132
|
+
|
|
133
|
+
return callback(null, messageInfo);
|
|
134
|
+
})
|
|
135
|
+
.catch(function (err) {
|
|
136
|
+
log.error("sendMessage", err);
|
|
137
|
+
if (utils.getType(err) == "Object" && err.error === "Not logged in.") ctx.loggedIn = false;
|
|
138
|
+
return callback(err);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function send(form, threadID, messageAndOTID, callback, isGroup) {
|
|
143
|
+
// We're doing a query to this to check if the given id is the id of
|
|
144
|
+
// a user or of a group chat. The form will be different depending
|
|
145
|
+
// on that.
|
|
146
|
+
if (utils.getType(threadID) === "Array") sendContent(form, threadID, false, messageAndOTID, callback);
|
|
147
|
+
else {
|
|
148
|
+
if (utils.getType(isGroup) != "Boolean") sendContent(form, threadID, threadID.length === 15, messageAndOTID, callback);
|
|
149
|
+
else sendContent(form, threadID, !isGroup, messageAndOTID, callback);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleUrl(msg, form, callback, cb) {
|
|
154
|
+
if (msg.url) {
|
|
155
|
+
form["shareable_attachment[share_type]"] = "100";
|
|
156
|
+
getUrl(msg.url, function (err, params) {
|
|
157
|
+
if (err) return callback(err);
|
|
158
|
+
form["shareable_attachment[share_params]"] = params;
|
|
159
|
+
cb();
|
|
160
|
+
});
|
|
91
161
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
162
|
+
else cb();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function handleLocation(msg, form, callback, cb) {
|
|
166
|
+
if (msg.location) {
|
|
167
|
+
if (msg.location.latitude == null || msg.location.longitude == null) return callback({ error: "location property needs both latitude and longitude" });
|
|
168
|
+
form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
|
|
169
|
+
form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
|
|
170
|
+
form["location_attachment[is_current_location]"] = !!msg.location.current;
|
|
171
|
+
}
|
|
172
|
+
cb();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleSticker(msg, form, callback, cb) {
|
|
176
|
+
if (msg.sticker) form["sticker_id"] = msg.sticker;
|
|
177
|
+
cb();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function handleEmoji(msg, form, callback, cb) {
|
|
181
|
+
if (msg.emojiSize != null && msg.emoji == null) return callback({ error: "emoji property is empty" });
|
|
182
|
+
if (msg.emoji) {
|
|
183
|
+
if (msg.emojiSize == null) msg.emojiSize = "medium";
|
|
184
|
+
if (msg.emojiSize != "small" && msg.emojiSize != "medium" && msg.emojiSize != "large") return callback({ error: "emojiSize property is invalid" });
|
|
185
|
+
if (form["body"] != null && form["body"] != "") return callback({ error: "body is not empty" });
|
|
186
|
+
form["body"] = msg.emoji;
|
|
187
|
+
form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
|
|
188
|
+
}
|
|
189
|
+
cb();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleAttachment(msg, form, callback, cb) {
|
|
193
|
+
if (msg.attachment) {
|
|
194
|
+
form["image_ids"] = [];
|
|
195
|
+
form["gif_ids"] = [];
|
|
196
|
+
form["file_ids"] = [];
|
|
197
|
+
form["video_ids"] = [];
|
|
198
|
+
form["audio_ids"] = [];
|
|
199
|
+
|
|
200
|
+
if (utils.getType(msg.attachment) !== "Array") msg.attachment = [msg.attachment];
|
|
201
|
+
if (msg.attachment.every(e=>/_id$/.test(e[0]))) {
|
|
202
|
+
//console.log(msg.attachment)
|
|
203
|
+
msg.attachment.map(e=>form[`${e[0]}s`].push(e[1]));
|
|
204
|
+
return cb();
|
|
205
|
+
}
|
|
206
|
+
uploadAttachment(msg.attachment, function (err, files) {
|
|
207
|
+
if (err) return callback(err);
|
|
208
|
+
files.forEach(function (file) {
|
|
209
|
+
var key = Object.keys(file);
|
|
210
|
+
var type = key[0]; // image_id, file_id, etc
|
|
211
|
+
form["" + type + "s"].push(file[type]); // push the id
|
|
212
|
+
});
|
|
213
|
+
cb();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
else cb();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function handleMention(msg, form, callback, cb) {
|
|
220
|
+
if (msg.mentions) {
|
|
221
|
+
for (let i = 0; i < msg.mentions.length; i++) {
|
|
222
|
+
const mention = msg.mentions[i];
|
|
223
|
+
const tag = mention.tag;
|
|
224
|
+
if (typeof tag !== "string") return callback({ error: "Mention tags must be strings." });
|
|
225
|
+
const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
|
|
226
|
+
if (offset < 0) log.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
|
|
227
|
+
if (mention.id == null) log.warn("handleMention", "Mention id should be non-null.");
|
|
228
|
+
|
|
229
|
+
const id = mention.id || 0;
|
|
230
|
+
const emptyChar = '\u200E';
|
|
231
|
+
form["body"] = emptyChar + msg.body;
|
|
232
|
+
form["profile_xmd[" + i + "][offset]"] = offset + 1;
|
|
233
|
+
form["profile_xmd[" + i + "][length]"] = tag.length;
|
|
234
|
+
form["profile_xmd[" + i + "][id]"] = id;
|
|
235
|
+
form["profile_xmd[" + i + "][type]"] = "p";
|
|
96
236
|
}
|
|
97
|
-
throw new Error(resData);
|
|
98
237
|
}
|
|
99
|
-
|
|
100
|
-
return { threadID: v.thread_fbid, messageID: v.message_id, timestamp: v.timestamp } || p;
|
|
101
|
-
}, null);
|
|
102
|
-
return messageInfo;
|
|
238
|
+
cb();
|
|
103
239
|
}
|
|
104
240
|
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
isSingleUser = replyToMessage;
|
|
241
|
+
return function sendMessage(msg, threadID, callback, replyToMessage, isGroup) {
|
|
242
|
+
typeof isGroup == "undefined" ? isGroup = null : "";
|
|
243
|
+
if (!callback && (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction")) return threadID({ error: "Pass a threadID as a second argument." });
|
|
244
|
+
if (!replyToMessage && utils.getType(callback) === "String") {
|
|
110
245
|
replyToMessage = callback;
|
|
111
|
-
callback = function() {};
|
|
112
|
-
} else if (typeof callback !== "function") {
|
|
113
|
-
callback = function() {};
|
|
246
|
+
callback = function () { };
|
|
114
247
|
}
|
|
115
248
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
249
|
+
var resolveFunc = function () { };
|
|
250
|
+
var rejectFunc = function () { };
|
|
251
|
+
var returnPromise = new Promise(function (resolve, reject) {
|
|
252
|
+
resolveFunc = resolve;
|
|
253
|
+
rejectFunc = reject;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (!callback) {
|
|
257
|
+
callback = function (err, data) {
|
|
258
|
+
if (err) return rejectFunc(err);
|
|
259
|
+
resolveFunc(data);
|
|
260
|
+
};
|
|
128
261
|
}
|
|
129
|
-
|
|
130
|
-
|
|
262
|
+
|
|
263
|
+
var msgType = utils.getType(msg);
|
|
264
|
+
var threadIDType = utils.getType(threadID);
|
|
265
|
+
var messageIDType = utils.getType(replyToMessage);
|
|
266
|
+
|
|
267
|
+
if (msgType !== "String" && msgType !== "Object") return callback({ error: "Message should be of type string or object and not " + msgType + "." });
|
|
268
|
+
|
|
269
|
+
// Changing this to accomodate an array of users
|
|
270
|
+
if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") return callback({ error: "ThreadID should be of type number, string, or array and not " + threadIDType + "." });
|
|
271
|
+
|
|
272
|
+
if (replyToMessage && messageIDType !== 'String') return callback({ error: "MessageID should be of type string and not " + threadIDType + "." });
|
|
273
|
+
|
|
274
|
+
if (msgType === "String") msg = { body: msg };
|
|
275
|
+
var disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
|
|
276
|
+
if (disallowedProperties.length > 0) return callback({ error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`" });
|
|
277
|
+
|
|
278
|
+
var messageAndOTID = utils.generateOfflineThreadingID();
|
|
279
|
+
// console.log(messageAndOTID)
|
|
280
|
+
var form = {
|
|
131
281
|
client: "mercury",
|
|
132
282
|
action_type: "ma-type:user-generated-message",
|
|
133
283
|
author: "fbid:" + ctx.userID,
|
|
@@ -146,9 +296,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
146
296
|
is_spoof_warning: false,
|
|
147
297
|
source: "source:chat:web",
|
|
148
298
|
"source_tags[0]": "source:chat",
|
|
149
|
-
|
|
150
|
-
body: msg.body
|
|
151
|
-
}),
|
|
299
|
+
body: msg.body ? msg.body.toString() : "",
|
|
152
300
|
html_body: false,
|
|
153
301
|
ui_push_phase: "V3",
|
|
154
302
|
status: "0",
|
|
@@ -159,76 +307,23 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
159
307
|
manual_retry_cnt: "0",
|
|
160
308
|
has_attachment: !!(msg.attachment || msg.url || msg.sticker),
|
|
161
309
|
signatureID: utils.getSignatureID(),
|
|
162
|
-
|
|
163
|
-
replied_to_message_id: replyToMessage
|
|
164
|
-
})
|
|
310
|
+
replied_to_message_id: replyToMessage
|
|
165
311
|
};
|
|
312
|
+
// console.log(form)
|
|
166
313
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
form.audio_ids = [];
|
|
182
|
-
if (utils.getType(msg.attachment) !== "Array") {
|
|
183
|
-
msg.attachment = [msg.attachment];
|
|
184
|
-
}
|
|
185
|
-
const files = await uploadAttachment(msg.attachment);
|
|
186
|
-
files.forEach(file => {
|
|
187
|
-
const type = Object.keys(file)[0];
|
|
188
|
-
form["" + type + "s"].push(file[type]);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
if (msg.url) {
|
|
192
|
-
form["shareable_attachment[share_type]"] = "100";
|
|
193
|
-
const params = await getUrl(msg.url);
|
|
194
|
-
form["shareable_attachment[share_params]"] = params;
|
|
195
|
-
}
|
|
196
|
-
if (msg.emoji) {
|
|
197
|
-
if (!msg.emojiSize) {
|
|
198
|
-
msg.emojiSize = "medium";
|
|
199
|
-
}
|
|
200
|
-
if (msg.emojiSize !== "small" && msg.emojiSize !== "medium" && msg.emojiSize !== "large") {
|
|
201
|
-
throw new Error("emojiSize property is invalid");
|
|
202
|
-
}
|
|
203
|
-
if (!form.body) {
|
|
204
|
-
throw new Error("body is not empty");
|
|
205
|
-
}
|
|
206
|
-
form.body = msg.emoji;
|
|
207
|
-
form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
|
|
208
|
-
}
|
|
209
|
-
if (msg.mentions) {
|
|
210
|
-
for (let i = 0; i < msg.mentions.length; i++) {
|
|
211
|
-
const mention = msg.mentions[i];
|
|
212
|
-
const tag = mention.tag;
|
|
213
|
-
if (typeof tag !== "string") {
|
|
214
|
-
throw new Error("Mention tags must be strings.");
|
|
215
|
-
}
|
|
216
|
-
const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
|
|
217
|
-
if (offset < 0) utils.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
|
|
218
|
-
if (!mention.id) utils.warn("handleMention", "Mention id should be non-null.");
|
|
219
|
-
const id = mention.id || 0;
|
|
220
|
-
const emptyChar = '\u200E';
|
|
221
|
-
form["body"] = emptyChar + msg.body;
|
|
222
|
-
form["profile_xmd[" + i + "][offset]"] = offset + 1;
|
|
223
|
-
form["profile_xmd[" + i + "][length]"] = tag.length;
|
|
224
|
-
form["profile_xmd[" + i + "][id]"] = id;
|
|
225
|
-
form["profile_xmd[" + i + "][type]"] = "p";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const result = await sendContent(form, threadID, isSingleUser, messageAndOTID);
|
|
229
|
-
if (callback && typeof callback === "function") {
|
|
230
|
-
callback(null, result);
|
|
231
|
-
}
|
|
232
|
-
return result;
|
|
314
|
+
handleLocation(msg, form, callback, () =>
|
|
315
|
+
handleSticker(msg, form, callback, () =>
|
|
316
|
+
handleAttachment(msg, form, callback, () =>
|
|
317
|
+
handleUrl(msg, form, callback, () =>
|
|
318
|
+
handleEmoji(msg, form, callback, () =>
|
|
319
|
+
handleMention(msg, form, callback, () =>
|
|
320
|
+
send(form, threadID, messageAndOTID, callback, isGroup)
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
);
|
|
327
|
+
return returnPromise;
|
|
233
328
|
};
|
|
234
329
|
};
|
package/src/listenMqtt.js
CHANGED
|
@@ -246,11 +246,17 @@ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
|
|
|
246
246
|
let fmtMsg;
|
|
247
247
|
try {
|
|
248
248
|
fmtMsg = utils.formatDeltaMessage(v);
|
|
249
|
-
// Detect if it's a DM or group thread
|
|
249
|
+
// Detect if it's a DM or group thread - enhanced detection
|
|
250
250
|
const otherUserFbId = v.delta.messageMetadata.threadKey.otherUserFbId;
|
|
251
251
|
const threadFbId = v.delta.messageMetadata.threadKey.threadFbId;
|
|
252
|
-
|
|
252
|
+
|
|
253
|
+
// A thread is a DM if it has otherUserFbId and no threadFbId
|
|
254
|
+
fmtMsg.isSingleUser = !!otherUserFbId && !threadFbId;
|
|
253
255
|
fmtMsg.isGroup = !!threadFbId;
|
|
256
|
+
|
|
257
|
+
// Store thread type in context for sendMessage to use
|
|
258
|
+
if (!ctx.threadTypes) ctx.threadTypes = {};
|
|
259
|
+
ctx.threadTypes[fmtMsg.threadID] = fmtMsg.isSingleUser ? 'dm' : 'group';
|
|
254
260
|
} catch (err) {
|
|
255
261
|
return globalCallback({
|
|
256
262
|
error: "Problem parsing message object.",
|
package/src/sendMessage.js
CHANGED
|
@@ -102,7 +102,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
102
102
|
return messageInfo;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
return async (msg, threadID, callback, replyToMessage, isSingleUser =
|
|
105
|
+
return async (msg, threadID, callback, replyToMessage, isSingleUser = null) => {
|
|
106
106
|
// Handle different parameter patterns for backward compatibility
|
|
107
107
|
if (typeof callback === "string" || (callback && typeof callback === "object")) {
|
|
108
108
|
// callback is actually replyToMessage, shift parameters
|
|
@@ -122,6 +122,15 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
122
122
|
if (msgType === "String") {
|
|
123
123
|
msg = { body: msg };
|
|
124
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
|
+
}
|
|
125
134
|
let disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
|
|
126
135
|
if (disallowedProperties.length > 0) {
|
|
127
136
|
throw new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`");
|
package/utils.js
CHANGED
|
@@ -733,17 +733,23 @@ function formatDeltaMessage(m) {
|
|
|
733
733
|
var args = body == "" ? [] : body.trim().split(/\s+/);
|
|
734
734
|
for (var i = 0; i < m_id.length; i++) mentions[m_id[i]] = m.delta.body.substring(m_offset[i], m_offset[i] + m_length[i]);
|
|
735
735
|
|
|
736
|
+
// Determine if this is a DM or group
|
|
737
|
+
const otherUserFbId = md.threadKey.otherUserFbId;
|
|
738
|
+
const threadFbId = md.threadKey.threadFbId;
|
|
739
|
+
const isSingleUser = !!otherUserFbId && !threadFbId;
|
|
740
|
+
|
|
736
741
|
return {
|
|
737
742
|
type: "message",
|
|
738
743
|
senderID: formatID(md.actorFbId.toString()),
|
|
739
|
-
threadID: formatID((
|
|
744
|
+
threadID: formatID((threadFbId || otherUserFbId).toString()),
|
|
740
745
|
messageID: md.messageId,
|
|
741
746
|
args: args,
|
|
742
747
|
body: body,
|
|
743
748
|
attachments: (m.delta.attachments || []).map(v => _formatAttachment(v)),
|
|
744
749
|
mentions: mentions,
|
|
745
750
|
timestamp: md.timestamp,
|
|
746
|
-
isGroup: !!
|
|
751
|
+
isGroup: !!threadFbId,
|
|
752
|
+
isSingleUser: isSingleUser,
|
|
747
753
|
participantIDs: m.delta.participants || (md.cid ? md.cid.canonicalParticipantFbids : []) || []
|
|
748
754
|
};
|
|
749
755
|
}
|
|
@@ -753,6 +759,11 @@ function formatID(id) {
|
|
|
753
759
|
else return id;
|
|
754
760
|
}
|
|
755
761
|
|
|
762
|
+
function isDMThread(threadID) {
|
|
763
|
+
// DM threads don't have 't_' prefix and are typically numeric user IDs
|
|
764
|
+
return typeof threadID === 'string' && !threadID.includes('t_');
|
|
765
|
+
}
|
|
766
|
+
|
|
756
767
|
function formatMessage(m) {
|
|
757
768
|
var originalMessage = m.message ? m.message : m;
|
|
758
769
|
var obj = {
|
|
@@ -2872,5 +2883,6 @@ module.exports = {
|
|
|
2872
2883
|
decodeClientPayload,
|
|
2873
2884
|
getAppState,
|
|
2874
2885
|
getAdminTextMessageType,
|
|
2875
|
-
setProxy
|
|
2886
|
+
setProxy,
|
|
2887
|
+
isDMThread
|
|
2876
2888
|
};
|