UNPKG

meta-horizonn-fca-huh

Version:

Facebook-Chat-API Protect and Deploy by Kanzu and HZI Team. Kem is redeveloped. Rename package is Meta Horizonn and package supported ChatBot Messenger. and make it lightweight package by jonell huh

2,192 lines 107 kB
// /* eslint-disable no-redeclare */
// "use strict";
// const utils = require("../utils");
// const log = require("npmlog");
// const mqtt = require('mqtt');
// const Websocket = require('ws');
// const HttpsProxyAgent = require('https-proxy-agent');
// const EventEmitter = require('events');
// const Duplexify = require('duplexify');
// const Transform = require('readable-stream').Transform;
// var identity = function () { };
// var form = {};
// var getSeqID = function () { };


// var topics = ["/ls_req","/ls_resp","/legacy_web","/webrtc","/rtc_multi","/onevc","/br_sr","/sr_res","/t_ms","/thread_typing","/orca_typing_notifications","/notify_disconnect","/orca_presence","/inbox","/mercury", "/messaging_events", "/orca_message_notifications", "/pp","/webrtc_response"];

// /* [ Noti ? ]
// !   "/br_sr", //Notification
//     * => Need to publish /br_sr right after this
   
// !   "/notify_disconnect",
//     * => Need to publish /messenger_sync_create_queue right after this

// !   "/orca_presence",
//     * => Will receive /sr_res right here.
//   */

// var WebSocket_Global;

// function buildProxy() {
//     var Proxy = new Transform({
//         objectMode: false
//     });

//     Proxy._write = function socketWriteNode(chunk, enc, next) {
//         if (WebSocket_Global.readyState !== WebSocket_Global.OPEN) {
//             return next();
//         }
    
//         if (typeof chunk === 'string') {
//             chunk = new Buffer.from(chunk, 'utf8');
//         }
//         WebSocket_Global.send(chunk, next);
//     };

//     Proxy._flush = function(done) {
//         WebSocket_Global.close();
//         done();
//     };

//     Proxy._writev = function(chunks, cb) {
//         var buffers = new Array(chunks.length);
//         for (var i = 0; i < chunks.length; i++) {
//             if (typeof chunks[i].chunk === 'string') {
//                 buffers[i] = new Buffer.from(chunks[i], 'utf8');
//             } else {
//                 buffers[i] = chunks[i].chunk;
//             }
//         }
//         this._write(new Buffer.concat(buffers), 'binary', cb);
//     };

//     return Proxy;
// }

// function buildStream(options, WebSocket, Proxy) {
//     const Stream = Duplexify(undefined, undefined, options);
//     Stream.socket = WebSocket;
    
//     WebSocket 
//         .onclose = function() {
//             Stream.end();
//             Stream.destroy();
//         };
//     WebSocket
//         .onerror = function(err) {
//             Stream.destroy(err);
//         };
//     WebSocket
//         .onmessage = function(event) {
//             var data = event.data;
//             if (data instanceof ArrayBuffer) data = new Buffer.from(data);
//             else data = new Buffer.from(data, 'utf8');
//             Stream.push(data);
//         };
//     WebSocket
//         .onopen = function() {
//             Stream.setReadable(Proxy);
//             Stream.setWritable(Proxy);
//             Stream.emit('connect');
//         };
//     WebSocket_Global = WebSocket;
//     Proxy.on('close', function() { WebSocket.close(); });
//     return Stream;
// }


// function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
//     //Don't really know what this does but I think it's for the act`ive state?
//     //TODO: Move to ctx when implemented
//     var chatOn = ctx.globalOptions.online;
//     var foreground = false;

//     var sessionID = Math.floor((Math.random() * Number.MAX_SAFE_INTEGER)+1);
//     var username = {u: ctx.userID,s: sessionID,chat_on: chatOn,fg: foreground,d: utils.getGUID(),ct: "websocket",aid: "219994525426954", mqtt_sid: "",cp: 3,ecp: 10,st: [],pm: [],dc: "",no_auto_fg: true,gas: null,pack: []};
//     var cookies = ctx.jar.getCookies('https://www.facebook.com').join("; ");

//     var host;
//     if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}`;
//     else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLocaleLowerCase()}&sid=${sessionID}`;
//     else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}`;

//     var options = { 
//         clientId: "mqttwsclient",
//         protocolId: 'MQIsdp',
//         protocolVersion: 3,
//         username: JSON.stringify(username), 
//         clean: true,
//         wsOptions: {
//             headers: {
//                 'Cookie': cookies,
//                 'Origin': 'https://www.facebook.com',
//                 'User-Agent': (ctx.globalOptions.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36'),
//                 'Referer': 'https://www.facebook.com/',
//                 'Host': new URL(host).hostname //'edge-chat.facebook.com'
//             },
//             origin: 'https://www.facebook.com',
//             protocolVersion: 13,
//             binaryType: 'arraybuffer',
//         },
//         keepalive: 60,
//         reschedulePings: true,
//         reconnectPeriod: 3
//     };

//     if (typeof ctx.globalOptions.proxy != "undefined") {
//         var agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
//         options.wsOptions.agent = agent;
//     }
//     ctx.mqttClient = new mqtt.Client(_ => buildStream(options, new Websocket(host, options.wsOptions), buildProxy()), options);
//     global.mqttClient = ctx.mqttClient;

//     global.mqttClient.on('error', function (err) {
//         log.error("listenMqtt", err);
//         global.mqttClient.end();

//         if (ctx.globalOptions.autoReconnect) getSeqID();
//         else {
//             globalCallback({ type: "stop_listen", error: "Server Đã Sập - Auto Restart" }, null);
//             return process.exit(1);
//         }
//     });

//     global.mqttClient.on('connect', function () {
//         if (!global.Fca.Data.Setup || global.Fca.Data.Setup == undefined) {
//             if (global.Fca.Require.FastConfig.RestartMQTT_Minutes != 0 && global.Fca.Data.StopListening != true) { 
//                 global.Fca.Data.Setup = true;
//                 setTimeout(() => {
//                     global.Fca.Require.logger.Warning("Closing MQTT Client...");
//                     ctx.mqttClient.end();
//                     global.Fca.Require.logger.Warning("Reconnecting MQTT Client...");
//                     global.Fca.Data.Setup = false;
//                     getSeqID();
//                 }, Number(global.Fca.Require.FastConfig.RestartMQTT_Minutes) * 60 * 1000);
//             }        
//         }
//         if (process.env.OnStatus == undefined) {
//             global.Fca.Require.logger.Normal("Bạn Đang Sài Phiên Bản: Premium Access");
//             if (Number(global.Fca.Require.FastConfig.AutoRestartMinutes) == 0) {
//                 // something
//             }
//             else if (Number(global.Fca.Require.FastConfig.AutoRestartMinutes < 10)) {
//                 log.warn("AutoRestartMinutes","The number of minutes to automatically restart must be more than 10 minutes");
//             }
//             else if (Number(global.Fca.Require.FastConfig.AutoRestartMinutes) < 0) {
//                 log.warn("AutoRestartMinutes","Invalid auto-restart minutes!");
//             }
//             else {
//                 global.Fca.Require.logger.Normal(global.Fca.getText(global.Fca.Require.Language.Src.AutoRestart,global.Fca.Require.FastConfig.AutoRestartMinutes));
//                 global.Fca.Require.logger.Normal("Auto Restart MQTT Client After: " + global.Fca.Require.FastConfig.RestartMQTT_Minutes + " Minutes");
//                 setInterval(() => { 
//                     global.Fca.Require.logger.Normal(global.Fca.Require.Language.Src.OnRestart);
//                     process.exit(1);
//                 }, Number(global.Fca.Require.FastConfig.AutoRestartMinutes) * 60000);
//             }
//             require('../broadcast');
//             process.env.OnStatus = true;
//         }
        
//         topics.forEach(topicsub => global.mqttClient.subscribe(topicsub));

//         var topic;
//         var queue = {
//             sync_api_version: 11,
//             max_deltas_able_to_process: 100,
//             delta_batch_size: 500,
//             encoding: "JSON",
//             entity_fbid: ctx.userID,
//         };

//         topic = "/messenger_sync_create_queue";
//         queue.initial_titan_sequence_id = ctx.lastSeqId;
//         queue.device_params = null;

//         global.mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });

//         var rTimeout = setTimeout(function () {
//             global.mqttClient.end();
//             getSeqID();
//         }, 3000);

//         ctx.tmsWait = function () {
//             clearTimeout(rTimeout);
//             ctx.globalOptions.emitReady ? globalCallback({type: "ready",error: null}) : '';
//             delete ctx.tmsWait;
//         };
//     });

//     global.mqttClient.on('message', function (topic, message, _packet) {
//         const jsonMessage = JSON.parse(message.toString());

//         if (topic === "/t_ms") {
//             if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();

//             if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
//                 ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
//                 ctx.syncToken = jsonMessage.syncToken;
//             }

//             if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
//             //If it contains more than 1 delta
//             for (var i in jsonMessage.deltas) {
//                 var delta = jsonMessage.deltas[i];
//                 parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
//             }
//         } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
//             var typ = {
//                 type: "typ",
//                 isTyping: !!jsonMessage.state,
//                 from: jsonMessage.sender_fbid.toString(),
//                 threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
//             };
//             (function () { globalCallback(null, typ); })();
//         } else if (topic === "/orca_presence") {
//             if (!ctx.globalOptions.updatePresence) {
//                 for (var i in jsonMessage.list) {
//                     var data = jsonMessage.list[i];
//                     var userID = data["u"];

