├── 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 | Step-By-Step Guide 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 |
41 |

Step-By-Step Guide

42 |

1. Install the Kiwi Browser from the Google Play Store and proceed to launch the application.

43 | 44 |

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 | 46 |

3. Once entered, a prompt will appear instructing you to download the file; proceed by clicking the download option.

47 | 48 |

4. After completing the download, tap the three dots in the upper-right corner, then select the "Extensions" option.

49 | 50 |

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 | 52 |

6. Locate the previously downloaded zip file and click on it to initiate the installation process.

53 | 54 |

7. Once the installation is complete, observe the C3C fbstate utility in the bottom-right corner and activate it by clicking on it.

55 | 56 |

8. Upon activation, navigate to Facebook.com using the Kiwi Browser and log in your account.

57 | 58 |

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 | 60 |

10. You'll be redirected to another page; locate the "Copy Clipboard" option and click it to copy your cookies for further use.

61 | 62 |

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 | 64 |

12. Finally, having carefully reviewed the Terms and Conditions and Privacy Policy, check the box to confirm your understanding and submit the information.

65 | 66 |

13. Certainly, please input "ai" to confirm the bot's operational status. If it responds, congratulations on successful verification.

67 | 68 |
69 |

Note:

70 |

• 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 | 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /public/image/guide_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_1.jpeg -------------------------------------------------------------------------------- /public/image/guide_10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_10.jpeg -------------------------------------------------------------------------------- /public/image/guide_11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_11.jpeg -------------------------------------------------------------------------------- /public/image/guide_12.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_12.jpeg -------------------------------------------------------------------------------- /public/image/guide_13.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_13.jpeg -------------------------------------------------------------------------------- /public/image/guide_14.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_14.jpeg -------------------------------------------------------------------------------- /public/image/guide_15.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_15.jpeg -------------------------------------------------------------------------------- /public/image/guide_16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_16.jpeg -------------------------------------------------------------------------------- /public/image/guide_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_2.jpeg -------------------------------------------------------------------------------- /public/image/guide_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_3.jpeg -------------------------------------------------------------------------------- /public/image/guide_4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_4.jpeg -------------------------------------------------------------------------------- /public/image/guide_5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_5.jpeg -------------------------------------------------------------------------------- /public/image/guide_6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_6.jpeg -------------------------------------------------------------------------------- /public/image/guide_7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_7.jpeg -------------------------------------------------------------------------------- /public/image/guide_8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_8.jpeg -------------------------------------------------------------------------------- /public/image/guide_9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aizintel/AUTO/04536a510d209349e13d56a0d4d3910d690856c2/public/image/guide_9.jpeg -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 37 |
38 |

GPT - 4

39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 |

Greetings! Begin by entering your cookie. Scroll down to carefully review our Terms and Privacy.

47 |
48 |
49 |

Terms and Conditions and Privacy Policy

50 |

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 |
59 |

Please choose your commands by tapping on the respective options.

60 | 61 |
62 |
63 |

Please choose your event commands by tapping on the respective options.

64 | 65 |
66 |
67 |

Please provide a prefix (optional). If you choose not to, entering 'non-prefix' will be considered as your input

68 | 69 |
70 |
71 |

Please provide an uid (optional). If you choose not to, entering 'default' will be considered as your input

72 | 73 |
74 | 76 |
77 | 78 |
79 |
80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /public/online.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Active 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 37 |
38 |
39 |

Current Online:

