UNPKG

hut-chat-api

Version:

A Facebook chat API that doesn't rely on XMPP and This new Logined and get the seqID ( CC PROJECTS AND CHATBOT COMMUNITY )

895 lines (825 loc) 31 kB
/* eslint-disable no-redeclare */ "use strict"; const utils = require("../utils"); const log = require("npmlog"); const mqtt = require('mqtt'); const websocket = require('websocket-stream'); const HttpsProxyAgent = require('https-proxy-agent'); const EventEmitter = require('events'); const identity = function () { }; let form = {}; let getSeqId = function () { }; const topics = [ "/legacy_web", "/webrtc", "/rtc_multi", "/onevc", "/br_sr", //Notification //Need to publish /br_sr right after this "/sr_res", "/t_ms", "/thread_typing", "/orca_typing_notifications", "/notify_disconnect", //Need to publish /messenger_sync_create_queue right after this "/orca_presence", //Will receive /sr_res right here. "/legacy_web_mtouch" // "/inbox", // "/mercury", // "/messaging_events", // "/orca_message_notifications", // "/pp", // "/webrtc_response", ]; function listenMqtt(defaultFuncs, api, ctx, globalCallback) { //Don't really know what this does but I think it's for the active state? //TODO: Move to ctx when implemented const chatOn = ctx.globalOptions.online; const foreground = false; const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1; const GUID = utils.getGUID(); const username = { u: ctx.i_userID || ctx.userID, s: sessionID, chat_on: chatOn, fg: foreground, d: GUID, ct: "websocket", //App id from facebook aid: "219994525426954", mqtt_sid: "", cp: 3, ecp: 10, st: [], pm: [], dc: "", no_auto_fg: true, gas: null, pack: [], a: ctx.globalOptions.userAgent, aids: null }; const cookies = ctx.jar.getCookies('https://www.facebook.com').join('; '); let host; if (ctx.mqttEndpoint) { host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${GUID}`; } else if (ctx.region) { host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${GUID}`; } else { host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${GUID}`; } const 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 }, origin: 'https://www.facebook.com', protocolVersion: 13, binaryType: 'arraybuffer' }, keepalive: 60, reschedulePings: true, reconnectPeriod: 3 }; if (typeof ctx.globalOptions.proxy != "undefined") { const agent = new HttpsProxyAgent(ctx.globalOptions.proxy); options.wsOptions.agent = agent; } ctx.mqttClient = new mqtt.Client(_ => websocket(host, options.wsOptions), options); const mqttClient = ctx.mqttClient; mqttClient.on('error', function (err) { log.error("listenMqtt", err); mqttClient.end(); if (ctx.globalOptions.autoReconnect) { listenMqtt(defaultFuncs, api, ctx, globalCallback); } else { utils.checkLiveCookie(ctx, defaultFuncs) .then(res => { globalCallback({ type: "stop_listen", error: "Connection refused: Server unavailable" }, null); }) .catch(err => { globalCallback({ type: "account_inactive", error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com" }, null); }); } }); mqttClient.on('close', function () { }); mqttClient.on('connect', function () { topics.forEach(function (topicsub) { mqttClient.subscribe(topicsub); }); let topic; const queue = { sync_api_version: 10, max_deltas_able_to_process: 1000, delta_batch_size: 500, encoding: "JSON", entity_fbid: ctx.i_userID || ctx.userID }; 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 }); // set status online // fix by NTKhang mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 }); mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 }); const rTimeout = setTimeout(function () { mqttClient.end(); listenMqtt(defaultFuncs, api, ctx, globalCallback); }, 5000); ctx.tmsWait = function () { clearTimeout(rTimeout); ctx.globalOptions.emitReady ? globalCallback({ type: "ready", error: null }) : ""; delete ctx.tmsWait; }; }); mqttClient.on('message', function (topic, message, _packet) { let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message; try { jsonMessage = JSON.parse(jsonMessage); } catch (e) { jsonMessage = {}; } if (jsonMessage.type === "jewel_requests_add") { globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() }); } else if (jsonMessage.type === "jewel_requests_remove_old") { globalCallback(null, { type: "friend_request_cancel", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() }); } else 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 (const i in jsonMessage.deltas) { const delta = jsonMessage.deltas[i]; parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta }); } } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") { const 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 (const i in jsonMessage.list) { const data = jsonMessage.list[i]; const userID = data["u"]; const presence = { type: "presence", userID: userID.toString(), //Convert to ms timestamp: data["l"] * 1000, statuses: data["p"] }; (function () { globalCallback(null, presence); })(); } } } }); } 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 (i == (v.delta.attachments || []).length) { let fmtMsg; try { fmtMsg = utils.formatDeltaMessage(v); } catch (err) { return globalCallback({ error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-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.i_userID || fmtMsg.senderID === ctx.userID) ? undefined : (function () { globalCallback(null, fmtMsg); })(); } else { if (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") { const clientPayload = utils.decodeClientPayload( v.delta.payload ); if (clientPayload && clientPayload.deltas) { for (const i in clientPayload.deltas) { const 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 == 0 ? delta.deltaMessageReaction.userId.toString() : delta.deltaMessageReaction.senderId.toString(), userID: (delta.deltaMessageReaction.userId || delta.deltaMessageReaction.senderId).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.deltaRemoveMessage && !!ctx.globalOptions.listenEvents) { (function () { globalCallback(null, { type: "message_self_delete", threadID: (delta.deltaRemoveMessage.threadKey.threadFbId ? delta.deltaRemoveMessage.threadKey.threadFbId : delta.deltaRemoveMessage.threadKey .otherUserFbId).toString(), messageID: delta.deltaRemoveMessage.messageIds.length == 1 ? delta.deltaRemoveMessage.messageIds[0] : delta.deltaRemoveMessage.messageIds, senderID: api.getCurrentUserID(), deletionTimestamp: delta.deltaRemoveMessage.deletionTimestamp, timestamp: delta.deltaRemoveMessage.timestamp }); })(); } else if (delta.deltaMessageReply) { //Mention block - #1 let mdata = delta.deltaMessageReply.message === undefined ? [] : delta.deltaMessageReply.message.data === undefined ? [] : delta.deltaMessageReply.message.data.prng === undefined ? [] : JSON.parse(delta.deltaMessageReply.message.data.prng); let m_id = mdata.map(u => u.i); let m_offset = mdata.map(u => u.o); let m_length = mdata.map(u => u.l); const mentions = {}; for (let 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# const 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) { const mercury = JSON.parse(att.mercuryJSON); Object.assign(att, mercury); return att; }).map(att => { let x; try { x = utils._formatAttachment(att); } catch (ex) { x = att; x.error = ex; x.type = "unknown"; } return x; }), body: delta.deltaMessageReply.message.body || "", isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId, mentions: mentions, timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp, participantIDs: (delta.deltaMessageReply.message.messageMetadata.cid.canonicalParticipantFbids || 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); const rmentions = {}; for (let 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) { const mercury = JSON.parse(att.mercuryJSON); Object.assign(att, mercury); return att; }).map(att => { let x; try { x = utils._formatAttachment(att); } catch (ex) { x = att; x.error = ex; x.type = "unknown"; } return x; }), body: delta.deltaMessageReply.repliedToMessage.body || "", isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId, mentions: rmentions, timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp }; } 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 }; } const fetchData = resData[0].o0.data.message; const mobj = {}; for (const 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 = { threadID: callbackToReturn.threadID, messageID: fetchData.message_id, senderID: fetchData.message_sender.id.toString(), attachments: fetchData.message.blob_attachment.map(att => { let x; try { x = utils._formatAttachment({ blob_attachment: att }); } catch (ex) { x = att; x.error = ex; x.type = "unknown"; } return x; }), 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.i_userID || 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.i_userID || 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/ntkhang03/fb-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": case "change_thread_quick_reaction": case "change_thread_admins": case "group_poll": case "joinable_group_link_mode_change": case "magic_words": case "change_thread_approval_mode": case "messenger_call_log": case "participant_joined_group_call": 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/ntkhang03/fb-chat-api/issues.", detail: err, res: v.delta, type: "parse_error" }); } return (function () { globalCallback(null, fmtMsg); })(); default: return; } //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 }; } const fetchData = resData[0].o0.data.message; if (utils.getType(fetchData) == "Object") { log.info("forcedFetch", fetchData); switch (fetchData.__typename) { case "ThreadImageMessage": (!ctx.globalOptions.selfListenEvent && (fetchData.message_sender.id.toString() === ctx.i_userID || fetchData.message_sender.id.toString() === ctx.userID)) || !ctx.loggedIn ? undefined : (function () { globalCallback(null, { type: "event", threadID: utils.formatID(tid.toString()), messageID: fetchData.message_id, logMessageType: "log:thread-image", logMessageData: { 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 }, logMessageBody: fetchData.snippet, timestamp: fetchData.timestamp_precise, author: fetchData.message_sender.id }); })(); 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), participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []), 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), participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []), 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": case "ApprovalQueue": 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/ntkhang03/fb-chat-api/issues.", detail: err, res: v.delta, type: "parse_error" }); } return (!ctx.globalOptions.selfListenEvent && (formattedEvent.author.toString() === ctx.i_userID || 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("markAsDelivered", err); } else { if (ctx.globalOptions.autoMarkRead) { api.markAsRead(threadID, (err) => { if (err) { log.error("markAsDelivered", err); } }); } } }); } } // function getSeqId(defaultFuncs, api, ctx, globalCallback) { // const jar = ctx.jar; // utils // .get('https://www.facebook.com/', jar, null, ctx.globalOptions, { noRef: true }) // .then(utils.saveCookies(jar)) // .then(function (resData) { // const html = resData.body; // const oldFBMQTTMatch = html.match(/irisSeqID:"(.+?)",appID:219994525426954,endpoint:"(.+?)"/); // let mqttEndpoint = null; // let region = null; // let irisSeqID = null; // let noMqttData = null; // if (oldFBMQTTMatch) { // irisSeqID = oldFBMQTTMatch[1]; // mqttEndpoint = oldFBMQTTMatch[2]; // region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase(); // log.info("login", `Got this account's message region: ${region}`); // } else { // const newFBMQTTMatch = html.match(/{"app_id":"219994525426954","endpoint":"(.+?)","iris_seq_id":"(.+?)"}/); // if (newFBMQTTMatch) { // irisSeqID = newFBMQTTMatch[2]; // mqttEndpoint = newFBMQTTMatch[1].replace(/\\\//g, "/"); // region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase(); // log.info("login", `Got this account's message region: ${region}`); // } else { // const legacyFBMQTTMatch = html.match(/(\["MqttWebConfig",\[\],{fbid:")(.+?)(",appID:219994525426954,endpoint:")(.+?)(",pollingEndpoint:")(.+?)(3790])/); // if (legacyFBMQTTMatch) { // mqttEndpoint = legacyFBMQTTMatch[4]; // region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase(); // log.warn("login", `Cannot get sequence ID with new RegExp. Fallback to old RegExp (without seqID)...`); // log.info("login", `Got this account's message region: ${region}`); // log.info("login", `[Unused] Polling endpoint: ${legacyFBMQTTMatch[6]}`); // } else { // log.warn("login", "Cannot get MQTT region & sequence ID."); // noMqttData = html; // } // } // } // ctx.lastSeqId = irisSeqID; // ctx.mqttEndpoint = mqttEndpoint; // ctx.region = region; // if (noMqttData) { // api["htmlData"] = noMqttData; // } // listenMqtt(defaultFuncs, api, ctx, globalCallback); // }) // .catch(function (err) { // log.error("getSeqId", err); // }); // } module.exports = function (defaultFuncs, api, ctx) { let 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") throw { error: "Not logged in", res: resData }; 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 === "Not logged in") 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) { callback(data); ctx.mqttClient = undefined; }); } } async stopListeningAsync() { return new Promise((resolve) => { this.stopListening(resolve); }); } } const 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; 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(defaultFuncs, api, ctx, globalCallback); } else { listenMqtt(defaultFuncs, api, ctx, globalCallback); } api.stopListening = msgEmitter.stopListening; api.stopListeningAsync = msgEmitter.stopListeningAsync; return msgEmitter; }; };