//                     var presence = {
//                         type: "presence",
//                         userID: userID.toString(),
//                         //Convert to ms
//                         timestamp: data["l"] * 1000,
//                         statuses: data["p"]
//                     };
//                     (function () { globalCallback(null, presence); })();
//                 }
//             }
//         }

//     });

//     process.on('SIGINT', function () {
//         LogUptime();process.kill(process.pid);
//     });

//     process.on('exit', () => {
//         LogUptime();
//     });
    
// }

// function LogUptime() {
//     var uptime = process.uptime();
//     var { join } = require('path');
//     if (global.Fca.Require.fs.existsSync(join(__dirname, '../CountTime.json'))) {
//         var Time1 = (Number(global.Fca.Require.fs.readFileSync(join(__dirname, '../CountTime.json'), 'utf8')) || 0);
//         global.Fca.Require.fs.writeFileSync(join(__dirname, '../CountTime.json'), String(Number(uptime) + Time1), 'utf8');
//     }
//     else {
//         var Time1 = 0;
//         global.Fca.Require.fs.writeFileSync(join(__dirname, '../CountTime.json'), String(Number(uptime) + Time1), 'utf8');
//     }
// }
// function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
//     if (v.delta.class == "NewMessage") {
//         //Not tested for pages
//         if (ctx.globalOptions.pageID && ctx.globalOptions.pageID != v.queue) return;

//         (function resolveAttachmentUrl(i) {
//             if (v.delta.attachments && (i == v.delta.attachments.length) || utils.getType(v.delta.attachments) !== "Array") {
//                 var fmtMsg;
//                 try {
//                     fmtMsg = utils.formatDeltaMessage(v);
//                 } catch (err) {
//                     return log.error("Lỗi Nhẹ", err);
//                 }
//                 global.Fca.Data.event = fmtMsg;
//                 try {
//                     var { updateMessageCount,getData,hasData } = require('../Extra/ExtraGetThread');
//                     if (hasData(fmtMsg.threadID)) {
//                         var x = getData(fmtMsg.threadID);
//                         x.messageCount+=1;
//                         updateMessageCount(fmtMsg.threadID,x);
//                     }   
//                 }
//                 catch (e) {
//                     //temp
//                 }
//                 if (fmtMsg)
//                     if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);

//                 return !ctx.globalOptions.selfListen && fmtMsg.senderID === ctx.userID ? undefined : (function () { globalCallback(null, fmtMsg); })();
//             } else {
//                 if (v.delta.attachments && (v.delta.attachments[i].mercury.attach_type == "photo")) {
//                     api.resolvePhotoUrl(v.delta.attachments[i].fbid, (err, url) => {
//                         if (!err) v.delta.attachments[i].mercury.metadata.url = url;
//                         return resolveAttachmentUrl(i + 1);
//                     });
//                 } else return resolveAttachmentUrl(i + 1);
//             }
//         })(0);
//     }

//     if (v.delta.class == "ClientPayload") {
//         var clientPayload = utils.decodeClientPayload(v.delta.payload);
//         if (clientPayload && clientPayload.deltas) {
//             for (var i in clientPayload.deltas) {
//                 var delta = clientPayload.deltas[i];
//                 if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
//                     (function () {
//                         globalCallback(null, {
//                             type: "message_reaction",
//                             threadID: (delta.deltaMessageReaction.threadKey.threadFbId ? delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey.otherUserFbId).toString(),
//                             messageID: delta.deltaMessageReaction.messageId,
//                             reaction: delta.deltaMessageReaction.reaction,
//                             senderID: delta.deltaMessageReaction.senderId.toString(),
//                             userID: delta.deltaMessageReaction.userId.toString()
//                         });
//                     })();
//                 } else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
//                     (function () {
//                         globalCallback(null, {
//                             type: "message_unsend",
//                             threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ? delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey.otherUserFbId).toString(),
//                             messageID: delta.deltaRecallMessageData.messageID,
//                             senderID: delta.deltaRecallMessageData.senderID.toString(),
//                             deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
//                             timestamp: delta.deltaRecallMessageData.timestamp
//                         });
//                     })();
//                 } else if (delta.deltaMessageReply) {
//                     //Mention block - #1
//                     var mdata =
//                         delta.deltaMessageReply.message === undefined ? [] :
//                             delta.deltaMessageReply.message.data === undefined ? [] :
//                                 delta.deltaMessageReply.message.data.prng === undefined ? [] :
//                                     JSON.parse(delta.deltaMessageReply.message.data.prng);
//                     var m_id = mdata.map(u => u.i);
//                     var m_offset = mdata.map(u => u.o);
//                     var m_length = mdata.map(u => u.l);

//                     var mentions = {};

//                     for (var i = 0; i < m_id.length; i++) mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
//                     //Mention block - 1#
//                     var callbackToReturn = {
//                         type: "message_reply",
//                         threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey.otherUserFbId).toString(),
//                         messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
//                         senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
//                         attachments: delta.deltaMessageReply.message.attachments.map(function (att) {
//                             var mercury = JSON.parse(att.mercuryJSON);
//                             Object.assign(att, mercury);
//                             return att;
//                         }).map(att => {
//                             var x;
//                             try {
//                                 x = utils._formatAttachment(att);
//                             } catch (ex) {
//                                 x = att;
//                                 x.error = ex;
//                                 x.type = "unknown";
//                             }
//                             return x;
//                         }),
//                         args: (delta.deltaMessageReply.message.body || "").trim().split(/\s+/),
//                         body: (delta.deltaMessageReply.message.body || ""),
//                         isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
//                         mentions: mentions,
//                         timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
//                         participantIDs: (delta.deltaMessageReply.message.participants || []).map(e => e.toString())
//                     };

//                     if (delta.deltaMessageReply.repliedToMessage) {
//                         //Mention block - #2
//                         mdata =
//                             delta.deltaMessageReply.repliedToMessage === undefined ? [] :
//                                 delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
//                                     delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
//                                         JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
//                         m_id = mdata.map(u => u.i);
//                         m_offset = mdata.map(u => u.o);
//                         m_length = mdata.map(u => u.l);

//                         var rmentions = {};

//                         for (var i = 0; i < m_id.length; i++) rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
//                         //Mention block - 2#
//                         callbackToReturn.messageReply = {
//                             threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.otherUserFbId).toString(),
//                             messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
//                             senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
//                             attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
//                                 var mercury = JSON.parse(att.mercuryJSON);
//                                 Object.assign(att, mercury);
//                                 return att;
//                             }).map(att => {
//                                 var x;
//                                 try {
//                                     x = utils._formatAttachment(att);
//                                 } catch (ex) {
//                                     x = att;
//                                     x.error = ex;
//                                     x.type = "unknown";
//                                 }
//                                 return x;
//                             }),
//                             args: (delta.deltaMessageReply.repliedToMessage.body || "").trim().split(/\s+/),
//                             body: delta.deltaMessageReply.repliedToMessage.body || "",
//                             isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
//                             mentions: rmentions,
//                             timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp,
//                             participantIDs: (delta.deltaMessageReply.repliedToMessage.participants || []).map(e => e.toString())
//                         };
//                     } else if (delta.deltaMessageReply.replyToMessageId) {
//                         return defaultFuncs
//                             .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
//                                 "av": ctx.globalOptions.pageID,
//                                 "queries": JSON.stringify({
//                                     "o0": {
//                                         //Using the same doc_id as forcedFetch
//                                         "doc_id": "2848441488556444",
//                                         "query_params": {
//                                             "thread_and_message_id": {
//                                                 "thread_id": callbackToReturn.threadID,
//                                                 "message_id": delta.deltaMessageReply.replyToMessageId.id,
//                                             }
//                                         }
//                                     }
//                                 })
//                             })
//                             .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
//                             .then((resData) => {
//                                 if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
//                                 if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
//                                 var fetchData = resData[0].o0.data.message;
//                                 var mobj = {};
//                                 for (var n in fetchData.message.ranges) mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);

//                                 callbackToReturn.messageReply = {
//                                     type: "Message",
//                                     threadID: callbackToReturn.threadID,
//                                     messageID: fetchData.message_id,
//                                     senderID: fetchData.message_sender.id.toString(),
//                                     attachments: fetchData.message.blob_attachment.map(att => {
//                                         var x;
//                                         try {
//                                             x = utils._formatAttachment({ blob_attachment: att });
//                                         } catch (ex) {
//                                             x = att;
//                                             x.error = ex;
//                                             x.type = "unknown";
//                                         }
//                                         return x;
//                                     }),
//                                     args: (fetchData.message.text || "").trim().split(/\s+/) || [],
//                                     body: fetchData.message.text || "",
//                                     isGroup: callbackToReturn.isGroup,
//                                     mentions: mobj,
//                                     timestamp: parseInt(fetchData.timestamp_precise)
//                                 };
//                             })
//                             .catch(err => log.error("forcedFetch", err))
//                             .finally(function () {
//                                 if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
//                                 !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
//                             });
//                     } else callbackToReturn.delta = delta;
            
//                     if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);

