fca-nazrul-remastered
Version:
Facebook-chat-api protect and deploy by Kanzu and HZI Team
431 lines (347 loc) • 10.9 kB
JavaScript
/* global document */
;
const {promisify} = require('util');
const fs = require('fs');
const fileUrl = require('file-url');
const puppeteer = require('puppeteer');
const toughCookie = require('tough-cookie');
const writeFile = promisify(fs.writeFile);
const isUrl = string => /^(https?|file):\/\/|^data:/.test(string);
const scrollToElement = (element, options) => {
const isOverflown = element => {
return (
element.scrollHeight > element.clientHeight ||
element.scrollWidth > element.clientWidth
);
};
const findScrollParent = element => {
if (element === undefined) {
return;
}
if (isOverflown(element)) {
return element;
}
return findScrollParent(element.parentElement);
};
const calculateOffset = (rect, options) => {
if (options === undefined) {
return {
x: rect.left,
y: rect.top
};
}
const offset = options.offset || 0;
switch (options.offsetFrom) {
case 'top':
return {
x: rect.left,
y: rect.top + offset
};
case 'right':
return {
x: rect.left - offset,
y: rect.top
};
case 'bottom':
return {
x: rect.left,
y: rect.top - offset
};
case 'left':
return {
x: rect.left + offset,
y: rect.top
};
default:
throw new Error('Invalid `scrollToElement.offsetFrom` value');
}
};
const rect = element.getBoundingClientRect();
const offset = calculateOffset(rect, options);
const parent = findScrollParent(element);
if (parent !== undefined) {
parent.scrollIntoView(true);
parent.scrollTo(offset.x, offset.y);
}
};
const disableAnimations = () => {
const rule = `
*,
::before,
::after {
animation: initial !important;
transition: initial !important;
}
`;
const style = document.createElement('style');
document.body.append(style);
style.sheet.insertRule(rule);
};
const getBoundingClientRect = element => {
const {top, left, height, width, x, y} = element.getBoundingClientRect();
return {top, left, height, width, x, y};
};
const parseCookie = (url, cookie) => {
if (typeof cookie === 'object') {
return cookie;
}
const jar = new toughCookie.CookieJar(undefined, {rejectPublicSuffixes: false});
jar.setCookieSync(cookie, url);
const returnValue = jar.serializeSync().cookies[0];
// Use this instead of the above when the following issue is fixed:
// https://github.com/salesforce/tough-cookie/issues/149
// const ret = toughCookie.parse(cookie).serializeSync();
returnValue.name = returnValue.key;
delete returnValue.key;
if (returnValue.expires) {
returnValue.expires = Math.floor(new Date(returnValue.expires) / 1000);
}
return returnValue;
};
const imagesHaveLoaded = () => [...document.images].map(element => element.complete);
const captureWebsite = async (input, options) => {
options = {
inputType: 'url',
width: 1920,
height: 1080,
scaleFactor: 2,
fullPage: false,
defaultBackground: true,
timeout: 60, // The Puppeteer default of 30 is too short
delay: 0,
debug: false,
darkMode: true,
launchOptions: { devtools:true },
_keepAlive: true,
isJavaScriptEnabled: true,
inset: 0,
args: ["--webview-disable-safebrowsing-support",
"--disable-web-security"],
ignoreHTTPSErrors: true,
...options
};
const isHTMLContent = options.inputType === 'html';
input = isHTMLContent || isUrl(input) ? input : fileUrl(input);
const timeoutInSeconds = options.timeout * 1000;
const viewportOptions = {
width: options.width,
height: options.height,
deviceScaleFactor: options.scaleFactor
};
const screenshotOptions = {};
if (options.type) {
screenshotOptions.type = options.type;
}
if (options.quality) {
screenshotOptions.quality = options.quality * 100;
}
if (options.fullPage) {
screenshotOptions.fullPage = options.fullPage;
}
if (typeof options.defaultBackground === 'boolean') {
screenshotOptions.omitBackground = !options.defaultBackground;
}
const launchOptions = {...options.launchOptions};
if (options.debug) {
launchOptions.headless = false;
launchOptions.slowMo = 100;
}
const browser = options._browser || await puppeteer.launch(launchOptions);
const page = await browser.newPage();
if (options.preloadFunction) {
await page.evaluateOnNewDocument(options.preloadFunction);
}
await page.setJavaScriptEnabled(options.isJavaScriptEnabled);
if (options.debug) {
page.on('console', message => {
let {url, lineNumber, columnNumber} = message.location();
lineNumber = lineNumber ? `:${lineNumber}` : '';
columnNumber = columnNumber ? `:${columnNumber}` : '';
const location = url ? ` (${url}${lineNumber}${columnNumber})` : '';
console.log(`\nPage log:${location}\n${message.text()}\n`);
});
page.on('pageerror', error => {
console.log('\nPage error:', error, '\n');
});
// TODO: Add more events from https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#event-requestfailed
}
if (options.authentication) {
await page.authenticate(options.authentication);
}
if (options.cookies) {
const cookies = options.cookies.map(cookie => parseCookie(isHTMLContent ? 'about:blank' : input, cookie));
await page.setCookie(...cookies);
}
if (options.headers) {
await page.setExtraHTTPHeaders(options.headers);
}
if (options.userAgent) {
await page.setUserAgent(options.userAgent);
}
await page.setViewport(viewportOptions);
if (options.emulateDevice) {
if (!(options.emulateDevice in puppeteer.devices)) {
throw new Error(`The device name \`${options.emulateDevice}\` is not supported`);
}
await page.emulate(puppeteer.devices[options.emulateDevice]);
}
await page.emulateMediaFeatures([{
name: 'prefers-color-scheme',
value: options.darkMode ? 'dark' : 'light'
}]);
await page[isHTMLContent ? 'setContent' : 'goto'](input, {
timeout: timeoutInSeconds,
waitUntil: 'networkidle2'
});
if (options.disableAnimations) {
await page.evaluate(disableAnimations, options.disableAnimations);
}
if (options.hideElements) {
await page.addStyleTag({
content: `${options.hideElements.join(', ')} { visibility: hidden !important; }`
});
}
if (options.removeElements) {
await page.addStyleTag({
content: `${options.removeElements.join(', ')} { display: none !important; }`
});
}
if (options.clickElement) {
await page.click(options.clickElement);
}
const getInjectKey = (ext, value) => isUrl(value) ? 'url' : (value.endsWith(`.${ext}`) ? 'path' : 'content');
if (!options.isJavaScriptEnabled) {
// Enable JavaScript again for `modules` and `scripts`.
await page.setJavaScriptEnabled(true);
}
if (options.modules) {
await Promise.all(options.modules.map(module_ => {
return page.addScriptTag({
[getInjectKey('js', module_)]: module_,
type: 'module'
});
}));
}
if (options.scripts) {
await Promise.all(options.scripts.map(script => {
return page.addScriptTag({
[getInjectKey('js', script)]: script
});
}));
}
if (options.styles) {
await Promise.all(options.styles.map(style => {
return page.addStyleTag({
[getInjectKey('css', style)]: style
});
}));
}
if (options.waitForElement) {
await page.waitForSelector(options.waitForElement, {
visible: true,
timeout: timeoutInSeconds
});
}
if (options.beforeScreenshot) {
await options.beforeScreenshot(page, browser);
}
if (options.element) {
await page.waitForSelector(options.element, {
visible: true,
timeout: timeoutInSeconds
});
screenshotOptions.clip = await page.$eval(options.element, getBoundingClientRect);
screenshotOptions.fullPage = false;
}
if (options.delay) {
await page.waitForTimeout(options.delay * 1000);
}
if (options.scrollToElement) {
// eslint-disable-next-line unicorn/prefer-ternary
if (typeof options.scrollToElement === 'object') {
await page.$eval(options.scrollToElement.element, scrollToElement, options.scrollToElement);
} else {
await page.$eval(options.scrollToElement, scrollToElement);
}
}
if (screenshotOptions.fullPage) {
// Get the height of the rendered page
const bodyHandle = await page.$('body');
const bodyBoundingHeight = await bodyHandle.boundingBox();
await bodyHandle.dispose();
// Scroll one viewport at a time, pausing to let content load
const viewportHeight = viewportOptions.height;
let viewportIncrement = 0;
while (viewportIncrement + viewportHeight < bodyBoundingHeight) {
const navigationPromise = page.waitForNavigation({waitUntil: 'networkidle0'});
/* eslint-disable no-await-in-loop */
await page.evaluate(_viewportHeight => {
/* eslint-disable no-undef */
window.scrollBy(0, _viewportHeight);
/* eslint-enable no-undef */
}, viewportHeight);
await navigationPromise;
/* eslint-enable no-await-in-loop */
viewportIncrement += viewportHeight;
}
// Scroll back to top
await page.evaluate(_ => {
/* eslint-disable no-undef */
window.scrollTo(0, 0);
/* eslint-enable no-undef */
});
// Some extra delay to let images load
await page.waitForFunction(imagesHaveLoaded, {timeout: timeoutInSeconds});
}
if (options.inset && !screenshotOptions.fullPage) {
const inset = {top: 0, right: 0, bottom: 0, left: 0};
for (const key of Object.keys(inset)) {
if (typeof options.inset === 'number') {
inset[key] = options.inset;
} else {
inset[key] = options.inset[key] || 0;
}
}
let clipOptions = screenshotOptions.clip;
if (!clipOptions) {
clipOptions = await page.evaluate(() => ({
x: 0,
y: 0,
/* eslint-disable no-undef */
height: window.innerHeight,
width: window.innerWidth
/* eslint-enable no-undef */
}));
}
const x = clipOptions.x + inset.left;
const y = clipOptions.y + inset.top;
const width = clipOptions.width - (inset.left + inset.right);
const height = clipOptions.height - (inset.top + inset.bottom);
if (width === 0 || height === 0) {
await page.close();
throw new Error('When using the `clip` option, the width or height of the screenshot cannot be equal to 0.');
}
screenshotOptions.clip = {x, y, width, height};
}
const buffer = await page.screenshot(screenshotOptions);
await page.close();
if (!options._keepAlive) {
await browser.close();
}
return buffer;
};
module.exports.file = async (url, filePath, options = {}) => {
const screenshot = await captureWebsite(url, options);
await writeFile(filePath, screenshot, {
flag: options.overwrite ? 'w' : 'wx'
});
};
module.exports.buffer = async (url, options) => captureWebsite(url, options);
module.exports.base64 = async (url, options) => {
const screenshot = await captureWebsite(url, options);
return screenshot.toString('base64');
};
module.exports.devices = Object.values(puppeteer.devices).map(device => device.name);
if (process.env.NODE_ENV === 'test') {
module.exports._startBrowser = puppeteer.launch.bind(puppeteer);
}