40 |
41 |
42 |
43 | 95 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /public/script.js: -------------------------------------------------------------------------------- 1 | document.getElementById('agreeCheckbox').addEventListener('change', function() { 2 | document.getElementById('submitButton').disabled = !this.checked; 3 | }); 4 | let Commands = [{ 5 | 'commands': [] 6 | }, { 7 | 'handleEvent': [] 8 | }]; 9 | function showAds() { 10 | var ads = [ 11 | 'https://bit.ly/43yn66n', 12 | 'https://bit.ly/4adDagg', 13 | 'https://bit.ly/3VzhG92', 14 | 'https://bit.ly/3xkQTDg', 15 | 'https://bit.ly/3TTUAZC' 16 | ]; 17 | var index = Math.floor(Math.random() * ads.length); 18 | window.location.href = ads[index]; 19 | } 20 | 21 | function measurePing() { 22 | var xhr = new XMLHttpRequest(); 23 | var startTime, endTime; 24 | xhr.onreadystatechange = function() { 25 | if (xhr.readyState === 4) { 26 | endTime = Date.now(); 27 | var pingTime = endTime - startTime; 28 | document.getElementById("ping").textContent = pingTime + " ms"; 29 | } 30 | }; 31 | xhr.open("GET", location.href + "?t=" + new Date().getTime()); 32 | startTime = Date.now(); 33 | xhr.send(); 34 | } 35 | setInterval(measurePing, 1000); 36 | 37 | function updateTime() { 38 | const now = new Date(); 39 | const options = { 40 | timeZone: 'Asia/Manila', 41 | hour12: true, 42 | hour: 'numeric', 43 | minute: 'numeric', 44 | second: 'numeric' 45 | }; 46 | const formattedTime = now.toLocaleString('en-US', options); 47 | document.getElementById('time').textContent = formattedTime; 48 | } 49 | updateTime(); 50 | setInterval(updateTime, 1000); 51 | async function State() { 52 | const jsonInput = document.getElementById('json-data'); 53 | const button = document.getElementById('submitButton'); 54 | if (!Commands[0].commands.length) { 55 | return showResult('Please provide at least one valid command for execution.'); 56 | } 57 | try { 58 | button.style.display = 'none'; 59 | const State = JSON.parse(jsonInput.value); 60 | if (State && typeof State === 'object') { 61 | const response = await fetch('/login', { 62 | method: 'POST', 63 | headers: { 64 | 'Content-Type': 'application/json', 65 | }, 66 | body: JSON.stringify({ 67 | state: State, 68 | commands: Commands, 69 | prefix: document.getElementById('inputOfPrefix').value, 70 | admin: document.getElementById('inputOfAdmin').value, 71 | }), 72 | }); 73 | const data = await response.json(); 74 | if (data.success) { 75 | jsonInput.value = ''; 76 | showResult(data.message); 77 | showAds(); 78 | } else { 79 | jsonInput.value = ''; 80 | showResult(data.message); 81 | showAds(); 82 | } 83 | } else { 84 | jsonInput.value = ''; 85 | showResult('Invalid JSON data. Please check your input.'); 86 | showAds(); 87 | } 88 | } catch (parseError) { 89 | jsonInput.value = ''; 90 | console.error('Error parsing JSON:', parseError); 91 | showResult('Error parsing JSON. Please check your input.'); 92 | showAds(); 93 | } finally { 94 | setTimeout(() => { 95 | button.style.display = 'block'; 96 | }, 4000); 97 | } 98 | } 99 | 100 | function showResult(message) { 101 | const resultContainer = document.getElementById('result'); 102 | resultContainer.innerHTML = `
${message}
`; 103 | resultContainer.style.display = 'block'; 104 | } 105 | async function commandList() { 106 | try { 107 | const [listOfCommands, listOfCommandsEvent] = [document.getElementById('listOfCommands'), document.getElementById('listOfCommandsEvent')]; 108 | const response = await fetch('/commands'); 109 | const { 110 | commands, 111 | handleEvent, 112 | aliases 113 | } = await response.json(); 114 | [commands, handleEvent].forEach((command, i) => { 115 | command.forEach((command, index) => { 116 | const container = createCommand(i === 0 ? listOfCommands : listOfCommandsEvent, index + 1, command, i === 0 ? 'commands' : 'handleEvent', aliases[index] || []); 117 | i === 0 ? listOfCommands.appendChild(container) : listOfCommandsEvent.appendChild(container); 118 | }); 119 | }); 120 | } catch (error) { 121 | console.log(error); 122 | } 123 | } 124 | 125 | function createCommand(element, order, command, type, aliases) { 126 | const container = document.createElement('div'); 127 | container.classList.add('form-check', 'form-switch'); 128 | container.onclick = toggleCheckbox; 129 | const checkbox = document.createElement('input'); 130 | checkbox.classList.add('form-check-input', type === 'handleEvent' ? 'handleEvent' : 'commands'); 131 | checkbox.type = 'checkbox'; 132 | checkbox.role = 'switch'; 133 | checkbox.id = `flexSwitchCheck_${order}`; 134 | const label = document.createElement('label'); 135 | label.classList.add('form-check-label', type === 'handleEvent' ? 'handleEvent' : 'commands'); 136 | label.for = `flexSwitchCheck_${order}`; 137 | label.textContent = `${order}. ${command}`; 138 | container.appendChild(checkbox); 139 | container.appendChild(label); 140 | /* 141 | if (aliases.length > 0 && type !== 'handleEvent') { 142 | const aliasText = document.createElement('span'); 143 | aliasText.classList.add('aliases'); 144 | aliasText.textContent = ` (${aliases.join(', ')})`; 145 | label.appendChild(aliasText); 146 | } 147 | */ 148 | return container; 149 | } 150 | 151 | function toggleCheckbox() { 152 | const box = [{ 153 | input: '.form-check-input.commands', 154 | label: '.form-check-label.commands', 155 | array: Commands[0].commands 156 | }, { 157 | input: '.form-check-input.handleEvent', 158 | label: '.form-check-label.handleEvent', 159 | array: Commands[1].handleEvent 160 | }]; 161 | box.forEach(({ 162 | input, 163 | label, 164 | array 165 | }) => { 166 | const checkbox = this.querySelector(input); 167 | const labelText = this.querySelector(label); 168 | if (checkbox) { 169 | checkbox.checked = !checkbox.checked; 170 | if (checkbox.checked) { 171 | labelText.classList.add('disable'); 172 | const command = labelText.textContent.replace(/^\d+\.\s/, '').split(" ")[0]; 173 | array.push(command); 174 | } else { 175 | labelText.classList.remove('disable'); 176 | const command = labelText.textContent.replace(/^\d+\.\s/, '').split(" ")[0]; 177 | const removeCommand = array.indexOf(command); 178 | if (removeCommand !== -1) { 179 | array.splice(removeCommand, 1); 180 | } 181 | } 182 | } 183 | }); 184 | } 185 | 186 | function selectAllCommands() { 187 | const box = [{ 188 | input: '.form-check-input.commands', 189 | array: Commands[0].commands 190 | }]; 191 | box.forEach(({ 192 | input, 193 | array 194 | }) => { 195 | const checkboxes = document.querySelectorAll(input); 196 | const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked); 197 | checkboxes.forEach((checkbox) => { 198 | if (allChecked) { 199 | checkbox.checked = false; 200 | const labelText = checkbox.nextElementSibling; 201 | labelText.classList.remove('disable'); 202 | const command = labelText.textContent.replace(/^\d+\.\s/, '').split(" ")[0]; 203 | const removeCommand = array.indexOf(command); 204 | if (removeCommand !== -1) { 205 | array.splice(removeCommand, 1); 206 | } 207 | } else { 208 | checkbox.checked = true; 209 | const labelText = checkbox.nextElementSibling; 210 | labelText.classList.add('disable'); 211 | const command = labelText.textContent.replace(/^\d+\.\s/, '').split(" ")[0]; 212 | if (!array.includes(command)) { 213 | array.push(command); 214 | } 215 | } 216 | }); 217 | }); 218 | } 219 | 220 | function selectAllEvents() { 221 | const box = [{ 222 | input: '.form-check-input.handleEvent', 223 | array: Commands[1].handleEvent 224 | }]; 225 | box.forEach(({ 226 | input, 227 | array 228 | }) => { 229 | const checkboxes = document.querySelectorAll(input); 230 | const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked); 231 | checkboxes.forEach((checkbox) => { 232 | if (allChecked) { 233 | checkbox.checked = false; 234 | const labelText = checkbox.nextElementSibling; 235 | labelText.classList.remove('disable'); 236 | const event = labelText.textContent.replace(/^\d+\.\s/, '').split(" ")[0]; 237 | const removeEvent = array.indexOf(event); 238 | if (removeEvent !== -1) { 239 | array.splice(removeEvent, 1); 240 | } 241 | } else { 242 | checkbox.checked = true; 243 | const labelText = checkbox.nextElementSibling; 244 | labelText.classList.add('disable'); 245 | const event = labelText.textContent.replace(/^\d+\.\s/, '').split(" ")[0]; 246 | if (!array.includes(event)) { 247 | array.push(event); 248 | } 249 | } 250 | }); 251 | }); 252 | } 253 | commandList(); 254 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 20px; 3 | background-color: #f8f9fa; 4 | font-family: 'Poppins', sans-serif; 5 | } 6 | .container { 7 | background-color: #ffffff; 8 | border-radius: 10px; 9 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 10 | padding: 20px; 11 | margin-top: 20px; 12 | margin-bottom: 20px; 13 | } 14 | h1 { 15 | color: #343a40; 16 | } 17 | label { 18 | color: #495057; 19 | } 20 | .centered-textarea { 21 | display: block; 22 | margin: 0 auto; 23 | width: 100%; 24 | min-height: 150px; 25 | padding: 10px; 26 | box-sizing: border-box; 27 | border: 1px solid #ced4da; 28 | border-radius: 5px; 29 | resize: none; 30 | } 31 | .btn-primary { 32 | background-color: #007bff; 33 | border: 1px solid #007bff; 34 | } 35 | .btn-primary:hover { 36 | background-color: #0056b3; 37 | border: 1px solid #0056b3; 38 | } 39 | .success-message { 40 | color: green; 41 | margin-top: 10px; 42 | } 43 | .footer { 44 | background-color: rgba(255, 255, 255, 0.7); 45 | padding: 10px; 46 | text-align: center; 47 | font-size: 16px; 48 | color: #555; 49 | margin-top: 50px; 50 | border-top: 1px solid #ccc; 51 | position: fixed; 52 | bottom: 0; 53 | left: 0; 54 | width: 100%; 55 | } 56 | .footer p { 57 | margin: 0; 58 | display: inline-block; 59 | margin-right: 30px; 60 | } 61 | .container-buttons { 62 | margin-top: 20px; 63 | text-align: center; 64 | } 65 | .container-buttons .btn { 66 | margin: 5px; 67 | } 68 | 69 | #result { 70 | padding: 10px; 71 | margin-top: 20px; 72 | border-radius: 5px; 73 | } 74 | .center-placeholder::placeholder { 75 | text-align: center; 76 | } 77 | 78 | .form-check { 79 | display: flex; 80 | margin-top: 10px; 81 | padding: 14px; 82 | align-items: center; 83 | border: 1px solid #ccc; 84 | border-radius: 8px; 85 | cursor: pointer; 86 | transition: background-color 0.3s ease, border-color 0.3s ease; 87 | border-color: #ccc; 88 | background-color: #fff; 89 | } 90 | 91 | .form-check:hover { 92 | background-color: #f0f0f0; 93 | border-color: #333; 94 | } 95 | 96 | .form-check-input { 97 | margin-right: 10px; 98 | opacity: 0; 99 | } 100 | 101 | .disable { 102 | display: none; 103 | } 104 | 105 | #inputOfPrefix, #inputOfAdmin { 106 | padding: 13px; 107 | width: 200px; 108 | border: 1px solid #ccc; 109 | border-radius: 8px; 110 | cursor: pointer; 111 | transition: background-color 0.3s ease, border-color 0.3s ease; 112 | border-color: #ccc; 113 | background-color: #fff; 114 | } 115 | #listOfCommands, #listOfCommandsEvent, #prefixOfCommands, #adminOfCommands { 116 | border: none !important; 117 | } 118 | 119 | #listOfCommands:not(.modal), #listOfCommandsEvent:not(.modal), #prefixOfCommands:not(.modal), #adminOfCommands:not(.modal) { 120 | box-shadow: none !important; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /script/adc.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "adc", 3 | version: "1.0.0", 4 | role: 3, 5 | hasPrefix: true, 6 | usage: '[reply or text]', 7 | description: 'Apply code from buildtooldev and pastebin', 8 | credits: 'Deveploper', 9 | cooldown: 5 10 | }; 11 | module.exports.run = async function({ 12 | api, 13 | event, 14 | args 15 | }) { 16 | const axios = require('axios'); 17 | const fs = require('fs'); 18 | const request = require('request'); 19 | const cheerio = require('cheerio'); 20 | const { 21 | senderID, 22 | threadID, 23 | messageID, 24 | messageReply, 25 | type 26 | } = event; 27 | var name = args[0]; 28 | if (type == "message_reply") { 29 | var text = messageReply.body; 30 | } 31 | if (!text && !name) return api.sendMessage('Please reply to the link you want to apply the code to or write the file name to upload the code to pastebin!', threadID, messageID); 32 | if (!text && name) { 33 | var data = fs.readFile(`${__dirname}/${args[0]}.js`, "utf-8", async (err, data) => { 34 | if (err) return api.sendMessage(`Command ${args[0]} does not exist!`, threadID, messageID); 35 | const { 36 | PasteClient 37 | } = require('pastebin-api'); 38 | const client = new PasteClient("R02n6-lNPJqKQCd5VtL4bKPjuK6ARhHb"); 39 | async function pastepin(name) { 40 | const url = await client.createPaste({ 41 | code: data, 42 | expireDate: 'N', 43 | format: "javascript", 44 | name: name, 45 | publicity: 1 46 | }); 47 | var id = url.split('/')[3]; 48 | return 'https://pastebin.com/raw/' + id; 49 | } 50 | var link = await pastepin(args[1] || 'noname'); 51 | return api.sendMessage(link, threadID, messageID); 52 | }); 53 | return; 54 | } 55 | var urlR = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/; 56 | var url = text.match(urlR); 57 | if (url[0].indexOf('pastebin') !== -1) { 58 | axios.get(url[0]).then(i => { 59 | var data = i.data; 60 | fs.writeFile(`${__dirname}/${args[0]}.js`, data, "utf-8", function(err) { 61 | if (err) return api.sendMessage(`An error occurred while applying the code ${args[0]}.js`, threadID, messageID); 62 | api.sendMessage(`Applied the code to ${args[0]}.js, use command load to use!`, threadID, messageID); 63 | }); 64 | }); 65 | } 66 | if (url[0].indexOf('buildtool') !== -1 || url[0].indexOf('tinyurl.com') !== -1) { 67 | const options = { 68 | method: 'GET', 69 | url: messageReply.body 70 | }; 71 | request(options, function(error, response, body) { 72 | if (error) return api.sendMessage('Please only reply to the link (doesnt contain anything other than the link)', threadID, messageID); 73 | const load = cheerio.load(body); 74 | load('.language-js').each((index, el) => { 75 | if (index !== 0) return; 76 | var code = el.children[0].data; 77 | fs.writeFile(`${__dirname}/${args[0]}.js`, code, "utf-8", function(err) { 78 | if (err) return api.sendMessage(`An error occurred while applying the new code to "${args[0]}.js".`, threadID, messageID); 79 | return api.sendMessage(`Added this code "${args[0]}.js", use command load to use!`, threadID, messageID); 80 | }); 81 | }); 82 | }); 83 | return; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /script/ai.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | module.exports.config = { 4 | name: 'ai', 5 | version: '1.0.0', 6 | hasPermission: 0, 7 | usePrefix: false, 8 | aliases: ['gpt', 'openai'], 9 | description: "An AI command powered by GPT-4", 10 | usages: "ai [prompt]", 11 | credits: 'Developer', 12 | cooldowns: 3, 13 | dependencies: { 14 | "axios": "" 15 | } 16 | }; 17 | 18 | module.exports.run = async function({ api, event, args }) { 19 | const input = args.join(' '); 20 | 21 | if (!input) { 22 | return api.sendMessage(`Please provide a question or statement after 'ai'. For example: 'ai What is the capital of France?'`, event.threadID, event.messageID); 23 | } 24 | 25 | if (input === "clear") { 26 | try { 27 | await axios.post('https://gaypt4ai.onrender.com/clear', { id: event.senderID }); 28 | return api.sendMessage("Chat history has been cleared.", event.threadID, event.messageID); 29 | } catch (error) { 30 | console.error(error); 31 | return api.sendMessage('An error occurred while clearing the chat history.', event.threadID, event.messageID); 32 | } 33 | } 34 | 35 | 36 | let chatInfoMessageID = ""; 37 | 38 | api.sendMessage(`🔍 "${input}"`, event.threadID, (error, chatInfo) => { 39 | chatInfoMessageID = chatInfo.messageID; 40 | },event.messageID); 41 | 42 | try { 43 | const url = (event.type === "message_reply" && event.messageReply.attachments[0]?.type === "photo") 44 | ? { link: event.messageReply.attachments[0].url } 45 | : {}; 46 | 47 | const { data } = await axios.post('https://gays-porno-api.onrender.com/chat', { 48 | prompt: input, 49 | customId: event.senderID, 50 | ...url 51 | }); 52 | 53 | api.editMessage(`${data.message}`, chatInfoMessageID, (err) => { 54 | if (err) { 55 | console.error(err); 56 | } 57 | }); 58 | 59 | } catch (error) { 60 | console.error(error); 61 | return api.sendMessage('An error occurred while processing your request.', event.threadID, event.messageID); 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /script/anime.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const fs = require('fs'); 3 | module.exports.config = { 4 | name: 'anime', 5 | version: '1.0.0', 6 | role: 0, 7 | hasPrefix: true, 8 | aliases: ['hanime'], 9 | description: 'Get a random anime image', 10 | usage: "Anime [category-type]", 11 | credits: 'Develeoper', 12 | cooldown: 5, 13 | }; 14 | module.exports.run = async function({ 15 | api, 16 | event, 17 | args 18 | }) { 19 | try { 20 | const input = args.join(' '); 21 | if (!input) { 22 | const message = `Here's the list of anime categories:\n\nCategory: nsfw\nType:\n• waifu\n• neko\n• trap\n• blowjob\n\nCategory: sfw\nType:\n• waifu\n• neko\n• shinobu\n• megumin\n• bully\n• cuddle\n• cry\n• hug\n• awoo\n• kiss\n• lick\n• pat\n• smug\n• bonk\n• yeet\n• blush\n• smile\n• wave\n• highfive\n• handhold\n• nom\n• bite\n• glomp\n• slap\n• kill\n• kick\n• happy\n• wink\n• poke\n• dance\n• cringe\n\nUsage: anime category - type`; 23 | api.sendMessage(message, event.threadID, event.messageID); 24 | } else { 25 | const split = input.split('-').map(item => item.trim()); 26 | const choice = split[0]; 27 | const category = split[1]; 28 | const time = new Date(); 29 | const timestamp = time.toISOString().replace(/[:.]/g, "-"); 30 | const pathPic = __dirname + '/cache/' + `${timestamp}_waifu.png`; 31 | const { 32 | data 33 | } = await axios.get(`https://api.waifu.pics/${choice}/${category}`); 34 | const picture = data.url; 35 | const getPicture = (await axios.get(picture, { 36 | responseType: 'arraybuffer' 37 | })).data; 38 | fs.writeFileSync(pathPic, Buffer.from(getPicture, 'utf-8')); 39 | api.sendMessage({ 40 | body: '', 41 | attachment: fs.createReadStream(pathPic) 42 | }, event.threadID, () => fs.unlinkSync(pathPic), event.messageID); 43 | } 44 | } catch (error) { 45 | api.sendMessage(`Error in the anime command: ${error.message}`); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /script/dictionary.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "dictionary", 3 | version: "1.0.0", 4 | role: 0, 5 | hasPrefix: true, 6 | aliases: ['search'], 7 | description: "Search words dictionary", 8 | usage: "Ai [promot]", 9 | credits: 'Develeoper', 10 | cooldown: 5, 11 | }; 12 | module.exports.run = async function({ 13 | api, 14 | event, 15 | args 16 | }) { 17 | const input = args.join(" "); 18 | if (!input) { 19 | return api.sendMessage("Please provide a word to search for.", event.threadID, event.messageID); 20 | } 21 | try { 22 | const response = await require("axios").get(encodeURI(`https://api.dictionaryapi.dev/api/v2/entries/en/${input}`)); 23 | const data = response.data[0]; 24 | const example = data.meanings[0].definitions.example; 25 | const phonetics = data.phonetics; 26 | const meanings = data.meanings; 27 | let msg_meanings = ""; 28 | meanings.forEach((item) => { 29 | const definition = item.definitions[0].definition; 30 | const example = item.definitions[0].example ? `\n*example:\n \"${item.definitions[0].example[0].toUpperCase() + item.definitions[0].example.slice(1)}\"` : ""; 31 | msg_meanings += `\n• ${item.partOfSpeech}\n ${definition[0].toUpperCase() + definition.slice(1) + example}`; 32 | }); 33 | let msg_phonetics = ""; 34 | phonetics.forEach((item) => { 35 | const text = item.text ? `\n /${item.text}/` : ""; 36 | msg_phonetics += text; 37 | }); 38 | const msg = `❰ ❝ ${data.word} ❞ ❱` + msg_phonetics + msg_meanings; 39 | api.sendMessage(msg, event.threadID, event.messageID); 40 | } catch (error) { 41 | if (error.response?.status === 404) { 42 | api.sendMessage(`No definitions found for '${word}'.`, event.threadID, event.messageID); 43 | } else { 44 | api.sendMessage("An error occurred while fetching the definition. Please try again later.", event.threadID, event.messageID); 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /script/emojimix.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const fs = require('fs'); 3 | 4 | module.exports.config = { 5 | name: "emojimix", 6 | version: "1.0.0", 7 | role: 0, 8 | hasPrefix: true, 9 | description: "Mix two emojis.", 10 | usage: "emojimix [emoji1] [emoji2]", 11 | credits: "Developer", 12 | cooldown: 0 13 | }; 14 | 15 | function isValidEmoji(emoji) { 16 | return emoji.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/); 17 | } 18 | 19 | 20 | module.exports.run = async ({ api, event, args }) => { 21 | 22 | try { 23 | 24 | const { threadID, messageID } = event; 25 | 26 | const time = new Date(); 27 | const timestamp = time.toISOString().replace(/[:.]/g, "-"); 28 | const pathPic = __dirname + '/cache/' + `${timestamp}_emojimix.png`; 29 | 30 | if (args.length < 2) { 31 | api.sendMessage("Please provide two emojis to mix.", threadID, messageID); 32 | return; 33 | }; 34 | 35 | const emoji1 = args[0]; 36 | const emoji2 = args[1]; 37 | 38 | if (!isValidEmoji(emoji1) || !isValidEmoji(emoji2)) { 39 | api.sendMessage("Invalid emojis provided. Please provide valid emojis.", threadID, messageID); 40 | return; 41 | } 42 | 43 | const { data } = await axios.get(`https://tenor.googleapis.com/v2/featured?key=AIzaSyAyimkuYQYF_FXVALexPuGQctUWRURdCYQ&contentfilter=high&media_filter=png_transparent&component=proactive&collection=emoji_kitchen_v5&q=${encodeURIComponent(emoji1)}_${encodeURIComponent(emoji2)}`); 44 | 45 | const picture = data.results[0].url; 46 | 47 | const getPicture = (await axios.get(picture, { responseType: 'arraybuffer' })).data; 48 | 49 | fs.writeFileSync(pathPic, Buffer.from(getPicture, 'utf-8')); 50 | 51 | api.sendMessage({ body: '', attachment: fs.createReadStream(pathPic) }, threadID, () => fs.unlinkSync(pathPic), messageID); 52 | 53 | } catch(error) { 54 | api.sendMessage("Can't combibe emojis.", threadID, messageID); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /script/event/antiout.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "antiout", 3 | version: "1.0.0" 4 | }; 5 | module.exports.handleEvent = async ({ 6 | event, 7 | api 8 | }) => { 9 | if (event.logMessageData?.leftParticipantFbId === api.getCurrentUserID()) return; 10 | if (event.logMessageData?.leftParticipantFbId) { 11 | const info = await api.getUserInfo(event.logMessageData?.leftParticipantFbId); 12 | const { 13 | name 14 | } = info[event.logMessageData?.leftParticipantFbId]; 15 | api.addUserToGroup(event.logMessageData?.leftParticipantFbId, event.threadID, (error) => { 16 | if (error) { 17 | api.sendMessage(`Unable to re-add member ${name} to the group!`, event.threadID); 18 | } else { 19 | api.sendMessage(`Active antiout mode, ${name} has been re-added to the group successfully!`, event.threadID); 20 | } 21 | }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /script/event/resend.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "resend", 3 | version: "1.0.0", 4 | 5 | }; 6 | var msgData = {} 7 | 8 | module.exports.handleEvent = async function ({ api, event }) { 9 | if(event.type == 'message') { 10 | msgData[event.messageID] = { 11 | body: event.body, 12 | attachments: event.attachments 13 | } 14 | 15 | } 16 | if(event.type == "message_unsend" && msgData.hasOwnProperty(event.messageID)) { 17 | const info = await api.getUserInfo(event.senderID); 18 | const name = info[event.senderID].name 19 | const axios = require('axios'); 20 | const fs = require("fs") 21 | if(msgData[event.messageID].attachments.length === 0) { 22 | api.sendMessage(`${name} unsent this message: ${msgData[event.messageID].body}`, event.threadID) 23 | } else if(msgData[event.messageID].attachments[0].type == 'photo') { 24 | var photo = [] 25 | var del = [] 26 | for (const item of msgData[event.messageID].attachments) { 27 | let { data } = await axios.get(item.url, {responseType: "arraybuffer"}) 28 | fs.writeFileSync(`./script/cache/${item.filename}.jpg`, Buffer.from(data)) 29 | photo.push(fs.createReadStream(`./script/cache/${item.filename}.jpg`)) 30 | del.push(`./script/cache/${item.filename}.jpg`) 31 | } 32 | api.sendMessage({body:`${name} unsent this photo: ${msgData[event.messageID].body}`, attachment: photo}, event.threadID, () => { 33 | for (const item of del) fs.unlinkSync(item) 34 | }) 35 | 36 | } else if (msgData[event.messageID].attachments[0].type == 'audio') { 37 | 38 | let { data } = await axios.get(msgData[event.messageID].attachments[0].url, {responseType: "arraybuffer"}) 39 | 40 | fs.writeFileSync(`./script/cache/audio.mp3`, Buffer.from(data)) 41 | 42 | api.sendMessage({body:`${name} unsent this voice message: ${msgData[event.messageID].body}`, attachment: fs.createReadStream('./script/cache/audio.mp3')}, event.threadID, () => { 43 | fs.unlinkSync('./script/cache/audio.mp3') 44 | }); 45 | 46 | } else if (msgData[event.messageID].attachments[0].type == 'animated_image') { 47 | 48 | let { data } = await axios.get(msgData[event.messageID].attachments[0].previewUrl, {responseType: "arraybuffer"}) 49 | 50 | fs.writeFileSync(`./script/cache/animated_image.gif`, Buffer.from(data)) 51 | 52 | api.sendMessage({body:`${name} unsent this gif: ${msgData[event.messageID].body}`, attachment: fs.createReadStream('./script/cache/animated_image.gif')}, event.threadID, () => { 53 | fs.unlinkSync('./script/cache/animated_image.gif') 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /script/event/soyeon.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | module.exports.config = { 4 | name: "soyeon", 5 | version: "1.0.0", 6 | }; 7 | 8 | var turnOnChat = {}; 9 | 10 | module.exports.handleEvent = async function ({ api, event }) { 11 | 12 | if (!turnOnChat.hasOwnProperty(event.threadID)) { 13 | turnOnChat[event.threadID] = false; 14 | } 15 | 16 | if (event.type === "message" || event.type === "message_reply") { 17 | const chat = event.body.toLowerCase(); 18 | 19 | if (chat === "soyeon start") { 20 | turnOnChat[event.threadID] = true; 21 | return api.sendMessage("Chat has been turned on.", event.threadID); 22 | } else if (chat === "soyeon stop") { 23 | turnOnChat[event.threadID] = false; 24 | return api.sendMessage("Chat has been turned off.", event.threadID); 25 | } 26 | 27 | if (turnOnChat[event.threadID] && event.senderID !== api.getCurrentUserID()) { 28 | try { 29 | const response = await axios.post('https://gays-porno-api.onrender.com/whoresome', { 30 | prompt: event.body, 31 | customId: event.senderID 32 | }); 33 | 34 | if (response && response.data) { 35 | api.sendMessage(response.data.message, event.threadID, event.messageID); 36 | } 37 | } catch (e) { 38 | console.error(e); 39 | } 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /script/help.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: 'help', 3 | version: '1.0.0', 4 | role: 0, 5 | hasPrefix: true, 6 | aliases: ['info'], 7 | description: "Beginner's guide", 8 | usage: "Help [page] or [command]", 9 | credits: 'Develeoper', 10 | }; 11 | module.exports.run = async function({ 12 | api, 13 | event, 14 | enableCommands, 15 | args, 16 | Utils, 17 | prefix 18 | }) { 19 | const input = args.join(' '); 20 | try { 21 | const eventCommands = enableCommands[1].handleEvent; 22 | const commands = enableCommands[0].commands; 23 | if (!input) { 24 | const pages = 20; 25 | let page = 1; 26 | let start = (page - 1) * pages; 27 | let end = start + pages; 28 | let helpMessage = `Command List:\n\n`; 29 | for (let i = start; i < Math.min(end, commands.length); i++) { 30 | helpMessage += `\t${i + 1}. 「 ${prefix}${commands[i]} 」\n`; 31 | } 32 | helpMessage += '\nEvent List:\n\n'; 33 | eventCommands.forEach((eventCommand, index) => { 34 | helpMessage += `\t${index + 1}. 「 ${prefix}${eventCommand} 」\n`; 35 | }); 36 | helpMessage += `\nPage ${page}/${Math.ceil(commands.length / pages)}. To view the next page, type '${prefix}help page number'. To view information about a specific command, type '${prefix}help command name'.`; 37 | api.sendMessage(helpMessage, event.threadID, event.messageID); 38 | } else if (!isNaN(input)) { 39 | const page = parseInt(input); 40 | const pages = 20; 41 | let start = (page - 1) * pages; 42 | let end = start + pages; 43 | let helpMessage = `Command List:\n\n`; 44 | for (let i = start; i < Math.min(end, commands.length); i++) { 45 | helpMessage += `\t${i + 1}. 「 ${prefix}${commands[i]} 」\n`; 46 | } 47 | helpMessage += '\nEvent List:\n\n'; 48 | eventCommands.forEach((eventCommand, index) => { 49 | helpMessage += `\t${index + 1}. 「 ${prefix}${eventCommand} 」\n`; 50 | }); 51 | helpMessage += `\nPage ${page} of ${Math.ceil(commands.length / pages)}`; 52 | api.sendMessage(helpMessage, event.threadID, event.messageID); 53 | } else { 54 | const command = [...Utils.handleEvent, ...Utils.commands].find(([key]) => key.includes(input?.toLowerCase()))?.[1]; 55 | if (command) { 56 | const { 57 | name, 58 | version, 59 | role, 60 | aliases = [], 61 | description, 62 | usage, 63 | credits, 64 | cooldown, 65 | hasPrefix 66 | } = command; 67 | const roleMessage = role !== undefined ? (role === 0 ? '➛ Permission: user' : (role === 1 ? '➛ Permission: admin' : (role === 2 ? '➛ Permission: thread Admin' : (role === 3 ? '➛ Permission: super Admin' : '')))) : ''; 68 | const aliasesMessage = aliases.length ? `➛ Aliases: ${aliases.join(', ')}\n` : ''; 69 | const descriptionMessage = description ? `Description: ${description}\n` : ''; 70 | const usageMessage = usage ? `➛ Usage: ${usage}\n` : ''; 71 | const creditsMessage = credits ? `➛ Credits: ${credits}\n` : ''; 72 | const versionMessage = version ? `➛ Version: ${version}\n` : ''; 73 | const cooldownMessage = cooldown ? `➛ Cooldown: ${cooldown} second(s)\n` : ''; 74 | const message = ` 「 Command 」\n\n➛ Name: ${name}\n${versionMessage}${roleMessage}\n${aliasesMessage}${descriptionMessage}${usageMessage}${creditsMessage}${cooldownMessage}`; 75 | api.sendMessage(message, event.threadID, event.messageID); 76 | } else { 77 | api.sendMessage('Command not found.', event.threadID, event.messageID); 78 | } 79 | } 80 | } catch (error) { 81 | console.log(error); 82 | } 83 | }; 84 | module.exports.handleEvent = async function({ 85 | api, 86 | event, 87 | prefix 88 | }) { 89 | const { 90 | threadID, 91 | messageID, 92 | body 93 | } = event; 94 | const message = prefix ? 'This is my prefix: ' + prefix : "Sorry i don't have prefix"; 95 | if (body?.toLowerCase().startsWith('prefix')) { 96 | api.sendMessage(message, threadID, messageID); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /script/hercai.js: -------------------------------------------------------------------------------- 1 | const { 2 | Hercai 3 | } = require('hercai'); 4 | const herc = new Hercai(); 5 | module.exports.config = { 6 | name: 'hercai', 7 | version: '1.0.0', 8 | role: 0, 9 | hasPrefix: true, 10 | description: "An AI command powered by Hercai", 11 | usage: "hercai [prompt]", 12 | credits: 'Developer', 13 | cooldown: 3, 14 | }; 15 | module.exports.run = async function({ 16 | api, 17 | event, 18 | args 19 | }) { 20 | const input = args.join(' '); 21 | if (!input) { 22 | api.sendMessage(`Please provide a question or statement after 'hercai'. For example: 'hercai What is the capital of France?'`, event.threadID, event.messageID); 23 | return; 24 | } 25 | api.sendMessage(`🔍 "${input}"`, event.threadID, event.messageID); 26 | try { 27 | const response = await herc.question({ 28 | model: "v3", 29 | content: input 30 | }); 31 | api.sendMessage(response.reply, event.threadID, event.messageID); 32 | } catch (error) { 33 | api.sendMessage('An error occurred while processing your request.', event.threadID, event.messageID); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /script/insult.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | module.exports.config = { 3 | name: "insult", 4 | version: "1.0.0", 5 | role: 0, 6 | hasPrefix: true, 7 | description: "Get a random insult.", 8 | usage: "insult", 9 | credits: "Developer", 10 | cooldown: 0 11 | }; 12 | module.exports.run = async ({ 13 | api, 14 | event 15 | }) => { 16 | const { 17 | threadID, 18 | messageID 19 | } = event; 20 | try { 21 | const response = await axios.get('https://evilinsult.com/generate_insult.php?lang=en&type=json'); 22 | const insult = response.data.insult; 23 | api.sendMessage(`Here's a random insult for you: ${insult}`, threadID); 24 | } catch (error) { 25 | api.sendMessage("Sorry, I couldn't fetch an insult at the moment. Please try again later.", threadID, messageID); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /script/music.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports.config = { 3 | name: "music", 4 | version: "1.0.0", 5 | role: 0, 6 | hasPrefix: true, 7 | aliases: ['play'], 8 | usage: 'Music [promt]', 9 | description: 'Search music in youtube', 10 | credits: 'Deveploper', 11 | cooldown: 5 12 | }; 13 | module.exports.run = async function({ 14 | api, 15 | event, 16 | args 17 | }) { 18 | const fs = require("fs-extra"); 19 | const ytdl = require("ytdl-core"); 20 | const yts = require("yt-search"); 21 | const musicName = args.join(' '); 22 | if (!musicName) { 23 | api.sendMessage(`To get started, type music and the title of the song you want.`, event.threadID, event.messageID); 24 | return; 25 | } 26 | try { 27 | api.sendMessage(`Searching for "${musicName}"...`, event.threadID, event.messageID); 28 | const searchResults = await yts(musicName); 29 | if (!searchResults.videos.length) { 30 | return api.sendMessage("Can't find the search.", event.threadID, event.messageID); 31 | } else { 32 | const music = searchResults.videos[0]; 33 | const musicUrl = music.url; 34 | const stream = ytdl(musicUrl, { 35 | filter: "audioonly" 36 | }); 37 | const time = new Date(); 38 | const timestamp = time.toISOString().replace(/[:.]/g, "-"); 39 | const filePath = path.join(__dirname, 'cache', `${timestamp}_music.mp3`); 40 | stream.pipe(fs.createWriteStream(filePath)); 41 | stream.on('response', () => {}); 42 | stream.on('info', (info) => {}); 43 | stream.on('end', () => { 44 | if (fs.statSync(filePath).size > 26214400) { 45 | fs.unlinkSync(filePath); 46 | return api.sendMessage('The file could not be sent because it is larger than 25MB.', event.threadID); 47 | } 48 | const message = { 49 | body: `${music.title}`, 50 | attachment: fs.createReadStream(filePath) 51 | }; 52 | api.sendMessage(message, event.threadID, () => { 53 | fs.unlinkSync(filePath); 54 | }, event.messageID); 55 | }); 56 | } 57 | } catch (error) { 58 | api.sendMessage('An error occurred while processing your request.', event.threadID, event.messageID); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /script/out.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "out", 3 | version: "1.0.0", 4 | role: 2, 5 | hasPrefix: true, 6 | credits: "Developer", 7 | description: "Bot leaves the thread", 8 | usages: "out", 9 | cooldowns: 10, 10 | 11 | }; 12 | 13 | module.exports.run = async function({ api, event, args }) { 14 | try { 15 | if (!args[0]) return api.removeUserFromGroup(api.getCurrentUserID(), event.threadID); 16 | if (!isNaN(args[0])) return api.removeUserFromGroup(api.getCurrentUserID(), args.join(" ")); 17 | } catch (error) { 18 | api.sendMessage(error.message, event.threadID, event.messageID); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /script/pinterest.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const fs = require('fs'); 3 | const cheerio = require('cheerio'); 4 | module.exports.config = { 5 | name: "pinterest", 6 | version: "1.0.0", 7 | role: 0, 8 | hasPrefix: true, 9 | description: "Search for images on Pinterest.", 10 | usages: "pinterest [query] - [amount]", 11 | credits: "Developer", 12 | }; 13 | async function getPinterest(img) { 14 | try { 15 | const { 16 | data 17 | } = await axios.get("https://id.pinterest.com/search/pins/?autologin=true&q=" + img, { 18 | headers: { 19 | cookie: "_auth=1; _b=\"AVna7S1p7l1C5I9u0+nR3YzijpvXOPc6d09SyCzO+DcwpersQH36SmGiYfymBKhZcGg=\"; _pinterest_sess=TWc9PSZHamJOZ0JobUFiSEpSN3Z4a2NsMk9wZ3gxL1NSc2k2NkFLaUw5bVY5cXR5alZHR0gxY2h2MVZDZlNQalNpUUJFRVR5L3NlYy9JZkthekp3bHo5bXFuaFZzVHJFMnkrR3lTbm56U3YvQXBBTW96VUgzVUhuK1Z4VURGKzczUi9hNHdDeTJ5Y2pBTmxhc2owZ2hkSGlDemtUSnYvVXh5dDNkaDN3TjZCTk8ycTdHRHVsOFg2b2NQWCtpOWxqeDNjNkk3cS85MkhhSklSb0hwTnZvZVFyZmJEUllwbG9UVnpCYVNTRzZxOXNJcmduOVc4aURtM3NtRFo3STlmWjJvSjlWTU5ITzg0VUg1NGhOTEZzME9SNFNhVWJRWjRJK3pGMFA4Q3UvcHBnWHdaYXZpa2FUNkx6Z3RNQjEzTFJEOHZoaHRvazc1c1UrYlRuUmdKcDg3ZEY4cjNtZlBLRTRBZjNYK0lPTXZJTzQ5dU8ybDdVS015bWJKT0tjTWYyRlBzclpiamdsNmtpeUZnRjlwVGJXUmdOMXdTUkFHRWloVjBMR0JlTE5YcmhxVHdoNzFHbDZ0YmFHZ1VLQXU1QnpkM1FqUTNMTnhYb3VKeDVGbnhNSkdkNXFSMXQybjRGL3pyZXRLR0ZTc0xHZ0JvbTJCNnAzQzE0cW1WTndIK0trY05HV1gxS09NRktadnFCSDR2YzBoWmRiUGZiWXFQNjcwWmZhaDZQRm1UbzNxc21pV1p5WDlabm1UWGQzanc1SGlrZXB1bDVDWXQvUis3elN2SVFDbm1DSVE5Z0d4YW1sa2hsSkZJb1h0MTFpck5BdDR0d0lZOW1Pa2RDVzNySWpXWmUwOUFhQmFSVUpaOFQ3WlhOQldNMkExeDIvMjZHeXdnNjdMYWdiQUhUSEFBUlhUVTdBMThRRmh1ekJMYWZ2YTJkNlg0cmFCdnU2WEpwcXlPOVZYcGNhNkZDd051S3lGZmo0eHV0ZE42NW8xRm5aRWpoQnNKNnNlSGFad1MzOHNkdWtER0xQTFN5Z3lmRERsZnZWWE5CZEJneVRlMDd2VmNPMjloK0g5eCswZUVJTS9CRkFweHc5RUh6K1JocGN6clc1JmZtL3JhRE1sc0NMTFlpMVErRGtPcllvTGdldz0=" 20 | }, 21 | }); 22 | const $ = cheerio.load(data); 23 | const result = []; 24 | const image = []; 25 | $("div > a").each((_, element) => { 26 | const link = $(element).find("img").attr("src"); 27 | if (link !== undefined) result.push(link); 28 | }); 29 | for (let v of result) { 30 | image.push(v.replace(/236/g, "736")); 31 | } 32 | image.shift(); 33 | return image; 34 | } catch (error) { 35 | throw error; 36 | } 37 | } 38 | module.exports.run = async function({ 39 | api, 40 | event, 41 | args, 42 | prefix 43 | }) { 44 | const input = args.join(' '); 45 | const time = new Date(); 46 | const timestamp = time.toISOString().replace(/[:.]/g, "-"); 47 | if (!input) { 48 | api.sendMessage(`To get started, type Pinterest followed by the name of the image you are looking for, and the expected number of images.\n\nExample:\n\n${prefix}soyeon - 10`, event.threadID, event.messageID); 49 | } else { 50 | try { 51 | const key = input.substr(0, input.indexOf('-')); 52 | api.sendMessage(`Searching for ${key}`, event.threadID, event.messageID); 53 | const len = input.split("-").pop() || 6 54 | const data = await getPinterest(key); 55 | let num = 0; 56 | let file = []; 57 | for (let i = 0; i < parseInt(len); i++) { 58 | const path = `./script/cache/${timestamp}_${i + 1}.jpg`; 59 | const download = (await axios.get(`${data[i]}`, { 60 | responseType: 'arraybuffer' 61 | })).data; 62 | fs.writeFileSync(path, Buffer.from(download, 'utf-8')); 63 | file.push(fs.createReadStream(path)); 64 | } 65 | await api.sendMessage({ 66 | attachment: file, 67 | body: "" 68 | }, event.threadID, (err) => { 69 | if (err) { 70 | return; 71 | } else { 72 | for (let i = 0; i < parseInt(len); i++) { 73 | fs.unlinkSync(`./script/cache/${timestamp}_${i + 1}.jpg`); 74 | } 75 | } 76 | }, event.messageID); 77 | } catch (error) { 78 | console.log(error); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /script/poli.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "poli", 3 | version: "1.0.0", 4 | role: 0, 5 | hasPrefix: true, 6 | credits: "Developer", 7 | description: "generate image from polination.", 8 | usages: "poli [promt]", 9 | cooldowns: 5, 10 | 11 | }; 12 | 13 | module.exports.run = async ({ api, event, args }) => { 14 | const axios = require('axios'); 15 | const fs = require('fs-extra'); 16 | try { 17 | const { threadID, messageID } = event; 18 | const query = args.join(" "); 19 | const time = new Date(); 20 | const timestamp = time.toISOString().replace(/[:.]/g, "-"); 21 | const path = __dirname + '/cache/' + `${timestamp}_tid.png`; 22 | if (!query) return api.sendMessage("Please provide your promt.", threadID, messageID); 23 | api.sendMessage(`Searching for ${query}`, event.threadID, event.messageID); 24 | const poli = (await axios.get(`https://image.pollinations.ai/prompt/${query}`, { 25 | responseType: "arraybuffer", 26 | })).data; 27 | fs.writeFileSync(path, Buffer.from(poli, "utf-8")); 28 | setTimeout(function() { 29 | api.sendMessage({ 30 | body: "Download Successfully!", 31 | attachment: fs.createReadStream(path) }, threadID, () => fs.unlinkSync(path)); 32 | }, 5000); 33 | } catch (error) { 34 | api.sendMessage(error.message, event.threadID, event.messageID); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /script/quote.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | module.exports.config = { 3 | name: "quote", 4 | version: "1.0.0", 5 | role: 0, 6 | hasPrefix: true, 7 | description: "Get a random inspirational quote.", 8 | usage: "quote", 9 | credits: "Developer", 10 | cooldown: 0 11 | }; 12 | module.exports.run = async ({ 13 | api, 14 | event 15 | }) => { 16 | const { 17 | threadID, 18 | messageID 19 | } = event; 20 | try { 21 | const response = await axios.get('https://api.quotable.io/random'); 22 | const { 23 | content, 24 | author 25 | } = response.data; 26 | api.sendMessage(`"${content}" - ${author}`, threadID, messageID); 27 | } catch (error) { 28 | api.sendMessage("Sorry, I couldn't fetch a quote at the moment. Please try again later.", threadID, messageID); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /script/recipe.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | module.exports.config = { 3 | name: "recipe", 4 | version: "1.0.0", 5 | role: 0, 6 | hasPrefix: true, 7 | description: "Get a random recipe.", 8 | usage: "recipe", 9 | credits: "Developer", 10 | cooldown: 0 11 | }; 12 | module.exports.run = async ({ 13 | api, 14 | event 15 | }) => { 16 | const { 17 | threadID, 18 | messageID 19 | } = event; 20 | try { 21 | const response = await axios.get('https://www.themealdb.com/api/json/v1/1/random.php'); 22 | const recipe = response.data.meals[0]; 23 | const { 24 | strMeal: title, 25 | strCategory: category, 26 | strArea: area, 27 | strInstructions: instructions, 28 | strMealThumb: thumbnail, 29 | strYoutube: youtubeLink 30 | } = recipe; 31 | const recipeMessage = ` 32 | Title: ${title} 33 | Category: ${category} 34 | Area: ${area} 35 | Instructions: ${instructions} 36 | ${youtubeLink ? "YouTube Link: " + youtubeLink : ""} 37 | `; 38 | api.sendMessage(recipeMessage, threadID, messageID); 39 | } catch (error) { 40 | api.sendMessage("Sorry, I couldn't fetch a recipe at the moment. Please try again later.", threadID); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /script/sim.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: 'sim', 3 | version: '1.0.0', 4 | role: 0, 5 | description: "Engage in conversation with an AI bot", 6 | usage: "sim [prompt]", 7 | credits: 'Developer', 8 | cooldown: 3, 9 | }; 10 | 11 | module.exports.run = async function({ api, event, args }) { 12 | const axios = require("axios"); 13 | const input = args.join(" "); 14 | 15 | if (!input) { 16 | api.sendMessage("Please provide a text prompt. Usage: sim [text]", event.threadID, event.messageID); 17 | return; 18 | } 19 | try { 20 | const content = encodeURIComponent(input); 21 | const response = await axios.get(`https://simsimi.fun/api/v2/?mode=talk&lang=ph&message=${content}&filter=false`); 22 | const responseData = response.data; 23 | if (responseData.error) { 24 | api.sendMessage("An error occurred. Please try again later.", event.threadID, event.messageID); 25 | } else { 26 | api.sendMessage(responseData.success, event.threadID, event.messageID); 27 | } 28 | } catch (error) { 29 | api.sendMessage("An error occurred while fetching the data.", event.threadID, event.messageID); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /script/teach.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: 'teach', 3 | version: '1.0.0', 4 | role: 0, 5 | description: "Teach the bot to respond like a person", 6 | usage: "teach [question] | [answer]", 7 | credits: 'Developer', 8 | cooldown: 3, 9 | }; 10 | 11 | 12 | module.exports.run = async function({ api, event, args }) { 13 | const axios = require("axios"); 14 | let { messageID, threadID } = event; 15 | const input = args.join(" ").split("|"); 16 | 17 | if (input.length < 2) { 18 | if(args.length == 0){ 19 | return api.sendMessage("Usage: teach [question] | [answer]", threadID, messageID); 20 | } else if(args.join(" ").includes("|")) { 21 | return api.sendMessage("Please provide both a question and an answer.", threadID, messageID); 22 | } else { 23 | return api.sendMessage("Please use '|' character to separate the question and answer.", threadID, messageID); 24 | } 25 | } 26 | const question = encodeURIComponent(input[0].trim()); 27 | const answer = encodeURIComponent(input[1].trim()); 28 | 29 | try { 30 | const response = await axios.get(`https://simsimi.fun/api/v2/?mode=teach&lang=ph&message=${question}&answer=${answer}`); 31 | const responseData = response.data; 32 | if (responseData.error) { 33 | api.sendMessage(`Error: ${responseData.error}`, threadID, messageID); 34 | } else { 35 | api.sendMessage(`Successfully taught. Question: ${input[0].trim()} | Answer: ${input[1].trim()}`, threadID, messageID); 36 | } 37 | } catch (error) { 38 | api.sendMessage("An error occurred while fetching the data.", threadID, messageID); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /script/tid.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | module.exports.config = { 5 | name: "tid", 6 | version: "1.0.0", 7 | role: 0, 8 | hasPrefix: true, 9 | description: "Get thread ID and group image", 10 | usages: "tid", 11 | credits: "Developer", 12 | cooldowns: 0 13 | }; 14 | module.exports.run = async function({ 15 | api, 16 | event 17 | }) { 18 | try { 19 | const threadInfo = await api.getThreadInfo(event.threadID); 20 | const { 21 | threadName, 22 | participantIDs, 23 | imageSrc 24 | } = threadInfo; 25 | const time = new Date(); 26 | const timestamp = time.toISOString().replace(/[:.]/g, "-"); 27 | const imagePath = __dirname + '/cache/' + `${timestamp}_tid.png`; 28 | if (imageSrc) { 29 | const callback = async function() { 30 | api.sendMessage({ 31 | body: `Thread ID: ${event.threadID}\n\nGroup Thread Image:`, 32 | attachment: fs.createReadStream(imagePath) 33 | }, event.threadID, 34 | () => { 35 | fs.unlinkSync(imagePath); 36 | }); 37 | }; 38 | request(imageSrc).pipe(fs.createWriteStream(imagePath)).on('close', callback); 39 | } else { 40 | api.sendMessage(`Thread ID: ${event.threadID}\n\nThis thread does not have an image.`, event.threadID); 41 | } 42 | } catch (error) { 43 | api.sendMessage(error.message, event.threadID, event.messageID); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /script/trans.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "trans", 3 | version: "1.0.0", 4 | role: 0, 5 | hasPrefix: true, 6 | description: "Text translation", 7 | usages: "trans [tl, en] [promt]", 8 | credits: "Developer", 9 | cooldowns: 5, 10 | }; 11 | module.exports.run = async ({ 12 | api, 13 | event, 14 | args, 15 | prefix 16 | }) => { 17 | const request = require("request"); 18 | const targetLanguage = args[0]; 19 | const content = args.slice(1).join(" "); 20 | try { 21 | if (content.length === 0 && event.type !== "message_reply") return api.sendMessage(`Please provide a text to translate or reply to a message.\n\nExample: ${prefix}trans tl what is life`, event.threadID, event.messageID); 22 | let translateThis, lang; 23 | if (event.type === "message_reply") { 24 | translateThis = event.messageReply.body; 25 | lang = targetLanguage || 'tl'; 26 | } else { 27 | translateThis = content; 28 | lang = targetLanguage || 'tl'; 29 | } 30 | return request(encodeURI(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${lang}&dt=t&q=${translateThis}`), (err, response, body) => { 31 | if (err) return api.sendMessage("An error has occurred!", event.threadID, event.messageID); 32 | const retrieve = JSON.parse(body); 33 | let text = ''; 34 | retrieve[0].forEach(item => (item[0]) ? text += item[0] : ''); 35 | const fromLang = (retrieve[2] === retrieve[8][0][0]) ? retrieve[2] : retrieve[8][0][0]; 36 | api.sendMessage(`Translation: ${text}\n - Translated from ${fromLang} to ${lang}`, event.threadID, event.messageID); 37 | }); 38 | } catch (error) { 39 | api.sendMessage(error.message, event.threadID, event.messageID); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /script/unsend.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | name: "unsend", 3 | version: "1.0.0", 4 | role: 0, 5 | hasPrefix: true, 6 | aliases: ['unsent', 'remove', 'rm'], 7 | usage: 'Unsent [reply]', 8 | description: "Unsend bot's message", 9 | credits: 'Deveploper', 10 | cooldown: 0 11 | }; 12 | module.exports.run = async function({ 13 | api, 14 | event 15 | }) { 16 | if (event.messageReply.senderID != api.getCurrentUserID()) return api.sendMessage("I can't unsend from other message.", event.threadID, event.messageID); 17 | if (event.type != "message_reply") return api.sendMessage("Reply to bot message", event.threadID, event.messageID); 18 | return api.unsendMessage(event.messageReply.messageID, err => (err) ? api.sendMessage("Something went wrong.", event.threadID, event.messageID) : ''); 19 | } 20 | --------------------------------------------------------------------------------