//                     return !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
//                 }
//             }
//             return;
//         }
//     }
//     switch (v.delta.class) {
//         case "ReadReceipt": {
//             var fmtMsg;
//             try {
//                 fmtMsg = utils.formatDeltaReadReceipt(v.delta);
//             } catch (err) {
//                 return log.error("Lỗi Nhẹ", err);
//             }
//             return (function () { globalCallback(null, fmtMsg); })();
//         }
//         case "AdminTextMessage": {
//             switch (v.delta.type) {
//                 case "joinable_group_link_mode_change":
//                 case "magic_words":
//                 case "pin_messages_v2":
//                 case "change_thread_theme":
//                 case "change_thread_icon":
//                 case "change_thread_nickname":
//                 case "change_thread_admins":
//                 case "change_thread_approval_mode":
//                 case "group_poll":
//                 case "messenger_call_log":
//                 case "participant_joined_group_call":
//                     var fmtMsg;
//                     try {
//                         fmtMsg = utils.formatDeltaEvent(v.delta);
//                     } catch (err) {
//                         console.log(v.delta)
//                         return log.error("Lỗi Nhẹ", err);
//                     }
//                     return (function () { globalCallback(null, fmtMsg); })();
//                 }
//             break;
//         }
//         //For group images
//         case "ForcedFetch": {
//             if (!v.delta.threadKey) return;
//             var mid = v.delta.messageId;
//             var tid = v.delta.threadKey.threadFbId;
//             if (mid && tid) {
//                 const form = {
//                     "av": ctx.globalOptions.pageID,
//                     "queries": JSON.stringify({
//                         "o0": {
//                             //This doc_id is valid as of March 25, 2020
//                             "doc_id": "2848441488556444",
//                             "query_params": {
//                                 "thread_and_message_id": {
//                                     "thread_id": tid.toString(),
//                                     "message_id": mid,
//                                 }
//                             }
//                         }
//                     })
//                 };

//                 defaultFuncs
//                     .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
//                     .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
//                     .then((resData) => {
//                         if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;

//                         if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };

//                         var fetchData = resData[0].o0.data.message;

//                         if (utils.getType(fetchData) == "Object") {
//                             log.info("forcedFetch", fetchData);
//                             switch (fetchData.__typename) {
//                                 case "ThreadImageMessage":
//                                     (!ctx.globalOptions.selfListen &&
//                                         fetchData.message_sender.id.toString() === ctx.userID) ||
//                                         !ctx.loggedIn ?
//                                         undefined :
//                                         (function () {
//                                             globalCallback(null, {
//                                                 type: "change_thread_image",
//                                                 threadID: utils.formatID(tid.toString()),
//                                                 snippet: fetchData.snippet,
//                                                 timestamp: fetchData.timestamp_precise,
//                                                 author: fetchData.message_sender.id,
//                                                 image: {
//                                                     attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
//                                                     width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
//                                                     height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
//                                                     url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
//                                                 }
//                                             });
//                                         })();
//                                     break;
//                                 case "UserMessage":
//                                     log.info("ff-Return", {
//                                         type: "message",
//                                         senderID: utils.formatID(fetchData.message_sender.id),
//                                         body: fetchData.message.text || "",
//                                         threadID: utils.formatID(tid.toString()),
//                                         messageID: fetchData.message_id,
//                                         attachments: [{
//                                             type: "share",
//                                             ID: fetchData.extensible_attachment.legacy_attachment_id,
//                                             url: fetchData.extensible_attachment.story_attachment.url,

//                                             title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
//                                             description: fetchData.extensible_attachment.story_attachment.description.text,
//                                             source: fetchData.extensible_attachment.story_attachment.source,

//                                             image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
//                                             width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
//                                             height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
//                                             playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
//                                             duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,

//                                             subattachments: fetchData.extensible_attachment.subattachments,
//                                             properties: fetchData.extensible_attachment.story_attachment.properties,
//                                         }],
//                                         mentions: {},
//                                         timestamp: parseInt(fetchData.timestamp_precise),
//                                         isGroup: (fetchData.message_sender.id != tid.toString())
//                                     });
//                                     globalCallback(null, {
//                                         type: "message",
//                                         senderID: utils.formatID(fetchData.message_sender.id),
//                                         body: fetchData.message.text || "",
//                                         threadID: utils.formatID(tid.toString()),
//                                         messageID: fetchData.message_id,
//                                         attachments: [{
//                                             type: "share",
//                                             ID: fetchData.extensible_attachment.legacy_attachment_id,
//                                             url: fetchData.extensible_attachment.story_attachment.url,

//                                             title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
//                                             description: fetchData.extensible_attachment.story_attachment.description.text,
//                                             source: fetchData.extensible_attachment.story_attachment.source,

//                                             image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
//                                             width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
//                                             height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
//                                             playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
//                                             duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,

//                                             subattachments: fetchData.extensible_attachment.subattachments,
//                                             properties: fetchData.extensible_attachment.story_attachment.properties,
//                                         }],
//                                         mentions: {},
//                                         timestamp: parseInt(fetchData.timestamp_precise),
//                                         isGroup: (fetchData.message_sender.id != tid.toString())
//                                     });
//                             }
//                         } else log.error("forcedFetch", fetchData);
//                     })
//                     .catch((err) => log.error("forcedFetch", err));
//                 }
//             }
//             break;
//         case "ThreadName":
//         case "ParticipantsAddedToGroupThread":
//         case "ParticipantLeftGroupThread": {
//             var formattedEvent;
//             try {
//                 formattedEvent = utils.formatDeltaEvent(v.delta);
//             } catch (err) {
//                 console.log(err)
//                 return log.error("Lỗi Nhẹ", err);
//             }
//             return (!ctx.globalOptions.selfListen && formattedEvent.author.toString() === ctx.userID) || !ctx.loggedIn ? undefined : (function () { globalCallback(null, formattedEvent); })();
//         }
//         case "NewMessage": {
//             if (v.delta.attachments != undefined && v.delta.attachments.length == 1 && v.delta.attachments[0].mercury.extensible_attachment != undefined && v.delta.attachments[0].mercury.extensible_attachment.story_attachment.style_list.includes("message_live_location")) {
//                 v.delta.class = "UserLocation";
//                 var fmtMsg;
//                 try {
//                     fmtMsg = utils.formatDeltaEvent(v.delta);
//                 } catch (err) {
//                     console.log(v.delta);
//                     return log.error("Lỗi Nhẹ", err);
//                 }
//                 return (function () { globalCallback(null, fmtMsg); })();
//             }
//         }
//         break;
//     }
// }


// function markDelivery(ctx, api, threadID, messageID) {
//     if (threadID && messageID) {
//         api.markAsDelivered(threadID, messageID, (err) => {
//             if (err) log.error("markAsDelivered", err);
//             else {
//                 if (ctx.globalOptions.autoMarkRead) {
//                     api.markAsRead(threadID, (err) => {
//                         if (err) log.error("markAsDelivered", err);
//                     });
//                 }
//             }
//         });
//     }
// }

// module.exports = function (defaultFuncs, api, ctx) {
//     var globalCallback = identity;
//     getSeqID = function getSeqID() {
//         ctx.t_mqttCalled = false;
//         defaultFuncs
//             .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
//             .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
//             .then((resData) => {
//                 if (utils.getType(resData) != "Array") {
//                     if (global.Fca.Require.FastConfig.AutoLogin) {
//                         return global.Fca.Require.logger.Warning(global.Fca.Require.Language.Index.AutoLogin, function() {
//                             return global.Fca.Action('AutoLogin');
//                         });
//                     }
//                     else if (!global.Fca.Require.FastConfig.AutoLogin) {
//                         return global.Fca.Require.logger.Error(global.Fca.Require.Language.Index.ErrAppState);
//                     }
//                     return;
//                 }
//                 else {
//                     if (resData && resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
//                     if (resData[resData.length - 1].successful_results === 0) throw { error: "getSeqId: there was no successful_results", res: resData };
//                     if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
//                         ctx.lastSeqId = resData[0].o0.data.viewer.message_threads.sync_sequence_id;
//                         listenMqtt(defaultFuncs, api, ctx, globalCallback);
//                     } 
//                     else throw { error: "getSeqId: no sync_sequence_id found.", res: resData };
//                 }
//             })
//             .catch((err) => {
//                 log.error("getSeqId", err);
//                 if (utils.getType(err) == "Object" && err.error === global.Fca.Require.Language.Index.ErrAppState) ctx.loggedIn = false;
//                 return globalCallback(err);
//             });
//     };

//     return function (callback) {
//         class MessageEmitter extends EventEmitter {
//             stopListening(callback) {
//                 callback = callback || (() => { });
//                 globalCallback = identity;
//                 if (ctx.mqttClient) {
//                     ctx.mqttClient.unsubscribe("/webrtc");
//                     ctx.mqttClient.unsubscribe("/rtc_multi");
//                     ctx.mqttClient.unsubscribe("/onevc");
//                     ctx.mqttClient.publish("/browser_close", "{}");
//                     ctx.mqttClient.end(false, function (...data) {
//                         ctx.mqttClient = undefined;
//                     });
//                 }
//                 global.Fca.Data.StopListening = true;
//             }
//         }

//         var msgEmitter = new MessageEmitter();
//         globalCallback = (callback || function (error, message) {
//             if (error) return msgEmitter.emit("error", error);
//             msgEmitter.emit("message", message);
//         });

