├── BackupVer ├── UpdatedVer ├── auto.js ├── fb-chat-api ├── README.MD ├── index.js ├── src │ ├── addExternalModule.js │ ├── addUserToGroup.js │ ├── changeAdminStatus.js │ ├── changeArchivedStatus.js │ ├── changeAvatar.js │ ├── changeBio.js │ ├── changeBlockedStatus.js │ ├── changeGroupImage.js │ ├── changeNickname.js │ ├── changeThreadColor.js │ ├── changeThreadEmoji.js │ ├── createNewGroup.js │ ├── createPoll.js │ ├── deleteMessage.js │ ├── deleteThread.js │ ├── editMessage.js │ ├── forwardAttachment.js │ ├── getCurrentUserID.js │ ├── getEmojiUrl.js │ ├── getFriendsList.js │ ├── getMessage.js │ ├── getThreadHistory.js │ ├── getThreadInfo.js │ ├── getThreadList.js │ ├── getThreadPictures.js │ ├── getUserID.js │ ├── getUserInfo.js │ ├── handleFriendRequest.js │ ├── handleMessageRequest.js │ ├── httpGet.js │ ├── httpPost.js │ ├── httpPostFormData.js │ ├── listenMqtt.js │ ├── logout.js │ ├── markAsDelivered.js │ ├── markAsRead.js │ ├── markAsReadAll.js │ ├── markAsSeen.js │ ├── muteThread.js │ ├── refreshFb_dtsg.js │ ├── removeUserFromGroup.js │ ├── resolvePhotoUrl.js │ ├── searchForThread.js │ ├── sendMessage.js │ ├── sendTypingIndicator.js │ ├── setMessageReaction.js │ ├── setPostReaction.js │ ├── setTitle.js │ ├── threadColors.js │ ├── unfriend.js │ ├── unsendMessage.js │ └── uploadAttachment.js └── utils.js ├── index.js ├── package.json ├── public ├── guide.html ├── image │ ├── guide_1.jpeg │ ├── guide_10.jpeg │ ├── guide_11.jpeg │ ├── guide_12.jpeg │ ├── guide_13.jpeg │ ├── guide_14.jpeg │ ├── guide_15.jpeg │ ├── guide_16.jpeg │ ├── guide_2.jpeg │ ├── guide_3.jpeg │ ├── guide_4.jpeg │ ├── guide_5.jpeg │ ├── guide_6.jpeg │ ├── guide_7.jpeg │ ├── guide_8.jpeg │ └── guide_9.jpeg ├── index.html ├── online.html ├── script.js └── styles.css └── script ├── adc.js ├── ai.js ├── anime.js ├── dictionary.js ├── emojimix.js ├── event ├── antiout.js ├── resend.js └── soyeon.js ├── help.js ├── hercai.js ├── insult.js ├── music.js ├── out.js ├── pinterest.js ├── poli.js ├── quote.js ├── recipe.js ├── sim.js ├── teach.js ├── tid.js ├── trans.js └── unsend.js /BackupVer: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const login = require('./fb-chat-api/index'); 4 | const express = require('express'); 5 | const app = express(); 6 | const chalk = require('chalk'); 7 | const bodyParser = require('body-parser'); 8 | const script = path.join(__dirname, 'script'); 9 | const cron = require('node-cron'); 10 | const config = fs.existsSync('./data') && fs.existsSync('./data/config.json') ? JSON.parse(fs.readFileSync('./data/config.json', 'utf8')) : createConfig(); 11 | const Utils = new Object({ 12 | commands: new Map(), 13 | handleEvent: new Map(), 14 | account: new Map(), 15 | cooldowns: new Map(), 16 | }); 17 | fs.readdirSync(script).forEach((file) => { 18 | const scripts = path.join(script, file); 19 | const stats = fs.statSync(scripts); 20 | if (stats.isDirectory()) { 21 | fs.readdirSync(scripts).forEach((file) => { 22 | try { 23 | const { 24 | config, 25 | run, 26 | handleEvent 27 | } = require(path.join(scripts, file)); 28 | if (config) { 29 | const { 30 | name = [], role = '0', version = '1.0.0', hasPrefix = true, aliases = [], description = '', usage = '', credits = '', cooldown = '5' 31 | } = Object.fromEntries(Object.entries(config).map(([key, value]) => [key.toLowerCase(), value])); 32 | aliases.push(name); 33 | 34 | -------------------------------------------------------------------------------- /fb-chat-api/README.MD: -------------------------------------------------------------------------------- 1 | This repo is a fork from main repo and will usually have new features bundled faster than main repo (and maybe bundle some bugs, too). 2 | See main repo [here](https://github.com/Schmavery/facebook-chat-api). 3 | 4 | # Unofficial Facebook Chat API 5 | 6 | This is the folder that is detached from [this project](https://github.com/ntkhang03/fb-chat-api) -------------------------------------------------------------------------------- /fb-chat-api/src/addExternalModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function addExternalModule(moduleObj) { 8 | if (utils.getType(moduleObj) == "Object") { 9 | for (const apiName in moduleObj) { 10 | if (utils.getType(moduleObj[apiName]) == "Function") { 11 | api[apiName] = moduleObj[apiName](defaultFuncs, api, ctx, utils, log); 12 | } else { 13 | throw new Error(`Item "${apiName}" in moduleObj must be a function, not ${utils.getType(moduleObj[apiName])}!`); 14 | } 15 | } 16 | } else { 17 | throw new Error(`moduleObj must be an object, not ${utils.getType(moduleObj)}!`); 18 | } 19 | }; 20 | }; 21 | 22 | // example usage: 23 | // api.addExternalModule({ 24 | // getCtx: (defaultFuncs, api, ctx, utils, log) => { 25 | // return function getCtx() { 26 | // return ctx; 27 | // }; 28 | // } 29 | // }); 30 | -------------------------------------------------------------------------------- /fb-chat-api/src/addUserToGroup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function addUserToGroup(userID, threadID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if ( 16 | !callback && 17 | (utils.getType(threadID) === "Function" || 18 | utils.getType(threadID) === "AsyncFunction") 19 | ) { 20 | throw new utils.CustomError({ error: "please pass a threadID as a second argument." }); 21 | } 22 | 23 | if (!callback) { 24 | callback = function (err) { 25 | if (err) { 26 | return rejectFunc(err); 27 | } 28 | resolveFunc(); 29 | }; 30 | } 31 | 32 | if ( 33 | utils.getType(threadID) !== "Number" && 34 | utils.getType(threadID) !== "String" 35 | ) { 36 | throw new utils.CustomError({ 37 | error: 38 | "ThreadID should be of type Number or String and not " + 39 | utils.getType(threadID) + 40 | "." 41 | }); 42 | } 43 | 44 | if (utils.getType(userID) !== "Array") { 45 | userID = [userID]; 46 | } 47 | 48 | const messageAndOTID = utils.generateOfflineThreadingID(); 49 | const form = { 50 | client: "mercury", 51 | action_type: "ma-type:log-message", 52 | author: "fbid:" + (ctx.i_userID || ctx.userID), 53 | thread_id: "", 54 | timestamp: Date.now(), 55 | timestamp_absolute: "Today", 56 | timestamp_relative: utils.generateTimestampRelative(), 57 | timestamp_time_passed: "0", 58 | is_unread: false, 59 | is_cleared: false, 60 | is_forward: false, 61 | is_filtered_content: false, 62 | is_filtered_content_bh: false, 63 | is_filtered_content_account: false, 64 | is_spoof_warning: false, 65 | source: "source:chat:web", 66 | "source_tags[0]": "source:chat", 67 | log_message_type: "log:subscribe", 68 | status: "0", 69 | offline_threading_id: messageAndOTID, 70 | message_id: messageAndOTID, 71 | threading_id: utils.generateThreadingID(ctx.clientID), 72 | manual_retry_cnt: "0", 73 | thread_fbid: threadID 74 | }; 75 | 76 | for (let i = 0; i < userID.length; i++) { 77 | if ( 78 | utils.getType(userID[i]) !== "Number" && 79 | utils.getType(userID[i]) !== "String" 80 | ) { 81 | throw new utils.CustomError({ 82 | error: 83 | "Elements of userID should be of type Number or String and not " + 84 | utils.getType(userID[i]) + 85 | "." 86 | }); 87 | } 88 | 89 | form["log_message_data[added_participants][" + i + "]"] = 90 | "fbid:" + userID[i]; 91 | } 92 | 93 | defaultFuncs 94 | .post("https://www.facebook.com/messaging/send/", ctx.jar, form) 95 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 96 | .then(function (resData) { 97 | if (!resData) { 98 | throw new utils.CustomError({ error: "Add to group failed." }); 99 | } 100 | if (resData.error) { 101 | throw new utils.CustomError(resData); 102 | } 103 | 104 | return callback(); 105 | }) 106 | .catch(function (err) { 107 | log.error("addUserToGroup", err); 108 | return callback(err); 109 | }); 110 | 111 | return returnPromise; 112 | }; 113 | }; 114 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeAdminStatus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeAdminStatus(threadID, adminIDs, adminStatus, callback) { 8 | if (utils.getType(threadID) !== "String") { 9 | throw new utils.CustomError({ error: "changeAdminStatus: threadID must be a string" }); 10 | } 11 | 12 | if (utils.getType(adminIDs) === "String") { 13 | adminIDs = [adminIDs]; 14 | } 15 | 16 | if (utils.getType(adminIDs) !== "Array") { 17 | throw new utils.CustomError({ error: "changeAdminStatus: adminIDs must be an array or string" }); 18 | } 19 | 20 | if (utils.getType(adminStatus) !== "Boolean") { 21 | throw new utils.CustomError({ error: "changeAdminStatus: adminStatus must be a string" }); 22 | } 23 | 24 | let resolveFunc = function () { }; 25 | let rejectFunc = function () { }; 26 | const returnPromise = new Promise(function (resolve, reject) { 27 | resolveFunc = resolve; 28 | rejectFunc = reject; 29 | }); 30 | 31 | if (!callback) { 32 | callback = function (err) { 33 | if (err) { 34 | return rejectFunc(err); 35 | } 36 | resolveFunc(); 37 | }; 38 | } 39 | 40 | if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") { 41 | throw new utils.CustomError({ error: "changeAdminStatus: callback is not a function" }); 42 | } 43 | 44 | const form = { 45 | "thread_fbid": threadID 46 | }; 47 | 48 | let i = 0; 49 | for (const u of adminIDs) { 50 | form[`admin_ids[${i++}]`] = u; 51 | } 52 | form["add"] = adminStatus; 53 | 54 | defaultFuncs 55 | .post("https://www.facebook.com/messaging/save_admins/?dpr=1", ctx.jar, form) 56 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 57 | .then(function (resData) { 58 | if (resData.error) { 59 | switch (resData.error) { 60 | case 1976004: 61 | throw new utils.CustomError({ error: "Cannot alter admin status: you are not an admin.", rawResponse: resData }); 62 | case 1357031: 63 | throw new utils.CustomError({ error: "Cannot alter admin status: this thread is not a group chat.", rawResponse: resData }); 64 | default: 65 | throw new utils.CustomError({ error: "Cannot alter admin status: unknown error.", rawResponse: resData }); 66 | } 67 | } 68 | 69 | callback(); 70 | }) 71 | .catch(function (err) { 72 | log.error("changeAdminStatus", err); 73 | return callback(err); 74 | }); 75 | 76 | return returnPromise; 77 | }; 78 | }; 79 | 80 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeArchivedStatus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeArchivedStatus(threadOrThreads, archive, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(); 21 | }; 22 | } 23 | 24 | const form = {}; 25 | 26 | if (utils.getType(threadOrThreads) === "Array") { 27 | for (let i = 0; i < threadOrThreads.length; i++) { 28 | form["ids[" + threadOrThreads[i] + "]"] = archive; 29 | } 30 | } else { 31 | form["ids[" + threadOrThreads + "]"] = archive; 32 | } 33 | 34 | defaultFuncs 35 | .post( 36 | "https://www.facebook.com/ajax/mercury/change_archived_status.php", 37 | ctx.jar, 38 | form 39 | ) 40 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 41 | .then(function (resData) { 42 | if (resData.error) { 43 | throw resData; 44 | } 45 | 46 | return callback(); 47 | }) 48 | .catch(function (err) { 49 | log.error("changeArchivedStatus", err); 50 | return callback(err); 51 | }); 52 | 53 | return returnPromise; 54 | }; 55 | }; 56 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeAvatar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | function handleUpload(image, callback) { 8 | const uploads = []; 9 | 10 | const form = { 11 | profile_id: ctx.i_userID || ctx.userID, 12 | photo_source: 57, 13 | av: ctx.i_userID || ctx.userID, 14 | file: image 15 | }; 16 | 17 | uploads.push( 18 | defaultFuncs 19 | .postFormData( 20 | "https://www.facebook.com/profile/picture/upload/", 21 | ctx.jar, 22 | form, 23 | {} 24 | ) 25 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 26 | .then(function (resData) { 27 | if (resData.error) { 28 | throw resData; 29 | } 30 | return resData; 31 | }) 32 | ); 33 | 34 | // resolve all promises 35 | Promise 36 | .all(uploads) 37 | .then(function (resData) { 38 | callback(null, resData); 39 | }) 40 | .catch(function (err) { 41 | log.error("handleUpload", err); 42 | return callback(err); 43 | }); 44 | } 45 | 46 | return function changeAvatar(image, caption = "", timestamp = null, callback) { 47 | let resolveFunc = function () { }; 48 | let rejectFunc = function () { }; 49 | const returnPromise = new Promise(function (resolve, reject) { 50 | resolveFunc = resolve; 51 | rejectFunc = reject; 52 | }); 53 | 54 | if (!timestamp && utils.getType(caption) === "Number") { 55 | timestamp = caption; 56 | caption = ""; 57 | } 58 | 59 | if (!timestamp && !callback && (utils.getType(caption) == "Function" || utils.getType(caption) == "AsyncFunction")) { 60 | callback = caption; 61 | caption = ""; 62 | timestamp = null; 63 | } 64 | 65 | if (!callback) callback = function (err, data) { 66 | if (err) { 67 | return rejectFunc(err); 68 | } 69 | resolveFunc(data); 70 | }; 71 | 72 | if (!utils.isReadableStream(image)) 73 | return callback("Image is not a readable stream"); 74 | 75 | handleUpload(image, function (err, payload) { 76 | if (err) { 77 | return callback(err); 78 | } 79 | 80 | const form = { 81 | av: ctx.i_userID || ctx.userID, 82 | fb_api_req_friendly_name: "ProfileCometProfilePictureSetMutation", 83 | fb_api_caller_class: "RelayModern", 84 | doc_id: "5066134240065849", 85 | variables: JSON.stringify({ 86 | input: { 87 | caption, 88 | existing_photo_id: payload[0].payload.fbid, 89 | expiration_time: timestamp, 90 | profile_id: ctx.i_userID || ctx.userID, 91 | profile_pic_method: "EXISTING", 92 | profile_pic_source: "TIMELINE", 93 | scaled_crop_rect: { 94 | height: 1, 95 | width: 1, 96 | x: 0, 97 | y: 0 98 | }, 99 | skip_cropping: true, 100 | actor_id: ctx.i_userID || ctx.userID, 101 | client_mutation_id: Math.round(Math.random() * 19).toString() 102 | }, 103 | isPage: false, 104 | isProfile: true, 105 | scale: 3 106 | }) 107 | }; 108 | 109 | defaultFuncs 110 | .post("https://www.facebook.com/api/graphql/", ctx.jar, form) 111 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 112 | .then(function (resData) { 113 | if (resData.errors) { 114 | throw resData; 115 | } 116 | return callback(null, resData[0].data.profile_picture_set); 117 | }) 118 | .catch(function (err) { 119 | log.error("changeAvatar", err); 120 | return callback(err); 121 | }); 122 | }); 123 | 124 | return returnPromise; 125 | }; 126 | }; 127 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeBio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeBio(bio, publish, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | if (utils.getType(publish) == "Function" || utils.getType(publish) == "AsyncFunction") { 17 | callback = publish; 18 | } else { 19 | callback = function (err) { 20 | if (err) { 21 | return rejectFunc(err); 22 | } 23 | resolveFunc(); 24 | }; 25 | } 26 | } 27 | 28 | if (utils.getType(publish) != "Boolean") { 29 | publish = false; 30 | } 31 | 32 | if (utils.getType(bio) != "String") { 33 | bio = ""; 34 | publish = false; 35 | } 36 | 37 | const form = { 38 | fb_api_caller_class: "RelayModern", 39 | fb_api_req_friendly_name: "ProfileCometSetBioMutation", 40 | // This doc_is is valid as of May 23, 2020 41 | doc_id: "2725043627607610", 42 | variables: JSON.stringify({ 43 | input: { 44 | bio: bio, 45 | publish_bio_feed_story: publish, 46 | actor_id: ctx.i_userID || ctx.userID, 47 | client_mutation_id: Math.round(Math.random() * 1024).toString() 48 | }, 49 | hasProfileTileViewID: false, 50 | profileTileViewID: null, 51 | scale: 1 52 | }), 53 | av: ctx.i_userID || ctx.userID 54 | }; 55 | 56 | defaultFuncs 57 | .post( 58 | "https://www.facebook.com/api/graphql/", 59 | ctx.jar, 60 | form 61 | ) 62 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 63 | .then(function (resData) { 64 | if (resData.errors) { 65 | throw resData; 66 | } 67 | 68 | return callback(); 69 | }) 70 | .catch(function (err) { 71 | log.error("changeBio", err); 72 | return callback(err); 73 | }); 74 | 75 | return returnPromise; 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeBlockedStatus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeBlockedStatus(userID, block, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(); 21 | }; 22 | } 23 | 24 | defaultFuncs 25 | .post( 26 | `https://www.facebook.com/messaging/${block ? "" : "un"}block_messages/`, 27 | ctx.jar, 28 | { 29 | fbid: userID 30 | } 31 | ) 32 | .then(utils.saveCookies(ctx.jar)) 33 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 34 | .then(function (resData) { 35 | if (resData.error) { 36 | throw resData; 37 | } 38 | 39 | return callback(); 40 | }) 41 | .catch(function (err) { 42 | log.error("changeBlockedStatus", err); 43 | return callback(err); 44 | }); 45 | return returnPromise; 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeGroupImage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | function handleUpload(image, callback) { 8 | const uploads = []; 9 | 10 | const form = { 11 | images_only: "true", 12 | "attachment[]": image 13 | }; 14 | 15 | uploads.push( 16 | defaultFuncs 17 | .postFormData( 18 | "https://upload.facebook.com/ajax/mercury/upload.php", 19 | ctx.jar, 20 | form, 21 | {} 22 | ) 23 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 24 | .then(function (resData) { 25 | if (resData.error) { 26 | throw resData; 27 | } 28 | 29 | return resData.payload.metadata[0]; 30 | }) 31 | ); 32 | 33 | // resolve all promises 34 | Promise 35 | .all(uploads) 36 | .then(function (resData) { 37 | callback(null, resData); 38 | }) 39 | .catch(function (err) { 40 | log.error("handleUpload", err); 41 | return callback(err); 42 | }); 43 | } 44 | 45 | return function changeGroupImage(image, threadID, callback) { 46 | if ( 47 | !callback && 48 | (utils.getType(threadID) === "Function" || 49 | utils.getType(threadID) === "AsyncFunction") 50 | ) { 51 | throw { error: "please pass a threadID as a second argument." }; 52 | } 53 | 54 | if (!utils.isReadableStream(image)) { 55 | throw { error: "please pass a readable stream as a first argument." }; 56 | } 57 | 58 | let resolveFunc = function () { }; 59 | let rejectFunc = function () { }; 60 | const returnPromise = new Promise(function (resolve, reject) { 61 | resolveFunc = resolve; 62 | rejectFunc = reject; 63 | }); 64 | 65 | if (!callback) { 66 | callback = function (err) { 67 | if (err) { 68 | return rejectFunc(err); 69 | } 70 | resolveFunc(); 71 | }; 72 | } 73 | 74 | const messageAndOTID = utils.generateOfflineThreadingID(); 75 | const form = { 76 | client: "mercury", 77 | action_type: "ma-type:log-message", 78 | author: "fbid:" + (ctx.i_userID || ctx.userID), 79 | author_email: "", 80 | ephemeral_ttl_mode: "0", 81 | is_filtered_content: false, 82 | is_filtered_content_account: false, 83 | is_filtered_content_bh: false, 84 | is_filtered_content_invalid_app: false, 85 | is_filtered_content_quasar: false, 86 | is_forward: false, 87 | is_spoof_warning: false, 88 | is_unread: false, 89 | log_message_type: "log:thread-image", 90 | manual_retry_cnt: "0", 91 | message_id: messageAndOTID, 92 | offline_threading_id: messageAndOTID, 93 | source: "source:chat:web", 94 | "source_tags[0]": "source:chat", 95 | status: "0", 96 | thread_fbid: threadID, 97 | thread_id: "", 98 | timestamp: Date.now(), 99 | timestamp_absolute: "Today", 100 | timestamp_relative: utils.generateTimestampRelative(), 101 | timestamp_time_passed: "0" 102 | }; 103 | 104 | handleUpload(image, function (err, payload) { 105 | if (err) { 106 | return callback(err); 107 | } 108 | 109 | form["thread_image_id"] = payload[0]["image_id"]; 110 | form["thread_id"] = threadID; 111 | 112 | defaultFuncs 113 | .post("https://www.facebook.com/messaging/set_thread_image/", ctx.jar, form) 114 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 115 | .then(function (resData) { 116 | // check for errors here 117 | 118 | if (resData.error) { 119 | throw resData; 120 | } 121 | 122 | return callback(); 123 | }) 124 | .catch(function (err) { 125 | log.error("changeGroupImage", err); 126 | return callback(err); 127 | }); 128 | }); 129 | 130 | return returnPromise; 131 | }; 132 | }; 133 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeNickname.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeNickname(nickname, threadID, participantID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | if (!callback) { 15 | callback = function (err) { 16 | if (err) { 17 | return rejectFunc(err); 18 | } 19 | resolveFunc(); 20 | }; 21 | } 22 | 23 | const form = { 24 | nickname: nickname, 25 | participant_id: participantID, 26 | thread_or_other_fbid: threadID 27 | }; 28 | 29 | defaultFuncs 30 | .post( 31 | "https://www.facebook.com/messaging/save_thread_nickname/?source=thread_settings&dpr=1", 32 | ctx.jar, 33 | form 34 | ) 35 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 36 | .then(function (resData) { 37 | if (resData.error === 1545014) { 38 | throw { error: "Trying to change nickname of user isn't in thread" }; 39 | } 40 | if (resData.error === 1357031) { 41 | throw { 42 | error: 43 | "Trying to change user nickname of a thread that doesn't exist. Have at least one message in the thread before trying to change the user nickname." 44 | }; 45 | } 46 | if (resData.error) { 47 | throw resData; 48 | } 49 | 50 | return callback(); 51 | }) 52 | .catch(function (err) { 53 | log.error("changeNickname", err); 54 | return callback(err); 55 | }); 56 | 57 | return returnPromise; 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeThreadColor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeThreadColor(color, threadID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(err); 21 | }; 22 | } 23 | 24 | if (!isNaN(color)) { 25 | color = color.toString(); 26 | } 27 | const validatedColor = color !== null ? color.toLowerCase() : color; // API only accepts lowercase letters in hex string 28 | 29 | const form = { 30 | dpr: 1, 31 | queries: JSON.stringify({ 32 | o0: { 33 | //This doc_id is valid as of January 31, 2020 34 | doc_id: "1727493033983591", 35 | query_params: { 36 | data: { 37 | actor_id: ctx.i_userID || ctx.userID, 38 | client_mutation_id: "0", 39 | source: "SETTINGS", 40 | theme_id: validatedColor, 41 | thread_id: threadID 42 | } 43 | } 44 | } 45 | }) 46 | }; 47 | 48 | defaultFuncs 49 | .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form) 50 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 51 | .then(function (resData) { 52 | if (resData[resData.length - 1].error_results > 0) { 53 | throw new utils.CustomError(resData[0].o0.errors); 54 | } 55 | 56 | return callback(); 57 | }) 58 | .catch(function (err) { 59 | log.error("changeThreadColor", err); 60 | return callback(err); 61 | }); 62 | 63 | return returnPromise; 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /fb-chat-api/src/changeThreadEmoji.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function changeThreadEmoji(emoji, threadID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(); 21 | }; 22 | } 23 | const form = { 24 | emoji_choice: emoji, 25 | thread_or_other_fbid: threadID 26 | }; 27 | 28 | defaultFuncs 29 | .post( 30 | "https://www.facebook.com/messaging/save_thread_emoji/?source=thread_settings&__pc=EXP1%3Amessengerdotcom_pkg", 31 | ctx.jar, 32 | form 33 | ) 34 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 35 | .then(function (resData) { 36 | if (resData.error === 1357031) { 37 | throw { 38 | error: 39 | "Trying to change emoji of a chat that doesn't exist. Have at least one message in the thread before trying to change the emoji." 40 | }; 41 | } 42 | if (resData.error) { 43 | throw resData; 44 | } 45 | 46 | return callback(); 47 | }) 48 | .catch(function (err) { 49 | log.error("changeThreadEmoji", err); 50 | return callback(err); 51 | }); 52 | 53 | return returnPromise; 54 | }; 55 | }; 56 | -------------------------------------------------------------------------------- /fb-chat-api/src/createNewGroup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function createNewGroup(participantIDs, groupTitle, callback) { 8 | if (utils.getType(groupTitle) == "Function") { 9 | callback = groupTitle; 10 | groupTitle = null; 11 | } 12 | 13 | if (utils.getType(participantIDs) !== "Array") { 14 | throw { error: "createNewGroup: participantIDs should be an array." }; 15 | } 16 | 17 | if (participantIDs.length < 2) { 18 | throw { error: "createNewGroup: participantIDs should have at least 2 IDs." }; 19 | } 20 | 21 | let resolveFunc = function () { }; 22 | let rejectFunc = function () { }; 23 | const returnPromise = new Promise(function (resolve, reject) { 24 | resolveFunc = resolve; 25 | rejectFunc = reject; 26 | }); 27 | 28 | if (!callback) { 29 | callback = function (err, threadID) { 30 | if (err) { 31 | return rejectFunc(err); 32 | } 33 | resolveFunc(threadID); 34 | }; 35 | } 36 | 37 | const pids = []; 38 | for (const n in participantIDs) { 39 | pids.push({ 40 | fbid: participantIDs[n] 41 | }); 42 | } 43 | pids.push({ fbid: ctx.i_userID || ctx.userID }); 44 | 45 | const form = { 46 | fb_api_caller_class: "RelayModern", 47 | fb_api_req_friendly_name: "MessengerGroupCreateMutation", 48 | av: ctx.i_userID || ctx.userID, 49 | //This doc_id is valid as of January 11th, 2020 50 | doc_id: "577041672419534", 51 | variables: JSON.stringify({ 52 | input: { 53 | entry_point: "jewel_new_group", 54 | actor_id: ctx.i_userID || ctx.userID, 55 | participants: pids, 56 | client_mutation_id: Math.round(Math.random() * 1024).toString(), 57 | thread_settings: { 58 | name: groupTitle, 59 | joinable_mode: "PRIVATE", 60 | thread_image_fbid: null 61 | } 62 | } 63 | }) 64 | }; 65 | 66 | defaultFuncs 67 | .post( 68 | "https://www.facebook.com/api/graphql/", 69 | ctx.jar, 70 | form 71 | ) 72 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 73 | .then(function (resData) { 74 | if (resData.errors) { 75 | throw resData; 76 | } 77 | return callback(null, resData.data.messenger_group_thread_create.thread.thread_key.thread_fbid); 78 | }) 79 | .catch(function (err) { 80 | log.error("createNewGroup", err); 81 | return callback(err); 82 | }); 83 | 84 | return returnPromise; 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /fb-chat-api/src/createPoll.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function createPoll(title, threadID, options, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | if (utils.getType(options) == "Function") { 17 | callback = options; 18 | options = null; 19 | } else { 20 | callback = function (err) { 21 | if (err) { 22 | return rejectFunc(err); 23 | } 24 | resolveFunc(); 25 | }; 26 | } 27 | } 28 | if (!options) { 29 | options = {}; // Initial poll options are optional 30 | } 31 | 32 | const form = { 33 | target_id: threadID, 34 | question_text: title 35 | }; 36 | 37 | // Set fields for options (and whether they are selected initially by the posting user) 38 | let ind = 0; 39 | for (const opt in options) { 40 | // eslint-disable-next-line no-prototype-builtins 41 | if (options.hasOwnProperty(opt)) { 42 | form["option_text_array[" + ind + "]"] = opt; 43 | form["option_is_selected_array[" + ind + "]"] = options[opt] 44 | ? "1" 45 | : "0"; 46 | ind++; 47 | } 48 | } 49 | 50 | defaultFuncs 51 | .post( 52 | "https://www.facebook.com/messaging/group_polling/create_poll/?dpr=1", 53 | ctx.jar, 54 | form 55 | ) 56 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 57 | .then(function (resData) { 58 | if (resData.payload.status != "success") { 59 | throw resData; 60 | } 61 | 62 | return callback(); 63 | }) 64 | .catch(function (err) { 65 | log.error("createPoll", err); 66 | return callback(err); 67 | }); 68 | 69 | return returnPromise; 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /fb-chat-api/src/deleteMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function deleteMessage(messageOrMessages, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | if (!callback) { 15 | callback = function (err) { 16 | if (err) { 17 | return rejectFunc(err); 18 | } 19 | resolveFunc(); 20 | }; 21 | } 22 | 23 | const form = { 24 | client: "mercury" 25 | }; 26 | 27 | if (utils.getType(messageOrMessages) !== "Array") { 28 | messageOrMessages = [messageOrMessages]; 29 | } 30 | 31 | for (let i = 0; i < messageOrMessages.length; i++) { 32 | form["message_ids[" + i + "]"] = messageOrMessages[i]; 33 | } 34 | 35 | defaultFuncs 36 | .post( 37 | "https://www.facebook.com/ajax/mercury/delete_messages.php", 38 | ctx.jar, 39 | form 40 | ) 41 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 42 | .then(function (resData) { 43 | if (resData.error) { 44 | throw resData; 45 | } 46 | 47 | return callback(); 48 | }) 49 | .catch(function (err) { 50 | log.error("deleteMessage", err); 51 | return callback(err); 52 | }); 53 | 54 | return returnPromise; 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /fb-chat-api/src/deleteThread.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function deleteThread(threadOrThreads, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | if (!callback) { 15 | callback = function (err) { 16 | if (err) { 17 | return rejectFunc(err); 18 | } 19 | resolveFunc(); 20 | }; 21 | } 22 | 23 | const form = { 24 | client: "mercury" 25 | }; 26 | 27 | if (utils.getType(threadOrThreads) !== "Array") { 28 | threadOrThreads = [threadOrThreads]; 29 | } 30 | 31 | for (let i = 0; i < threadOrThreads.length; i++) { 32 | form["ids[" + i + "]"] = threadOrThreads[i]; 33 | } 34 | 35 | defaultFuncs 36 | .post( 37 | "https://www.facebook.com/ajax/mercury/delete_thread.php", 38 | ctx.jar, 39 | form 40 | ) 41 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 42 | .then(function (resData) { 43 | if (resData.error) { 44 | throw resData; 45 | } 46 | 47 | return callback(); 48 | }) 49 | .catch(function (err) { 50 | log.error("deleteThread", err); 51 | return callback(err); 52 | }); 53 | 54 | return returnPromise; 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /fb-chat-api/src/editMessage.js: -------------------------------------------------------------------------------- 1 | "use_strict"; 2 | /** 3 | * @author RFS-ADRENO 4 | * @rewrittenBy Isai Ivanov 5 | */ 6 | const generateOfflineThreadingId = require('../utils'); 7 | 8 | function canBeCalled(func) { 9 | try { 10 | Reflect.apply(func, null, []); 11 | return true; 12 | } catch (error) { 13 | return false; 14 | } 15 | } 16 | 17 | /** 18 | * A function for editing bot's messages. 19 | * @param {string} text - The text with which the bot will edit its messages. 20 | * @param {string} messageID - The message ID of the message the bot will edit. 21 | * @param {Object} callback - Callback for the function. 22 | */ 23 | 24 | module.exports = function (defaultFuncs, api, ctx) { 25 | return function editMessage(text, messageID, callback) { 26 | if (!ctx.mqttClient) { 27 | throw new Error('Not connected to MQTT'); 28 | } 29 | 30 | ctx.wsReqNumber += 1; 31 | ctx.wsTaskNumber += 1; 32 | 33 | const queryPayload = { 34 | message_id: messageID, 35 | text: text 36 | }; 37 | 38 | const query = { 39 | failure_count: null, 40 | label: '742', 41 | payload: JSON.stringify(queryPayload), 42 | queue_name: 'edit_message', 43 | task_id: ctx.wsTaskNumber 44 | }; 45 | 46 | const context = { 47 | app_id: '2220391788200892', 48 | payload: { 49 | data_trace_id: null, 50 | epoch_id: parseInt(generateOfflineThreadingId), 51 | tasks: [query], 52 | version_id: '6903494529735864' 53 | }, 54 | request_id: ctx.wsReqNumber, 55 | type: 3 56 | }; 57 | 58 | context.payload = JSON.stringify(context.payload); 59 | 60 | // if (canBeCalled(callback)) { 61 | // ctx.reqCallbacks[ctx.wsReqNumber] = callback; 62 | // } 63 | 64 | ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }); 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /fb-chat-api/src/forwardAttachment.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function forwardAttachment(attachmentID, userOrUsers, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | if (!callback) { 15 | callback = function (err) { 16 | if (err) { 17 | return rejectFunc(err); 18 | } 19 | resolveFunc(); 20 | }; 21 | } 22 | 23 | const form = { 24 | attachment_id: attachmentID 25 | }; 26 | 27 | if (utils.getType(userOrUsers) !== "Array") { 28 | userOrUsers = [userOrUsers]; 29 | } 30 | 31 | const timestamp = Math.floor(Date.now() / 1000); 32 | 33 | for (let i = 0; i < userOrUsers.length; i++) { 34 | //That's good, the key of the array is really timestmap in seconds + index 35 | //Probably time when the attachment will be sent? 36 | form["recipient_map[" + (timestamp + i) + "]"] = userOrUsers[i]; 37 | } 38 | 39 | defaultFuncs 40 | .post( 41 | "https://www.facebook.com/mercury/attachments/forward/", 42 | ctx.jar, 43 | form 44 | ) 45 | .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs)) 46 | .then(function (resData) { 47 | if (resData.error) { 48 | throw resData; 49 | } 50 | 51 | return callback(); 52 | }) 53 | .catch(function (err) { 54 | log.error("forwardAttachment", err); 55 | return callback(err); 56 | }); 57 | 58 | return returnPromise; 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /fb-chat-api/src/getCurrentUserID.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function (defaultFuncs, api, ctx) { 4 | return function getCurrentUserID() { 5 | return ctx.i_userID || ctx.userID; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /fb-chat-api/src/getEmojiUrl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const util = require("util"); 4 | 5 | module.exports = function () { 6 | return function getEmojiUrl(c, size, pixelRatio) { 7 | /* 8 | Resolves Facebook Messenger emoji image asset URL for an emoji character. 9 | Supported sizes are 32, 64, and 128. 10 | Supported pixel ratios are '1.0' and '1.5' (possibly more; haven't tested) 11 | */ 12 | const baseUrl = "https://static.xx.fbcdn.net/images/emoji.php/v8/z%s/%s"; 13 | pixelRatio = pixelRatio || "1.0"; 14 | 15 | const ending = util.format( 16 | "%s/%s/%s.png", 17 | pixelRatio, 18 | size, 19 | c.codePointAt(0).toString(16) 20 | ); 21 | let base = 317426846; 22 | for (let i = 0; i < ending.length; i++) { 23 | base = (base << 5) - base + ending.charCodeAt(i); 24 | } 25 | 26 | const hashed = (base & 255).toString(16); 27 | return util.format(baseUrl, hashed, ending); 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /fb-chat-api/src/getFriendsList.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | // [almost] copy pasted from one of FB's minified file (GenderConst) 7 | const GENDERS = { 8 | 0: "unknown", 9 | 1: "female_singular", 10 | 2: "male_singular", 11 | 3: "female_singular_guess", 12 | 4: "male_singular_guess", 13 | 5: "mixed", 14 | 6: "neuter_singular", 15 | 7: "unknown_singular", 16 | 8: "female_plural", 17 | 9: "male_plural", 18 | 10: "neuter_plural", 19 | 11: "unknown_plural" 20 | }; 21 | 22 | function formatData(obj) { 23 | return Object.keys(obj).map(function (key) { 24 | const user = obj[key]; 25 | return { 26 | alternateName: user.alternateName, 27 | firstName: user.firstName, 28 | gender: GENDERS[user.gender], 29 | userID: utils.formatID(user.id.toString()), 30 | isFriend: user.is_friend != null && user.is_friend ? true : false, 31 | fullName: user.name, 32 | profilePicture: user.thumbSrc, 33 | type: user.type, 34 | profileUrl: user.uri, 35 | vanity: user.vanity, 36 | isBirthday: !!user.is_birthday 37 | }; 38 | }); 39 | } 40 | 41 | module.exports = function (defaultFuncs, api, ctx) { 42 | return function getFriendsList(callback) { 43 | let resolveFunc = function () { }; 44 | let rejectFunc = function () { }; 45 | const returnPromise = new Promise(function (resolve, reject) { 46 | resolveFunc = resolve; 47 | rejectFunc = reject; 48 | }); 49 | 50 | if (!callback) { 51 | callback = function (err, friendList) { 52 | if (err) { 53 | return rejectFunc(err); 54 | } 55 | resolveFunc(friendList); 56 | }; 57 | } 58 | 59 | defaultFuncs 60 | .postFormData( 61 | "https://www.facebook.com/chat/user_info_all", 62 | ctx.jar, 63 | {}, 64 | { viewer: ctx.i_userID || ctx.userID } 65 | ) 66 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 67 | .then(function (resData) { 68 | if (!resData) { 69 | throw { error: "getFriendsList returned empty object." }; 70 | } 71 | if (resData.error) { 72 | throw resData; 73 | } 74 | callback(null, formatData(resData.payload)); 75 | }) 76 | .catch(function (err) { 77 | log.error("getFriendsList", err); 78 | return callback(err); 79 | }); 80 | 81 | return returnPromise; 82 | }; 83 | }; 84 | -------------------------------------------------------------------------------- /fb-chat-api/src/getThreadInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | function formatEventReminders(reminder) { 7 | return { 8 | reminderID: reminder.id, 9 | eventCreatorID: reminder.lightweight_event_creator.id, 10 | time: reminder.time, 11 | eventType: reminder.lightweight_event_type.toLowerCase(), 12 | locationName: reminder.location_name, 13 | // @TODO verify this 14 | locationCoordinates: reminder.location_coordinates, 15 | locationPage: reminder.location_page, 16 | eventStatus: reminder.lightweight_event_status.toLowerCase(), 17 | note: reminder.note, 18 | repeatMode: reminder.repeat_mode.toLowerCase(), 19 | eventTitle: reminder.event_title, 20 | triggerMessage: reminder.trigger_message, 21 | secondsToNotifyBefore: reminder.seconds_to_notify_before, 22 | allowsRsvp: reminder.allows_rsvp, 23 | relatedEvent: reminder.related_event, 24 | members: reminder.event_reminder_members.edges.map(function (member) { 25 | return { 26 | memberID: member.node.id, 27 | state: member.guest_list_state.toLowerCase() 28 | }; 29 | }) 30 | }; 31 | } 32 | 33 | function formatThreadGraphQLResponse(data) { 34 | if (data.errors) 35 | return data.errors; 36 | const messageThread = data.message_thread; 37 | if (!messageThread) 38 | return null; 39 | const threadID = messageThread.thread_key.thread_fbid 40 | ? messageThread.thread_key.thread_fbid 41 | : messageThread.thread_key.other_user_id; 42 | 43 | // Remove me 44 | const lastM = messageThread.last_message; 45 | const snippetID = 46 | lastM && 47 | lastM.nodes && 48 | lastM.nodes[0] && 49 | lastM.nodes[0].message_sender && 50 | lastM.nodes[0].message_sender.messaging_actor 51 | ? lastM.nodes[0].message_sender.messaging_actor.id 52 | : null; 53 | const snippetText = 54 | lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null; 55 | const lastR = messageThread.last_read_receipt; 56 | const lastReadTimestamp = 57 | lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise 58 | ? lastR.nodes[0].timestamp_precise 59 | : null; 60 | 61 | return { 62 | threadID: threadID, 63 | threadName: messageThread.name, 64 | participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id), 65 | userInfo: messageThread.all_participants.edges.map(d => ({ 66 | id: d.node.messaging_actor.id, 67 | name: d.node.messaging_actor.name, 68 | firstName: d.node.messaging_actor.short_name, 69 | vanity: d.node.messaging_actor.username, 70 | url: d.node.messaging_actor.url, 71 | thumbSrc: d.node.messaging_actor.big_image_src.uri, 72 | profileUrl: d.node.messaging_actor.big_image_src.uri, 73 | gender: d.node.messaging_actor.gender, 74 | type: d.node.messaging_actor.__typename, 75 | isFriend: d.node.messaging_actor.is_viewer_friend, 76 | isBirthday: !!d.node.messaging_actor.is_birthday //not sure? 77 | })), 78 | unreadCount: messageThread.unread_count, 79 | messageCount: messageThread.messages_count, 80 | timestamp: messageThread.updated_time_precise, 81 | muteUntil: messageThread.mute_until, 82 | isGroup: messageThread.thread_type == "GROUP", 83 | isSubscribed: messageThread.is_viewer_subscribed, 84 | isArchived: messageThread.has_viewer_archived, 85 | folder: messageThread.folder, 86 | cannotReplyReason: messageThread.cannot_reply_reason, 87 | eventReminders: messageThread.event_reminders 88 | ? messageThread.event_reminders.nodes.map(formatEventReminders) 89 | : null, 90 | emoji: messageThread.customization_info 91 | ? messageThread.customization_info.emoji 92 | : null, 93 | color: 94 | messageThread.customization_info && 95 | messageThread.customization_info.outgoing_bubble_color 96 | ? messageThread.customization_info.outgoing_bubble_color.slice(2) 97 | : null, 98 | threadTheme: messageThread.thread_theme, 99 | nicknames: 100 | messageThread.customization_info && 101 | messageThread.customization_info.participant_customizations 102 | ? messageThread.customization_info.participant_customizations.reduce( 103 | function (res, val) { 104 | if (val.nickname) res[val.participant_id] = val.nickname; 105 | return res; 106 | }, 107 | {} 108 | ) 109 | : {}, 110 | adminIDs: messageThread.thread_admins, 111 | approvalMode: Boolean(messageThread.approval_mode), 112 | approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({ 113 | inviterID: a.inviter.id, 114 | requesterID: a.requester.id, 115 | timestamp: a.request_timestamp, 116 | request_source: a.request_source // @Undocumented 117 | })), 118 | 119 | // @Undocumented 120 | reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(), 121 | mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(), 122 | isPinProtected: messageThread.is_pin_protected, 123 | relatedPageThread: messageThread.related_page_thread, 124 | 125 | // @Legacy 126 | name: messageThread.name, 127 | snippet: snippetText, 128 | snippetSender: snippetID, 129 | snippetAttachments: [], 130 | serverTimestamp: messageThread.updated_time_precise, 131 | imageSrc: messageThread.image ? messageThread.image.uri : null, 132 | isCanonicalUser: messageThread.is_canonical_neo_user, 133 | isCanonical: messageThread.thread_type != "GROUP", 134 | recipientsLoadable: true, 135 | hasEmailParticipant: false, 136 | readOnly: false, 137 | canReply: messageThread.cannot_reply_reason == null, 138 | lastMessageTimestamp: messageThread.last_message 139 | ? messageThread.last_message.timestamp_precise 140 | : null, 141 | lastMessageType: "message", 142 | lastReadTimestamp: lastReadTimestamp, 143 | threadType: messageThread.thread_type == "GROUP" ? 2 : 1, 144 | 145 | // update in Wed, 13 Jul 2022 19:41:12 +0700 146 | inviteLink: { 147 | enable: messageThread.joinable_mode ? messageThread.joinable_mode.mode == 1 : false, 148 | link: messageThread.joinable_mode ? messageThread.joinable_mode.link : null 149 | } 150 | }; 151 | } 152 | 153 | module.exports = function (defaultFuncs, api, ctx) { 154 | return function getThreadInfoGraphQL(threadID, callback) { 155 | let resolveFunc = function () { }; 156 | let rejectFunc = function () { }; 157 | const returnPromise = new Promise(function (resolve, reject) { 158 | resolveFunc = resolve; 159 | rejectFunc = reject; 160 | }); 161 | 162 | if (utils.getType(callback) != "Function" && utils.getType(callback) != "AsyncFunction") { 163 | callback = function (err, data) { 164 | if (err) { 165 | return rejectFunc(err); 166 | } 167 | resolveFunc(data); 168 | }; 169 | } 170 | 171 | if (utils.getType(threadID) !== "Array") { 172 | threadID = [threadID]; 173 | } 174 | 175 | let form = {}; 176 | // `queries` has to be a string. I couldn't tell from the dev console. This 177 | // took me a really long time to figure out. I deserve a cookie for this. 178 | threadID.map(function (t, i) { 179 | form["o" + i] = { 180 | doc_id: "3449967031715030", 181 | query_params: { 182 | id: t, 183 | message_limit: 0, 184 | load_messages: false, 185 | load_read_receipts: false, 186 | before: null 187 | } 188 | }; 189 | }); 190 | 191 | form = { 192 | queries: JSON.stringify(form), 193 | batch_name: "MessengerGraphQLThreadFetcher" 194 | }; 195 | 196 | defaultFuncs 197 | .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form) 198 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 199 | .then(function (resData) { 200 | 201 | if (resData.error) { 202 | throw resData; 203 | } 204 | // This returns us an array of things. The last one is the success / 205 | // failure one. 206 | // @TODO What do we do in this case? 207 | // if (resData[resData.length - 1].error_results !== 0) { 208 | // throw resData[0].o0.errors[0]; 209 | // } 210 | // if (!resData[0].o0.data.message_thread) { 211 | // throw new Error("can't find this thread"); 212 | // } 213 | const threadInfos = {}; 214 | for (let i = resData.length - 2; i >= 0; i--) { 215 | const threadInfo = formatThreadGraphQLResponse(resData[i][Object.keys(resData[i])[0]].data); 216 | threadInfos[threadInfo?.threadID || threadID[threadID.length - 1 - i]] = threadInfo; 217 | } 218 | if (Object.values(threadInfos).length == 1) { 219 | callback(null, Object.values(threadInfos)[0]); 220 | } 221 | else { 222 | callback(null, threadInfos); 223 | } 224 | }) 225 | .catch(function (err) { 226 | log.error("getThreadInfoGraphQL", err); 227 | return callback(err); 228 | }); 229 | 230 | return returnPromise; 231 | }; 232 | }; 233 | -------------------------------------------------------------------------------- /fb-chat-api/src/getThreadList.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | function formatEventReminders(reminder) { 7 | return { 8 | reminderID: reminder.id, 9 | eventCreatorID: reminder.lightweight_event_creator.id, 10 | time: reminder.time, 11 | eventType: reminder.lightweight_event_type.toLowerCase(), 12 | locationName: reminder.location_name, 13 | // @TODO verify this 14 | locationCoordinates: reminder.location_coordinates, 15 | locationPage: reminder.location_page, 16 | eventStatus: reminder.lightweight_event_status.toLowerCase(), 17 | note: reminder.note, 18 | repeatMode: reminder.repeat_mode.toLowerCase(), 19 | eventTitle: reminder.event_title, 20 | triggerMessage: reminder.trigger_message, 21 | secondsToNotifyBefore: reminder.seconds_to_notify_before, 22 | allowsRsvp: reminder.allows_rsvp, 23 | relatedEvent: reminder.related_event, 24 | members: reminder.event_reminder_members.edges.map(function (member) { 25 | return { 26 | memberID: member.node.id, 27 | state: member.guest_list_state.toLowerCase() 28 | }; 29 | }) 30 | }; 31 | } 32 | 33 | function formatThreadGraphQLResponse(messageThread) { 34 | const threadID = messageThread.thread_key.thread_fbid 35 | ? messageThread.thread_key.thread_fbid 36 | : messageThread.thread_key.other_user_id; 37 | 38 | // Remove me 39 | const lastM = messageThread.last_message; 40 | const snippetID = 41 | lastM && 42 | lastM.nodes && 43 | lastM.nodes[0] && 44 | lastM.nodes[0].message_sender && 45 | lastM.nodes[0].message_sender.messaging_actor 46 | ? lastM.nodes[0].message_sender.messaging_actor.id 47 | : null; 48 | const snippetText = 49 | lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null; 50 | const lastR = messageThread.last_read_receipt; 51 | const lastReadTimestamp = 52 | lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise 53 | ? lastR.nodes[0].timestamp_precise 54 | : null; 55 | 56 | return { 57 | threadID: threadID, 58 | threadName: messageThread.name, 59 | participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id), 60 | userInfo: messageThread.all_participants.edges.map(d => ({ 61 | id: d.node.messaging_actor.id, 62 | name: d.node.messaging_actor.name, 63 | firstName: d.node.messaging_actor.short_name, 64 | vanity: d.node.messaging_actor.username, 65 | url: d.node.messaging_actor.url, 66 | thumbSrc: d.node.messaging_actor.big_image_src.uri, 67 | profileUrl: d.node.messaging_actor.big_image_src.uri, 68 | gender: d.node.messaging_actor.gender, 69 | type: d.node.messaging_actor.__typename, 70 | isFriend: d.node.messaging_actor.is_viewer_friend, 71 | isBirthday: !!d.node.messaging_actor.is_birthday //not sure? 72 | })), 73 | unreadCount: messageThread.unread_count, 74 | messageCount: messageThread.messages_count, 75 | timestamp: messageThread.updated_time_precise, 76 | muteUntil: messageThread.mute_until, 77 | isGroup: messageThread.thread_type == "GROUP", 78 | isSubscribed: messageThread.is_viewer_subscribed, 79 | isArchived: messageThread.has_viewer_archived, 80 | folder: messageThread.folder, 81 | cannotReplyReason: messageThread.cannot_reply_reason, 82 | eventReminders: messageThread.event_reminders 83 | ? messageThread.event_reminders.nodes.map(formatEventReminders) 84 | : null, 85 | emoji: messageThread.customization_info 86 | ? messageThread.customization_info.emoji 87 | : null, 88 | color: 89 | messageThread.customization_info && 90 | messageThread.customization_info.outgoing_bubble_color 91 | ? messageThread.customization_info.outgoing_bubble_color.slice(2) 92 | : null, 93 | threadTheme: messageThread.thread_theme, 94 | nicknames: 95 | messageThread.customization_info && 96 | messageThread.customization_info.participant_customizations 97 | ? messageThread.customization_info.participant_customizations.reduce( 98 | function (res, val) { 99 | if (val.nickname) res[val.participant_id] = val.nickname; 100 | return res; 101 | }, 102 | {} 103 | ) 104 | : {}, 105 | adminIDs: messageThread.thread_admins, 106 | approvalMode: Boolean(messageThread.approval_mode), 107 | approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({ 108 | inviterID: a.inviter.id, 109 | requesterID: a.requester.id, 110 | timestamp: a.request_timestamp, 111 | request_source: a.request_source // @Undocumented 112 | })), 113 | 114 | // @Undocumented 115 | reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(), 116 | mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(), 117 | isPinProtected: messageThread.is_pin_protected, 118 | relatedPageThread: messageThread.related_page_thread, 119 | 120 | // @Legacy 121 | name: messageThread.name, 122 | snippet: snippetText, 123 | snippetSender: snippetID, 124 | snippetAttachments: [], 125 | serverTimestamp: messageThread.updated_time_precise, 126 | imageSrc: messageThread.image ? messageThread.image.uri : null, 127 | isCanonicalUser: messageThread.is_canonical_neo_user, 128 | isCanonical: messageThread.thread_type != "GROUP", 129 | recipientsLoadable: true, 130 | hasEmailParticipant: false, 131 | readOnly: false, 132 | canReply: messageThread.cannot_reply_reason == null, 133 | lastMessageTimestamp: messageThread.last_message 134 | ? messageThread.last_message.timestamp_precise 135 | : null, 136 | lastMessageType: "message", 137 | lastReadTimestamp: lastReadTimestamp, 138 | threadType: messageThread.thread_type == "GROUP" ? 2 : 1, 139 | 140 | // update in Wed, 13 Jul 2022 19:41:12 +0700 141 | inviteLink: { 142 | enable: messageThread.joinable_mode ? messageThread.joinable_mode.mode == 1 : false, 143 | link: messageThread.joinable_mode ? messageThread.joinable_mode.link : null 144 | } 145 | }; 146 | } 147 | 148 | function formatThreadList(data) { 149 | // console.log(JSON.stringify(data.find(t => t.thread_key.thread_fbid === "5095817367161431"), null, 2)); 150 | return data.map(t => formatThreadGraphQLResponse(t)); 151 | } 152 | 153 | module.exports = function (defaultFuncs, api, ctx) { 154 | return function getThreadList(limit, timestamp, tags, callback) { 155 | if (!callback && (utils.getType(tags) === "Function" || utils.getType(tags) === "AsyncFunction")) { 156 | callback = tags; 157 | tags = [""]; 158 | } 159 | if (utils.getType(limit) !== "Number" || !Number.isInteger(limit) || limit <= 0) { 160 | throw new utils.CustomError({ error: "getThreadList: limit must be a positive integer" }); 161 | } 162 | if (utils.getType(timestamp) !== "Null" && 163 | (utils.getType(timestamp) !== "Number" || !Number.isInteger(timestamp))) { 164 | throw new utils.CustomError({ error: "getThreadList: timestamp must be an integer or null" }); 165 | } 166 | if (utils.getType(tags) === "String") { 167 | tags = [tags]; 168 | } 169 | if (utils.getType(tags) !== "Array") { 170 | throw new utils.CustomError({ 171 | error: "getThreadList: tags must be an array", 172 | message: "getThreadList: tags must be an array" 173 | }); 174 | } 175 | 176 | let resolveFunc = function () { }; 177 | let rejectFunc = function () { }; 178 | const returnPromise = new Promise(function (resolve, reject) { 179 | resolveFunc = resolve; 180 | rejectFunc = reject; 181 | }); 182 | 183 | if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") { 184 | callback = function (err, data) { 185 | if (err) { 186 | return rejectFunc(err); 187 | } 188 | resolveFunc(data); 189 | }; 190 | } 191 | 192 | const form = { 193 | "av": ctx.i_userID || ctx.userID, 194 | "queries": JSON.stringify({ 195 | "o0": { 196 | // This doc_id was valid on 2020-07-20 197 | // "doc_id": "3336396659757871", 198 | "doc_id": "3426149104143726", 199 | "query_params": { 200 | "limit": limit + (timestamp ? 1 : 0), 201 | "before": timestamp, 202 | "tags": tags, 203 | "includeDeliveryReceipts": true, 204 | "includeSeqID": false 205 | } 206 | } 207 | }), 208 | "batch_name": "MessengerGraphQLThreadlistFetcher" 209 | }; 210 | 211 | defaultFuncs 212 | .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form) 213 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 214 | .then((resData) => { 215 | if (resData[resData.length - 1].error_results > 0) { 216 | throw new utils.CustomError(resData[0].o0.errors); 217 | } 218 | 219 | if (resData[resData.length - 1].successful_results === 0) { 220 | throw new utils.CustomError({ error: "getThreadList: there was no successful_results", res: resData }); 221 | } 222 | 223 | // When we ask for threads using timestamp from the previous request, 224 | // we are getting the last thread repeated as the first thread in this response. 225 | // .shift() gets rid of it 226 | // It is also the reason for increasing limit by 1 when timestamp is set 227 | // this way user asks for 10 threads, we are asking for 11, 228 | // but after removing the duplicated one, it is again 10 229 | if (timestamp) { 230 | resData[0].o0.data.viewer.message_threads.nodes.shift(); 231 | } 232 | callback(null, formatThreadList(resData[0].o0.data.viewer.message_threads.nodes)); 233 | }) 234 | .catch((err) => { 235 | log.error("getThreadList", err); 236 | return callback(err); 237 | }); 238 | 239 | return returnPromise; 240 | }; 241 | }; 242 | -------------------------------------------------------------------------------- /fb-chat-api/src/getThreadPictures.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function getThreadPictures(threadID, offset, limit, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | let form = { 25 | thread_id: threadID, 26 | offset: offset, 27 | limit: limit 28 | }; 29 | 30 | defaultFuncs 31 | .post( 32 | "https://www.facebook.com/ajax/messaging/attachments/sharedphotos.php", 33 | ctx.jar, 34 | form 35 | ) 36 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 37 | .then(function (resData) { 38 | if (resData.error) { 39 | throw resData; 40 | } 41 | return Promise.all( 42 | resData.payload.imagesData.map(function (image) { 43 | form = { 44 | thread_id: threadID, 45 | image_id: image.fbid 46 | }; 47 | return defaultFuncs 48 | .post( 49 | "https://www.facebook.com/ajax/messaging/attachments/sharedphotos.php", 50 | ctx.jar, 51 | form 52 | ) 53 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 54 | .then(function (resData) { 55 | if (resData.error) { 56 | throw resData; 57 | } 58 | // the response is pretty messy 59 | const queryThreadID = 60 | resData.jsmods.require[0][3][1].query_metadata.query_path[0] 61 | .message_thread; 62 | const imageData = 63 | resData.jsmods.require[0][3][1].query_results[queryThreadID] 64 | .message_images.edges[0].node.image2; 65 | return imageData; 66 | }); 67 | }) 68 | ); 69 | }) 70 | .then(function (resData) { 71 | callback(null, resData); 72 | }) 73 | .catch(function (err) { 74 | log.error("Error in getThreadPictures", err); 75 | callback(err); 76 | }); 77 | return returnPromise; 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /fb-chat-api/src/getUserID.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | function formatData(data) { 7 | return { 8 | userID: utils.formatID(data.uid.toString()), 9 | photoUrl: data.photo, 10 | indexRank: data.index_rank, 11 | name: data.text, 12 | isVerified: data.is_verified, 13 | profileUrl: data.path, 14 | category: data.category, 15 | score: data.score, 16 | type: data.type 17 | }; 18 | } 19 | 20 | module.exports = function (defaultFuncs, api, ctx) { 21 | return function getUserID(name, callback) { 22 | let resolveFunc = function () { }; 23 | let rejectFunc = function () { }; 24 | const returnPromise = new Promise(function (resolve, reject) { 25 | resolveFunc = resolve; 26 | rejectFunc = reject; 27 | }); 28 | 29 | if (!callback) { 30 | callback = function (err, friendList) { 31 | if (err) { 32 | return rejectFunc(err); 33 | } 34 | resolveFunc(friendList); 35 | }; 36 | } 37 | 38 | const form = { 39 | value: name.toLowerCase(), 40 | viewer: ctx.i_userID || ctx.userID, 41 | rsp: "search", 42 | context: "search", 43 | path: "/home.php", 44 | request_id: utils.getGUID() 45 | }; 46 | 47 | defaultFuncs 48 | .get("https://www.facebook.com/ajax/typeahead/search.php", ctx.jar, form) 49 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 50 | .then(function (resData) { 51 | if (resData.error) { 52 | throw resData; 53 | } 54 | 55 | const data = resData.payload.entries; 56 | 57 | callback(null, data.map(formatData)); 58 | }) 59 | .catch(function (err) { 60 | log.error("getUserID", err); 61 | return callback(err); 62 | }); 63 | 64 | return returnPromise; 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /fb-chat-api/src/getUserInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | function formatData(data) { 7 | const retObj = {}; 8 | 9 | for (const prop in data) { 10 | // eslint-disable-next-line no-prototype-builtins 11 | if (data.hasOwnProperty(prop)) { 12 | const innerObj = data[prop]; 13 | retObj[prop] = { 14 | name: innerObj.name, 15 | firstName: innerObj.firstName, 16 | vanity: innerObj.vanity, 17 | thumbSrc: innerObj.thumbSrc, 18 | profileUrl: innerObj.uri, 19 | gender: innerObj.gender, 20 | type: innerObj.type, 21 | isFriend: innerObj.is_friend, 22 | isBirthday: !!innerObj.is_birthday, 23 | searchTokens: innerObj.searchTokens, 24 | alternateName: innerObj.alternateName 25 | }; 26 | } 27 | } 28 | 29 | return retObj; 30 | } 31 | 32 | module.exports = function (defaultFuncs, api, ctx) { 33 | return function getUserInfo(id, callback) { 34 | let resolveFunc = function () { }; 35 | let rejectFunc = function () { }; 36 | const returnPromise = new Promise(function (resolve, reject) { 37 | resolveFunc = resolve; 38 | rejectFunc = reject; 39 | }); 40 | 41 | if (!callback) { 42 | callback = function (err, friendList) { 43 | if (err) { 44 | return rejectFunc(err); 45 | } 46 | resolveFunc(friendList); 47 | }; 48 | } 49 | 50 | if (utils.getType(id) !== "Array") { 51 | id = [id]; 52 | } 53 | 54 | const form = {}; 55 | id.map(function (v, i) { 56 | form["ids[" + i + "]"] = v; 57 | }); 58 | defaultFuncs 59 | .post("https://www.facebook.com/chat/user_info/", ctx.jar, form) 60 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 61 | .then(function (resData) { 62 | if (resData.error) { 63 | throw resData; 64 | } 65 | return callback(null, formatData(resData.payload.profiles)); 66 | }) 67 | .catch(function (err) { 68 | log.error("getUserInfo", err); 69 | return callback(err); 70 | }); 71 | 72 | return returnPromise; 73 | }; 74 | }; 75 | -------------------------------------------------------------------------------- /fb-chat-api/src/handleFriendRequest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function handleFriendRequest(userID, accept, callback) { 8 | if (utils.getType(accept) !== "Boolean") { 9 | throw { 10 | error: "Please pass a boolean as a second argument." 11 | }; 12 | } 13 | 14 | let resolveFunc = function () { }; 15 | let rejectFunc = function () { }; 16 | const returnPromise = new Promise(function (resolve, reject) { 17 | resolveFunc = resolve; 18 | rejectFunc = reject; 19 | }); 20 | 21 | if (!callback) { 22 | callback = function (err, friendList) { 23 | if (err) { 24 | return rejectFunc(err); 25 | } 26 | resolveFunc(friendList); 27 | }; 28 | } 29 | 30 | const form = { 31 | fb_api_caller_class: "RelayModern", 32 | fb_api_req_friendly_name: "FriendingCometFriendRequestConfirmMutation", 33 | doc_id: "7303313029748461", 34 | variables: JSON.stringify({ 35 | input: { 36 | friend_requester_id: String(userID), 37 | source: "friends_tab", 38 | actor_id: ctx.i_userID || ctx.userID, 39 | client_mutation_id: Math.round(Math.random() * 1024).toString() 40 | }, 41 | scale: 1, 42 | refresh_num: 0 43 | }), 44 | av: ctx.i_userID || ctx.userID 45 | }; 46 | 47 | defaultFuncs 48 | .post( 49 | "https://www.facebook.com/api/graphql/", 50 | ctx.jar, 51 | form 52 | ) 53 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 54 | .then(function (resData) { 55 | if (resData.error || resData.errors) { 56 | throw { 57 | err: resData.payload.err 58 | }; 59 | } 60 | 61 | return callback(null, resData); 62 | }) 63 | .catch(function (err) { 64 | log.error("handleFriendRequest", err); 65 | return callback(err); 66 | }); 67 | 68 | return returnPromise; 69 | }; 70 | }; 71 | -------------------------------------------------------------------------------- /fb-chat-api/src/handleMessageRequest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function handleMessageRequest(threadID, accept, callback) { 8 | if (utils.getType(accept) !== "Boolean") { 9 | throw { 10 | error: "Please pass a boolean as a second argument." 11 | }; 12 | } 13 | 14 | let resolveFunc = function () { }; 15 | let rejectFunc = function () { }; 16 | const returnPromise = new Promise(function (resolve, reject) { 17 | resolveFunc = resolve; 18 | rejectFunc = reject; 19 | }); 20 | 21 | if (!callback) { 22 | callback = function (err, friendList) { 23 | if (err) { 24 | return rejectFunc(err); 25 | } 26 | resolveFunc(friendList); 27 | }; 28 | } 29 | 30 | const form = { 31 | client: "mercury" 32 | }; 33 | 34 | if (utils.getType(threadID) !== "Array") { 35 | threadID = [threadID]; 36 | } 37 | 38 | const messageBox = accept ? "inbox" : "other"; 39 | 40 | for (let i = 0; i < threadID.length; i++) { 41 | form[messageBox + "[" + i + "]"] = threadID[i]; 42 | } 43 | 44 | defaultFuncs 45 | .post( 46 | "https://www.facebook.com/ajax/mercury/move_thread.php", 47 | ctx.jar, 48 | form 49 | ) 50 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 51 | .then(function (resData) { 52 | if (resData.error) { 53 | throw resData; 54 | } 55 | 56 | return callback(); 57 | }) 58 | .catch(function (err) { 59 | log.error("handleMessageRequest", err); 60 | return callback(err); 61 | }); 62 | 63 | return returnPromise; 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /fb-chat-api/src/httpGet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function httpGet(url, form, customHeader, callback, notAPI) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | 11 | const returnPromise = new Promise(function (resolve, reject) { 12 | resolveFunc = resolve; 13 | rejectFunc = reject; 14 | }); 15 | 16 | if (utils.getType(form) == "Function" || utils.getType(form) == "AsyncFunction") { 17 | callback = form; 18 | form = {}; 19 | } 20 | 21 | if (utils.getType(customHeader) == "Function" || utils.getType(customHeader) == "AsyncFunction") { 22 | callback = customHeader; 23 | customHeader = {}; 24 | } 25 | 26 | customHeader = customHeader || {}; 27 | 28 | callback = callback || function (err, data) { 29 | if (err) return rejectFunc(err); 30 | resolveFunc(data); 31 | }; 32 | 33 | if (notAPI) { 34 | utils 35 | .get(url, ctx.jar, form, ctx.globalOptions, ctx, customHeader) 36 | .then(function (resData) { 37 | callback(null, resData.body.toString()); 38 | }) 39 | .catch(function (err) { 40 | log.error("httpGet", err); 41 | return callback(err); 42 | }); 43 | } else { 44 | defaultFuncs 45 | .get(url, ctx.jar, form, null, customHeader) 46 | .then(function (resData) { 47 | callback(null, resData.body.toString()); 48 | }) 49 | .catch(function (err) { 50 | log.error("httpGet", err); 51 | return callback(err); 52 | }); 53 | } 54 | 55 | return returnPromise; 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /fb-chat-api/src/httpPost.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function httpPost(url, form, customHeader, callback, notAPI) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | 11 | const returnPromise = new Promise(function (resolve, reject) { 12 | resolveFunc = resolve; 13 | rejectFunc = reject; 14 | }); 15 | 16 | if (utils.getType(form) == "Function" || utils.getType(form) == "AsyncFunction") { 17 | callback = form; 18 | form = {}; 19 | } 20 | 21 | if (utils.getType(customHeader) == "Function" || utils.getType(customHeader) == "AsyncFunction") { 22 | callback = customHeader; 23 | customHeader = {}; 24 | } 25 | 26 | customHeader = customHeader || {}; 27 | 28 | callback = callback || function (err, data) { 29 | if (err) return rejectFunc(err); 30 | resolveFunc(data); 31 | }; 32 | 33 | if (notAPI) { 34 | utils 35 | .post(url, ctx.jar, form, ctx.globalOptions, ctx, customHeader) 36 | .then(function (resData) { 37 | callback(null, resData.body.toString()); 38 | }) 39 | .catch(function (err) { 40 | log.error("httpPost", err); 41 | return callback(err); 42 | }); 43 | } else { 44 | defaultFuncs 45 | .post(url, ctx.jar, form, {}, customHeader) 46 | .then(function (resData) { 47 | callback(null, resData.body.toString()); 48 | }) 49 | .catch(function (err) { 50 | log.error("httpPost", err); 51 | return callback(err); 52 | }); 53 | } 54 | 55 | return returnPromise; 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /fb-chat-api/src/httpPostFormData.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | 7 | module.exports = function (defaultFuncs, api, ctx) { 8 | return function httpPostFormData(url, form, customHeader, callback, notAPI) { 9 | let resolveFunc = function () { }; 10 | let rejectFunc = function () { }; 11 | 12 | const returnPromise = new Promise(function (resolve, reject) { 13 | resolveFunc = resolve; 14 | rejectFunc = reject; 15 | }); 16 | 17 | if (utils.getType(form) == "Function" || utils.getType(form) == "AsyncFunction") { 18 | callback = form; 19 | form = {}; 20 | } 21 | 22 | if (utils.getType(customHeader) == "Function" || utils.getType(customHeader) == "AsyncFunction") { 23 | callback = customHeader; 24 | customHeader = {}; 25 | } 26 | 27 | customHeader = customHeader || {}; 28 | 29 | if (utils.getType(callback) == "Boolean") { 30 | notAPI = callback; 31 | callback = null; 32 | } 33 | 34 | callback = callback || function (err, data) { 35 | if (err) return rejectFunc(err); 36 | resolveFunc(data); 37 | }; 38 | 39 | if (notAPI) { 40 | utils 41 | .postFormData(url, ctx.jar, form, ctx.globalOptions, ctx, customHeader) 42 | .then(function (resData) { 43 | callback(null, resData.body.toString()); 44 | }) 45 | .catch(function (err) { 46 | log.error("httpPostFormData", err); 47 | return callback(err); 48 | }); 49 | } else { 50 | defaultFuncs 51 | .postFormData(url, ctx.jar, form, null, customHeader) 52 | .then(function (resData) { 53 | callback(null, resData.body.toString()); 54 | }) 55 | .catch(function (err) { 56 | log.error("httpPostFormData", err); 57 | return callback(err); 58 | }); 59 | } 60 | 61 | return returnPromise; 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /fb-chat-api/src/logout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function logout(callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | const form = { 25 | pmid: "0" 26 | }; 27 | 28 | defaultFuncs 29 | .post( 30 | "https://www.facebook.com/bluebar/modern_settings_menu/?help_type=364455653583099&show_contextual_help=1", 31 | ctx.jar, 32 | form 33 | ) 34 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 35 | .then(function (resData) { 36 | const elem = resData.jsmods.instances[0][2][0].filter(function (v) { 37 | return v.value === "logout"; 38 | })[0]; 39 | 40 | const html = resData.jsmods.markup.filter(function (v) { 41 | return v[0] === elem.markup.__m; 42 | })[0][1].__html; 43 | 44 | const form = { 45 | fb_dtsg: utils.getFrom(html, '"fb_dtsg" value="', '"'), 46 | ref: utils.getFrom(html, '"ref" value="', '"'), 47 | h: utils.getFrom(html, '"h" value="', '"') 48 | }; 49 | 50 | return defaultFuncs 51 | .post("https://www.facebook.com/logout.php", ctx.jar, form) 52 | .then(utils.saveCookies(ctx.jar)); 53 | }) 54 | .then(function (res) { 55 | if (!res.headers) { 56 | throw { error: "An error occurred when logging out." }; 57 | } 58 | 59 | return defaultFuncs 60 | .get(res.headers.location, ctx.jar) 61 | .then(utils.saveCookies(ctx.jar)); 62 | }) 63 | .then(function () { 64 | ctx.loggedIn = false; 65 | log.info("logout", "Logged out successfully."); 66 | callback(); 67 | }) 68 | .catch(function (err) { 69 | log.error("logout", err); 70 | return callback(err); 71 | }); 72 | 73 | return returnPromise; 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /fb-chat-api/src/markAsDelivered.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function markAsDelivered(threadID, messageID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | if (!threadID || !messageID) { 25 | return callback("Error: messageID or threadID is not defined"); 26 | } 27 | 28 | const form = {}; 29 | 30 | form["message_ids[0]"] = messageID; 31 | form["thread_ids[" + threadID + "][0]"] = messageID; 32 | 33 | defaultFuncs 34 | .post( 35 | "https://www.facebook.com/ajax/mercury/delivery_receipts.php", 36 | ctx.jar, 37 | form 38 | ) 39 | .then(utils.saveCookies(ctx.jar)) 40 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 41 | .then(function (resData) { 42 | if (resData.error) { 43 | throw resData; 44 | } 45 | 46 | return callback(); 47 | }) 48 | .catch(function (err) { 49 | log.error("markAsDelivered", err); 50 | if (utils.getType(err) == "Object" && err.error === "Not logged in.") { 51 | ctx.loggedIn = false; 52 | } 53 | return callback(err); 54 | }); 55 | 56 | return returnPromise; 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /fb-chat-api/src/markAsRead.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return async function markAsRead(threadID, read, callback) { 8 | if (utils.getType(read) === 'Function' || utils.getType(read) === 'AsyncFunction') { 9 | callback = read; 10 | read = true; 11 | } 12 | if (read == undefined) { 13 | read = true; 14 | } 15 | 16 | if (!callback) { 17 | callback = () => { }; 18 | } 19 | 20 | const form = {}; 21 | 22 | if (typeof ctx.globalOptions.pageID !== 'undefined') { 23 | form["source"] = "PagesManagerMessagesInterface"; 24 | form["request_user_id"] = ctx.globalOptions.pageID; 25 | form["ids[" + threadID + "]"] = read; 26 | form["watermarkTimestamp"] = new Date().getTime(); 27 | form["shouldSendReadReceipt"] = true; 28 | form["commerce_last_message_type"] = ""; 29 | //form["titanOriginatedThreadId"] = utils.generateThreadingID(ctx.clientID); 30 | 31 | let resData; 32 | try { 33 | resData = await ( 34 | defaultFuncs 35 | .post( 36 | "https://www.facebook.com/ajax/mercury/change_read_status.php", 37 | ctx.jar, 38 | form 39 | ) 40 | .then(utils.saveCookies(ctx.jar)) 41 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 42 | ); 43 | } catch (e) { 44 | callback(e); 45 | return e; 46 | } 47 | 48 | if (resData.error) { 49 | const err = resData.error; 50 | log.error("markAsRead", err); 51 | if (utils.getType(err) == "Object" && err.error === "Not logged in.") { 52 | ctx.loggedIn = false; 53 | } 54 | callback(err); 55 | return err; 56 | } 57 | 58 | callback(); 59 | return null; 60 | } else { 61 | try { 62 | if (ctx.mqttClient) { 63 | const err = await new Promise(r => ctx.mqttClient.publish("/mark_thread", JSON.stringify({ 64 | threadID, 65 | mark: "read", 66 | state: read 67 | }), { qos: 1, retain: false }, r)); 68 | if (err) throw err; 69 | } else { 70 | throw { 71 | error: "You can only use this function after you start listening." 72 | }; 73 | } 74 | } catch (e) { 75 | callback(e); 76 | return e; 77 | } 78 | } 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /fb-chat-api/src/markAsReadAll.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function markAsReadAll(callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | const form = { 25 | folder: 'inbox' 26 | }; 27 | 28 | defaultFuncs 29 | .post( 30 | "https://www.facebook.com/ajax/mercury/mark_folder_as_read.php", 31 | ctx.jar, 32 | form 33 | ) 34 | .then(utils.saveCookies(ctx.jar)) 35 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 36 | .then(function (resData) { 37 | if (resData.error) { 38 | throw resData; 39 | } 40 | 41 | return callback(); 42 | }) 43 | .catch(function (err) { 44 | log.error("markAsReadAll", err); 45 | return callback(err); 46 | }); 47 | 48 | return returnPromise; 49 | }; 50 | }; -------------------------------------------------------------------------------- /fb-chat-api/src/markAsSeen.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function markAsRead(seen_timestamp, callback) { 8 | if (utils.getType(seen_timestamp) == "Function" || 9 | utils.getType(seen_timestamp) == "AsyncFunction") { 10 | callback = seen_timestamp; 11 | seen_timestamp = Date.now(); 12 | } 13 | 14 | let resolveFunc = function () { }; 15 | let rejectFunc = function () { }; 16 | const returnPromise = new Promise(function (resolve, reject) { 17 | resolveFunc = resolve; 18 | rejectFunc = reject; 19 | }); 20 | 21 | if (!callback) { 22 | callback = function (err, friendList) { 23 | if (err) { 24 | return rejectFunc(err); 25 | } 26 | resolveFunc(friendList); 27 | }; 28 | } 29 | 30 | const form = { 31 | seen_timestamp: seen_timestamp 32 | }; 33 | 34 | defaultFuncs 35 | .post( 36 | "https://www.facebook.com/ajax/mercury/mark_seen.php", 37 | ctx.jar, 38 | form 39 | ) 40 | .then(utils.saveCookies(ctx.jar)) 41 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 42 | .then(function (resData) { 43 | if (resData.error) { 44 | throw resData; 45 | } 46 | 47 | return callback(); 48 | }) 49 | .catch(function (err) { 50 | log.error("markAsSeen", err); 51 | if (utils.getType(err) == "Object" && err.error === "Not logged in.") { 52 | ctx.loggedIn = false; 53 | } 54 | return callback(err); 55 | }); 56 | 57 | return returnPromise; 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /fb-chat-api/src/muteThread.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | // muteSecond: -1=permanent mute, 0=unmute, 60=one minute, 3600=one hour, etc. 8 | return function muteThread(threadID, muteSeconds, callback) { 9 | let resolveFunc = function () { }; 10 | let rejectFunc = function () { }; 11 | const returnPromise = new Promise(function (resolve, reject) { 12 | resolveFunc = resolve; 13 | rejectFunc = reject; 14 | }); 15 | 16 | if (!callback) { 17 | callback = function (err, friendList) { 18 | if (err) { 19 | return rejectFunc(err); 20 | } 21 | resolveFunc(friendList); 22 | }; 23 | } 24 | 25 | const form = { 26 | thread_fbid: threadID, 27 | mute_settings: muteSeconds 28 | }; 29 | 30 | defaultFuncs 31 | .post( 32 | "https://www.facebook.com/ajax/mercury/change_mute_thread.php", 33 | ctx.jar, 34 | form 35 | ) 36 | .then(utils.saveCookies(ctx.jar)) 37 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 38 | .then(function (resData) { 39 | if (resData.error) { 40 | throw resData; 41 | } 42 | 43 | return callback(); 44 | }) 45 | .catch(function (err) { 46 | log.error("muteThread", err); 47 | return callback(err); 48 | }); 49 | 50 | return returnPromise; 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /fb-chat-api/src/refreshFb_dtsg.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | /** 8 | * Refreshes the fb_dtsg and jazoest values. 9 | * @param {Function} callback 10 | * @returns {Promise} 11 | * @description if you don't update the value of fb_dtsg and jazoest for a long time an error "Please try closing and re-opening your browser window" will appear 12 | * @description you should refresh it every 48h or less 13 | */ 14 | return function refreshFb_dtsg(obj, callback) { 15 | let resolveFunc = function () { }; 16 | let rejectFunc = function () { }; 17 | const returnPromise = new Promise(function (resolve, reject) { 18 | resolveFunc = resolve; 19 | rejectFunc = reject; 20 | }); 21 | 22 | if (utils.getType(obj) === "Function" || utils.getType(obj) === "AsyncFunction") { 23 | callback = obj; 24 | obj = {}; 25 | } 26 | 27 | if (!obj) { 28 | obj = {}; 29 | } 30 | 31 | if (utils.getType(obj) !== "Object") { 32 | throw new utils.CustomError("the first parameter must be an object or a callback function"); 33 | } 34 | 35 | if (!callback) { 36 | callback = function (err, friendList) { 37 | if (err) { 38 | return rejectFunc(err); 39 | } 40 | resolveFunc(friendList); 41 | }; 42 | } 43 | 44 | if (Object.keys(obj).length == 0) { 45 | utils 46 | .get('https://m.facebook.com/', ctx.jar, null, ctx.globalOptions, { noRef: true }) 47 | .then(function (resData) { 48 | const html = resData.body; 49 | const fb_dtsg = utils.getFrom(html, 'name="fb_dtsg" value="', '"'); 50 | const jazoest = utils.getFrom(html, 'name="jazoest" value="', '"'); 51 | if (!fb_dtsg) { 52 | throw new utils.CustomError("Could not find fb_dtsg in HTML after requesting https://www.facebook.com/"); 53 | } 54 | ctx.fb_dtsg = fb_dtsg; 55 | ctx.jazoest = jazoest; 56 | callback(null, { 57 | data: { 58 | fb_dtsg: fb_dtsg, 59 | jazoest: jazoest 60 | }, 61 | message: "refreshed fb_dtsg and jazoest" 62 | }); 63 | }) 64 | .catch(function (err) { 65 | log.error("refreshFb_dtsg", err); 66 | return callback(err); 67 | }); 68 | } 69 | else { 70 | Object.keys(obj).forEach(function (key) { 71 | ctx[key] = obj[key]; 72 | }); 73 | callback(null, { 74 | data: obj, 75 | message: "refreshed " + Object.keys(obj).join(", ") 76 | }); 77 | } 78 | 79 | return returnPromise; 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /fb-chat-api/src/removeUserFromGroup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function removeUserFromGroup(userID, threadID, callback) { 8 | if ( 9 | !callback && 10 | (utils.getType(threadID) === "Function" || 11 | utils.getType(threadID) === "AsyncFunction") 12 | ) { 13 | throw { error: "please pass a threadID as a second argument." }; 14 | } 15 | if ( 16 | utils.getType(threadID) !== "Number" && 17 | utils.getType(threadID) !== "String" 18 | ) { 19 | throw { 20 | error: 21 | "threadID should be of type Number or String and not " + 22 | utils.getType(threadID) + 23 | "." 24 | }; 25 | } 26 | if ( 27 | utils.getType(userID) !== "Number" && 28 | utils.getType(userID) !== "String" 29 | ) { 30 | throw { 31 | error: 32 | "userID should be of type Number or String and not " + 33 | utils.getType(userID) + 34 | "." 35 | }; 36 | } 37 | 38 | let resolveFunc = function () { }; 39 | let rejectFunc = function () { }; 40 | const returnPromise = new Promise(function (resolve, reject) { 41 | resolveFunc = resolve; 42 | rejectFunc = reject; 43 | }); 44 | 45 | if (!callback) { 46 | callback = function (err, friendList) { 47 | if (err) { 48 | return rejectFunc(err); 49 | } 50 | resolveFunc(friendList); 51 | }; 52 | } 53 | 54 | const form = { 55 | uid: userID, 56 | tid: threadID 57 | }; 58 | 59 | defaultFuncs 60 | .post("https://www.facebook.com/chat/remove_participants", ctx.jar, form) 61 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 62 | .then(function (resData) { 63 | if (!resData) { 64 | throw { error: "Remove from group failed." }; 65 | } 66 | if (resData.error) { 67 | throw resData; 68 | } 69 | 70 | return callback(); 71 | }) 72 | .catch(function (err) { 73 | log.error("removeUserFromGroup", err); 74 | return callback(err); 75 | }); 76 | 77 | return returnPromise; 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /fb-chat-api/src/resolvePhotoUrl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function resolvePhotoUrl(photoID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | defaultFuncs 25 | .get("https://www.facebook.com/mercury/attachments/photo", ctx.jar, { 26 | photo_id: photoID 27 | }) 28 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 29 | .then(resData => { 30 | if (resData.error) { 31 | throw resData; 32 | } 33 | 34 | const photoUrl = resData.jsmods.require[0][3][0]; 35 | 36 | return callback(null, photoUrl); 37 | }) 38 | .catch(err => { 39 | log.error("resolvePhotoUrl", err); 40 | return callback(err); 41 | }); 42 | 43 | return returnPromise; 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /fb-chat-api/src/searchForThread.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | 5 | module.exports = function (defaultFuncs, api, ctx) { 6 | return function searchForThread(name, callback) { 7 | let resolveFunc = function () { }; 8 | let rejectFunc = function () { }; 9 | const returnPromise = new Promise(function (resolve, reject) { 10 | resolveFunc = resolve; 11 | rejectFunc = reject; 12 | }); 13 | 14 | if (!callback) { 15 | callback = function (err, friendList) { 16 | if (err) { 17 | return rejectFunc(err); 18 | } 19 | resolveFunc(friendList); 20 | }; 21 | } 22 | 23 | const tmpForm = { 24 | client: "web_messenger", 25 | query: name, 26 | offset: 0, 27 | limit: 21, 28 | index: "fbid" 29 | }; 30 | 31 | defaultFuncs 32 | .post( 33 | "https://www.facebook.com/ajax/mercury/search_threads.php", 34 | ctx.jar, 35 | tmpForm 36 | ) 37 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 38 | .then(function (resData) { 39 | if (resData.error) { 40 | throw resData; 41 | } 42 | if (!resData.payload.mercury_payload.threads) { 43 | return callback({ error: "Could not find thread `" + name + "`." }); 44 | } 45 | return callback( 46 | null, 47 | resData.payload.mercury_payload.threads.map(utils.formatThread) 48 | ); 49 | }); 50 | 51 | return returnPromise; 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /fb-chat-api/src/sendMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | const allowedProperties = { 7 | attachment: true, 8 | url: true, 9 | sticker: true, 10 | emoji: true, 11 | emojiSize: true, 12 | body: true, 13 | mentions: true, 14 | location: true 15 | }; 16 | 17 | function removeSpecialChar(inputString) { // remove char banned by facebook 18 | if (typeof inputString !== "string") 19 | return inputString; 20 | // Convert string to Buffer 21 | const buffer = Buffer.from(inputString, 'utf8'); 22 | 23 | // Filter buffer start with ef b8 8f 24 | let filteredBuffer = Buffer.alloc(0); 25 | for (let i = 0; i < buffer.length; i++) { 26 | if (buffer[i] === 0xEF && buffer[i + 1] === 0xB8 && buffer[i + 2] === 0x8F) { 27 | i += 2; // Skip 3 bytes of buffer starting with ef b8 8f 28 | } else { 29 | filteredBuffer = Buffer.concat([filteredBuffer, buffer.slice(i, i + 1)]); 30 | } 31 | } 32 | 33 | // Convert filtered buffer to string 34 | const convertedString = filteredBuffer.toString('utf8'); 35 | 36 | return convertedString; 37 | } 38 | 39 | module.exports = function (defaultFuncs, api, ctx) { 40 | function uploadAttachment(attachments, callback) { 41 | const uploads = []; 42 | 43 | // create an array of promises 44 | for (let i = 0; i < attachments.length; i++) { 45 | if (!utils.isReadableStream(attachments[i])) { 46 | throw { 47 | error: 48 | "Attachment should be a readable stream and not " + 49 | utils.getType(attachments[i]) + 50 | "." 51 | }; 52 | } 53 | 54 | const form = { 55 | upload_1024: attachments[i], 56 | voice_clip: "true" 57 | }; 58 | 59 | uploads.push( 60 | defaultFuncs 61 | .postFormData( 62 | "https://upload.facebook.com/ajax/mercury/upload.php", 63 | ctx.jar, 64 | form, 65 | {} 66 | ) 67 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 68 | .then(function (resData) { 69 | if (resData.error) { 70 | throw resData; 71 | } 72 | 73 | // We have to return the data unformatted unless we want to change it 74 | // back in sendMessage. 75 | return resData.payload.metadata[0]; 76 | }) 77 | ); 78 | } 79 | 80 | // resolve all promises 81 | Promise 82 | .all(uploads) 83 | .then(function (resData) { 84 | callback(null, resData); 85 | }) 86 | .catch(function (err) { 87 | log.error("uploadAttachment", err); 88 | return callback(err); 89 | }); 90 | } 91 | 92 | function getUrl(url, callback) { 93 | const form = { 94 | image_height: 960, 95 | image_width: 960, 96 | uri: url 97 | }; 98 | 99 | defaultFuncs 100 | .post( 101 | "https://www.facebook.com/message_share_attachment/fromURI/", 102 | ctx.jar, 103 | form 104 | ) 105 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 106 | .then(function (resData) { 107 | if (resData.error) { 108 | return callback(resData); 109 | } 110 | 111 | if (!resData.payload) { 112 | return callback({ error: "Invalid url" }); 113 | } 114 | 115 | callback(null, resData.payload.share_data.share_params); 116 | }) 117 | .catch(function (err) { 118 | log.error("getUrl", err); 119 | return callback(err); 120 | }); 121 | } 122 | 123 | function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) { 124 | // There are three cases here: 125 | // 1. threadID is of type array, where we're starting a new group chat with users 126 | // specified in the array. 127 | // 2. User is sending a message to a specific user. 128 | // 3. No additional form params and the message goes to an existing group chat. 129 | if (utils.getType(threadID) === "Array") { 130 | for (let i = 0; i < threadID.length; i++) { 131 | form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i]; 132 | } 133 | form["specific_to_list[" + threadID.length + "]"] = "fbid:" + (ctx.i_userID || ctx.userID); 134 | form["client_thread_id"] = "root:" + messageAndOTID; 135 | log.info("sendMessage", "Sending message to multiple users: " + threadID); 136 | } else { 137 | // This means that threadID is the id of a user, and the chat 138 | // is a single person chat 139 | if (isSingleUser) { 140 | form["specific_to_list[0]"] = "fbid:" + threadID; 141 | form["specific_to_list[1]"] = "fbid:" + (ctx.i_userID || ctx.userID); 142 | form["other_user_fbid"] = threadID; 143 | } else { 144 | form["thread_fbid"] = threadID; 145 | } 146 | } 147 | 148 | if (ctx.globalOptions.pageID) { 149 | form["author"] = "fbid:" + ctx.globalOptions.pageID; 150 | form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID; 151 | form["creator_info[creatorID]"] = ctx.i_userID || ctx.userID; 152 | form["creator_info[creatorType]"] = "direct_admin"; 153 | form["creator_info[labelType]"] = "sent_message"; 154 | form["creator_info[pageID]"] = ctx.globalOptions.pageID; 155 | form["request_user_id"] = ctx.globalOptions.pageID; 156 | form["creator_info[profileURI]"] = 157 | "https://www.facebook.com/profile.php?id=" + (ctx.i_userID || ctx.userID); 158 | } 159 | 160 | defaultFuncs 161 | .post("https://www.facebook.com/messaging/send/", ctx.jar, form) 162 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 163 | .then(function (resData) { 164 | if (!resData) { 165 | return callback({ error: "Send message failed." }); 166 | } 167 | 168 | if (resData.error) { 169 | if (resData.error === 1545012) { 170 | log.warn( 171 | "sendMessage", 172 | "Got error 1545012. This might mean that you're not part of the conversation " + 173 | threadID 174 | ); 175 | } 176 | else { 177 | log.error("sendMessage", resData); 178 | } 179 | return callback(resData); 180 | } 181 | 182 | const messageInfo = resData.payload.actions.reduce(function (p, v) { 183 | return ( 184 | { 185 | threadID: v.thread_fbid, 186 | messageID: v.message_id, 187 | timestamp: v.timestamp 188 | } || p 189 | ); 190 | }, null); 191 | 192 | return callback(null, messageInfo); 193 | }) 194 | .catch(function (err) { 195 | log.error("sendMessage", err); 196 | if (utils.getType(err) == "Object" && err.error === "Not logged in.") { 197 | ctx.loggedIn = false; 198 | } 199 | return callback(err); 200 | }); 201 | } 202 | 203 | function send(form, threadID, messageAndOTID, callback, isGroup) { 204 | // We're doing a query to this to check if the given id is the id of 205 | // a user or of a group chat. The form will be different depending 206 | // on that. 207 | if (utils.getType(threadID) === "Array") { 208 | sendContent(form, threadID, false, messageAndOTID, callback); 209 | } else { 210 | if (utils.getType(isGroup) != "Boolean") { 211 | // Removed the use of api.getUserInfo() in the old version to reduce account lockout 212 | sendContent(form, threadID, threadID.toString().length < 16, messageAndOTID, callback); 213 | } else { 214 | sendContent(form, threadID, !isGroup, messageAndOTID, callback); 215 | } 216 | } 217 | } 218 | 219 | function handleUrl(msg, form, callback, cb) { 220 | if (msg.url) { 221 | form["shareable_attachment[share_type]"] = "100"; 222 | getUrl(msg.url, function (err, params) { 223 | if (err) { 224 | return callback(err); 225 | } 226 | 227 | form["shareable_attachment[share_params]"] = params; 228 | cb(); 229 | }); 230 | } else { 231 | cb(); 232 | } 233 | } 234 | 235 | function handleLocation(msg, form, callback, cb) { 236 | if (msg.location) { 237 | if (msg.location.latitude == null || msg.location.longitude == null) { 238 | return callback({ error: "location property needs both latitude and longitude" }); 239 | } 240 | 241 | form["location_attachment[coordinates][latitude]"] = msg.location.latitude; 242 | form["location_attachment[coordinates][longitude]"] = msg.location.longitude; 243 | form["location_attachment[is_current_location]"] = !!msg.location.current; 244 | } 245 | 246 | cb(); 247 | } 248 | 249 | function handleSticker(msg, form, callback, cb) { 250 | if (msg.sticker) { 251 | form["sticker_id"] = msg.sticker; 252 | } 253 | cb(); 254 | } 255 | 256 | function handleEmoji(msg, form, callback, cb) { 257 | if (msg.emojiSize != null && msg.emoji == null) { 258 | return callback({ error: "emoji property is empty" }); 259 | } 260 | if (msg.emoji) { 261 | if (msg.emojiSize == null) { 262 | msg.emojiSize = "medium"; 263 | } 264 | if ( 265 | msg.emojiSize != "small" && 266 | msg.emojiSize != "medium" && 267 | msg.emojiSize != "large" 268 | ) { 269 | return callback({ error: "emojiSize property is invalid" }); 270 | } 271 | if (form["body"] != null && form["body"] != "") { 272 | return callback({ error: "body is not empty" }); 273 | } 274 | form["body"] = msg.emoji; 275 | form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize; 276 | } 277 | cb(); 278 | } 279 | 280 | function handleAttachment(msg, form, callback, cb) { 281 | if (msg.attachment) { 282 | form["image_ids"] = []; 283 | form["gif_ids"] = []; 284 | form["file_ids"] = []; 285 | form["video_ids"] = []; 286 | form["audio_ids"] = []; 287 | 288 | if (utils.getType(msg.attachment) !== "Array") { 289 | msg.attachment = [msg.attachment]; 290 | } 291 | 292 | uploadAttachment(msg.attachment, function (err, files) { 293 | if (err) { 294 | return callback(err); 295 | } 296 | 297 | files.forEach(function (file) { 298 | const key = Object.keys(file); 299 | const type = key[0]; // image_id, file_id, etc 300 | form["" + type + "s"].push(file[type]); // push the id 301 | }); 302 | cb(); 303 | }); 304 | } else { 305 | cb(); 306 | } 307 | } 308 | 309 | function handleMention(msg, form, callback, cb) { 310 | if (msg.mentions) { 311 | for (let i = 0; i < msg.mentions.length; i++) { 312 | const mention = msg.mentions[i]; 313 | 314 | const tag = mention.tag; 315 | if (typeof tag !== "string") { 316 | return callback({ error: "Mention tags must be strings." }); 317 | } 318 | 319 | const offset = msg.body.indexOf(tag, mention.fromIndex || 0); 320 | 321 | if (offset < 0) { 322 | log.warn( 323 | "handleMention", 324 | 'Mention for "' + tag + '" not found in message string.' 325 | ); 326 | } 327 | 328 | if (mention.id == null) { 329 | log.warn("handleMention", "Mention id should be non-null."); 330 | } 331 | 332 | const id = mention.id || 0; 333 | form["profile_xmd[" + i + "][offset]"] = offset; 334 | form["profile_xmd[" + i + "][length]"] = tag.length; 335 | form["profile_xmd[" + i + "][id]"] = id; 336 | form["profile_xmd[" + i + "][type]"] = "p"; 337 | } 338 | } 339 | cb(); 340 | } 341 | 342 | return function sendMessage(msg, threadID, callback, replyToMessage, isGroup) { 343 | typeof isGroup == "undefined" ? isGroup = null : ""; 344 | if ( 345 | !callback && 346 | (utils.getType(threadID) === "Function" || 347 | utils.getType(threadID) === "AsyncFunction") 348 | ) { 349 | return threadID({ error: "Pass a threadID as a second argument." }); 350 | } 351 | if ( 352 | !replyToMessage && 353 | utils.getType(callback) === "String" 354 | ) { 355 | replyToMessage = callback; 356 | callback = function () { }; 357 | } 358 | 359 | let resolveFunc = function () { }; 360 | let rejectFunc = function () { }; 361 | const returnPromise = new Promise(function (resolve, reject) { 362 | resolveFunc = resolve; 363 | rejectFunc = reject; 364 | }); 365 | 366 | if (!callback) { 367 | callback = function (err, friendList) { 368 | if (err) { 369 | return rejectFunc(err); 370 | } 371 | resolveFunc(friendList); 372 | }; 373 | } 374 | 375 | const msgType = utils.getType(msg); 376 | const threadIDType = utils.getType(threadID); 377 | const messageIDType = utils.getType(replyToMessage); 378 | 379 | if (msgType !== "String" && msgType !== "Object") { 380 | return callback({ 381 | error: 382 | "Message should be of type string or object and not " + msgType + "." 383 | }); 384 | } 385 | 386 | // Changing this to accomodate an array of users 387 | if ( 388 | threadIDType !== "Array" && 389 | threadIDType !== "Number" && 390 | threadIDType !== "String" 391 | ) { 392 | return callback({ 393 | error: 394 | "ThreadID should be of type number, string, or array and not " + 395 | threadIDType + 396 | "." 397 | }); 398 | } 399 | 400 | if (replyToMessage && messageIDType !== 'String') { 401 | return callback({ 402 | error: 403 | "MessageID should be of type string and not " + 404 | threadIDType + 405 | "." 406 | }); 407 | } 408 | 409 | if (msgType === "String") { 410 | msg = { body: msg }; 411 | } 412 | 413 | if (utils.getType(msg.body) === "String") { 414 | msg.body = removeSpecialChar(msg.body); 415 | } 416 | 417 | const disallowedProperties = Object.keys(msg).filter( 418 | prop => !allowedProperties[prop] 419 | ); 420 | if (disallowedProperties.length > 0) { 421 | return callback({ 422 | error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`" 423 | }); 424 | } 425 | 426 | const messageAndOTID = utils.generateOfflineThreadingID(); 427 | 428 | const form = { 429 | client: "mercury", 430 | action_type: "ma-type:user-generated-message", 431 | author: "fbid:" + (ctx.i_userID || ctx.userID), 432 | timestamp: Date.now(), 433 | timestamp_absolute: "Today", 434 | timestamp_relative: utils.generateTimestampRelative(), 435 | timestamp_time_passed: "0", 436 | is_unread: false, 437 | is_cleared: false, 438 | is_forward: false, 439 | is_filtered_content: false, 440 | is_filtered_content_bh: false, 441 | is_filtered_content_account: false, 442 | is_filtered_content_quasar: false, 443 | is_filtered_content_invalid_app: false, 444 | is_spoof_warning: false, 445 | source: "source:chat:web", 446 | "source_tags[0]": "source:chat", 447 | body: msg.body ? msg.body.toString() : "", 448 | html_body: false, 449 | ui_push_phase: "V3", 450 | status: "0", 451 | offline_threading_id: messageAndOTID, 452 | message_id: messageAndOTID, 453 | threading_id: utils.generateThreadingID(ctx.clientID), 454 | "ephemeral_ttl_mode:": "0", 455 | manual_retry_cnt: "0", 456 | has_attachment: !!(msg.attachment || msg.url || msg.sticker), 457 | signatureID: utils.getSignatureID(), 458 | replied_to_message_id: replyToMessage 459 | }; 460 | 461 | handleLocation(msg, form, callback, () => 462 | handleSticker(msg, form, callback, () => 463 | handleAttachment(msg, form, callback, () => 464 | handleUrl(msg, form, callback, () => 465 | handleEmoji(msg, form, callback, () => 466 | handleMention(msg, form, callback, () => 467 | send(form, threadID, messageAndOTID, callback, isGroup) 468 | ) 469 | ) 470 | ) 471 | ) 472 | ) 473 | ); 474 | 475 | return returnPromise; 476 | }; 477 | }; 478 | -------------------------------------------------------------------------------- /fb-chat-api/src/sendTypingIndicator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | function makeTypingIndicator(typ, threadID, callback, isGroup) { 8 | const form = { 9 | typ: +typ, 10 | to: "", 11 | source: "mercury-chat", 12 | thread: threadID 13 | }; 14 | 15 | // Check if thread is a single person chat or a group chat 16 | // More info on this is in api.sendMessage 17 | if (utils.getType(isGroup) == "Boolean") { 18 | if (!isGroup) { 19 | form.to = threadID; 20 | } 21 | defaultFuncs 22 | .post("https://www.facebook.com/ajax/messaging/typ.php", ctx.jar, form) 23 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 24 | .then(function (resData) { 25 | if (resData.error) { 26 | throw resData; 27 | } 28 | 29 | return callback(); 30 | }) 31 | .catch(function (err) { 32 | log.error("sendTypingIndicator", err); 33 | if (utils.getType(err) == "Object" && err.error === "Not logged in") { 34 | ctx.loggedIn = false; 35 | } 36 | return callback(err); 37 | }); 38 | } else { 39 | api.getUserInfo(threadID, function (err, res) { 40 | if (err) { 41 | return callback(err); 42 | } 43 | 44 | // If id is single person chat 45 | if (Object.keys(res).length > 0) { 46 | form.to = threadID; 47 | } 48 | 49 | defaultFuncs 50 | .post("https://www.facebook.com/ajax/messaging/typ.php", ctx.jar, form) 51 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 52 | .then(function (resData) { 53 | if (resData.error) { 54 | throw resData; 55 | } 56 | 57 | return callback(); 58 | }) 59 | .catch(function (err) { 60 | log.error("sendTypingIndicator", err); 61 | if (utils.getType(err) == "Object" && err.error === "Not logged in.") { 62 | ctx.loggedIn = false; 63 | } 64 | return callback(err); 65 | }); 66 | }); 67 | } 68 | } 69 | 70 | return function sendTypingIndicator(threadID, callback, isGroup) { 71 | if ( 72 | utils.getType(callback) !== "Function" && 73 | utils.getType(callback) !== "AsyncFunction" 74 | ) { 75 | if (callback) { 76 | log.warn( 77 | "sendTypingIndicator", 78 | "callback is not a function - ignoring." 79 | ); 80 | } 81 | callback = () => { }; 82 | } 83 | 84 | makeTypingIndicator(true, threadID, callback, isGroup); 85 | 86 | return function end(cb) { 87 | if ( 88 | utils.getType(cb) !== "Function" && 89 | utils.getType(cb) !== "AsyncFunction" 90 | ) { 91 | if (cb) { 92 | log.warn( 93 | "sendTypingIndicator", 94 | "callback is not a function - ignoring." 95 | ); 96 | } 97 | cb = () => { }; 98 | } 99 | 100 | makeTypingIndicator(false, threadID, cb, isGroup); 101 | }; 102 | }; 103 | }; 104 | -------------------------------------------------------------------------------- /fb-chat-api/src/setMessageReaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function setMessageReaction(reaction, messageID, callback, forceCustomReaction) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | if (ctx.i_userID) { 25 | throw { error: "Cannot set reaction as another profile." }; 26 | } 27 | 28 | switch (reaction) { 29 | case "\uD83D\uDE0D": //:heart_eyes: 30 | case "\uD83D\uDE06": //:laughing: 31 | case "\uD83D\uDE2E": //:open_mouth: 32 | case "\uD83D\uDE22": //:cry: 33 | case "\uD83D\uDE20": //:angry: 34 | case "\uD83D\uDC4D": //:thumbsup: 35 | case "\uD83D\uDC4E": //:thumbsdown: 36 | case "\u2764": //:heart: 37 | case "\uD83D\uDC97": //:glowingheart: 38 | case "": 39 | //valid 40 | break; 41 | case ":heart_eyes:": 42 | case ":love:": 43 | reaction = "\uD83D\uDE0D"; 44 | break; 45 | case ":laughing:": 46 | case ":haha:": 47 | reaction = "\uD83D\uDE06"; 48 | break; 49 | case ":open_mouth:": 50 | case ":wow:": 51 | reaction = "\uD83D\uDE2E"; 52 | break; 53 | case ":cry:": 54 | case ":sad:": 55 | reaction = "\uD83D\uDE22"; 56 | break; 57 | case ":angry:": 58 | reaction = "\uD83D\uDE20"; 59 | break; 60 | case ":thumbsup:": 61 | case ":like:": 62 | reaction = "\uD83D\uDC4D"; 63 | break; 64 | case ":thumbsdown:": 65 | case ":dislike:": 66 | reaction = "\uD83D\uDC4E"; 67 | break; 68 | case ":heart:": 69 | reaction = "\u2764"; 70 | break; 71 | case ":glowingheart:": 72 | reaction = "\uD83D\uDC97"; 73 | break; 74 | default: 75 | if (forceCustomReaction) { 76 | break; 77 | } 78 | return callback({ error: "Reaction is not a valid emoji." }); 79 | } 80 | 81 | const variables = { 82 | data: { 83 | client_mutation_id: ctx.clientMutationId++, 84 | actor_id: ctx.i_userID || ctx.userID, 85 | action: reaction == "" ? "REMOVE_REACTION" : "ADD_REACTION", 86 | message_id: messageID, 87 | reaction: reaction 88 | } 89 | }; 90 | 91 | const qs = { 92 | doc_id: "1491398900900362", 93 | variables: JSON.stringify(variables), 94 | dpr: 1 95 | }; 96 | 97 | defaultFuncs 98 | .postFormData( 99 | "https://www.facebook.com/webgraphql/mutation/", 100 | ctx.jar, 101 | {}, 102 | qs 103 | ) 104 | .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs)) 105 | .then(function (resData) { 106 | if (!resData) { 107 | throw { error: "setReaction returned empty object." }; 108 | } 109 | if (resData.error || resData.errors) { 110 | throw resData; 111 | } 112 | callback(null); 113 | }) 114 | .catch(function (err) { 115 | log.error("setReaction", err); 116 | return callback(err); 117 | }); 118 | 119 | return returnPromise; 120 | }; 121 | }; 122 | -------------------------------------------------------------------------------- /fb-chat-api/src/setPostReaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fix by NTKhang 3 | * update as Thursday, 10 February 2022 4 | * do not remove the author name to get more updates 5 | */ 6 | 7 | "use strict"; 8 | 9 | const utils = require("../utils"); 10 | const log = require("npmlog"); 11 | 12 | function formatData(resData) { 13 | return { 14 | viewer_feedback_reaction_info: resData.feedback_react.feedback.viewer_feedback_reaction_info, 15 | supported_reactions: resData.feedback_react.feedback.supported_reactions, 16 | top_reactions: resData.feedback_react.feedback.top_reactions.edges, 17 | reaction_count: resData.feedback_react.feedback.reaction_count 18 | }; 19 | } 20 | 21 | module.exports = function (defaultFuncs, api, ctx) { 22 | return function setPostReaction(postID, type, callback) { 23 | let resolveFunc = function () { }; 24 | let rejectFunc = function () { }; 25 | const returnPromise = new Promise(function (resolve, reject) { 26 | resolveFunc = resolve; 27 | rejectFunc = reject; 28 | }); 29 | 30 | if (!callback) { 31 | if (utils.getType(type) === "Function" || utils.getType(type) === "AsyncFunction") { 32 | callback = type; 33 | type = 0; 34 | } 35 | else { 36 | callback = function (err, data) { 37 | if (err) { 38 | return rejectFunc(err); 39 | } 40 | resolveFunc(data); 41 | }; 42 | } 43 | } 44 | 45 | const map = { 46 | unlike: 0, 47 | like: 1, 48 | heart: 2, 49 | love: 16, 50 | haha: 4, 51 | wow: 3, 52 | sad: 7, 53 | angry: 8 54 | }; 55 | 56 | if (utils.getType(type) !== "Number" && utils.getType(type) === "String") { 57 | type = map[type.toLowerCase()]; 58 | } 59 | 60 | if (utils.getType(type) !== "Number" && utils.getType(type) !== "String") { 61 | throw { 62 | error: "setPostReaction: Invalid reaction type" 63 | }; 64 | } 65 | 66 | if (type != 0 && !type) { 67 | throw { 68 | error: "setPostReaction: Invalid reaction type" 69 | }; 70 | } 71 | 72 | const form = { 73 | av: ctx.i_userID || ctx.userID, 74 | fb_api_caller_class: "RelayModern", 75 | fb_api_req_friendly_name: "CometUFIFeedbackReactMutation", 76 | doc_id: "4769042373179384", 77 | variables: JSON.stringify({ 78 | input: { 79 | actor_id: ctx.i_userID || ctx.userID, 80 | feedback_id: (new Buffer("feedback:" + postID)).toString("base64"), 81 | feedback_reaction: type, 82 | feedback_source: "OBJECT", 83 | is_tracking_encrypted: true, 84 | tracking: [], 85 | session_id: "f7dd50dd-db6e-4598-8cd9-561d5002b423", 86 | client_mutation_id: Math.round(Math.random() * 19).toString() 87 | }, 88 | useDefaultActor: false, 89 | scale: 3 90 | }) 91 | }; 92 | 93 | defaultFuncs 94 | .post("https://www.facebook.com/api/graphql/", ctx.jar, form) 95 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 96 | .then(function (resData) { 97 | if (resData.errors) { 98 | throw resData; 99 | } 100 | return callback(null, formatData(resData.data)); 101 | }) 102 | .catch(function (err) { 103 | log.error("setPostReaction", err); 104 | return callback(err); 105 | }); 106 | 107 | return returnPromise; 108 | }; 109 | }; -------------------------------------------------------------------------------- /fb-chat-api/src/setTitle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function setTitle(newTitle, threadID, callback) { 8 | if ( 9 | !callback && 10 | (utils.getType(threadID) === "Function" || 11 | utils.getType(threadID) === "AsyncFunction") 12 | ) { 13 | throw { error: "please pass a threadID as a second argument." }; 14 | } 15 | 16 | let resolveFunc = function () { }; 17 | let rejectFunc = function () { }; 18 | const returnPromise = new Promise(function (resolve, reject) { 19 | resolveFunc = resolve; 20 | rejectFunc = reject; 21 | }); 22 | 23 | if (!callback) { 24 | callback = function (err, friendList) { 25 | if (err) { 26 | return rejectFunc(err); 27 | } 28 | resolveFunc(friendList); 29 | }; 30 | } 31 | 32 | const messageAndOTID = utils.generateOfflineThreadingID(); 33 | const form = { 34 | client: "mercury", 35 | action_type: "ma-type:log-message", 36 | author: "fbid:" + (ctx.i_userID || ctx.userID), 37 | author_email: "", 38 | coordinates: "", 39 | timestamp: Date.now(), 40 | timestamp_absolute: "Today", 41 | timestamp_relative: utils.generateTimestampRelative(), 42 | timestamp_time_passed: "0", 43 | is_unread: false, 44 | is_cleared: false, 45 | is_forward: false, 46 | is_filtered_content: false, 47 | is_spoof_warning: false, 48 | source: "source:chat:web", 49 | "source_tags[0]": "source:chat", 50 | status: "0", 51 | offline_threading_id: messageAndOTID, 52 | message_id: messageAndOTID, 53 | threading_id: utils.generateThreadingID(ctx.clientID), 54 | manual_retry_cnt: "0", 55 | thread_fbid: threadID, 56 | thread_name: newTitle, 57 | thread_id: threadID, 58 | log_message_type: "log:thread-name" 59 | }; 60 | 61 | defaultFuncs 62 | .post("https://www.facebook.com/messaging/set_thread_name/", ctx.jar, form) 63 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 64 | .then(function (resData) { 65 | if (resData.error && resData.error === 1545012) { 66 | throw { error: "Cannot change chat title: Not member of chat." }; 67 | } 68 | 69 | if (resData.error && resData.error === 1545003) { 70 | throw { error: "Cannot set title of single-user chat." }; 71 | } 72 | 73 | if (resData.error) { 74 | throw resData; 75 | } 76 | 77 | return callback(); 78 | }) 79 | .catch(function (err) { 80 | log.error("setTitle", err); 81 | return callback(err); 82 | }); 83 | 84 | return returnPromise; 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /fb-chat-api/src/threadColors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function (_defaultFuncs, _api, _ctx) { 4 | // Currently the only colors that can be passed to api.changeThreadColor(); may change if Facebook adds more 5 | return { 6 | //Old hex colors. 7 | ////MessengerBlue: null, 8 | ////Viking: "#44bec7", 9 | ////GoldenPoppy: "#ffc300", 10 | ////RadicalRed: "#fa3c4c", 11 | ////Shocking: "#d696bb", 12 | ////PictonBlue: "#6699cc", 13 | ////FreeSpeechGreen: "#13cf13", 14 | ////Pumpkin: "#ff7e29", 15 | ////LightCoral: "#e68585", 16 | ////MediumSlateBlue: "#7646ff", 17 | ////DeepSkyBlue: "#20cef5", 18 | ////Fern: "#67b868", 19 | ////Cameo: "#d4a88c", 20 | ////BrilliantRose: "#ff5ca1", 21 | ////BilobaFlower: "#a695c7" 22 | 23 | //#region This part is for backward compatibly 24 | //trying to match the color one-by-one. kill me plz 25 | MessengerBlue: "196241301102133", //DefaultBlue 26 | Viking: "1928399724138152", //TealBlue 27 | GoldenPoppy: "174636906462322", //Yellow 28 | RadicalRed: "2129984390566328", //Red 29 | Shocking: "2058653964378557", //LavenderPurple 30 | FreeSpeechGreen: "2136751179887052", //Green 31 | Pumpkin: "175615189761153", //Orange 32 | LightCoral: "980963458735625", //CoralPink 33 | MediumSlateBlue: "234137870477637", //BrightPurple 34 | DeepSkyBlue: "2442142322678320", //AquaBlue 35 | BrilliantRose: "169463077092846", //HotPink 36 | //i've tried my best, everything else can't be mapped. (or is it?) -UIRI 2020 37 | //#endregion 38 | 39 | DefaultBlue: "196241301102133", 40 | HotPink: "169463077092846", 41 | AquaBlue: "2442142322678320", 42 | BrightPurple: "234137870477637", 43 | CoralPink: "980963458735625", 44 | Orange: "175615189761153", 45 | Green: "2136751179887052", 46 | LavenderPurple: "2058653964378557", 47 | Red: "2129984390566328", 48 | Yellow: "174636906462322", 49 | TealBlue: "1928399724138152", 50 | Aqua: "417639218648241", 51 | Mango: "930060997172551", 52 | Berry: "164535220883264", 53 | Citrus: "370940413392601", 54 | Candy: "205488546921017", 55 | 56 | /** 57 | * July 06, 2022 58 | * added by @NTKhang 59 | */ 60 | Earth: "1833559466821043", 61 | Support: "365557122117011", 62 | Music: "339021464972092", 63 | Pride: "1652456634878319", 64 | DoctorStrange: "538280997628317", 65 | LoFi: "1060619084701625", 66 | Sky: "3190514984517598", 67 | LunarNewYear: "357833546030778", 68 | Celebration: "627144732056021", 69 | Chill: "390127158985345", 70 | StrangerThings: "1059859811490132", 71 | Dune: "1455149831518874", 72 | Care: "275041734441112", 73 | Astrology: "3082966625307060", 74 | JBalvin: "184305226956268", 75 | Birthday: "621630955405500", 76 | Cottagecore: "539927563794799", 77 | Ocean: "736591620215564", 78 | Love: "741311439775765", 79 | TieDye: "230032715012014", 80 | Monochrome: "788274591712841", 81 | Default: "3259963564026002", 82 | Rocket: "582065306070020", 83 | Berry2: "724096885023603", 84 | Candy2: "624266884847972", 85 | Unicorn: "273728810607574", 86 | Tropical: "262191918210707", 87 | Maple: "2533652183614000", 88 | Sushi: "909695489504566", 89 | Citrus2: "557344741607350", 90 | Lollipop: "280333826736184", 91 | Shadow: "271607034185782", 92 | Rose: "1257453361255152", 93 | Lavender: "571193503540759", 94 | Tulip: "2873642949430623", 95 | Classic: "3273938616164733", 96 | Peach: "3022526817824329", 97 | Honey: "672058580051520", 98 | Kiwi: "3151463484918004", 99 | Grape: "193497045377796", 100 | 101 | /** 102 | * July 15, 2022 103 | * added by @NTKhang 104 | */ 105 | NonBinary: "737761000603635", 106 | 107 | /** 108 | * November 25, 2022 109 | * added by @NTKhang 110 | */ 111 | ThankfulForFriends: "1318983195536293", 112 | Transgender: "504518465021637", 113 | TaylorSwift: "769129927636836", 114 | NationalComingOutDay: "788102625833584", 115 | Autumn: "822549609168155", 116 | Cyberpunk2077: "780962576430091", 117 | 118 | /** 119 | * May 13, 2023 120 | */ 121 | MothersDay: "1288506208402340", 122 | APAHM: "121771470870245", 123 | Parenthood: "810978360551741", 124 | StarWars: "1438011086532622", 125 | GuardianOfTheGalaxy: "101275642962533", 126 | Bloom: "158263147151440", 127 | BubbleTea: "195296273246380", 128 | Basketball: "6026716157422736", 129 | ElephantsAndFlowers: "693996545771691" 130 | }; 131 | }; 132 | -------------------------------------------------------------------------------- /fb-chat-api/src/unfriend.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function unfriend(userID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | const form = { 25 | uid: userID, 26 | unref: "bd_friends_tab", 27 | floc: "friends_tab", 28 | "nctr[_mod]": "pagelet_timeline_app_collection_" + (ctx.i_userID || ctx.userID) + ":2356318349:2" 29 | }; 30 | 31 | defaultFuncs 32 | .post( 33 | "https://www.facebook.com/ajax/profile/removefriendconfirm.php", 34 | ctx.jar, 35 | form 36 | ) 37 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 38 | .then(function (resData) { 39 | if (resData.error) { 40 | throw resData; 41 | } 42 | 43 | return callback(null, true); 44 | }) 45 | .catch(function (err) { 46 | log.error("unfriend", err); 47 | return callback(err); 48 | }); 49 | 50 | return returnPromise; 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /fb-chat-api/src/unsendMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("../utils"); 4 | const log = require("npmlog"); 5 | 6 | module.exports = function (defaultFuncs, api, ctx) { 7 | return function unsendMessage(messageID, callback) { 8 | let resolveFunc = function () { }; 9 | let rejectFunc = function () { }; 10 | const returnPromise = new Promise(function (resolve, reject) { 11 | resolveFunc = resolve; 12 | rejectFunc = reject; 13 | }); 14 | 15 | if (!callback) { 16 | callback = function (err, friendList) { 17 | if (err) { 18 | return rejectFunc(err); 19 | } 20 | resolveFunc(friendList); 21 | }; 22 | } 23 | 24 | const form = { 25 | message_id: messageID 26 | }; 27 | 28 | defaultFuncs 29 | .post( 30 | "https://www.facebook.com/messaging/unsend_message/", 31 | ctx.jar, 32 | form 33 | ) 34 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 35 | .then(function (resData) { 36 | if (resData.error) { 37 | throw resData; 38 | } 39 | 40 | return callback(); 41 | }) 42 | .catch(function (err) { 43 | log.error("unsendMessage", err); 44 | return callback(err); 45 | }); 46 | 47 | return returnPromise; 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /fb-chat-api/src/uploadAttachment.js: -------------------------------------------------------------------------------- 1 | const utils = require("../utils"); 2 | const log = require("npmlog"); 3 | 4 | module.exports = function (defaultFuncs, api, ctx) { 5 | function upload(attachments, callback) { 6 | callback = callback || function () { }; 7 | const uploads = []; 8 | 9 | // create an array of promises 10 | for (let i = 0; i < attachments.length; i++) { 11 | if (!utils.isReadableStream(attachments[i])) { 12 | throw { 13 | error: 14 | "Attachment should be a readable stream and not " + 15 | utils.getType(attachments[i]) + 16 | "." 17 | }; 18 | } 19 | 20 | const form = { 21 | upload_1024: attachments[i], 22 | voice_clip: "true" 23 | }; 24 | 25 | uploads.push( 26 | defaultFuncs 27 | .postFormData( 28 | "https://upload.facebook.com/ajax/mercury/upload.php", 29 | ctx.jar, 30 | form, 31 | {} 32 | ) 33 | .then(utils.parseAndCheckLogin(ctx, defaultFuncs)) 34 | .then(function (resData) { 35 | if (resData.error) { 36 | throw resData; 37 | } 38 | 39 | // We have to return the data unformatted unless we want to change it 40 | // back in sendMessage. 41 | return resData.payload.metadata[0]; 42 | }) 43 | ); 44 | } 45 | 46 | // resolve all promises 47 | Promise 48 | .all(uploads) 49 | .then(function (resData) { 50 | callback(null, resData); 51 | }) 52 | .catch(function (err) { 53 | log.error("uploadAttachment", err); 54 | return callback(err); 55 | }); 56 | } 57 | 58 | return function uploadAttachment(attachments, callback) { 59 | if ( 60 | !attachments && 61 | !utils.isReadableStream(attachments) && 62 | !utils.getType(attachments) === "Array" && 63 | (utils.getType(attachments) === "Array" && !attachments.length) 64 | ) 65 | throw { error: "Please pass an attachment or an array of attachments." }; 66 | 67 | let resolveFunc = function () { }; 68 | let rejectFunc = function () { }; 69 | const returnPromise = new Promise(function (resolve, reject) { 70 | resolveFunc = resolve; 71 | rejectFunc = reject; 72 | }); 73 | 74 | if (!callback) { 75 | callback = function (err, info) { 76 | if (err) { 77 | return rejectFunc(err); 78 | } 79 | resolveFunc(info); 80 | }; 81 | } 82 | 83 | if (utils.getType(attachments) !== "Array") 84 | attachments = [attachments]; 85 | 86 | upload(attachments, (err, info) => { 87 | if (err) { 88 | return callback(err); 89 | } 90 | callback(null, info); 91 | }); 92 | 93 | return returnPromise; 94 | }; 95 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require("child_process"); 2 | const path = require('path'); 3 | 4 | const SCRIPT_FILE = "auto.js"; 5 | const SCRIPT_PATH = path.join(__dirname, SCRIPT_FILE); 6 | 7 | 8 | function start() { 9 | const main = spawn("node", [SCRIPT_PATH], { 10 | cwd: __dirname, 11 | stdio: "inherit", 12 | shell: true 13 | }); 14 | 15 | main.on("close", (exitCode) => { 16 | if (exitCode === 0) { 17 | console.log("Main process exited with code 0"); 18 | } else if (exitCode === 1) { 19 | console.log("Main process exited with code 1. Restarting..."); 20 | start(); 21 | } else { 22 | console.error(`Main process exited with code ${exitCode}`); 23 | } 24 | }); 25 | } 26 | 27 | start(); 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-messenger-bot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@types/node": "^18.0.6", 15 | "axios": "^1.6.5", 16 | "bluebird": "^2.11.0", 17 | "body-parser": "^1.20.2", 18 | "chalk": "^3.0.0", 19 | "cheerio": "^0.22.0", 20 | "child_process": "^1.0.2", 21 | "express": "^4.18.2", 22 | "fs-extra": "^11.1.1", 23 | "hercai": "^12.2.0", 24 | "https-proxy-agent": "^4.0.0", 25 | "mqtt": "^3.0.0", 26 | "node-cron": "^3.0.3", 27 | "node-fetch": "^3.2.6", 28 | "npmlog": "^1.2.0", 29 | "pastebin-api": "^7.0.0", 30 | "request": "^2.53.0", 31 | "websocket-stream": "^5.5.0", 32 | "yt-search": "^2.10.4", 33 | "ytdl-core": "^4.11.4" 34 | }, 35 | "devDependencies": { 36 | "eslint": "^7.5.0", 37 | "mocha": "^7.0.1", 38 | "prettier": "^1.11.1" 39 | }, 40 | "eslintConfig": { 41 | "env": { 42 | "commonjs": true, 43 | "es2021": true, 44 | "node": true 45 | }, 46 | "extends": "eslint:recommended", 47 | "parserOptions": { 48 | "ecmaVersion": 13 49 | }, 50 | "rules": { 51 | "no-prototype-builtins": 0, 52 | "no-unused-vars": 1, 53 | "comma-dangle": 1, 54 | "no-redeclare": 0, 55 | "prefer-const": 1, 56 | "no-useless-escape": 0, 57 | "no-mixed-spaces-and-tabs": 0, 58 | "semi": 1, 59 | "no-useless-catch": 0, 60 | "no-empty": 0, 61 | "use-isnan": 0, 62 | "no-extra-semi": 1, 63 | "no-async-promise-executor": 0, 64 | "no-unreachable": 1, 65 | "valid-typeof": 0, 66 | "no-case-declarations": 0 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/guide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |1. Install the Kiwi Browser from the Google Play Store and proceed to launch the application.
43 |2. Upon launching the app, navigate to the search function and enter the following link: https://github.com/c3cbot/c3c-fbstate/archive/refs/tags/1.5.zip
45 |3. Once entered, a prompt will appear instructing you to download the file; proceed by clicking the download option.
47 |4. After completing the download, tap the three dots in the upper-right corner, then select the "Extensions" option.
49 |5. In the Extensions menu, enable Developer Mode, then click the "+" button to add the extension from either a zip file, crx file, or user.js file.
51 |6. Locate the previously downloaded zip file and click on it to initiate the installation process.
53 |7. Once the installation is complete, observe the C3C fbstate utility in the bottom-right corner and activate it by clicking on it.
55 |8. Upon activation, navigate to Facebook.com using the Kiwi Browser and log in your account.
57 |9. After logging in, access the menu by clicking the three dots in the upper right corner, scroll down, and locate the C3C fbstate utility.
59 |10. You'll be redirected to another page; locate the "Copy Clipboard" option and click it to copy your cookies for further use.
61 |11. Paste your copied cookies at https://auto-bot-soyeon.onrender.com, then scroll down to thoroughly review the Terms and Conditions and Privacy Policy.
63 |12. Finally, having carefully reviewed the Terms and Conditions and Privacy Policy, check the box to confirm your understanding and submit the information.
65 |13. Certainly, please input "ai" to confirm the bot's operational status. If it responds, congratulations on successful verification.
67 |• In case the bot encounters a login issue during the initial log, it may be due to a lock. Simply unlock it and repeat the process.
71 |• If the bot is already logged into Facebook/FB Lite, refrain from logging in on other devices or initiating a logout.
72 |• While the bot is active, avoid logging into the bot account or changing the password.
73 |• Please be aware that there is a possibility of encountering account locks or suspensions, and we are not responsible for such occurrences.
74 |• Please be aware that there is a possibility of encountering account locks or suspensions, and we are not responsible for such occurrences.
75 |To register another bot/Al, simply tap on "logout" (not die appstate) to acquire a new appstate for another account.
76 |Greetings! Begin by entering your cookie. Scroll down to carefully review our Terms and Privacy.
47 |Upon deployment or logging in, your chatbot connection remains active unless you personally access the bot's account or modify the password. This measure is in place to ensure the seamless operation and security of the chatbot.
51 |Exercise caution and refrain from sharing your cookie with any third party. Your cookie contains sensitive information crucial to the confidentiality and security of your account and the connected chatbot.
52 |As the service provider, we disclaim responsibility for any unauthorized account access. Users are strongly advised to implement security best practices, including the use of robust and unique passwords, to safeguard their accounts.
53 |Accessing the bot's account may lead to a disruption in service. It is strongly recommended not to attempt to access the bot's account to maintain the continuous functionality of the chatbot.
54 |If the chatbot is unresponsive, kindly return to this website. If issues persist, consider changing the account to ensure uninterrupted service.
55 |We, as the service provider, disclaim responsibility for any account locking or suspension on the provided account. Users are expected to adhere to the terms of service of the hosting platform.
56 |For added security, it is highly recommended to use dummy or disposable accounts when interacting with the chatbot.
57 |Users are strictly prohibited from employing the chatbot for malicious activities. Violation of this rule may result in the termination of access to the chatbot and may have legal consequences.
58 |