Spaces:
Running
Running
/* | |
Origin Source: https://github.com/ntkhang03/Goat-Bot-V2/blob/main/fb-chat-api/ | |
Converter: Khang | |
*/ | |
var utils = require("../utils"); | |
var allowedProperties = { | |
attachments: true, | |
url: true, | |
sticker: true, | |
emoji: true, | |
emojiSize: true, | |
body: true, | |
mentions: true, | |
location: true | |
} | |
function removeSpecialChar(inputString) { | |
if (typeof inputString !== "string") | |
return inputString; | |
var buffer = Buffer.from(inputString, "utf8"); | |
let filteredBuffer = Buffer.alloc(0); | |
for (let i = 0; i < buffer.length; i++) { | |
if (buffer[i] === 0xEF && buffer[i + 1] === 0xB8 && buffer[i + 2] === 0x8F) | |
i += 2; | |
else | |
filteredBuffer = Buffer.concat([filteredBuffer, buffer.slice(i, i + 1)]); | |
} | |
var convertedString = filteredBuffer.toString("utf8"); | |
return convertedString; | |
} | |
module.exports = function (http, apis, ctx) { | |
function edit(message, messageID, callback) { | |
var pCallback; | |
var returnPromise = new Promise(function (resolve, reject) { | |
pCallback = error => error ? reject(error) : resolve(); | |
}); | |
if (typeof message === "function") { | |
callback = message; | |
message = ""; | |
} | |
if (typeof messageID === "function") { | |
callback = messageID; | |
messageID = null; | |
} | |
if (typeof callback !== "function") | |
callback = pCallback; | |
if (typeof messageID !== "string") | |
callback(new Error("messageID must be an string")); | |
else if (!ctx.Client) | |
callback(new Error("You must connect mqtt first")); | |
else { | |
var queryPayload = { | |
message_id: messageID, | |
text: message | |
} | |
var query = { | |
failure_count: null, | |
label: "742", | |
payload: JSON.stringify(queryPayload), | |
queue_name: "edit_message", | |
task_id: Math.round(Math.random() * 312312721413).toString() | |
} | |
var context = { | |
app_id: "2220391788200892", | |
payload: JSON.stringify({ | |
data_trace_id: null, | |
epoch_id: 0, | |
tasks: [query], | |
version_id: "6903494529735864" | |
}), | |
request_id: Math.round(Math.random() * 312312721413).toString(), | |
type: 3 | |
} | |
ctx.Client.publish("/ls_req", JSON.stringify(context), { qos: 1, retain: false }); | |
callback(); | |
} | |
return returnPromise; | |
} | |
function react(reaction, messageID, callback) { | |
var pCallback; | |
var returnPromise = new Promise(function (resolve, reject) { | |
pCallback = error => error ? reject(error) : resolve(); | |
}); | |
if (typeof reaction === "function") { | |
callback = reaction; | |
reaction = ""; | |
} | |
if (typeof messageID === "function") { | |
callback = messageID; | |
messageID = null; | |
} | |
if (typeof callback !== "function") | |
callback = pCallback; | |
if (typeof messageID !== "string") | |
callback(new Error("messageID must be an string")); | |
else { | |
var variables = { | |
data: { | |
client_mutation_id: Math.round(Math.random() * 312312721413).toString(), | |
actor_id: ctx.userID, | |
action: reaction === "" ? "REMOVE_REACTION" : "ADD_REACTION", | |
message_id: messageID, | |
reaction | |
} | |
} | |
var qs = { | |
doc_id: "1491398900900362", | |
variables: JSON.stringify(variables), | |
dpr: 1 | |
} | |
http | |
.postData("https://www.facebook.com/webgraphql/mutation/", ctx.jar, {}, qs) | |
.then(utils.parseAndCheckLogin(ctx.jar, http)) | |
.then(function (res) { | |
if (res.error || res.errors) | |
throw res; | |
return callback(); | |
}) | |
.catch(function (error) { | |
if (error.type === "logout.") | |
ctx.isLogin = false; | |
return callback(error); | |
}); | |
} | |
return returnPromise; | |
} | |
function unsend(messageID, callback) { | |
var pCallback; | |
var returnPromise = new Promise(function (resolve, reject) { | |
pCallback = error => error ? reject(error) : resolve(); | |
}); | |
if (typeof messageID === "function") { | |
callback = messageID; | |
messageID = null; | |
} | |
if (typeof callback !== "function") | |
callback = pCallback; | |
if (typeof messageID !== "string") | |
callback(new Error("messageID must be an string")); | |
else { | |
http | |
.post("https://www.facebook.com/messaging/unsend_message/", ctx.jar, { message_id: messageID }) | |
.then(utils.parseAndCheckLogin(ctx, http)) | |
.then(function (res) { | |
if (res.error || res.errors) | |
throw res; | |
return callback(); | |
}) | |
.catch(function (error) { | |
if (error.type === "logout.") | |
ctx.isLogin = false; | |
return callback(error); | |
}); | |
} | |
return returnPromise; | |
} | |
function send(message, threadID, messageID, callback, isGroup) { | |
var pCallback; | |
var returnPromise = new Promise(function (resolve, reject) { | |
pCallback = error => error ? reject(error) : resolve(); | |
}); | |
if (typeof message === "function") { | |
callback = message; | |
message = null; | |
} | |
if (typeof threadID === "function") { | |
callback = threadID; | |
threadID = null; | |
} | |
if (typeof messageID === "function") { | |
callback = messageID; | |
messageID = null; | |
} | |
if (typeof callback !== "function") | |
callback = pCallback; | |
var messageType = utils.getType(message); | |
var threadIDType = utils.getType(threadID); | |
var messageIDType = utils.getType(messageID); | |
var error; | |
if (messageType !== "String" && messageType !== "Object") | |
error = new Error("Message should be of type string or object and not " + messageType + "."); | |
if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") | |
error = new Error("ThreadID should be of type number, string, or array and not " + threadIDType + "."); | |
if (messageID && messageIDType !== "String") | |
error = new Error("MessageID should be of type string and not " + messageIDType + "."); | |
if (messageType === "String") | |
message = { | |
body: message | |
} | |
if (utils.getType(message.body) === "String") | |
message.body = removeSpecialChar(message.body); | |
var disallowedProperties = Object.keys(message).filter(prop => !allowedProperties[prop]); | |
if (disallowedProperties.length > 0) | |
error = new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`"); | |
if (error) | |
callback(error); | |
else { | |
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: message.body ? message.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: !!(message.attachments || message.url || message.sticker), | |
signatureID: utils.getSignatureID(), | |
replied_to_message_id: messageID | |
} | |
handleLocation(message, form, threadID, messageAndOTID, callback, isGroup) | |
.then(handleSticker) | |
.then(handleAttachment) | |
.then(handleURL) | |
.then(handleEmoji) | |
.then(handleMention) | |
.then(function (input) { | |
if (utils.getType(input[2]) === "Array") | |
input[5] = false; | |
else { | |
if (utils.getType(input[5]) !== "Boolean") | |
input[5] = input[2].toString().length < 16; | |
else | |
input[5] = !input[5]; | |
} | |
return input; | |
}) | |
.then(sendContent) | |
.catch(function (error) { | |
if (error.type === "logout.") | |
ctx.isLogin = false; | |
return callback(error); | |
}); | |
} | |
return returnPromise; | |
} | |
function handleLocation(...input) { | |
if (input[0].location) { | |
if (input[0].location.latitude === null || input[0].location.longitude === null) | |
return Promise.reject(new Error("location property needs both latitude and longitude")); | |
input[1]["location_attachment[coordinates][latitude]"] = input[0].location.latitude; | |
input[1]["location_attachment[coordinates][longitude]"] = input[0].location.longitude; | |
input[1]["location_attachment[is_current_location]"] = !!input[0].location.current; | |
} | |
return Promise.resolve(input); | |
} | |
function handleEmoji(input) { | |
if (input[0].emojiSize !== null && input[0].emoji === null) | |
return Promise.reject(new Error("emoji property is empty")); | |
if (input[0].emoji) { | |
if (input[0].emojiSize == null) | |
input[0].emojiSize = "medium"; | |
if (input[0].emojiSize !== "small" && input[0].emojiSize !== "medium" && input[0].emojiSize !== "large") | |
return Promise.reject(new Error("emojiSize property is invalid")); | |
if (input[1].body !== null && input[1].body !== "") | |
return Promise.reject(new Error("body is not empty")); | |
input[1].body = input[0].emoji; | |
input[1]["tags[0]"] = "hot_emoji_size:" + input[0].emojiSize; | |
} | |
return Promise.resolve(input); | |
} | |
function handleSticker(input) { | |
if (input[0].sticker) | |
input[1].sticker_id = input[0].sticker; | |
return Promise.resolve(input); | |
} | |
function handleAttachment(input) { | |
if (input[0].attachments) { | |
input[1].image_ids = []; | |
input[1].gif_ids = []; | |
input[1].file_ids = []; | |
input[1].video_ids = []; | |
input[1].audio_ids = []; | |
input[0].attachments = Array.isArray(input[0].attachments) ? input[0].attachments : [input[0].attachments]; | |
return handleUpload(input[0].attachments) | |
.then(function (files) { | |
files.forEach(function (file) { | |
var key = Object.keys(file); | |
var type = key[0]; | |
input[1][type + "s"].push(file[type]); | |
}); | |
return input; | |
}); | |
} | |
return Promise.resolve(input); | |
} | |
function handleUpload(attachments) { | |
var uploads = []; | |
for (var i = 0; i < attachments.length; i++) { | |
if (!utils.isReadableStream(attachments[i])) | |
throw new Error("Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "."); | |
var form = { | |
upload_1024: attachments[i], | |
voice_clip: "true" | |
} | |
uploads.push( | |
http | |
.postData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form) | |
.then(utils.parseAndCheckLogin(ctx, http)) | |
.then(function (res) { | |
if (res.error || res.errors) | |
throw res; | |
return res.payload.metadata[0]; | |
}) | |
); | |
} | |
return Promise.all(uploads); | |
} | |
function handleURL(input) { | |
if (input[0].url) { | |
input[1]["shareable_attachment[share_type]"] = "100"; | |
return getURL() | |
.then(function (params) { | |
input[1]["shareable_attachment[share_params]"] = params; | |
return input; | |
}); | |
} | |
return Promise.resolve(input); | |
} | |
function getURL(url) { | |
var form = { | |
image_height: 960, | |
image_width: 960, | |
uri: url | |
} | |
return http | |
.post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, form) | |
.then(utils.parseAndCheckLogin(ctx, http)) | |
.then(function (res) { | |
if (res.error || res.errors || !res.payload) | |
throw res; | |
return res.payload.share_data.share_params; | |
}); | |
} | |
function handleMention(input) { | |
if (input[0].mentions) { | |
for (var i = 0; i < input[0].mentions.length; i++) { | |
var mention = input[0].mentions[i]; | |
var tag = mention.tag; | |
if (typeof tag !== "string") | |
return Promise.reject(new Error("Mention tags must be strings.")); | |
var offset = input[0].body.indexOf(tag, mention.fromIndex || 0); | |
var id = mention.id || 0; | |
input[1]["profile_xmd[" + i + "][offset]"] = offset; | |
input[1]["profile_xmd[" + i + "][length]"] = tag.length; | |
input[1]["profile_xmd[" + i + "][id]"] = id; | |
input[1]["profile_xmd[" + i + "][type]"] = "p"; | |
} | |
} | |
return Promise.resolve(input); | |
} | |
function sendContent(input) { | |
if (utils.getType(input[2]) === "Array") { | |
for (var i = 0; i < input[2].length; i++) | |
input[1]["specific_to_list[" + i + "]"] = "fbid:" + input[2][i]; | |
input[1]["specific_to_list[" + input[2].length + "]"] = "fbid:" + ctx.userID; | |
input[1]["client_thread_id"] = "root:" + input[3]; | |
} else { | |
if (input[5]) { | |
input[1]["specific_to_list[0]"] = "fbid:" + input[2]; | |
input[1]["specific_to_list[1]"] = "fbid:" + ctx.userID; | |
input[1]["other_user_fbid"] = input[2]; | |
} else | |
input[1]["thread_fbid"] = input[2]; | |
} | |
return http | |
.post("https://www.facebook.com/messaging/send/", ctx.jar, input[1]) | |
.then(utils.parseAndCheckLogin(ctx, http)) | |
.then(function (res) { | |
if (!res || res.error || res.errors) | |
throw res | |
var messageInfo = res.payload.actions.reduce(function (p, v) { | |
return { | |
threadID: v.thread_fbid || v.other_user_fbid, | |
messageID: v.message_id, | |
timestamp: v.timestamp | |
} | |
}, null); | |
return input[4](null, messageInfo); | |
}); | |
} | |
return { | |
send, | |
edit, | |
react, | |
unsend | |
} | |
} |