//         //Reset some stuff
//         if (!ctx.firstListen) ctx.lastSeqId = null;
//         ctx.syncToken = undefined;
//         ctx.t_mqttCalled = false;

//         //Same request as getThreadList
//         form = {
//             "av": ctx.globalOptions.pageID,
//             "queries": JSON.stringify({
//                 "o0": {
//                     "doc_id": "3336396659757871",
//                     "query_params": {
//                         "limit": 1,
//                         "before": null,
//                         "tags": ["INBOX"],
//                         "includeDeliveryReceipts": false,
//                         "includeSeqID": true
//                     }
//                 }
//             })
//         };

//         if (!ctx.firstListen || !ctx.lastSeqId) getSeqID();
//         else listenMqtt(defaultFuncs, api, ctx, globalCallback);
//         ctx.firstListen = false;
//         return msgEmitter;
//     };
// };

// // "use strict";
// // var utils = require("../utils");
// // var log = require("npmlog");
// // var mqtt = require('mqtt');
// // var websocket = require('websocket-stream');

// // var identity = function () {};
// // var chatOn = true;
// // var foreground = false;

// // var topics = [
// //   "/t_ms",
// //   "/thread_typing",
// //   "/orca_typing_notifications",
// //   "/orca_presence",
// //   "/legacy_web",
// //   "/br_sr",
// //   "/sr_res",
// //   "/webrtc",
// //   "/onevc",
// //   "/notify_disconnect",
// //   "/inbox",
// //   "/mercury",
// //   "/messaging_events",
// //   "/orca_message_notifications",
// //   "/pp",
// //   "/webrtc_response",
// // ];

// // function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
// //   var sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
// //   var username = {
// //     u: ctx.userID,
// //     s: sessionID,
// //     chat_on: chatOn,
// //     fg: foreground,
// //     d: utils.getGUID(),
// //     ct: "websocket",
// //     aid: "219994525426954",
// //     mqtt_sid: "",
// //     cp: 3,
// //     ecp: 10,
// //     st: topics,
// //     pm: [],
// //     dc: "",
// //     no_auto_fg: true,
// //     gas: null
// //   };
// //   var cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
// //   var host = 'wss://edge-chat.facebook.com/chat?sid=' + sessionID;

// //   var options = {
// //     clientId: "mqttwsclient",
// //     protocolId: 'MQIsdp',
// //     protocolVersion: 3,
// //     username: JSON.stringify(username),
// //     clean: true,
// //     wsOptions: {
// //       headers: {
// //         'Cookie': cookies,
// //         'Origin': 'https://www.facebook.com',
// //         'User-Agent': ctx.globalOptions.userAgent,
// //         'Referer': 'https://www.facebook.com',
// //         'Host': 'edge-chat.facebook.com'
// //       },
// //       origin: 'https://www.facebook.com',
// //       protocolVersion: 13
// //     }
// //   };

// //   ctx.mqttClient = new mqtt.Client(_ => websocket(host, options.wsOptions), options);

// //   var mqttClient = ctx.mqttClient;

// //   mqttClient.on('error', function(err) {
// //     log.error(err);
// //     mqttClient.end();
// //     globalCallback("Connection refused: Server unavailable", null);
// //   });

// //   mqttClient.on('connect', function() {
// //     var topic;
// //     var queue = {
// //       sync_api_version: 10,
// //       max_deltas_able_to_process: 1000,
// //       delta_batch_size: 500,
// //       encoding: "JSON",
// //       entity_fbid: ctx.userID,
// //     };

// //     if(ctx.globalOptions.pageID) {
// //       queue.entity_fbid = ctx.globalOptions.pageID;
// //     }

// //     if(ctx.syncToken) {
// //       topic = "/messenger_sync_get_diffs";
// //       queue.last_seq_id = ctx.lastSeqId;
// //       queue.sync_token = ctx.syncToken;
// //     } else {
// //       topic = "/messenger_sync_create_queue";
// //       queue.initial_titan_sequence_id = ctx.lastSeqId;
// //       queue.device_params = null;
// //     }
    
// //     mqttClient.publish(topic, JSON.stringify(queue), {qos: 1, retain: false});
// //   });

// //   mqttClient.on('message', function(topic, message, packet) {
// //     var jsonMessage = JSON.parse(message);
// //     if(topic === "/t_ms") {
// //       if(jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
// //         ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
// //         ctx.syncToken = jsonMessage.syncToken;
// //       }

// //       if(jsonMessage.lastIssuedSeqId) {
// //         ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
// //       }

// //       if(jsonMessage.queueEntityId && ctx.globalOptions.pageID &&
// //         ctx.globalOptions.pageID != jsonMessage.queueEntityId) {
// //         return;
// //       }

// //       //If it contains more than 1 delta
// //       for (var i in jsonMessage.deltas) {
// //         var delta = jsonMessage.deltas[i];
// //         parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
// //       }
// //     } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
// //       var typ = {
// //         type: "typ",
// //         isTyping: !!jsonMessage.state,
// //         from: jsonMessage.sender_fbid.toString(),
// //         threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
// //       };
// //       (function () { globalCallback(null, typ); })();
// //     } else if (topic === "/orca_presence") {
// //       if (!ctx.globalOptions.updatePresence) {
// //         for (var i in jsonMessage.list) {
// //           var data = jsonMessage.list[i];
// //           var userID = data["u"];

// //           var presence = {
// //             type: "presence",
// //             userID: userID.toString(),
// //             //Convert to ms
// //             timestamp: data["l"] * 1000,
// //             statuses: data["p"]
// //           };
// //           (function () { globalCallback(null, presence); })();
// //         }
// //       }
// //     }

// //   });

// //   mqttClient.on('close', function() {
// //     // client.end();
// //   });
// // }

// // function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
// //   if(v.delta.class == "NewMessage") {
// //     (function resolveAttachmentUrl(i) {
// //       // sometimes, with sticker message in group, delta does not contain 'attachments' property.
// //       if (v.delta.attachments && (i == v.delta.attachments.length)) {
// //         var fmtMsg;
// //         try {
// //           fmtMsg = utils.formatDeltaMessage(v);
// //         } catch (err) {
// //           return globalCallback({
// //             error: "Problem parsing message object. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues.",
// //             detail: err,
// //             res: v,
// //             type: "parse_error"
// //           });
// //         }
// //         if (fmtMsg) {
// //           if (ctx.globalOptions.autoMarkDelivery) {
// //             markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
// //           }
// //         }
// //         return !ctx.globalOptions.selfListen &&
// //           fmtMsg.senderID === ctx.userID ?
// //           undefined :
// //           (function () { globalCallback(null, fmtMsg); })();
// //       } else {
// //         if (
// //           v.delta.attachments && (v.delta.attachments[i].mercury.attach_type == "photo")
// //         ) {
// //           api.resolvePhotoUrl(
// //             v.delta.attachments[i].fbid,
// //             (err, url) => {
// //               if (!err)
// //                 v.delta.attachments[
// //                   i
// //                 ].mercury.metadata.url = url;
// //               return resolveAttachmentUrl(i + 1);
// //             }
// //           );
// //         } else {
// //           return resolveAttachmentUrl(i + 1);
// //         }
// //       }
// //     })(0);
// //   }

// //   if (v.delta.class == "ClientPayload") {
// //     var clientPayload = utils.decodeClientPayload(
// //       v.delta.payload
// //     );
// //     if (clientPayload && clientPayload.deltas) {
// //       for (var i in clientPayload.deltas) {
// //         var delta = clientPayload.deltas[i];
        
// //         if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
// //           (function () { globalCallback(null, {
// //             type: "message_reaction",
// //             threadID: (delta.deltaMessageReaction.threadKey
// //               .threadFbId ?
// //               delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey
// //                 .otherUserFbId).toString(),
// //             messageID: delta.deltaMessageReaction.messageId,
// //             reaction: delta.deltaMessageReaction.reaction,
// //             senderID: delta.deltaMessageReaction.senderId.toString(),
// //             userID: delta.deltaMessageReaction.userId.toString()
// //           }); })();
// //         } else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
// //           (function () { globalCallback(null, {
// //             type: "message_unsend",
// //             threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ?
// //               delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey
// //                 .otherUserFbId).toString(),
// //             messageID: delta.deltaRecallMessageData.messageID,
// //             senderID: delta.deltaRecallMessageData.senderID.toString(),
// //             deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
// //             timestamp: delta.deltaRecallMessageData.timestamp
// //           }); })();
// //         } else if (delta.deltaMessageReply) {
// //           //Mention block - #1
// //           var mdata =
// //             delta.deltaMessageReply.message === undefined ? [] :
// //               delta.deltaMessageReply.message.data === undefined ? [] :
// //                 delta.deltaMessageReply.message.data.prng === undefined ? [] :
// //                   JSON.parse(delta.deltaMessageReply.message.data.prng);
// //           var m_id = mdata.map(u => u.i);
// //           var m_offset = mdata.map(u => u.o);
// //           var m_length = mdata.map(u => u.l);

// //           var mentions = {};

