aminul-remake-fca
Version:
Aminul's remake of ws3-fca — next-generation Facebook Chat API fork
442 lines (393 loc) • 12.9 kB
JavaScript
;
var utils = require("../utils");
// @NethWs3Dev
var allowedProperties = {
attachment: true,
url: true,
sticker: true,
emoji: true,
emojiSize: true,
body: true,
mentions: true,
location: true,
};
module.exports = function (defaultFuncs, api, ctx) {
function uploadAttachment(attachments, callback) {
var uploads = [];
// create an array of promises
for (var i = 0; i < attachments.length; i++) {
if (!utils.isReadableStream(attachments[i])) {
throw {
error:
"Attachment should be a readable stream and not " +
utils.getType(attachments[i]) +
"."
};
}
var form = {
upload_1024: attachments[i],
voice_clip: "true"
};
uploads.push(
defaultFuncs
.postFormData(
"https://upload.facebook.com/ajax/mercury/upload.php",
ctx.jar,
form,
{}
)
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
.then(function (resData) {
if (resData.error) {
throw resData;
}
// We have to return the data unformatted unless we want to change it
// back in sendMessage.
return resData.payload.metadata[0];
})
);
}
// resolve all promises
Promise.all(uploads)
.then(function (resData) {
callback(null, resData);
})
.catch(function (err) {
utils.error("uploadAttachment", err);
return callback(err);
});
}
function getUrl(url, callback) {
var form = {
image_height: 960,
image_width: 960,
uri: url
};
defaultFuncs
.post(
"https://www.facebook.com/message_share_attachment/fromURI/",
ctx.jar,
form
)
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
.then(function (resData) {
if (resData.error) {
return callback(resData);
}
if (!resData.payload) {
return callback({ error: "Invalid url" });
}
callback(null, resData.payload.share_data.share_params);
})
.catch(function (err) {
utils.error("getUrl", err);
return callback(err);
});
}
function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
// There are three cases here:
// 1. threadID is of type array, where we're starting a new group chat with users
// specified in the array.
// 2. User is sending a message to a specific user.
// 3. No additional form params and the message goes to an existing group chat.
if (utils.getType(threadID) === "Array") {
for (var i = 0; i < threadID.length; i++) {
form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
}
form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
form["client_thread_id"] = "root:" + messageAndOTID;
utils.log("sendMessage", "Sending message to multiple users: " + threadID);
} else {
// This means that threadID is the id of a user, and the chat
// is a single person chat
if (isSingleUser) {
form["specific_to_list[0]"] = "fbid:" + threadID;
form["specific_to_list[1]"] = "fbid:" + ctx.userID;
form["other_user_fbid"] = threadID;
} else {
form["thread_fbid"] = threadID;
}
}
if (ctx.globalOptions.pageID) {
form["author"] = "fbid:" + ctx.globalOptions.pageID;
form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
form["creator_info[creatorID]"] = ctx.userID;
form["creator_info[creatorType]"] = "direct_admin";
form["creator_info[labelType]"] = "sent_message";
form["creator_info[pageID]"] = ctx.globalOptions.pageID;
form["request_user_id"] = ctx.globalOptions.pageID;
form["creator_info[profileURI]"] =
"https://www.facebook.com/profile.php?id=" + ctx.userID;
}
defaultFuncs
.post("https://www.facebook.com/messaging/send/", ctx.jar, form)
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
.then(function (resData) {
if (!resData) {
return callback({ error: "Send message failed." });
}
if (resData.error) {
if (resData.error === 1545012) {
utils.warn(
"sendMessage",
"Got error 1545012. This might mean that you're not part of the conversation " +
threadID
);
}
return callback(resData);
}
var messageInfo = resData.payload.actions.reduce(function (p, v) {
return (
{
threadID: v.thread_fbid,
messageID: v.message_id,
timestamp: v.timestamp
} || p
);
}, null);
return callback(null, messageInfo);
})
.catch(function (err) {
utils.error("sendMessage", err);
return callback(err);
});
}
function send(form, threadID, messageAndOTID, callback, isGroup) {
// We're doing a query to this to check if the given id is the id of
// a user or of a group chat. The form will be different depending
// on that.
if (utils.getType(threadID) === "Array") {
sendContent(form, threadID, false, messageAndOTID, callback);
} else {
if (utils.getType(isGroup) != "Boolean")
sendContent(form, threadID, threadID.length <= 15, messageAndOTID, callback);
else
sendContent(form, threadID, !isGroup, messageAndOTID, callback);
}
}
function handleUrl(msg, form, callback, cb) {
if (msg.url) {
form["shareable_attachment[share_type]"] = "100";
getUrl(msg.url, function (err, params) {
if (err) {
return callback(err);
}
form["shareable_attachment[share_params]"] = params;
cb();
});
} else {
cb();
}
}
function handleLocation(msg, form, callback, cb) {
if (msg.location) {
if (msg.location.latitude == null || msg.location.longitude == null) {
return callback({ error: "location property needs both latitude and longitude" });
}
form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
form["location_attachment[is_current_location]"] = !!msg.location.current;
}
cb();
}
function handleSticker(msg, form, callback, cb) {
if (msg.sticker) {
form["sticker_id"] = msg.sticker;
}
cb();
}
function handleEmoji(msg, form, callback, cb) {
if (msg.emojiSize != null && msg.emoji == null) {
return callback({ error: "emoji property is empty" });
}
if (msg.emoji) {
if (msg.emojiSize == null) {
msg.emojiSize = "medium";
}
if (
msg.emojiSize != "small" &&
msg.emojiSize != "medium" &&
msg.emojiSize != "large"
) {
return callback({ error: "emojiSize property is invalid" });
}
if (form["body"] != null && form["body"] != "") {
return callback({ error: "body is not empty" });
}
form["body"] = msg.emoji;
form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
}
cb();
}
function handleAttachment(msg, form, callback, cb) {
if (msg.attachment) {
form["image_ids"] = [];
form["gif_ids"] = [];
form["file_ids"] = [];
form["video_ids"] = [];
form["audio_ids"] = [];
if (utils.getType(msg.attachment) !== "Array") {
msg.attachment = [msg.attachment];
}
uploadAttachment(msg.attachment, function (err, files) {
if (err) {
return callback(err);
}
files.forEach(function (file) {
var key = Object.keys(file);
var type = key[0]; // image_id, file_id, etc
form["" + type + "s"].push(file[type]); // push the id
});
cb();
});
} else {
cb();
}
}
function handleMention(msg, form, callback, cb) {
if (msg.mentions) {
for (let i = 0; i < msg.mentions.length; i++) {
const mention = msg.mentions[i];
const tag = mention.tag;
if (typeof tag !== "string") {
return callback({ error: "Mention tags must be strings." });
}
const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
if (offset < 0) {
utils.warn(
"handleMention",
'Mention for "' + tag + '" not found in message string.'
);
}
if (mention.id == null) {
utils.warn("handleMention", "Mention id should be non-null.");
}
const id = mention.id || 0;
const emptyChar = '\u200E';
form["body"] = emptyChar + msg.body;
form["profile_xmd[" + i + "][offset]"] = offset + 1;
form["profile_xmd[" + i + "][length]"] = tag.length;
form["profile_xmd[" + i + "][id]"] = id;
form["profile_xmd[" + i + "][type]"] = "p";
}
}
cb();
}
return function sendMessage(msg, threadID, callback, replyToMessage, isGroup) {
typeof isGroup == "undefined" ? isGroup = null : "";
if (
!callback &&
(utils.getType(threadID) === "Function" ||
utils.getType(threadID) === "AsyncFunction")
) {
return threadID({ error: "Pass a threadID as a second argument." });
}
if (
!replyToMessage &&
utils.getType(callback) === "String"
) {
replyToMessage = callback;
callback = undefined;
}
var resolveFunc = function () { };
var rejectFunc = function () { };
var returnPromise = new Promise(function (resolve, reject) {
resolveFunc = resolve;
rejectFunc = reject;
});
if (!callback) {
callback = function (err, data) {
if (err) return rejectFunc(err);
resolveFunc(data);
};
}
var msgType = utils.getType(msg);
var threadIDType = utils.getType(threadID);
var messageIDType = utils.getType(replyToMessage);
if (msgType !== "String" && msgType !== "Object") {
return callback({
error:
"Message should be of type string or object and not " + msgType + "."
});
}
// Changing this to accomodate an array of users
if (
threadIDType !== "Array" &&
threadIDType !== "Number" &&
threadIDType !== "String"
) {
return callback({
error:
"ThreadID should be of type number, string, or array and not " +
threadIDType +
"."
});
}
if (replyToMessage && messageIDType !== 'String') {
return callback({
error:
"MessageID should be of type string and not " +
threadIDType +
"."
});
}
if (msgType === "String") {
msg = { body: msg };
}
var disallowedProperties = Object.keys(msg).filter(
prop => !allowedProperties[prop]
);
if (disallowedProperties.length > 0) {
return callback({
error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`"
});
}
var messageAndOTID = utils.generateOfflineThreadingID();
var form = {
client: "mercury",
action_type: "ma-type:user-generated-message",
author: "fbid:" + ctx.userID,
timestamp: Date.now(),
timestamp_absolute: "Today",
timestamp_relative: utils.generateTimestampRelative(),
timestamp_time_passed: "0",
is_unread: false,
is_cleared: false,
is_forward: false,
is_filtered_content: false,
is_filtered_content_bh: false,
is_filtered_content_account: false,
is_filtered_content_quasar: false,
is_filtered_content_invalid_app: false,
is_spoof_warning: false,
source: "source:chat:web",
"source_tags[0]": "source:chat",
body: msg.body ? msg.body.toString() : "",
html_body: false,
ui_push_phase: "V3",
status: "0",
offline_threading_id: messageAndOTID,
message_id: messageAndOTID,
threading_id: utils.generateThreadingID(ctx.clientID),
"ephemeral_ttl_mode:": "0",
manual_retry_cnt: "0",
has_attachment: !!(msg.attachment || msg.url || msg.sticker),
signatureID: utils.getSignatureID(),
replied_to_message_id: replyToMessage
};
handleLocation(msg, form, callback, () =>
handleSticker(msg, form, callback, () =>
handleAttachment(msg, form, callback, () =>
handleUrl(msg, form, callback, () =>
handleEmoji(msg, form, callback, () =>
handleMention(msg, form, callback, () =>
send(form, threadID, messageAndOTID, callback, isGroup)
)
)
)
)
)
);
return returnPromise;
};
};