UNPKG

@dongdev/fca-unofficial

Version:

A Facebook chat API without XMPP, will not be deprecated after April 30th, 2015.

379 lines (370 loc) 12.6 kB
"use strict"; const utils = require("./utils"); const log = require("npmlog"); const { execSync, exec } = require('child_process'); const { promises: fsPromises, readFileSync } = require('fs'); const fs = require('fs'); const axios = require('axios'); const path = require('path'); const models = require("./lib/database/models"); const logger = require("./lib/logger"); let checkVerified = null; const defaultLogRecordSize = 100; log.maxRecordSize = defaultLogRecordSize; const defaultConfig = { autoUpdate: true, mqtt: { enabled: true, reconnectInterval: 3600, } }; const configPath = path.join(process.cwd(), "fca-config.json"); let config; if (!fs.existsSync(configPath)) { fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2)); config = defaultConfig; } else { try { const fileContent = fs.readFileSync(configPath, 'utf8'); config = JSON.parse(fileContent); config = { ...defaultConfig, ...config }; } catch (err) { logger("Error reading config file, using defaults", "error"); config = defaultConfig; } } global.fca = { config: config }; const Boolean_Option = [ "online", "selfListen", "listenEvents", "updatePresence", "forceLogin", "autoMarkDelivery", "autoMarkRead", "listenTyping", "autoReconnect", "emitReady", ]; function setOptions(globalOptions, options) { Object.keys(options).map(function (key) { switch (Boolean_Option.includes(key)) { case true: { globalOptions[key] = Boolean(options[key]); break; } case false: { switch (key) { case "pauseLog": { if (options.pauseLog) log.pause(); else log.resume(); break; } case "logLevel": { log.level = options.logLevel; globalOptions.logLevel = options.logLevel; break; } case "logRecordSize": { log.maxRecordSize = options.logRecordSize; globalOptions.logRecordSize = options.logRecordSize; break; } case "pageID": { globalOptions.pageID = options.pageID.toString(); break; } case "userAgent": { globalOptions.userAgent = options.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"; break; } case "proxy": { if (typeof options.proxy != "string") { delete globalOptions.proxy; utils.setProxy(); } else { globalOptions.proxy = options.proxy; utils.setProxy(globalOptions.proxy); } break; } default: { log.warn( "setOptions", "Unrecognized option given to setOptions: " + key ); break; } } break; } } }); } function buildAPI(globalOptions, html, jar) { const cookies = jar.getCookies("https://www.facebook.com"); const userCookie = cookies.find(c => c.cookieString().startsWith("c_user=")); const tiktikCookie = cookies.find(c => c.cookieString().startsWith("i_user=")); if (userCookie.length === 0 && tiktikCookie.length === 0) { return log.error('login', "Không tìm thấy cookie cho người dùng, vui lòng kiểm tra lại thông tin đăng nhập") } else if (!userCookie && !tiktikCookie) { return log.error('login', "Không tìm thấy cookie cho người dùng, vui lòng kiểm tra lại thông tin đăng nhập") } else if (html.includes("/checkpoint/block/?next")) { return log.error('login', "Appstate die, vui lòng thay cái mới!", 'error'); } const userID = (tiktikCookie || userCookie).cookieString().split("=")[1]; const i_userID = tiktikCookie ? tiktikCookie.cookieString().split("=")[1] : null; logger(`Logged in as ${userID}`, 'info'); try { clearInterval(checkVerified); } catch (_) { } const clientID = ((Math.random() * 2147483648) | 0).toString(16); let mqttEndpoint, region, fb_dtsg, irisSeqID; try { const endpointMatch = html.match(/"endpoint":"([^"]+)"/); if (endpointMatch) { mqttEndpoint = endpointMatch[1].replace(/\\\//g, "/"); const url = new URL(mqttEndpoint); region = url.searchParams.get("region")?.toUpperCase() || "PRN"; } logger(`Sever region ${region}`, 'info'); } catch (e) { log.warning("login", "Not MQTT endpoint"); } const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/); if (tokenMatch) { fb_dtsg = tokenMatch[1]; } (async () => { try { await models.sequelize.authenticate(); await models.syncAll(); } catch (error) { console.error(error); console.error('Database connection failed:', error.message); } })(); logger(`FCA fix by DongDev`, 'info'); const ctx = { userID: userID, i_userID: i_userID, jar: jar, clientID: clientID, globalOptions: globalOptions, loggedIn: true, access_token: "NONE", clientMutationId: 0, mqttClient: undefined, lastSeqId: irisSeqID, syncToken: undefined, mqttEndpoint, region, firstListen: true, fb_dtsg, wsReqNumber: 0, wsTaskNumber: 0 }; const api = { setOptions: setOptions.bind(null, globalOptions), getAppState: function getAppState() { const appState = utils.getAppState(jar); return appState.filter( (item, index, self) => self.findIndex((t) => { return t.key === item.key; }) === index ); }, }; const defaultFuncs = utils.makeDefaults(html, i_userID || userID, ctx); require("fs") .readdirSync(__dirname + "/src/") .filter((v) => v.endsWith(".js")) .map(function (v) { api[v.replace(".js", "")] = require("./src/" + v)(defaultFuncs, api, ctx); }); api.listen = api.listenMqtt; setInterval(async () => { api .refreshFb_dtsg() .then(() => { logger("Successfully refreshed fb_dtsg", 'info'); }) .catch((err) => { console.error("An error occurred while refreshing fb_dtsg", err); }); }, 1000 * 60 * 60 * 24); return { ctx, defaultFuncs, api }; } function loginHelper(appState, email, password, globalOptions, callback, prCallback) { let mainPromise = null; const jar = utils.getJar(); if (appState) { try { appState = JSON.parse(appState); } catch (e) { try { appState = appState; } catch (e) { return callback(new Error("Failed to parse appState")); } } try { appState.forEach(c => { const str = `${c.key}=${c.value}; expires=${c.expires}; domain=${c.domain}; path=${c.path};`; jar.setCookie(str, "http://" + c.domain); }); mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }) .then(utils.saveCookies(jar)); } catch (e) { process.exit(0); } } else { mainPromise = utils .get("https://www.facebook.com/", null, null, globalOptions, { noRef: true }) .then(utils.saveCookies(jar)) .then(makeLogin(jar, email, password, globalOptions, callback, prCallback)) .then(() => utils.get('https://www.facebook.com/', jar, null, globalOptions).then(utils.saveCookies(jar))); } function handleRedirect(res) { const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/; const redirect = reg.exec(res.body); if (redirect && redirect[1]) { return utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar)); } return res; } let ctx, api; mainPromise = mainPromise .then(handleRedirect) .then(res => { const mobileAgentRegex = /MPageLoadClientMetrics/gs; if (!mobileAgentRegex.test(res.body)) { globalOptions.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"; return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar)); } return res; }) .then(handleRedirect) .then(res => { const html = res.body; const Obj = buildAPI(globalOptions, html, jar); ctx = Obj.ctx; api = Obj.api; return res; }); if (globalOptions.pageID) { mainPromise = mainPromise .then(() => utils.get(`https://www.facebook.com/${globalOptions.pageID}/messages/?section=messages&subsection=inbox`, jar, null, globalOptions)) .then(resData => { let 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, jar, null, globalOptions); }); } mainPromise .then(async () => { // if (global.fca.config.autoUpdate) { // try { // exec('npm view @dongdev/fca-unofficial version', async (error, stdout, stderr) => { // if (error) { // logger('Error checking version: ' + error, 'error'); // return; // } // const npmVersion = stdout.trim(); // const localbrand = JSON.parse(readFileSync('./node_modules/@dongdev/fca-unofficial/package.json')).version; // if (localbrand !== npmVersion) { // logger(`New Version Published: ${localbrand} => ${npmVersion}`, 'warn'); // logger(`Perform Automatic Update to the Latest Version !`, 'warn'); // try { // execSync('npm install @dongdev/fca-unofficial@latest', { stdio: 'inherit' }); // logger("Upgrade Version Successfully!", "[ FCA-UNO ] >") // logger('Restarting...', '[ FCA-UNO ] >'); // await new Promise(resolve => setTimeout(resolve, 5 * 1000)); // console.clear(); // process.exit(1); // } catch (err) { // logger('Error Auto Update ! ' + err, 'error'); // } // } else { // logger(`You Are Currently Using Version: ${localbrand} !`, "[ FCA-UNO ] >"); // logger(`Have a good day !`, "[ FCA-UNO ] >") // await new Promise(resolve => setTimeout(resolve, 5 * 1000)); // callback(null, api); // } // }); // } catch (err) { // logger('Error checking for updates: ' + err, 'error'); // callback(null, api); // } // } logger('Login successful!', '[ FCA-UNO ] >'); callback(null, api); }) .catch(e => { callback(e); }); } function login(loginData, options, callback) { if ( utils.getType(options) === "Function" || utils.getType(options) === "AsyncFunction" ) { callback = options; options = {}; } const globalOptions = { selfListen: false, selfListenEvent: false, listenEvents: false, listenTyping: false, updatePresence: false, forceLogin: false, autoMarkDelivery: true, autoMarkRead: false, autoReconnect: true, logRecordSize: defaultLogRecordSize, online: true, emitReady: false, userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", }; 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;