// //           for (var i = 0; i < m_id.length; i++) {
// //             mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(
// //               m_offset[i],
// //               m_offset[i] + m_length[i]
// //             );
// //           }
// //           //Mention block - 1#
// //           var callbackToReturn = {
// //             type: "message_reply",
// //             threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ?
// //               delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey
// //                 .otherUserFbId).toString(),
// //             messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
// //             senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
// //             attachments: delta.deltaMessageReply.message.attachments.map(function (att) {
// //               var mercury = JSON.parse(att.mercuryJSON);
// //               Object.assign(att, mercury);
// //               return att;
// //             }).map(att => {
// //               var x;
// //               try {
// //                 x = utils._formatAttachment(att);
// //               } catch (ex) {
// //                 x = att;
// //                 x.error = ex;
// //                 x.type = "unknown";
// //               }
// //               return x;
// //             }),
// //             args: (delta.deltaMessageReply.message.body || '').split(' '),
// //             body: delta.deltaMessageReply.message.body || "",
// //             participantIDs: delta.deltaMessageReply.message.participants,
// //             isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
// //             mentions: mentions,
// //             timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
// //           };

// //           if (delta.deltaMessageReply.repliedToMessage) {
// //             //Mention block - #2
// //             mdata =
// //               delta.deltaMessageReply.repliedToMessage === undefined ? [] :
// //                 delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
// //                   delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
// //                     JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
// //             m_id = mdata.map(u => u.i);
// //             m_offset = mdata.map(u => u.o);
// //             m_length = mdata.map(u => u.l);

// //             var rmentions = {};

// //             for (var i = 0; i < m_id.length; i++) {
// //               rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(
// //                 m_offset[i],
// //                 m_offset[i] + m_length[i]
// //               );
// //             }
// //             //Mention block - 2#
// //             callbackToReturn.messageReply = {
// //               threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ?
// //                 delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey
// //                   .otherUserFbId).toString(),
// //               messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
// //               senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
// //               attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
// //                 var mercury = JSON.parse(att.mercuryJSON);
// //                 Object.assign(att, mercury);
// //                 return att;
// //               }).map(att => {
// //                 var x;
// //                 try {
// //                   x = utils._formatAttachment(att);
// //                 } catch (ex) {
// //                   x = att;
// //                   x.error = ex;
// //                   x.type = "unknown";
// //                 }
// //                 return x;
// //               }),
// //               args: (delta.deltaMessageReply.repliedToMessage.body || "").split(' '),
// //               body: delta.deltaMessageReply.repliedToMessage.body || "",
// //               isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
// //               mentions: rmentions,
// //               timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp,
// //             };
// //           }

// //           if (ctx.globalOptions.autoMarkDelivery) {
// //             markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
// //           }

// //           return !ctx.globalOptions.selfListen &&
// //             callbackToReturn.senderID === ctx.userID ?
// //             undefined :
// //             (function () { globalCallback(null, callbackToReturn); })();
// //         }
// //       }
// //       return;
// //     }
// //   }

// //   if (v.delta.class !== "NewMessage" &&
// //     !ctx.globalOptions.listenEvents
// //   )
// //     return;

// //   switch (v.delta.class) {
// //     case "ReadReceipt":
// //       var fmtMsg;
// //       try {
// //         fmtMsg = utils.formatDeltaReadReceipt(v.delta);
// //       } catch (err) {
// //         return globalCallback({
// //           error: "Problem parsing message object. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues.",
// //           detail: err,
// //           res: v.delta,
// //           type: "parse_error"
// //         });
// //       }
// //       return (function () { globalCallback(null, fmtMsg); })();
// //     case "AdminTextMessage":
// //       switch (v.delta.type) {
// //         case "change_thread_theme":
// //         case "change_thread_nickname":
// //         case "change_thread_icon":
// //           break;
// //         case "group_poll":
// //           var fmtMsg;
// //           try {
// //             fmtMsg = utils.formatDeltaEvent(v.delta);
// //           } catch (err) {
// //             return globalCallback({
// //               error: "Problem parsing message object. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues.",
// //               detail: err,
// //               res: v.delta,
// //               type: "parse_error"
// //             });
// //           }
// //           return (function () { globalCallback(null, fmtMsg); })();
// //         default:
// //           return;
// //       }
// //       break;
// //     //For group images
// //     case "ForcedFetch":
// //       if (!v.delta.threadKey) return;
// //       var mid = v.delta.messageId;
// //       var tid = v.delta.threadKey.threadFbId;
// //       if (mid && tid) {
// //         const form = {
// //           "av": ctx.globalOptions.pageID,
// //           "queries": JSON.stringify({
// //             "o0": {
// //               //This doc_id is valid as of ? (prob January 18, 2020)
// //               "doc_id": "1768656253222505",
// //               "query_params": {
// //                 "thread_and_message_id": {
// //                   "thread_id": tid.toString(),
// //                   "message_id": mid.toString(),
// //                 }
// //               }
// //             }
// //           })
// //         };

// //         defaultFuncs
// //           .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
// //           .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
// //           .then((resData) => {
// //             if (resData[resData.length - 1].error_results > 0) {
// //               throw resData[0].o0.errors;
// //             }

// //             if (resData[resData.length - 1].successful_results === 0) {
// //               throw { error: "forcedFetch: there was no successful_results", res: resData };
// //             }

// //             var fetchData = resData[0].o0.data.message;
// //             if (fetchData && fetchData.__typename === "ThreadImageMessage") {
// //               (!ctx.globalOptions.selfListen &&
// //                 fetchData.message_sender.id.toString() === ctx.userID) ||
// //                 !ctx.loggedIn ?
// //                 undefined :
// //                 (function () { globalCallback(null, {
// //                   type: "change_thread_image",
// //                   threadID: utils.formatID(tid.toString()),
// //                   snippet: fetchData.snippet,
// //                   timestamp: fetchData.timestamp_precise,
// //                   author: fetchData.message_sender.id,
// //                   image: {
// //                     attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
// //                     width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
// //                     height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
// //                     url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
// //                   }
// //                 }); })();
// //             }
// //           })
// //           .catch((err) => {
// //             log.error("forcedFetch", err);
// //           });
// //       }
// //       break;
// //     case "ThreadName":
// //     case "ParticipantsAddedToGroupThread":
// //     case "ParticipantLeftGroupThread":
// //       var formattedEvent;
// //       try {
// //         formattedEvent = utils.formatDeltaEvent(v.delta);
// //       } catch (err) {
// //         return globalCallback({
// //           error: "Problem parsing message object. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues.",
// //           detail: err,
// //           res: v.delta,
// //           type: "parse_error"
// //         });
// //       }
// //       return (!ctx.globalOptions.selfListen &&
// //         formattedEvent.author.toString() === ctx.userID) ||
// //         !ctx.loggedIn ?
// //         undefined :
// //         (function () { globalCallback(null, formattedEvent); })();
// //   }
// // }

// // function markDelivery(ctx, api, threadID, messageID) {
// //   if (threadID && messageID) {
// //     api.markAsDelivered(threadID, messageID, (err) => {
// //       if (err) {
// //         log.error(err);
// //       } else {
// //         if (ctx.globalOptions.autoMarkRead) {
// //           api.markAsRead(threadID, (err) => {
// //             if (err) {
// //               log.error(err);
// //             }
// //           });
// //         }
// //       }
// //     });
// //   }
// // }

// // module.exports = function (defaultFuncs, api, ctx) {
// //   var globalCallback = identity;
// //   return function (callback) {
// //     globalCallback = callback;

// //     //Reset some stuff
// //     ctx.lastSeqId = 0;
// //     ctx.syncToken = undefined;

// //     //Same request as getThreadList
// //     const form = {
// //       "av": ctx.globalOptions.pageID,
// //       "queries": JSON.stringify({
// //         "o0": {
// //           "doc_id": "1349387578499440",
// //           "query_params": {
// //             "limit": 1,
// //             "before": null,
// //             "tags": ["INBOX"],
// //             "includeDeliveryReceipts": false,
// //             "includeSeqID": true
// //           }
// //         }
// //       })
// //     };

// //     defaultFuncs
// //       .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
// //       .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
// //       .then((resData) => {
// //         if (resData && resData.length > 0 && resData[resData.length - 1].error_results > 0) {
// //           throw resData[0].o0.errors;
// //         }

// //         if (resData[resData.length - 1].successful_results === 0) {
// //           throw { error: "getSeqId: there was no successful_results", res: resData };
// //         }

// //         if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
// //           ctx.lastSeqId = resData[0].o0.data.viewer.message_threads.sync_sequence_id;
// //           listenMqtt(defaultFuncs, api, ctx, globalCallback);
// //         }

// //       })
// //       .catch((err) => {
// //         log.error("getSeqId", err);
// //         return callback(err);
// //       });
// //     var stopListening = function () {
// //       globalCallback = identity;

// //       if(ctx.mqttClient)
// //       {
// //         ctx.mqttClient.end();
// //         ctx.mqttClient = undefined;
// //       }
// //     };

// //     return stopListening;
// //   };
// // };


// V1 - 07/05/2024 Kem
/* eslint-disable no-redeclare */
"use strict";
const utils = require("../utils");
const log = require("npmlog");
const mqtt = require('mqtt');
const Websocket = require('ws');
const HttpsProxyAgent = require('https-proxy-agent');
const EventEmitter = require('events');
const Duplexify = require('duplexify');
const Transform = require('readable-stream').Transform;
var identity = function () { };
var form = {};
var getSeqID = function () { };


var topics = ["/ls_req","/ls_resp","/legacy_web","/webrtc","/rtc_multi","/onevc","/br_sr","/sr_res","/t_ms","/thread_typing","/orca_typing_notifications","/notify_disconnect","/orca_presence","/inbox","/mercury", "/messaging_events", "/orca_message_notifications", "/pp","/webrtc_response"];

