ws3-fca
Version:
A node.js package for automating Facebook Messenger bot, and is one of the most advanced next-generation Facebook Chat API (FCA) by @NethWs3Dev & @ExocoreCommunity
177 lines (157 loc) • 6.04 kB
JavaScript
;
const { makeParsable, log, warn } = require("./constants");
/**
* Formats a cookie array into a string for use in a cookie jar.
* @param {Array<string>} arr - An array containing cookie parts.
* @param {string} url - The base URL for the cookie domain.
* @returns {string} The formatted cookie string.
*/
function formatCookie(arr, url) {
return arr[0] + "=" + arr[1] + "; Path=" + arr[3] + "; Domain=" + url + ".com";
}
/**
* Parses a response from Facebook, checks for login status, and handles retries.
* @param {Object} ctx - The application context.
* @param {Object} http - The HTTP request functions.
* @param {number} [retryCount=0] - The current retry count for the request.
* @returns {function(data: Object): Promise<Object>} A function that processes the response data.
*/
function parseAndCheckLogin(ctx, http, retryCount = 0) {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
return async (data) => {
if (data.statusCode >= 500 && data.statusCode < 600) {
if (retryCount >= 5) {
const err = new Error("Request retry failed. Check the `res` and `statusCode` property on this error.");
err.statusCode = data.statusCode;
err.res = data.body;
err.error = "Request retry failed. Check the `res` and `statusCode` property on this error.";
throw err;
}
retryCount++;
const retryTime = Math.floor(Math.random() * 5000);
const url = data.request.uri.protocol + "//" + data.request.uri.hostname + data.request.uri.pathname;
await delay(retryTime);
if (data.request.headers["content-type"].split(";")[0] === "multipart/form-data") {
const newData = await http.postFormData(
url,
ctx.jar,
data.request.formData,
data.request.qs,
ctx.globalOptions,
ctx
);
return await parseAndCheckLogin(ctx, http, retryCount)(newData);
} else {
const newData = await http.post(
url,
ctx.jar,
data.request.form,
ctx.globalOptions,
ctx
);
return await parseAndCheckLogin(ctx, http, retryCount)(newData);
}
}
if (data.statusCode === 404) return;
if (data.statusCode !== 200) {
throw new Error("parseAndCheckLogin got status code: " + data.statusCode + ". Bailing out of trying to parse response.");
}
let res = null;
if (typeof data.body === 'object' && data.body !== null) {
res = data.body;
} else if (typeof data.body === 'string') {
try {
res = JSON.parse(makeParsable(data.body));
} catch (e) {
const err = new Error("JSON.parse error. Check the `detail` property on this error.");
err.error = "JSON.parse error. Check the `detail` property on this error.";
err.detail = e;
err.res = data.body;
throw err;
}
} else {
throw new Error("Unknown response body type: " + typeof data.body);
}
if (res.redirect && data.request.method === "GET") {
const redirectRes = await http.get(res.redirect, ctx.jar);
return await parseAndCheckLogin(ctx, http)(redirectRes);
}
if (res.jsmods && res.jsmods.require && Array.isArray(res.jsmods.require[0]) && res.jsmods.require[0][0] === "Cookie") {
res.jsmods.require[0][3][0] = res.jsmods.require[0][3][0].replace("_js_", "");
const requireCookie = res.jsmods.require[0][3];
ctx.jar.setCookie(formatCookie(requireCookie, "facebook"), "https://www.facebook.com");
ctx.jar.setCookie(formatCookie(requireCookie, "messenger"), "https://www.messenger.com");
}
if (res.jsmods && Array.isArray(res.jsmods.require)) {
const arr = res.jsmods.require;
for (const i in arr) {
if (arr[i][0] === "DTSG" && arr[i][1] === "setToken") {
ctx.fb_dtsg = arr[i][3][0];
ctx.ttstamp = "2";
for (let j = 0; j < ctx.fb_dtsg.length; j++) {
ctx.ttstamp += ctx.fb_dtsg.charCodeAt(j);
}
}
}
}
if (res.error === 1357001) {
const err = new Error("Facebook blocked the login");
err.error = "Not logged in.";
throw err;
}
return res;
};
}
/**
* Saves cookies from a response to the cookie jar.
* @param {Object} jar - The cookie jar instance.
* @returns {function(res: Object): Object} A function that processes the response and returns it.
*/
function saveCookies(jar) {
return function (res) {
const cookies = res.headers["set-cookie"] || [];
cookies.forEach(function (c) {
if (c.indexOf(".facebook.com") > -1) {
jar.setCookie(c, "https://www.facebook.com");
}
const c2 = c.replace(/domain=\.facebook\.com/, "domain=.messenger.com");
jar.setCookie(c2, "https://www.messenger.com");
});
return res;
};
}
/**
* Retrieves an access token from a business account page.
* @param {Object} jar - The cookie jar instance.
* @param {Object} Options - Global request options.
* @returns {function(res: Object): Promise<[string, string|null]>}
*/
function getAccessFromBusiness(jar, Options) {
return async function (res) {
const html = res ? res.body : null;
const { get } = require("./request");
try {
const businessRes = await get("https://business.facebook.com/content_management", jar, null, Options, null, { noRef: true });
const token = /"accessToken":"([^.]+)","clientID":/g.exec(businessRes.body)[1];
return [html, token];
} catch (e) {
return [html, null];
}
};
}
/**
* Retrieves all cookies from the jar for both Facebook and Messenger domains.
* @param {Object} jar - The cookie jar instance.
* @returns {Array<Object>} An array of cookie objects.
*/
function getAppState(jar) {
return jar
.getCookiesSync("https://www.facebook.com")
.concat(jar.getCookiesSync("https://www.messenger.com"));
}
module.exports = {
parseAndCheckLogin,
saveCookies,
getAccessFromBusiness,
getAppState,
};