priyanshu-fca
Version:
A Facebook chat API
520 lines (477 loc) • 19.1 kB
JavaScript
;
const utils = require("./utils");
const log = require("npmlog");
let checkVerified = null;
const defaultLogRecordSize = 100;
log.maxRecordSize = defaultLogRecordSize;
function setOptions(globalOptions, options) {
Object.keys(options).map(function (key) {
switch (key) {
case 'online':
globalOptions.online = Boolean(options.online);
break;
case 'logLevel':
log.level = options.logLevel;
globalOptions.logLevel = options.logLevel;
break;
case 'logRecordSize':
log.maxRecordSize = options.logRecordSize;
globalOptions.logRecordSize = options.logRecordSize;
break;
case 'selfListen':
globalOptions.selfListen = Boolean(options.selfListen);
break;
case 'selfListenEvent':
globalOptions.selfListenEvent = options.selfListenEvent;
break;
case 'listenEvents':
globalOptions.listenEvents = Boolean(options.listenEvents);
break;
case 'pageID':
globalOptions.pageID = options.pageID.toString();
break;
case 'updatePresence':
globalOptions.updatePresence = Boolean(options.updatePresence);
break;
case 'forceLogin':
globalOptions.forceLogin = Boolean(options.forceLogin);
break;
case 'userAgent':
globalOptions.userAgent = options.userAgent;
break;
case 'autoMarkDelivery':
globalOptions.autoMarkDelivery = Boolean(options.autoMarkDelivery);
break;
case 'autoMarkRead':
globalOptions.autoMarkRead = Boolean(options.autoMarkRead);
break;
case 'listenTyping':
globalOptions.listenTyping = Boolean(options.listenTyping);
break;
case 'proxy':
if (typeof options.proxy != "string") {
delete globalOptions.proxy;
utils.setProxy();
} else {
globalOptions.proxy = options.proxy;
utils.setProxy(globalOptions.proxy);
}
break;
case 'autoReconnect':
globalOptions.autoReconnect = Boolean(options.autoReconnect);
break;
case 'emitReady':
globalOptions.emitReady = Boolean(options.emitReady);
break;
default:
log.warn("setOptions", "Unrecognized option given to setOptions: " + key);
break;
}
});
}
//new update...
function BypassAutomationNotification(resp, jar, globalOptions, appstate, ID) {
global.BypassAutomationNotification = BypassAutomationNotification
try {
let UID;
if (ID) UID = ID
else {
UID = (appstate.find(i => i.key == 'c_user') || appstate.find(i => i.key == 'i_user'))
UID = UID.value;
}
if (resp !== undefined) {
if (resp.request.uri && resp.request.uri.href.includes("https://www.facebook.com/checkpoint/")) {
if (resp.request.uri.href.includes('601051028565049')) {
const fb_dtsg = utils.getFrom(resp.body, '["DTSGInitData",[],{"token":"', '","');
const jazoest = utils.getFrom(resp.body, 'jazoest=', '",');
const lsd = utils.getFrom(resp.body, "[\"LSD\",[],{\"token\":\"", "\"}");
const FormBypass = {
av: UID,
fb_dtsg, jazoest, lsd,
fb_api_caller_class: "RelayModern",
fb_api_req_friendly_name: "FBScrapingWarningMutation",
variables: JSON.stringify({}),
server_timestamps: true,
doc_id: 6339492849481770
}
return utils.post("https://www.facebook.com/api/graphql/", jar, FormBypass, globalOptions)
.then(utils.saveCookies(jar)).then(function(res) {
log.warn("login", "Checkpoint detected. Bypass done...");
return process.exit(1);
});
}
else {
return resp;
}
}
else {
return resp
}
}
else {
return utils.get('https://www.facebook.com/', jar, null, globalOptions).then(function(res) {
if (res.request.uri && res.request.uri.href.includes("https://www.facebook.com/checkpoint/")) {
if (res.request.uri.href.includes('601051028565049')) return { Status: true, Body: res.body }
else return { Status: false, Body: res.body }
}
else return { Status: false, Body: res.body }
}).then(function(res) {
if (res.Status === true) {
const fb_dtsg = utils.getFrom(res.Body, '["DTSGInitData",[],{"token":"', '","');
const jazoest = utils.getFrom(res.Body, 'jazoest=', '",');
const lsd = utils.getFrom(res.Body, "[\"LSD\",[],{\"token\":\"", "\"}");
const FormBypass = {
av: UID,
fb_dtsg, jazoest, lsd,
fb_api_caller_class: "RelayModern",
fb_api_req_friendly_name: "FBScrapingWarningMutation",
variables: JSON.stringify({}),
server_timestamps: true,
doc_id: 6339492849481770
}
return utils.post("https://www.facebook.com/api/graphql/", jar, FormBypass, globalOptions).then(utils.saveCookies(jar))
.then(res => {
log.warn("login", "Checkpoint detected. Bypass done.....");
return res
})
}
else return res;
})
.then(function(res) {
return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar))
})
.then(function(res) {
return process.exit(1)
})
}
}
catch (e) {
console.log(e)
}
}
function buildAPI(globalOptions, html, jar) {
const fb_dtsg = utils.getFroms(html, '["DTSGInitData",[],{"token":"', '","')[0]; //my brain is not braining on here.
const maybeCookie = jar.getCookies("https://www.facebook.com").filter(function(val) {
return val.cookieString().split("=")[0] === "c_user";
});
if (maybeCookie.length === 0) throw { error: "Error retrieving userID. This can be caused by a lot of things, including getting blocked by Facebook for logging in from an unknown location. Try logging in with a browser to verify." };
if (html.indexOf("/checkpoint/block/?next") > -1) log.warn("login", "Checkpoint detected. Please log in with a browser to verify.");
const userID = maybeCookie[0].cookieString().split("=")[1].toString();
log.info("login", `Logged in as ${userID}`);
try {
clearInterval(checkVerified);
} catch (_) { }
const clientID = (Math.random() * 2147483648 | 0).toString(16);
const CHECK_MQTT = {
oldFBMQTTMatch: html.match(/irisSeqID:"(.+?)",appID:219994525426954,endpoint:"(.+?)"/),
newFBMQTTMatch: html.match(/{"app_id":"219994525426954","endpoint":"(.+?)","iris_seq_id":"(.+?)"}/),
legacyFBMQTTMatch: html.match(/\["MqttWebConfig",\[\],{"fbid":"(.*?)","appID":219994525426954,"endpoint":"(.*?)","pollingEndpoint":"(.*?)"/)
}
// all available regions =))
/**
* PRN = Pacific Northwest Region
* VLL = Valley Region
* ASH = Ashburn Region
* DFW = Dallas/Fort Worth Region
* LLA = Los Angeles Region
* FRA = Frankfurt
* SIN = Singapore
* NRT = Tokyo (Japan)
* HKG = Hong Kong
* SYD = Sydney
*/
let Slot = Object.keys(CHECK_MQTT);
var mqttEndpoint,region,irisSeqID;
Object.keys(CHECK_MQTT).map(function(MQTT) {
if (CHECK_MQTT[MQTT] && !region) {
switch (Slot.indexOf(MQTT)) {
case 0: {
irisSeqID = CHECK_MQTT[MQTT][1];
mqttEndpoint = CHECK_MQTT[MQTT][2].replace(/\\\//g, "/");
region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
return;
}
case 1: {
irisSeqID = CHECK_MQTT[MQTT][2];
mqttEndpoint = CHECK_MQTT[MQTT][1].replace(/\\\//g, "/");
region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
return;
}
case 2: {
mqttEndpoint = CHECK_MQTT[MQTT][2].replace(/\\\//g, "/"); //this really important.
region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
return;
}
}
return;
}
});
const regions = [
{
code: "PRN",
name: "Pacific Northwest Region",
location: "Khu vực Tây Bắc Thái Bình Dương"
},
{
code: "VLL",
name: "Valley Region",
location: "Valley"
},
{
code: "ASH",
name: "Ashburn Region",
location: "Ashburn"
},
{
code: "DFW",
name: "Dallas/Fort Worth Region",
location: "Dallas/Fort Worth"
},
{
code: "LLA",
name: "Los Angeles Region",
location: "Los Angeles"
},
{
code: "FRA",
name: "Frankfurt",
location: "Frankfurt"
},
{
code: "SIN",
name: "Singapore",
location: "Singapore"
},
{
code: "NRT",
name: "Tokyo",
location: "Japan"
},
{
code: "HKG",
name: "Hong Kong",
location: "Hong Kong"
},
{
code: "SYD",
name: "Sydney",
location: "Sydney"
},
{
code: "PNB",
name: "Pacific Northwest - Beta",
location: "Pacific Northwest "
}
];
if (!region) {
region = ['prn',"pnb","vll","hkg","sin"][Math.random()*5|0];
}
if (!mqttEndpoint) {
mqttEndpoint = "wss://edge-chat.facebook.com/chat?region=" + region;
}
log.info('login', `Sever region ${region}`);
const Location = regions.find(r => r.code === region.toUpperCase());
const ctx = {
userID: userID,
jar: jar,
clientID: clientID,
globalOptions: globalOptions,
loggedIn: true,
access_token: 'NONE',
clientMutationId: 0,
mqttClient: undefined,
lastSeqId: irisSeqID,
syncToken: undefined,
mqttEndpoint: mqttEndpoint,
region: region,
firstListen: true,
req_ID: 0,
callback_Task: {},
fb_dtsg
};
const api = {
setOptions: setOptions.bind(null, globalOptions),
getAppState: function getAppState() {
return utils.getAppState(jar);
// return appState.filter((item, index, self) => self.findIndex((t) => { return t.key === item.key }) === index);
}
};
if (region && mqttEndpoint) {
//fuck this shit..
}
else {
if (bypass_region) {
}
else {
api["htmlData"] = html;
}
};
// if (noMqttData) api["htmlData"] = noMqttData;
const defaultFuncs = utils.makeDefaults(html, userID, ctx);
require('fs').readdirSync(__dirname + '/src/')
.filter((v) => v.endsWith('.js'))
.map((v) => {
const functionName = v.replace('.js', '');
api[functionName] = require('./src/' + v)(defaultFuncs, api, ctx);
});
return {
ctx: ctx,
defaultFuncs: defaultFuncs,
api: api
};
}
// unfortunately login via credentials no longer works,so instead of login via credentials, use login via appstate intead.
function loginHelper(appState, email, password, globalOptions, callback, prCallback ) {
let mainPromise = null;
const jar = utils.getJar();
// If we're given an appState we loop through it and save each cookie
// back into the jar.
if (appState) {
// check and convert cookie to appState
if (utils.getType(appState) === 'Array' && appState.some(c => c.name)) {
appState = appState.map(c => {
c.key = c.name;
delete c.name;
return c;
})
}
else if (utils.getType(appState) === 'String') {
const arrayAppState = [];
appState.split(';').forEach(c => {
const [key, value] = c.split('=');
arrayAppState.push({
key: (key || "").trim(),
value: (value || "").trim(),
domain: ".facebook.com",
path: "/",
expires: new Date().getTime() + 1000 * 60 * 60 * 24 * 365
});
});
appState = arrayAppState;
}
appState.map(function (c) {
const str = c.key + "=" + c.value + "; expires=" + c.expires + "; domain=" + c.domain + "; path=" + c.path + ";";
jar.setCookie(str, "http://" + c.domain);
});
// Load the main page.
mainPromise = utils
.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
.then(utils.saveCookies(jar));
} else {
if (email) {
throw { error: "Unfortunately login via credentials is no longer work, please use login via appstate instead." };
}
else {
throw { error: "Please provide appstate." };
}
}
function CheckAndFixErr(res, fastSwitch) {
if (fastSwitch) return res;
let reg_antierr = /7431627028261359627/gs; // :>
if (reg_antierr.test(res.body)) {
const Data = JSON.stringify(res.body);
const Dt_Check = Data.split('2Fhome.php&gfid=')[1];
if (Dt_Check == undefined) return res
const fid = Dt_Check.split("\\\\")[0];//fix
if (Dt_Check == undefined || Dt_Check == "") return res
const final_fid = fid.split(`\\`)[0];
if (final_fid == undefined || final_fid == '') return res;
const redirectlink = redirect[1] + "a/preferences.php?basic_site_devices=m_basic&uri=" + encodeURIComponent("https://m.facebook.com/home.php") + "&gfid=" + final_fid;
bypass_region_err = true;
return utils.get(redirectlink, jar, null, globalOptions).then(utils.saveCookies(jar));
}
else return res
}
function Redirect(res,fastSwitch) {
if (fastSwitch) return res;
var reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
redirect = reg.exec(res.body);
if (redirect && redirect[1]) return utils.get(redirect[1], jar, null, globalOptions)
return res;
}
let redirect = [1, "https://m.facebook.com/"];
let bypass_region_err = false;
var ctx,api;
mainPromise = mainPromise
.then(res => Redirect(res))
.then(res => CheckAndFixErr(res))
//fix via login with defaut UA return WWW.facebook.com not m.facebook.com
.then(function(res) {
if (global.OnAutoLoginProcess) return res;
else {
let Regex_Via = /MPageLoadClientMetrics/gs; //default for normal account, can easily get region, without this u can't get region in some case but u can run normal
if (!Regex_Via.test(res.body)) {
return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
}
else return res
}
})
.then(res => BypassAutomationNotification(res, jar, globalOptions, appState))
.then(res => Redirect(res, global.OnAutoLoginProcess))
.then(res => CheckAndFixErr(res, global.OnAutoLoginProcess))
.then(function(res){
const html = res.body,Obj = buildAPI(globalOptions, html, jar,bypass_region_err);
ctx = Obj.ctx;
api = Obj.api;
return res;
});
if (globalOptions.pageID) {
mainPromise = mainPromise
.then(function() {
return utils.get('https://www.facebook.com/' + ctx.globalOptions.pageID + '/messages/?section=messages&subsection=inbox', ctx.jar, null, globalOptions);
})
.then(function(resData) {
const url = utils.getFrom(resData.body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
url = url.substring(0, url.length - 1);
return utils.get('https://www.facebook.com' + url, ctx.jar, null, globalOptions);
});
}
// At the end we call the callback or catch an exception
mainPromise
.then(function () {
log.info("login", 'Done logging in.');
return callback(null, api);
})
.catch(function (e) {
log.error("login", e.error || e);
callback(e);
});
}
function login(loginData, options, callback) {
if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
callback = options;
options = {};
}
const globalOptions = {
selfListen: false,
listenEvents: true,
listenTyping: false,
updatePresence: false,
forceLogin: false,
autoMarkDelivery: true,
autoMarkRead: false,
autoReconnect: true,
logRecordSize: defaultLogRecordSize,
online: true,
emitReady: false,
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:132.0) Gecko/20100101 Firefox/132.0"
};
setOptions(globalOptions, options);
let prCallback = null;
if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
let rejectFunc = null;
let resolveFunc = null;
var returnPromise = new Promise(function(resolve, reject) {
resolveFunc = resolve;
rejectFunc = reject;
});
prCallback = function(error, api) {
if (error) return rejectFunc(error);
return resolveFunc(api);
};
callback = prCallback;
}
loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
return returnPromise;
}
module.exports = login;