/* [ Noti ? ]
!   "/br_sr", //Notification
    * => Need to publish /br_sr right after this
   
!   "/notify_disconnect",
    * => Need to publish /messenger_sync_create_queue right after this

!   "/orca_presence",
    * => Will receive /sr_res right here.
  */

var WebSocket_Global;

function buildProxy() {
    var Proxy = new Transform({
        objectMode: false
    });

    Proxy._write = function socketWriteNode(chunk, enc, next) {
        if (WebSocket_Global.readyState !== WebSocket_Global.OPEN) {
            return next();
        }
    
        if (typeof chunk === 'string') {
            chunk = new Buffer.from(chunk, 'utf8');
        }
        WebSocket_Global.send(chunk, next);
    };

    Proxy._flush = function(done) {
        WebSocket_Global.close();
        done();
    };

    Proxy._writev = function(chunks, cb) {
        var buffers = new Array(chunks.length);
        for (var i = 0; i < chunks.length; i++) {
            if (typeof chunks[i].chunk === 'string') {
                buffers[i] = new Buffer.from(chunks[i], 'utf8');
            } else {
                buffers[i] = chunks[i].chunk;
            }
        }
        this._write(new Buffer.concat(buffers), 'binary', cb);
    };

    return Proxy;
}

function buildStream(options, WebSocket, Proxy) {
    const Stream = Duplexify(undefined, undefined, options);
    Stream.socket = WebSocket;
    
    WebSocket 
        .onclose = function() {
            Stream.end();
            Stream.destroy();
        };
    WebSocket
        .onerror = function(err) {
            Stream.destroy(err);
        };
    WebSocket
        .onmessage = function(event) {
            var data = event.data;
            if (data instanceof ArrayBuffer) data = new Buffer.from(data);
            else data = new Buffer.from(data, 'utf8');
            Stream.push(data);
        };
    WebSocket
        .onopen = function() {
            Stream.setReadable(Proxy);
            Stream.setWritable(Proxy);
            Stream.emit('connect');
        };
    WebSocket_Global = WebSocket;
    Proxy.on('close', function() { WebSocket.close(); });
    return Stream;
}


function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
    //Don't really know what this does but I think it's for the act`ive state?
    //TODO: Move to ctx when implemented
    var chatOn = ctx.globalOptions.online;
    var foreground = false;

    var sessionID = Math.floor((Math.random() * Number.MAX_SAFE_INTEGER)+1);
    var username = {u: ctx.userID,s: sessionID,chat_on: chatOn,fg: foreground,d: utils.getGUID(),ct: "websocket",aid: "219994525426954", mqtt_sid: "",cp: 3,ecp: 10,st: [],pm: [],dc: "",no_auto_fg: true,gas: null,pack: []};
    var cookies = ctx.jar.getCookies('https://www.facebook.com').join("; ");

    var host;
    if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}`;
    else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLocaleLowerCase()}&sid=${sessionID}`;
    else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}`;

    var options = { 
        clientId: "mqttwsclient",
        protocolId: 'MQIsdp',
        protocolVersion: 3,
        username: JSON.stringify(username), 
        clean: true,
        wsOptions: {
            headers: {
                'Cookie': cookies,
                'Origin': 'https://www.facebook.com',
                'User-Agent': (ctx.globalOptions.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36'),
                'Referer': 'https://www.facebook.com/',
                'Host': new URL(host).hostname //'edge-chat.facebook.com'
            },
            origin: 'https://www.facebook.com',
            protocolVersion: 13,
            binaryType: 'arraybuffer',
        },
        keepalive: 60,
        reschedulePings: true,
        reconnectPeriod: 3
    };

    if (typeof ctx.globalOptions.proxy != "undefined") {
        var agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
        options.wsOptions.agent = agent;
    }
    ctx.mqttClient = new mqtt.Client(_ => buildStream(options, new Websocket(host, options.wsOptions), buildProxy()), options);
    global.mqttClient = ctx.mqttClient;

    global.mqttClient.on('error', function (err) {
        log.error("listenMqtt", err);
        global.mqttClient.end();

        if (ctx.globalOptions.autoReconnect) getSeqID();
        else {
            globalCallback({ type: "stop_listen", error: "Server Đã Sập - Auto Restart" }, null);
            return process.exit(1);
        }
    });

    global.mqttClient.on('connect', function () {
        if (!global.Fca.Data.Setup || global.Fca.Data.Setup == undefined) {
            if (global.Fca.Require.FastConfig.RestartMQTT_Minutes != 0 && global.Fca.Data.StopListening != true) { 
                global.Fca.Data.Setup = true;
                setTimeout(() => {
                    global.Fca.Require.logger.Warning("Closing MQTT Client...");
                    ctx.mqttClient.end();
                    global.Fca.Require.logger.Warning("Reconnecting MQTT Client...");
                    global.Fca.Data.Setup = false;
                    getSeqID();
                }, Number(global.Fca.Require.FastConfig.RestartMQTT_Minutes) * 60 * 1000);
            }        
        }
        if (process.env.OnStatus == undefined) {
            global.Fca.Require.logger.Normal("Bạn Đang Sài Phiên Bản: Premium Access");
            if (Number(global.Fca.Require.FastConfig.AutoRestartMinutes) == 0) {
                // something
            }
            else if (Number(global.Fca.Require.FastConfig.AutoRestartMinutes < 10)) {
                log.warn("AutoRestartMinutes","The number of minutes to automatically restart must be more than 10 minutes");
            }
            else if (Number(global.Fca.Require.FastConfig.AutoRestartMinutes) < 0) {
                log.warn("AutoRestartMinutes","Invalid auto-restart minutes!");
            }
            else {
                global.Fca.Require.logger.Normal(global.Fca.getText(global.Fca.Require.Language.Src.AutoRestart,global.Fca.Require.FastConfig.AutoRestartMinutes));
                global.Fca.Require.logger.Normal("Auto Restart MQTT Client After: " + global.Fca.Require.FastConfig.RestartMQTT_Minutes + " Minutes");
                setInterval(() => { 
                    global.Fca.Require.logger.Normal(global.Fca.Require.Language.Src.OnRestart);
                    process.exit(1);
                }, Number(global.Fca.Require.FastConfig.AutoRestartMinutes) * 60000);
            }
            require('../broadcast').startBroadcasting();
            const MemoryManager = require('../Extra/Src/Release_Memory');
            const path = require('path');

            const memoryManager = new MemoryManager({
                warningThreshold: 0.6,
                releaseThreshold: 0.7, 
                maxThreshold: 0.8, 
                interval: 30 * 1000,
                logLevel: 'warn',
                logFile: path.join(__dirname, 'memory.log'),
                smartReleaseEnabled: true,
            });


            memoryManager.autoStart(60 * 60 * 1000); 

            process.env.OnStatus = true;
        }
        
        topics.forEach(topicsub => global.mqttClient.subscribe(topicsub));

        var topic;
        var queue = {
            sync_api_version: 11,
            max_deltas_able_to_process: 100,
            delta_batch_size: 500,
            encoding: "JSON",
            entity_fbid: ctx.userID,
        };

        topic = "/messenger_sync_create_queue";
        queue.initial_titan_sequence_id = ctx.lastSeqId;
        queue.device_params = null;

        global.mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });

        var rTimeout = setTimeout(function () {
            global.mqttClient.end();
            getSeqID();
        }, 3000);

        ctx.tmsWait = function () {
            clearTimeout(rTimeout);
            ctx.globalOptions.emitReady ? globalCallback({type: "ready",error: null}) : '';
            delete ctx.tmsWait;
        };
    });

    global.mqttClient.on('message', function (topic, message, _packet) {
        const jsonMessage = JSON.parse(message.toString());
        console.log(message.toString())
        if (topic === "/t_ms") {
            if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();

            if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
                ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
                ctx.syncToken = jsonMessage.syncToken;
            }

            if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
            //If it contains more than 1 delta
            for (var i in jsonMessage.deltas) {
                var delta = jsonMessage.deltas[i];
                parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
            }
        } 
        else if (topic === "/ls_resp") {
            console.log((jsonMessage.payload));
            const payload = JSON.parse(jsonMessage.payload); //'{"name":null,"step":[1,[1,[4,0,1,[5,"taskExists",[19,"415"]]],[23,[2,0],[1,[5,"replaceOptimsiticMessage","7192532113093667880","mid.$gABfX5li9LA6VdUymnWPRAdlkiawo"]]]],[1,[4,0,1,[5,"taskExists",[19,"415"]]],[23,[2,0],[1,[5,"mailboxTaskCompletionApiOnTaskCompletion",[19,"415"],true]]]],[1,[4,0,1,[5,"taskExists",[19,"415"]]],[23,[2,0],[1,[5,"removeTask",[19,"415"],[9]]]]]]}'
            const request_ID = jsonMessage.request_id;
            if (ctx.callback_Task[request_ID] != undefined) {
                const { callback, type } = ctx.callback_Task[request_ID];
                const Data = new getRespData(type, payload);

                if (!Data) {
                    callback("Something went wrong 🐳", null);
                }
                else {
                    callback(null, Data);
                }
            }
        }
        else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
            var typ = {
                type: "typ",
                isTyping: !!jsonMessage.state,
                from: jsonMessage.sender_fbid.toString(),
                threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
            };
            (function () { globalCallback(null, typ); })();
        } else if (topic === "/orca_presence") {
            if (!ctx.globalOptions.updatePresence) {
                for (var i in jsonMessage.list) {
                    var data = jsonMessage.list[i];
                    var userID = data["u"];

                    var presence = {
                        type: "presence",
                        userID: userID.toString(),
                        //Convert to ms
                        timestamp: data["l"] * 1000,
                        statuses: data["p"]
                    };
                    (function () { globalCallback(null, presence); })();
                }
            }
        }

    });

    process.on('SIGINT', function () {
        LogUptime();
        process.kill(process.pid);
    });

    process.on('exit', () => {
        LogUptime();
    });
    
}

function getRespData(Type, payload) {
    try { 
        switch (Type) {
            case "sendMqttMessage": {
                return {
                    type: Type,
                    threadID: payload.step[1][2][2][1][2], //this is sick bro
                    messageID: payload.step[1][2][2][1][3],
                    payload: payload.step[1][2]
                };
            }
            default: { //!very LAZY :> cook yourself
                return {
                    Data: payload.step[1][2][2][1],
                    type: Type,
                    payload: payload.step[1][2] 
                };
            }
        }
    }
    catch (e) {
        return null;
    }
}

function LogUptime() {
    var uptime = process.uptime();
    var { join } = require('path');
    if (global.Fca.Require.fs.existsSync(join(__dirname, '../CountTime.json'))) {
        var Time1 = (Number(global.Fca.Require.fs.readFileSync(join(__dirname, '../CountTime.json'), 'utf8')) || 0);
        global.Fca.Require.fs.writeFileSync(join(__dirname, '../CountTime.json'), String(Number(uptime) + Time1), 'utf8');
    }
    else {
        var Time1 = 0;
        global.Fca.Require.fs.writeFileSync(join(__dirname, '../CountTime.json'), String(Number(uptime) + Time1), 'utf8');
    }
}
function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
    if (v.delta.class == "NewMessage") {
        //Not tested for pages
        if (ctx.globalOptions.pageID && ctx.globalOptions.pageID != v.queue) return;

        (function resolveAttachmentUrl(i) {
            if (v.delta.attachments && (i == v.delta.attachments.length) || utils.getType(v.delta.attachments) !== "Array") {
                var fmtMsg;
                try {
                    fmtMsg = utils.formatDeltaMessage(v);
                } catch (err) {
                    return log.error("Lỗi Nhẹ", err);
                }
                global.Fca.Data.event = fmtMsg;
                try {
                    var { updateMessageCount,getData,hasData } = require('../Extra/ExtraGetThread');
                    if (hasData(fmtMsg.threadID)) {
                        var x = getData(fmtMsg.threadID);
                        x.messageCount+=1;
                        updateMessageCount(fmtMsg.threadID,x);
                    }   
                }
                catch (e) {
                    //temp
                }
                if (fmtMsg)
                    if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);

                return !ctx.globalOptions.selfListen && fmtMsg.senderID === ctx.userID ? undefined : (function () { globalCallback(null, fmtMsg); })();
            } else {
                if (v.delta.attachments && (v.delta.attachments[i].mercury.attach_type == "photo")) {
                    api.resolvePhotoUrl(v.delta.attachments[i].fbid, (err, url) => {
                        if (!err) v.delta.attachments[i].mercury.metadata.url = url;
                        return resolveAttachmentUrl(i + 1);
                    });
                } else return resolveAttachmentUrl(i + 1);
            }
        })(0);
    }

    if (v.delta.class == "ClientPayload") {
        var clientPayload = utils.decodeClientPayload(v.delta.payload);
        if (clientPayload && clientPayload.deltas) {
            for (var i in clientPayload.deltas) {
                var delta = clientPayload.deltas[i];
                if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
                    (function () {
                        globalCallback(null, {
                            type: "message_reaction",
                            threadID: (delta.deltaMessageReaction.threadKey.threadFbId ? delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey.otherUserFbId).toString(),
                            messageID: delta.deltaMessageReaction.messageId,
                            reaction: delta.deltaMessageReaction.reaction,
                            senderID: delta.deltaMessageReaction.senderId.toString(),
                            userID: delta.deltaMessageReaction.userId.toString()
                        });
                    })();
                } else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
                    (function () {
                        globalCallback(null, {
                            type: "message_unsend",
                            threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ? delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey.otherUserFbId).toString(),
                            messageID: delta.deltaRecallMessageData.messageID,
                            senderID: delta.deltaRecallMessageData.senderID.toString(),
                            deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
                            timestamp: delta.deltaRecallMessageData.timestamp
                        });
                    })();
                } else if (delta.deltaMessageReply) {
                    //Mention block - #1
                    var mdata =
                        delta.deltaMessageReply.message === undefined ? [] :
                            delta.deltaMessageReply.message.data === undefined ? [] :
                                delta.deltaMessageReply.message.data.prng === undefined ? [] :
                                    JSON.parse(delta.deltaMessageReply.message.data.prng);
                    var m_id = mdata.map(u => u.i);
                    var m_offset = mdata.map(u => u.o);
                    var m_length = mdata.map(u => u.l);

                    var mentions = {};

                    for (var i = 0; i < m_id.length; i++) mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
                    //Mention block - 1#
                    var callbackToReturn = {
                        type: "message_reply",
                        threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey.otherUserFbId).toString(),
                        messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
                        senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
                        attachments: delta.deltaMessageReply.message.attachments.map(function (att) {
                            var mercury = JSON.parse(att.mercuryJSON);
                            Object.assign(att, mercury);
                            return att;
                        }).map(att => {
                            var x;
                            try {
                                x = utils._formatAttachment(att);
                            } catch (ex) {
                                x = att;
                                x.error = ex;
                                x.type = "unknown";
                            }
                            return x;
                        }),
                        args: (delta.deltaMessageReply.message.body || "").trim().split(/\s+/),
                        body: (delta.deltaMessageReply.message.body || ""),
                        isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
                        mentions: mentions,
                        timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
                        participantIDs: (delta.deltaMessageReply.message.participants || []).map(e => e.toString())
                    };

                    if (delta.deltaMessageReply.repliedToMessage) {
                        //Mention block - #2
                        mdata =
                            delta.deltaMessageReply.repliedToMessage === undefined ? [] :
                                delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
                                    delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
                                        JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
                        m_id = mdata.map(u => u.i);
                        m_offset = mdata.map(u => u.o);
                        m_length = mdata.map(u => u.l);

                        var rmentions = {};

                        for (var i = 0; i < m_id.length; i++) rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
                        //Mention block - 2#
                        callbackToReturn.messageReply = {
                            threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.otherUserFbId).toString(),
                            messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
                            senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
                            attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
                                var mercury = JSON.parse(att.mercuryJSON);
                                Object.assign(att, mercury);
                                return att;
                            }).map(att => {
                                var x;
                                try {
                                    x = utils._formatAttachment(att);
                                } catch (ex) {
                                    x = att;
                                    x.error = ex;
                                    x.type = "unknown";
                                }
                                return x;
                            }),
                            args: (delta.deltaMessageReply.repliedToMessage.body || "").trim().split(/\s+/),
                            body: delta.deltaMessageReply.repliedToMessage.body || "",
                            isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
                            mentions: rmentions,
                            timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp,
                            participantIDs: (delta.deltaMessageReply.repliedToMessage.participants || []).map(e => e.toString())
                        };
                    } else if (delta.deltaMessageReply.replyToMessageId) {
                        return defaultFuncs
                            .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
                                "av": ctx.globalOptions.pageID,
                                "queries": JSON.stringify({
                                    "o0": {
                                        //Using the same doc_id as forcedFetch
                                        "doc_id": "2848441488556444",
                                        "query_params": {
                                            "thread_and_message_id": {
                                                "thread_id": callbackToReturn.threadID,
                                                "message_id": delta.deltaMessageReply.replyToMessageId.id,
                                            }
                                        }
                                    }
                                })
                            })
                            .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
                            .then((resData) => {
                                if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
                                if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
                                var fetchData = resData[0].o0.data.message;
                                var mobj = {};
                                for (var n in fetchData.message.ranges) mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);

                                callbackToReturn.messageReply = {
                                    type: "Message",
                                    threadID: callbackToReturn.threadID,
                                    messageID: fetchData.message_id,
                                    senderID: fetchData.message_sender.id.toString(),
                                    attachments: fetchData.message.blob_attachment.map(att => {
                                        var x;
                                        try {
                                            x = utils._formatAttachment({ blob_attachment: att });
                                        } catch (ex) {
                                            x = att;
                                            x.error = ex;
                                            x.type = "unknown";
                                        }
                                        return x;
                                    }),
                                    args: (fetchData.message.text || "").trim().split(/\s+/) || [],
                                    body: fetchData.message.text || "",
                                    isGroup: callbackToReturn.isGroup,
                                    mentions: mobj,
                                    timestamp: parseInt(fetchData.timestamp_precise)
                                };
                            })
                            .catch(err => log.error("forcedFetch", err))
                            .finally(function () {
                                if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
                                !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
                            });
                    } else callbackToReturn.delta = delta;
            
                    if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);

                    return !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
                }
            }
            return;
        }
    }
    switch (v.delta.class) {
        case "ReadReceipt": {
            var fmtMsg;
            try {
                fmtMsg = utils.formatDeltaReadReceipt(v.delta);
            } catch (err) {
                return log.error("Lỗi Nhẹ", err);
            }
            return (function () { globalCallback(null, fmtMsg); })();
        }
        case "AdminTextMessage": {
            switch (v.delta.type) {
                case "joinable_group_link_mode_change":
                case "magic_words":
                case "pin_messages_v2":
                case "change_thread_theme":
                case "change_thread_icon":
                case "change_thread_nickname":
                case "change_thread_admins":
                case "change_thread_approval_mode":
                case "group_poll":
                case "messenger_call_log":
                case "participant_joined_group_call":
                    var fmtMsg;
                    try {
                        fmtMsg = utils.formatDeltaEvent(v.delta);
                    } catch (err) {
                        console.log(v.delta);
                        return log.error("Lỗi Nhẹ", err);
                    }
                    return (function () { globalCallback(null, fmtMsg); })();
                }
            break;
        }
        //For group images
        case "ForcedFetch": {
            if (!v.delta.threadKey) return;
            var mid = v.delta.messageId;
            var tid = v.delta.threadKey.threadFbId;
            if (mid && tid) {
                const form = {
                    "av": ctx.globalOptions.pageID,
                    "queries": JSON.stringify({
                        "o0": {
                            //This doc_id is valid as of March 25, 2020
                            "doc_id": "2848441488556444",
                            "query_params": {
                                "thread_and_message_id": {
                                    "thread_id": tid.toString(),
                                    "message_id": mid,
                                }
                            }
                        }
                    })
                };

                defaultFuncs
                    .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
                    .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
                    .then((resData) => {
                        if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;

                        if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };

                        var fetchData = resData[0].o0.data.message;

                        if (utils.getType(fetchData) == "Object") {
                            log.info("forcedFetch", fetchData);
                            switch (fetchData.__typename) {
                                case "ThreadImageMessage":
                                    (!ctx.globalOptions.selfListen &&
                                        fetchData.message_sender.id.toString() === ctx.userID) ||
                                        !ctx.loggedIn ?
                                        undefined :
                                        (function () {
                                            globalCallback(null, {
                                                type: "change_thread_image",
                                                threadID: utils.formatID(tid.toString()),
                                                snippet: fetchData.snippet,
                                                timestamp: fetchData.timestamp_precise,
                                                author: fetchData.message_sender.id,
                                                image: {
                                                    attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
                                                    width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
                                                    height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
                                                    url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
                                                }
                                            });
                                        })();
                                    break;
                                case "UserMessage":
                                    log.info("ff-Return", {
                                        type: "message",
                                        senderID: utils.formatID(fetchData.message_sender.id),
                                        body: fetchData.message.text || "",
                                        threadID: utils.formatID(tid.toString()),
                                        messageID: fetchData.message_id,
                                        attachments: [{
                                            type: "share",
                                            ID: fetchData.extensible_attachment.legacy_attachment_id,
                                            url: fetchData.extensible_attachment.story_attachment.url,

                                            title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
                                            description: fetchData.extensible_attachment.story_attachment.description.text,
                                            source: fetchData.extensible_attachment.story_attachment.source,

                                            image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
                                            width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
                                            height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
                                            playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
                                            duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,

                                            subattachments: fetchData.extensible_attachment.subattachments,
                                            properties: fetchData.extensible_attachment.story_attachment.properties,
                                        }],
                                        mentions: {},
                                        timestamp: parseInt(fetchData.timestamp_precise),
                                        isGroup: (fetchData.message_sender.id != tid.toString())
                                    });
                                    globalCallback(null, {
                                        type: "message",
                                        senderID: utils.formatID(fetchData.message_sender.id),
                                        body: fetchData.message.text || "",
                                        threadID: utils.formatID(tid.toString()),
                                        messageID: fetchData.message_id,
                                        attachments: [{
                                            type: "share",
                                            ID: fetchData.extensible_attachment.legacy_attachment_id,
                                            url: fetchData.extensible_attachment.story_attachment.url,

                                            title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
                                            description: fetchData.extensible_attachment.story_attachment.description.text,
                                            source: fetchData.extensible_attachment.story_attachment.source,

                                            image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
                                            width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
                                            height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
                                            playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
                                            duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,

                                            subattachments: fetchData.extensible_attachment.subattachments,
                                            properties: fetchData.extensible_attachment.story_attachment.properties,
                                        }],
                                        mentions: {},
                                        timestamp: parseInt(fetchData.timestamp_precise),
                                        isGroup: (fetchData.message_sender.id != tid.toString())
                                    });
                            }
                        } else log.error("forcedFetch", fetchData);
                    })
                    .catch((err) => log.error("forcedFetch", err));
                }
            }
            break;
        case "ThreadName":
        case "ParticipantsAddedToGroupThread":
        case "ParticipantLeftGroupThread": {
            var formattedEvent;
            try {
                formattedEvent = utils.formatDeltaEvent(v.delta);
            } catch (err) {
                console.log(err);
                return log.error("Lỗi Nhẹ", err);
            }
            return (!ctx.globalOptions.selfListen && formattedEvent.author.toString() === ctx.userID) || !ctx.loggedIn ? undefined : (function () { globalCallback(null, formattedEvent); })();
        }
        case "NewMessage": {
            if (v.delta.attachments != undefined && v.delta.attachments.length == 1 && v.delta.attachments[0].mercury.extensible_attachment != undefined && v.delta.attachments[0].mercury.extensible_attachment.story_attachment.style_list.includes("message_live_location")) {
                v.delta.class = "UserLocation";
                var fmtMsg;
                try {
                    fmtMsg = utils.formatDeltaEvent(v.delta);
                } catch (err) {
                    console.log(v.delta);
                    return log.error("Lỗi Nhẹ", err);
                }
                return (function () { globalCallback(null, fmtMsg); })();
            }
        }
        break;
    }
}


function markDelivery(ctx, api, threadID, messageID) {
    if (threadID && messageID) {
        api.markAsDelivered(threadID, messageID, (err) => {
            if (err) log.error("markAsDelivered", err);
            else {
                if (ctx.globalOptions.autoMarkRead) {
                    api.markAsRead(threadID, (err) => {
                        if (err) log.error("markAsDelivered", err);
                    });
                }
            }
        });
    }
}

module.exports = function (defaultFuncs, api, ctx) {
    var globalCallback = identity;
    getSeqID = function getSeqID() {
        ctx.t_mqttCalled = false;
        defaultFuncs
            .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
            .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
            .then((resData) => {
                if (utils.getType(resData) != "Array") {
                    if (global.Fca.Require.FastConfig.AutoLogin) {
                        return global.Fca.Require.logger.Warning(global.Fca.Require.Language.Index.AutoLogin, function() {
                            return global.Fca.Action('AutoLogin');
                        });
                    }
                    else if (!global.Fca.Require.FastConfig.AutoLogin) {
                        return global.Fca.Require.logger.Error(global.Fca.Require.Language.Index.ErrAppState);
                    }
                    return;
                }
                else {
                    if (resData && resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
                    if (resData[resData.length - 1].successful_results === 0) throw { error: "getSeqId: there was no successful_results", res: resData };
                    if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
                        ctx.lastSeqId = resData[0].o0.data.viewer.message_threads.sync_sequence_id;
                        listenMqtt(defaultFuncs, api, ctx, globalCallback);
                    } 
                    else throw { error: "getSeqId: no sync_sequence_id found.", res: resData };
                }
            })
            .catch((err) => {
                log.error("getSeqId", err);
                if (utils.getType(err) == "Object" && err.error === global.Fca.Require.Language.Index.ErrAppState) ctx.loggedIn = false;
                return globalCallback(err);
            });
    };

    return function (callback) {
        class MessageEmitter extends EventEmitter {
            stopListening(callback) {
                callback = callback || (() => { });
                globalCallback = identity;
                if (ctx.mqttClient) {
                    ctx.mqttClient.unsubscribe("/webrtc");
                    ctx.mqttClient.unsubscribe("/rtc_multi");
                    ctx.mqttClient.unsubscribe("/onevc");
                    ctx.mqttClient.publish("/browser_close", "{}");
                    ctx.mqttClient.end(false, function (...data) {
                        ctx.mqttClient = undefined;
                    });
                }
                global.Fca.Data.StopListening = true;
            }
        }

        var msgEmitter = new MessageEmitter();
        globalCallback = (callback || function (error, message) {
            if (error) return msgEmitter.emit("error", error);
            msgEmitter.emit("message", message);
        });

        //Reset some stuff
        if (!ctx.firstListen) ctx.lastSeqId = null;
        ctx.syncToken = undefined;
        ctx.t_mqttCalled = false;

        //Same request as getThreadList
        form = {
            "av": ctx.globalOptions.pageID,
            "queries": JSON.stringify({
                "o0": {
                    "doc_id": "3336396659757871",
                    "query_params": {
                        "limit": 1,
                        "before": null,
                        "tags": ["INBOX"],
                        "includeDeliveryReceipts": false,
                        "includeSeqID": true
                    }
                }
            })
        };

        if (!ctx.firstListen || !ctx.lastSeqId) getSeqID();
        else listenMqtt(defaultFuncs, api, ctx, globalCallback);
        ctx.firstListen = false;
        return msgEmitter;
    };
};