Resolved merge conflicts

pull/32/head
MillenniumEarl 2020-10-16 10:21:02 +02:00
commit 42f8de6c6b
13 changed files with 180 additions and 180 deletions

View File

@ -1,28 +1,28 @@
'use strict'; "use strict";
// Core modules // Core modules
const fs = require('fs'); const fs = require("fs");
// Modules from file // Modules from file
const shared = require('./scripts/shared.js'); const shared = require("./scripts/shared.js");
const constURLs = require('./scripts/constants/urls.js'); const constURLs = require("./scripts/constants/urls.js");
const selectors = require('./scripts/constants/css-selectors.js'); const selectors = require("./scripts/constants/css-selectors.js");
const { const {
isStringAValidURL, isStringAValidURL,
urlExists, urlExists,
isF95URL isF95URL,
} = require('./scripts/urls-helper.js'); } = require("./scripts/urls-helper.js");
const scraper = require('./scripts/game-scraper.js'); const scraper = require("./scripts/game-scraper.js");
const { const {
prepareBrowser, prepareBrowser,
preparePage preparePage,
} = require('./scripts/puppeteer-helper.js'); } = require("./scripts/puppeteer-helper.js");
const searcher = require('./scripts/game-searcher.js'); const searcher = require("./scripts/game-searcher.js");
// Classes from file // Classes from file
const GameInfo = require('./scripts/classes/game-info.js'); const GameInfo = require("./scripts/classes/game-info.js");
const LoginResult = require('./scripts/classes/login-result.js'); const LoginResult = require("./scripts/classes/login-result.js");
const UserData = require('./scripts/classes/user-data.js'); const UserData = require("./scripts/classes/user-data.js");
//#region Export classes //#region Export classes
module.exports.GameInfo = GameInfo; module.exports.GameInfo = GameInfo;
@ -99,27 +99,27 @@ var _browser = null;
*/ */
module.exports.login = async function (username, password) { module.exports.login = async function (username, password) {
if (shared.isLogged) { if (shared.isLogged) {
if (shared.debug) console.log('Already logged in'); if (shared.debug) console.log("Already logged in");
const result = new LoginResult(); const result = new LoginResult();
result.success = true; result.success = true;
result.message = 'Already logged in'; result.message = "Already logged in";
return result; return result;
} }
// If cookies are loaded, use them to authenticate // If cookies are loaded, use them to authenticate
shared.cookies = loadCookies(); shared.cookies = loadCookies();
if (shared.cookies !== null) { if (shared.cookies !== null) {
if (shared.debug) console.log('Valid session, no need to re-authenticate'); if (shared.debug) console.log("Valid session, no need to re-authenticate");
shared.isLogged = true; shared.isLogged = true;
const result = new LoginResult(); const result = new LoginResult();
result.success = true; result.success = true;
result.message = 'Logged with cookies'; result.message = "Logged with cookies";
return result; return result;
} }
// Else, log in throught browser // Else, log in throught browser
if (shared.debug) if (shared.debug)
console.log('No saved sessions or expired session, login on the platform'); console.log("No saved sessions or expired session, login on the platform");
if (_browser === null && !shared.isolation) _browser = await prepareBrowser(); if (_browser === null && !shared.isolation) _browser = await prepareBrowser();
const browser = shared.isolation ? await prepareBrowser() : _browser; const browser = shared.isolation ? await prepareBrowser() : _browser;
@ -130,9 +130,9 @@ module.exports.login = async function (username, password) {
if (result.success) { if (result.success) {
// Reload cookies // Reload cookies
shared.cookies = loadCookies(); shared.cookies = loadCookies();
if (shared.debug) console.log('User logged in through the platform'); if (shared.debug) console.log("User logged in through the platform");
} else { } else {
console.warn('Error during authentication: ' + result.message); console.warn("Error during authentication: " + result.message);
} }
if (shared.isolation) await browser.close(); if (shared.isolation) await browser.close();
return result; return result;
@ -146,11 +146,11 @@ module.exports.login = async function (username, password) {
*/ */
module.exports.loadF95BaseData = async function () { module.exports.loadF95BaseData = async function () {
if (!shared.isLogged || !shared.cookies) { if (!shared.isLogged || !shared.cookies) {
console.warn('User not authenticated, unable to continue'); console.warn("User not authenticated, unable to continue");
return false; return false;
} }
if (shared.debug) console.log('Loading base data...'); if (shared.debug) console.log("Loading base data...");
// Prepare a new web page // Prepare a new web page
if (_browser === null && !shared.isolation) _browser = await prepareBrowser(); if (_browser === null && !shared.isolation) _browser = await prepareBrowser();
@ -161,7 +161,7 @@ module.exports.loadF95BaseData = async function () {
// Go to latest update page and wait for it to load // Go to latest update page and wait for it to load
await page.goto(constURLs.F95_LATEST_UPDATES, { await page.goto(constURLs.F95_LATEST_UPDATES, {
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}); });
// Obtain engines (disc/online) // Obtain engines (disc/online)
@ -170,7 +170,7 @@ module.exports.loadF95BaseData = async function () {
page, page,
shared.enginesCachePath, shared.enginesCachePath,
selectors.ENGINE_ID_SELECTOR, selectors.ENGINE_ID_SELECTOR,
'engines' "engines"
); );
// Obtain statuses (disc/online) // Obtain statuses (disc/online)
@ -179,11 +179,11 @@ module.exports.loadF95BaseData = async function () {
page, page,
shared.statusesCachePath, shared.statusesCachePath,
selectors.STATUS_ID_SELECTOR, selectors.STATUS_ID_SELECTOR,
'statuses' "statuses"
); );
if (shared.isolation) await browser.close(); if (shared.isolation) await browser.close();
if (shared.debug) console.log('Base data loaded'); if (shared.debug) console.log("Base data loaded");
return true; return true;
}; };
/** /**
@ -195,7 +195,7 @@ module.exports.loadF95BaseData = async function () {
*/ */
module.exports.chekIfGameHasUpdate = async function (info) { module.exports.chekIfGameHasUpdate = async function (info) {
if (!shared.isLogged || !shared.cookies) { if (!shared.isLogged || !shared.cookies) {
console.warn('user not authenticated, unable to continue'); console.warn("user not authenticated, unable to continue");
return info.version; return info.version;
} }
@ -225,7 +225,7 @@ module.exports.chekIfGameHasUpdate = async function (info) {
*/ */
module.exports.getGameData = async function (name, includeMods) { module.exports.getGameData = async function (name, includeMods) {
if (!shared.isLogged || !shared.cookies) { if (!shared.isLogged || !shared.cookies) {
console.warn('user not authenticated, unable to continue'); console.warn("user not authenticated, unable to continue");
return null; return null;
} }
@ -262,13 +262,13 @@ module.exports.getGameData = async function (name, includeMods) {
*/ */
module.exports.getGameDataFromURL = async function (url) { module.exports.getGameDataFromURL = async function (url) {
if (!shared.isLogged || !shared.cookies) { if (!shared.isLogged || !shared.cookies) {
console.warn('user not authenticated, unable to continue'); console.warn("user not authenticated, unable to continue");
return null; return null;
} }
// Check URL // Check URL
if (!urlExists(url)) return null; if (!urlExists(url)) return null;
if (!isF95URL(url)) throw new Error(url + ' is not a valid F95Zone URL'); if (!isF95URL(url)) throw new Error(url + " is not a valid F95Zone URL");
// Gets the search results of the game being searched for // Gets the search results of the game being searched for
if (_browser === null && !shared.isolation) _browser = await prepareBrowser(); if (_browser === null && !shared.isolation) _browser = await prepareBrowser();
@ -288,7 +288,7 @@ module.exports.getGameDataFromURL = async function (url) {
*/ */
module.exports.getUserData = async function () { module.exports.getUserData = async function () {
if (!shared.isLogged || !shared.cookies) { if (!shared.isLogged || !shared.cookies) {
console.warn('user not authenticated, unable to continue'); console.warn("user not authenticated, unable to continue");
return null; return null;
} }
@ -313,7 +313,7 @@ module.exports.getUserData = async function () {
const avatarSrc = await page.evaluate( const avatarSrc = await page.evaluate(
/* istanbul ignore next */ (selector) => /* istanbul ignore next */ (selector) =>
document.querySelector(selector).getAttribute('src'), document.querySelector(selector).getAttribute("src"),
selectors.AVATAR_PIC selectors.AVATAR_PIC
); );
@ -334,7 +334,7 @@ module.exports.getUserData = async function () {
*/ */
module.exports.logout = async function () { module.exports.logout = async function () {
if (!shared.isLogged || !shared.cookies) { if (!shared.isLogged || !shared.cookies) {
console.warn('user not authenticated, unable to continue'); console.warn("user not authenticated, unable to continue");
return; return;
} }
shared.isLogged = false; shared.isLogged = false;
@ -385,14 +385,14 @@ function isCookieExpired(cookie) {
// Ignore cookies that never expire // Ignore cookies that never expire
const expirationUnixTimestamp = cookie.expire; const expirationUnixTimestamp = cookie.expire;
if (expirationUnixTimestamp !== '-1') { if (expirationUnixTimestamp !== "-1") {
// Convert UNIX epoch timestamp to normal Date // Convert UNIX epoch timestamp to normal Date
const expirationDate = new Date(expirationUnixTimestamp * 1000); const expirationDate = new Date(expirationUnixTimestamp * 1000);
if (expirationDate < Date.now()) { if (expirationDate < Date.now()) {
if (shared.debug) if (shared.debug)
console.log( console.log(
'Cookie ' + cookie.name + ' expired, you need to re-authenticate' "Cookie " + cookie.name + " expired, you need to re-authenticate"
); );
expiredCookies = true; expiredCookies = true;
} }
@ -421,7 +421,7 @@ async function loadValuesFromLatestPage(
elementRequested elementRequested
) { ) {
// If the values already exist they are loaded from disk without having to connect to F95 // If the values already exist they are loaded from disk without having to connect to F95
if (shared.debug) console.log('Load ' + elementRequested + ' from disk...'); if (shared.debug) console.log("Load " + elementRequested + " from disk...");
if (fs.existsSync(path)) { if (fs.existsSync(path)) {
const valueJSON = fs.readFileSync(path); const valueJSON = fs.readFileSync(path);
return JSON.parse(valueJSON); return JSON.parse(valueJSON);
@ -429,11 +429,11 @@ async function loadValuesFromLatestPage(
// Otherwise, connect and download the data from the portal // Otherwise, connect and download the data from the portal
if (shared.debug) if (shared.debug)
console.log('No ' + elementRequested + ' cached, downloading...'); console.log("No " + elementRequested + " cached, downloading...");
const values = await getValuesFromLatestPage( const values = await getValuesFromLatestPage(
page, page,
selector, selector,
'Getting ' + elementRequested + ' from page' "Getting " + elementRequested + " from page"
); );
fs.writeFileSync(path, JSON.stringify(values)); fs.writeFileSync(path, JSON.stringify(values));
return values; return values;
@ -489,8 +489,8 @@ async function loginF95(browser, username, password) {
await Promise.all([ await Promise.all([
page.click(selectors.LOGIN_BUTTON), // Click on the login button page.click(selectors.LOGIN_BUTTON), // Click on the login button
page.waitForNavigation({ page.waitForNavigation({
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}) // Wait for page to load }), // Wait for page to load
]); ]);
// Prepare result // Prepare result
@ -507,7 +507,7 @@ async function loginF95(browser, username, password) {
if (result.success) { if (result.success) {
const c = await page.cookies(); const c = await page.cookies();
fs.writeFileSync(shared.cookiesCachePath, JSON.stringify(c)); fs.writeFileSync(shared.cookiesCachePath, JSON.stringify(c));
result.message = 'Authentication successful'; result.message = "Authentication successful";
} else if ( } else if (
// Obtain the error message // Obtain the error message
await page.evaluate( await page.evaluate(
@ -522,15 +522,15 @@ async function loginF95(browser, username, password) {
selectors.LOGIN_MESSAGE_ERROR selectors.LOGIN_MESSAGE_ERROR
); );
if (errorMessage === 'Incorrect password. Please try again.') { if (errorMessage === "Incorrect password. Please try again.") {
result.message = 'Incorrect password'; result.message = "Incorrect password";
} else if ( } else if (
errorMessage === errorMessage ===
'The requested user "' + username + '" could not be found.' 'The requested user "' + username + '" could not be found.'
) { ) {
result.message = 'Incorrect username'; result.message = "Incorrect username";
} else result.message = errorMessage; } else result.message = errorMessage;
} else result.message = 'Unknown error'; } else result.message = "Unknown error";
await page.close(); // Close the page await page.close(); // Close the page
return result; return result;
@ -557,7 +557,7 @@ async function getUserWatchedGameThreads(browser) {
// Set the filters // Set the filters
await page.evaluate( await page.evaluate(
/* istanbul ignore next */ (selector) => /* istanbul ignore next */ (selector) =>
document.querySelector(selector).removeAttribute('checked'), document.querySelector(selector).removeAttribute("checked"),
selectors.UNREAD_THREAD_CHECKBOX selectors.UNREAD_THREAD_CHECKBOX
); // Also read the threads already read ); // Also read the threads already read
@ -578,7 +578,7 @@ async function getUserWatchedGameThreads(browser) {
handle handle
); );
// If 'unread' is left, it will redirect to the last unread post // If 'unread' is left, it will redirect to the last unread post
const url = src.replace('/unread', ''); const url = src.replace("/unread", "");
urls.push(url); urls.push(url);
} }

View File

@ -1,16 +1,16 @@
/* istanbul ignore file */ /* istanbul ignore file */
'use strict'; "use strict";
// Core modules // Core modules
const fs = require('fs'); const fs = require("fs");
// Public modules from npm // Public modules from npm
// const { File } = require('megajs'); // const { File } = require('megajs');
// Modules from file // Modules from file
const { prepareBrowser, preparePage } = require('../puppeteer-helper.js'); const { prepareBrowser, preparePage } = require("../puppeteer-helper.js");
const shared = require('../shared.js'); const shared = require("../shared.js");
class GameDownload { class GameDownload {
constructor() { constructor() {
@ -19,7 +19,7 @@ class GameDownload {
* Platform that hosts game files * Platform that hosts game files
* @type String * @type String
*/ */
this.hosting = ''; this.hosting = "";
/** /**
* @public * @public
* Link to game files * Link to game files
@ -43,9 +43,9 @@ class GameDownload {
* @return {Promise<Boolean>} Result of the operation * @return {Promise<Boolean>} Result of the operation
*/ */
async download(path) { async download(path) {
if (this.link.includes('mega.nz')) if (this.link.includes("mega.nz"))
return await downloadMEGA(this.link, path); return await downloadMEGA(this.link, path);
else if (this.link.includes('nopy.to')) else if (this.link.includes("nopy.to"))
return await downloadNOPY(this.link, path); return await downloadNOPY(this.link, path);
} }
} }
@ -57,13 +57,13 @@ async function downloadMEGA(url, savepath) {
const page = await preparePage(browser); const page = await preparePage(browser);
await page.setCookie(...shared.cookies); // Set cookies to avoid login await page.setCookie(...shared.cookies); // Set cookies to avoid login
await page.goto(url); await page.goto(url);
await page.waitForSelector('a.host_link'); await page.waitForSelector("a.host_link");
// Obtain the link for the unmasked page and click it // Obtain the link for the unmasked page and click it
const link = await page.$('a.host_link'); const link = await page.$("a.host_link");
await link.click(); await link.click();
await page.goto(url, { await page.goto(url, {
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}); // Go to the game page and wait until it loads }); // Go to the game page and wait until it loads
// Obtain the URL after the redirect // Obtain the URL after the redirect
@ -84,22 +84,22 @@ async function downloadNOPY(url, savepath) {
const browser = await prepareBrowser(); const browser = await prepareBrowser();
const page = await preparePage(browser); const page = await preparePage(browser);
await page.goto(url); await page.goto(url);
await page.waitForSelector('#download'); await page.waitForSelector("#download");
// Set the save path // Set the save path
await page._client.send('Page.setDownloadBehavior', { await page._client.send("Page.setDownloadBehavior", {
behavior: 'allow', behavior: "allow",
downloadPath: path.basename(path.dirname(savepath)) // It's a directory downloadPath: path.basename(path.dirname(savepath)), // It's a directory
}); });
// Obtain the download button and click it // Obtain the download button and click it
const downloadButton = await page.$('#download'); const downloadButton = await page.$("#download");
await downloadButton.click(); await downloadButton.click();
// Await for all the connections to close // Await for all the connections to close
await page.waitForNavigation({ await page.waitForNavigation({
waitUntil: 'networkidle0', waitUntil: "networkidle0",
timeout: 0 // Disable timeout timeout: 0, // Disable timeout
}); });
// Close browser and page // Close browser and page

View File

@ -1,6 +1,6 @@
'use strict'; "use strict";
const UNKNOWN = 'Unknown'; const UNKNOWN = "Unknown";
class GameInfo { class GameInfo {
constructor() { constructor() {
@ -103,7 +103,7 @@ class GameInfo {
isMod: this.isMod, isMod: this.isMod,
changelog: this.changelog, changelog: this.changelog,
gameDir: this.gameDir, gameDir: this.gameDir,
downloadInfo: this.downloadInfo downloadInfo: this.downloadInfo,
}; };
} }

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
/** /**
* Object obtained in response to an attempt to login to the portal. * Object obtained in response to an attempt to login to the portal.
@ -14,7 +14,7 @@ class LoginResult {
* Login response message * Login response message
* @type String * @type String
*/ */
this.message = ''; this.message = "";
} }
} }
module.exports = LoginResult; module.exports = LoginResult;

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
/** /**
* Class containing the data of the user currently connected to the F95Zone platform. * Class containing the data of the user currently connected to the F95Zone platform.
@ -9,7 +9,7 @@ class UserData {
* User username. * User username.
* @type String * @type String
*/ */
this.username = ''; this.username = "";
/** /**
* Path to the user's profile picture. * Path to the user's profile picture.
* @type String * @type String

View File

@ -1,32 +1,32 @@
module.exports = Object.freeze({ module.exports = Object.freeze({
AVATAR_INFO: 'span.avatar', AVATAR_INFO: "span.avatar",
AVATAR_PIC: 'a[href="/account/"] > span.avatar > img[class^="avatar"]', AVATAR_PIC: 'a[href="/account/"] > span.avatar > img[class^="avatar"]',
ENGINE_ID_SELECTOR: 'div[id^="btn-prefix_1_"]>span', ENGINE_ID_SELECTOR: 'div[id^="btn-prefix_1_"]>span',
FILTER_THREADS_BUTTON: 'button[class="button--primary button"]', FILTER_THREADS_BUTTON: 'button[class="button--primary button"]',
GAME_IMAGES: 'img[src^="https://attachments.f95zone.to"]', GAME_IMAGES: 'img[src^="https://attachments.f95zone.to"]',
GAME_TAGS: 'a.tagItem', GAME_TAGS: "a.tagItem",
GAME_TITLE: 'h1.p-title-value', GAME_TITLE: "h1.p-title-value",
GAME_TITLE_PREFIXES: 'h1.p-title-value > a.labelLink > span[dir="auto"]', GAME_TITLE_PREFIXES: 'h1.p-title-value > a.labelLink > span[dir="auto"]',
LOGIN_BUTTON: 'button.button--icon--login', LOGIN_BUTTON: "button.button--icon--login",
LOGIN_MESSAGE_ERROR: LOGIN_MESSAGE_ERROR:
'div.blockMessage.blockMessage--error.blockMessage--iconic', "div.blockMessage.blockMessage--error.blockMessage--iconic",
ONLY_GAMES_THREAD_OPTION: 'select[name="nodes[]"] > option[value="2"]', ONLY_GAMES_THREAD_OPTION: 'select[name="nodes[]"] > option[value="2"]',
PASSWORD_INPUT: 'input[name="password"]', PASSWORD_INPUT: 'input[name="password"]',
SEARCH_BUTTON: 'form.block > * button.button--icon--search', SEARCH_BUTTON: "form.block > * button.button--icon--search",
SEARCH_FORM_TEXTBOX: 'input[name="keywords"]', SEARCH_FORM_TEXTBOX: 'input[name="keywords"]',
STATUS_ID_SELECTOR: 'div[id^="btn-prefix_4_"]>span', STATUS_ID_SELECTOR: 'div[id^="btn-prefix_4_"]>span',
THREAD_POSTS: THREAD_POSTS:
'article.message-body:first-child > div.bbWrapper:first-of-type', "article.message-body:first-child > div.bbWrapper:first-of-type",
THREAD_TITLE: 'h3.contentRow-title', THREAD_TITLE: "h3.contentRow-title",
TITLE_ONLY_CHECKBOX: 'form.block > * input[name="c[title_only]"]', TITLE_ONLY_CHECKBOX: 'form.block > * input[name="c[title_only]"]',
UNREAD_THREAD_CHECKBOX: 'input[type="checkbox"][name="unread"]', UNREAD_THREAD_CHECKBOX: 'input[type="checkbox"][name="unread"]',
USERNAME_ELEMENT: 'a[href="/account/"] > span.p-navgroup-linkText', USERNAME_ELEMENT: 'a[href="/account/"] > span.p-navgroup-linkText',
USERNAME_INPUT: 'input[name="login"]', USERNAME_INPUT: 'input[name="login"]',
WATCHED_THREAD_FILTER_POPUP_BUTTON: 'a.filterBar-menuTrigger', WATCHED_THREAD_FILTER_POPUP_BUTTON: "a.filterBar-menuTrigger",
WATCHED_THREAD_NEXT_PAGE: 'a.pageNav-jump--next', WATCHED_THREAD_NEXT_PAGE: "a.pageNav-jump--next",
WATCHED_THREAD_URLS: 'a[href^="/threads/"][data-tp-primary]', WATCHED_THREAD_URLS: 'a[href^="/threads/"][data-tp-primary]',
DOWNLOAD_LINKS_CONTAINER: 'span[style="font-size: 18px"]', DOWNLOAD_LINKS_CONTAINER: 'span[style="font-size: 18px"]',
SEARCH_THREADS_RESULTS_BODY: 'div.contentRow-main', SEARCH_THREADS_RESULTS_BODY: "div.contentRow-main",
SEARCH_THREADS_MEMBERSHIP: 'li > a:not(.username)', SEARCH_THREADS_MEMBERSHIP: "li > a:not(.username)",
THREAD_LAST_CHANGELOG: 'div.bbCodeBlock-content > div:first-of-type' THREAD_LAST_CHANGELOG: "div.bbCodeBlock-content > div:first-of-type",
}); });

View File

@ -1,7 +1,7 @@
module.exports = Object.freeze({ module.exports = Object.freeze({
F95_BASE_URL: 'https://f95zone.to', F95_BASE_URL: "https://f95zone.to",
F95_SEARCH_URL: 'https://f95zone.to/search', F95_SEARCH_URL: "https://f95zone.to/search",
F95_LATEST_UPDATES: 'https://f95zone.to/latest', F95_LATEST_UPDATES: "https://f95zone.to/latest",
F95_LOGIN_URL: 'https://f95zone.to/login', F95_LOGIN_URL: "https://f95zone.to/login",
F95_WATCHED_THREADS: 'https://f95zone.to/watched/threads' F95_WATCHED_THREADS: "https://f95zone.to/watched/threads",
}); });

View File

@ -1,16 +1,16 @@
'use strict'; "use strict";
// Public modules from npm // Public modules from npm
const HTMLParser = require('node-html-parser'); const HTMLParser = require("node-html-parser");
const puppeteer = require('puppeteer'); // skipcq: JS-0128 const puppeteer = require("puppeteer"); // skipcq: JS-0128
// Modules from file // Modules from file
const shared = require('./shared.js'); const shared = require("./shared.js");
const selectors = require('./constants/css-selectors.js'); const selectors = require("./constants/css-selectors.js");
const { preparePage } = require('./puppeteer-helper.js'); const { preparePage } = require("./puppeteer-helper.js");
const GameDownload = require('./classes/game-download.js'); const GameDownload = require("./classes/game-download.js");
const GameInfo = require('./classes/game-info.js'); const GameInfo = require("./classes/game-info.js");
const { isStringAValidURL, isF95URL, urlExists } = require('./urls-helper.js'); const { isStringAValidURL, isF95URL, urlExists } = require("./urls-helper.js");
/** /**
* @protected * @protected
@ -21,17 +21,17 @@ const { isStringAValidURL, isF95URL, urlExists } = require('./urls-helper.js');
* looking for or null if the URL doesn't exists * looking for or null if the URL doesn't exists
*/ */
module.exports.getGameInfo = async function (browser, url) { module.exports.getGameInfo = async function (browser, url) {
if (shared.debug) console.log('Obtaining game info'); if (shared.debug) console.log("Obtaining game info");
// Verify the correctness of the URL // Verify the correctness of the URL
if (!isF95URL(url)) throw new Error(url + ' is not a valid F95Zone URL'); if (!isF95URL(url)) throw new Error(url + " is not a valid F95Zone URL");
const exists = await urlExists(url); const exists = await urlExists(url);
if (!exists) return null; if (!exists) return null;
const page = await preparePage(browser); // Set new isolated page const page = await preparePage(browser); // Set new isolated page
await page.setCookie(...shared.cookies); // Set cookies to avoid login await page.setCookie(...shared.cookies); // Set cookies to avoid login
await page.goto(url, { await page.goto(url, {
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}); // Go to the game page and wait until it loads }); // Go to the game page and wait until it loads
// It asynchronously searches for the elements and // It asynchronously searches for the elements and
@ -59,7 +59,7 @@ module.exports.getGameInfo = async function (browser, url) {
? parsedInfos.UPDATED ? parsedInfos.UPDATED
: parsedInfos.THREAD_UPDATED; : parsedInfos.THREAD_UPDATED;
info.previewSource = await previewSource; info.previewSource = await previewSource;
info.changelog = (await changelog) || 'Unknown changelog'; info.changelog = (await changelog) || "Unknown changelog";
//info.downloadInfo = await downloadData; //info.downloadInfo = await downloadData;
/* Downloading games without going directly to /* Downloading games without going directly to
* the platform appears to be prohibited by * the platform appears to be prohibited by
@ -67,7 +67,7 @@ module.exports.getGameInfo = async function (browser, url) {
* keep the links for downloading the games. */ * keep the links for downloading the games. */
await page.close(); // Close the page await page.close(); // Close the page
if (shared.debug) console.log('Founded data for ' + info.name); if (shared.debug) console.log("Founded data for " + info.name);
return info; return info;
}; };
@ -81,7 +81,7 @@ module.exports.getGameVersionFromTitle = async function (browser, info) {
const page = await preparePage(browser); // Set new isolated page const page = await preparePage(browser); // Set new isolated page
await page.setCookie(...shared.cookies); // Set cookies to avoid login await page.setCookie(...shared.cookies); // Set cookies to avoid login
await page.goto(info.f95url, { await page.goto(info.f95url, {
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}); // Go to the game page and wait until it loads }); // Go to the game page and wait until it loads
// Get the title // Get the title
@ -93,10 +93,10 @@ module.exports.getGameVersionFromTitle = async function (browser, info) {
const title = HTMLParser.parse(titleHTML).childNodes.pop().rawText; const title = HTMLParser.parse(titleHTML).childNodes.pop().rawText;
// The title is in the following format: [PREFIXES] NAME GAME [VERSION] [AUTHOR] // The title is in the following format: [PREFIXES] NAME GAME [VERSION] [AUTHOR]
const startIndex = title.indexOf('[') + 1; const startIndex = title.indexOf("[") + 1;
const endIndex = title.indexOf(']', startIndex); const endIndex = title.indexOf("]", startIndex);
let version = title.substring(startIndex, endIndex).trim().toUpperCase(); let version = title.substring(startIndex, endIndex).trim().toUpperCase();
if (version.startsWith('V')) version = version.replace('V', ''); // Replace only the first occurrence if (version.startsWith("V")) version = version.replace("V", ""); // Replace only the first occurrence
return version; return version;
}; };
@ -112,9 +112,9 @@ module.exports.getGameVersionFromTitle = async function (browser, info) {
function getOverview(text, isMod) { function getOverview(text, isMod) {
// Get overview (different parsing for game and mod) // Get overview (different parsing for game and mod)
let overviewEndIndex; let overviewEndIndex;
if (isMod) overviewEndIndex = text.indexOf('Updated'); if (isMod) overviewEndIndex = text.indexOf("Updated");
else overviewEndIndex = text.indexOf('Thread Updated'); else overviewEndIndex = text.indexOf("Thread Updated");
return text.substring(0, overviewEndIndex).replace('Overview:\n', '').trim(); return text.substring(0, overviewEndIndex).replace("Overview:\n", "").trim();
} }
/** /**
@ -156,7 +156,7 @@ async function getGameAuthor(page) {
const gameTitle = structuredTitle.childNodes.pop().rawText; const gameTitle = structuredTitle.childNodes.pop().rawText;
// The last square brackets contain the author // The last square brackets contain the author
const startTitleIndex = gameTitle.lastIndexOf('[') + 1; const startTitleIndex = gameTitle.lastIndexOf("[") + 1;
return gameTitle.substring(startTitleIndex, gameTitle.length - 1).trim(); return gameTitle.substring(startTitleIndex, gameTitle.length - 1).trim();
} }
@ -171,17 +171,17 @@ function parseConversationPage(text) {
const dataPairs = {}; const dataPairs = {};
// The information searched in the game post are one per line // The information searched in the game post are one per line
const splittedText = text.split('\n'); const splittedText = text.split("\n");
for (const line of splittedText) { for (const line of splittedText) {
if (!line.includes(':')) continue; if (!line.includes(":")) continue;
// Create pair key/value // Create pair key/value
const splitted = line.split(':'); const splitted = line.split(":");
const key = splitted[0].trim().toUpperCase().replaceAll(' ', '_'); // Uppercase to avoid mismatch const key = splitted[0].trim().toUpperCase().replaceAll(" ", "_"); // Uppercase to avoid mismatch
const value = splitted[1].trim(); const value = splitted[1].trim();
// Add pair to the dict if valid // Add pair to the dict if valid
if (value !== '') dataPairs[key] = value; if (value !== "") dataPairs[key] = value;
} }
return dataPairs; return dataPairs;
@ -200,7 +200,7 @@ async function getGamePreviewSource(page) {
// Get the firs image available // Get the firs image available
const img = document.querySelector(selector); const img = document.querySelector(selector);
if (img) return img.getAttribute('src'); if (img) return img.getAttribute("src");
else return null; else return null;
}, },
selectors.GAME_IMAGES selectors.GAME_IMAGES
@ -227,7 +227,7 @@ async function getGameTitle(page) {
// The last element **shoud be** the title without prefixes (engines, status, other...) // The last element **shoud be** the title without prefixes (engines, status, other...)
const gameTitle = structuredTitle.childNodes.pop().rawText; const gameTitle = structuredTitle.childNodes.pop().rawText;
const endTitleIndex = gameTitle.indexOf('['); const endTitleIndex = gameTitle.indexOf("[");
return gameTitle.substring(0, endTitleIndex).trim(); return gameTitle.substring(0, endTitleIndex).trim();
} }
@ -261,10 +261,10 @@ async function getGameTags(page) {
* @returns {Promise<GameInfo>} GameInfo object passed in to which the identified information has been added * @returns {Promise<GameInfo>} GameInfo object passed in to which the identified information has been added
*/ */
async function parsePrefixes(page, info) { async function parsePrefixes(page, info) {
const MOD_PREFIX = 'MOD'; const MOD_PREFIX = "MOD";
// The 'Ongoing' status is not specified, only 'Abandoned'/'OnHold'/'Complete' // The 'Ongoing' status is not specified, only 'Abandoned'/'OnHold'/'Complete'
info.status = 'Ongoing'; info.status = "Ongoing";
for (const handle of await page.$$(selectors.GAME_TITLE_PREFIXES)) { for (const handle of await page.$$(selectors.GAME_TITLE_PREFIXES)) {
const value = await page.evaluate( const value = await page.evaluate(
/* istanbul ignore next */ /* istanbul ignore next */
@ -273,7 +273,7 @@ async function parsePrefixes(page, info) {
); );
// Clean the prefix // Clean the prefix
const prefix = value.toUpperCase().replace('[', '').replace(']', '').trim(); const prefix = value.toUpperCase().replace("[", "").replace("]", "").trim();
// Getting infos... // Getting infos...
if (shared.statuses.includes(prefix)) info.status = prefix; if (shared.statuses.includes(prefix)) info.status = prefix;
@ -303,7 +303,7 @@ async function getLastChangelog(page) {
spoiler spoiler
); );
const parsedText = HTMLParser.parse(changelogHTML).structuredText; const parsedText = HTMLParser.parse(changelogHTML).structuredText;
return parsedText.replace('Spoiler', '').trim(); return parsedText.replace("Spoiler", "").trim();
} }
/** /**
@ -316,17 +316,17 @@ async function getLastChangelog(page) {
async function getGameDownloadLink(page) { async function getGameDownloadLink(page) {
// Most used hosting platforms // Most used hosting platforms
const hostingPlatforms = [ const hostingPlatforms = [
'MEGA', "MEGA",
'NOPY', "NOPY",
'FILESUPLOAD', "FILESUPLOAD",
'MIXDROP', "MIXDROP",
'UPLOADHAVEN', "UPLOADHAVEN",
'PIXELDRAIN', "PIXELDRAIN",
'FILESFM' "FILESFM",
]; ];
// Supported OS platforms // Supported OS platforms
const platformOS = ['WIN', 'LINUX', 'MAC', 'ALL']; const platformOS = ["WIN", "LINUX", "MAC", "ALL"];
// Gets the <span> which contains the download links // Gets the <span> which contains the download links
const temp = await page.$$(selectors.DOWNLOAD_LINKS_CONTAINER); const temp = await page.$$(selectors.DOWNLOAD_LINKS_CONTAINER);
@ -383,13 +383,13 @@ async function getGameDownloadLink(page) {
* @returns {GameDownload[]} List of game download links for the selected platform * @returns {GameDownload[]} List of game download links for the selected platform
*/ */
function extractGameHostingData(platform, text) { function extractGameHostingData(platform, text) {
const PLATFORM_BOLD_OPEN = '<b>'; const PLATFORM_BOLD_OPEN = "<b>";
const CONTAINER_SPAN_CLOSE = '</span>'; const CONTAINER_SPAN_CLOSE = "</span>";
const LINK_OPEN = '<a'; const LINK_OPEN = "<a";
const LINK_CLOSE = '</a>'; const LINK_CLOSE = "</a>";
const HREF_START = 'href=\''; const HREF_START = "href='";
const HREF_END = '\''; const HREF_END = "'";
const TAG_CLOSE = '>'; const TAG_CLOSE = ">";
// Identify the individual platforms // Identify the individual platforms
let startIndex = text.indexOf(platform.toLowerCase()); let startIndex = text.indexOf(platform.toLowerCase());

View File

@ -1,14 +1,14 @@
'use strict'; "use strict";
// Public modules from npm // Public modules from npm
const puppeteer = require('puppeteer'); // skipcq: JS-0128 const puppeteer = require("puppeteer"); // skipcq: JS-0128
// Modules from file // Modules from file
const shared = require('./shared.js'); const shared = require("./shared.js");
const constURLs = require('./constants/urls.js'); const constURLs = require("./constants/urls.js");
const selectors = require('./constants/css-selectors.js'); const selectors = require("./constants/css-selectors.js");
const { preparePage } = require('./puppeteer-helper.js'); const { preparePage } = require("./puppeteer-helper.js");
const { isF95URL } = require('./urls-helper.js'); const { isF95URL } = require("./urls-helper.js");
/** /**
* @protected * @protected
@ -18,12 +18,12 @@ const { isF95URL } = require('./urls-helper.js');
* @returns {Promise<String[]>} List of URL of possible games obtained from the preliminary research on the F95 portal * @returns {Promise<String[]>} List of URL of possible games obtained from the preliminary research on the F95 portal
*/ */
module.exports.getSearchGameResults = async function (browser, gamename) { module.exports.getSearchGameResults = async function (browser, gamename) {
if (shared.debug) console.log('Searching ' + gamename + ' on F95Zone'); if (shared.debug) console.log("Searching " + gamename + " on F95Zone");
const page = await preparePage(browser); // Set new isolated page const page = await preparePage(browser); // Set new isolated page
await page.setCookie(...shared.cookies); // Set cookies to avoid login await page.setCookie(...shared.cookies); // Set cookies to avoid login
await page.goto(constURLs.F95_SEARCH_URL, { await page.goto(constURLs.F95_SEARCH_URL, {
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}); // Go to the search form and wait for it }); // Go to the search form and wait for it
// Explicitly wait for the required items to load // Explicitly wait for the required items to load
@ -36,21 +36,21 @@ module.exports.getSearchGameResults = async function (browser, gamename) {
await Promise.all([ await Promise.all([
page.click(selectors.SEARCH_BUTTON), // Execute search page.click(selectors.SEARCH_BUTTON), // Execute search
page.waitForNavigation({ page.waitForNavigation({
waitUntil: shared.WAIT_STATEMENT waitUntil: shared.WAIT_STATEMENT,
}) // Wait for page to load }), // Wait for page to load
]); ]);
// Select all conversation titles // Select all conversation titles
const resultsThread = await page.$$(selectors.SEARCH_THREADS_RESULTS_BODY); const resultsThread = await page.$$(selectors.SEARCH_THREADS_RESULTS_BODY);
// For each element found extract the info about the conversation // For each element found extract the info about the conversation
if (shared.debug) console.log('Extracting info from conversations'); if (shared.debug) console.log("Extracting info from conversations");
const results = []; const results = [];
for (const element of resultsThread) { for (const element of resultsThread) {
const gameUrl = await getOnlyGameThreads(page, element); const gameUrl = await getOnlyGameThreads(page, element);
if (gameUrl !== null) results.push(gameUrl); if (gameUrl !== null) results.push(gameUrl);
} }
if (shared.debug) console.log('Find ' + results.length + ' conversations'); if (shared.debug) console.log("Find " + results.length + " conversations");
await page.close(); // Close the page await page.close(); // Close the page
return results; return results;
@ -71,7 +71,7 @@ async function getOnlyGameThreads(page, divHandle) {
// Get the forum where the thread was posted // Get the forum where the thread was posted
const forum = await getMembershipForum(page, forumHandle); const forum = await getMembershipForum(page, forumHandle);
if (forum !== 'GAMES' && forum !== 'MODS') return null; if (forum !== "GAMES" && forum !== "MODS") return null;
// Get the URL of the thread from the title // Get the URL of the thread from the title
return await getThreadURL(page, titleHandle); return await getThreadURL(page, titleHandle);
@ -92,13 +92,13 @@ async function getMembershipForum(page, handle) {
let link = await page.evaluate( let link = await page.evaluate(
/* istanbul ignore next */ /* istanbul ignore next */
(e) => e.getAttribute('href'), (e) => e.getAttribute("href"),
handle handle
); );
// Parse link // Parse link
link = link.replace('/forums/', ''); link = link.replace("/forums/", "");
const endIndex = link.indexOf('.'); const endIndex = link.indexOf(".");
const forum = link.substring(0, endIndex); const forum = link.substring(0, endIndex);
return forum.toUpperCase(); return forum.toUpperCase();
@ -114,7 +114,7 @@ async function getMembershipForum(page, handle) {
async function getThreadURL(page, handle) { async function getThreadURL(page, handle) {
const relativeURLThread = await page.evaluate( const relativeURLThread = await page.evaluate(
/* istanbul ignore next */ /* istanbul ignore next */
(e) => e.querySelector('a').href, (e) => e.querySelector("a").href,
handle handle
); );

View File

@ -1,10 +1,10 @@
'use strict'; "use strict";
// Public modules from npm // Public modules from npm
const puppeteer = require('puppeteer'); const puppeteer = require("puppeteer");
// Modules from file // Modules from file
const shared = require('./shared.js'); const shared = require("./shared.js");
/** /**
* @protected * @protected
@ -18,11 +18,11 @@ module.exports.prepareBrowser = async function () {
if (shared.chromiumLocalPath) { if (shared.chromiumLocalPath) {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
executablePath: shared.chromiumLocalPath, executablePath: shared.chromiumLocalPath,
headless: !shared.debug // Use GUI when debug = true headless: !shared.debug, // Use GUI when debug = true
}); });
} else { } else {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: !shared.debug // Use GUI when debug = true headless: !shared.debug, // Use GUI when debug = true
}); });
} }
@ -42,9 +42,9 @@ module.exports.preparePage = async function (browser) {
// Block image download // Block image download
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', (request) => { page.on("request", (request) => {
if (request.resourceType() === 'image') request.abort(); if (request.resourceType() === "image") request.abort();
else if (request.resourceType === 'font') request.abort(); else if (request.resourceType === "font") request.abort();
// else if (request.resourceType() == 'stylesheet') request.abort(); // else if (request.resourceType() == 'stylesheet') request.abort();
// else if(request.resourceType == 'media') request.abort(); // else if(request.resourceType == 'media') request.abort();
else request.continue(); else request.continue();
@ -52,8 +52,8 @@ module.exports.preparePage = async function (browser) {
// Set custom user-agent // Set custom user-agent
const userAgent = const userAgent =
'Mozilla/5.0 (X11; Linux x86_64)' + "Mozilla/5.0 (X11; Linux x86_64)" +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36'; "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36";
await page.setUserAgent(userAgent); await page.setUserAgent(userAgent);
return page; return page;

View File

@ -1,7 +1,7 @@
'use strict'; "use strict";
// Core modules // Core modules
const { join } = require('path'); const { join } = require("path");
/** /**
* Class containing variables shared between modules. * Class containing variables shared between modules.
@ -37,12 +37,12 @@ class Shared {
* Wait instruction for the browser created by puppeteer. * Wait instruction for the browser created by puppeteer.
* @type String * @type String
*/ */
static WAIT_STATEMENT = 'domcontentloaded'; static WAIT_STATEMENT = "domcontentloaded";
/** /**
* Path to the directory to save the cache generated by the API. * Path to the directory to save the cache generated by the API.
* @type String * @type String
*/ */
static _cacheDir = './f95cache'; static _cacheDir = "./f95cache";
/** /**
* If true, it opens a new browser for each request to * If true, it opens a new browser for each request to
* the F95Zone platform, otherwise it reuses the same. * the F95Zone platform, otherwise it reuses the same.
@ -99,21 +99,21 @@ class Shared {
* @returns {String} * @returns {String}
*/ */
static get cookiesCachePath() { static get cookiesCachePath() {
return join(this._cacheDir, 'cookies.json'); return join(this._cacheDir, "cookies.json");
} }
/** /**
* Path to the game engine cache. * Path to the game engine cache.
* @returns {String} * @returns {String}
*/ */
static get enginesCachePath() { static get enginesCachePath() {
return join(this._cacheDir, 'engines.json'); return join(this._cacheDir, "engines.json");
} }
/** /**
* Path to the cache of possible game states. * Path to the cache of possible game states.
* @returns {String} * @returns {String}
*/ */
static get statusesCachePath() { static get statusesCachePath() {
return join(this._cacheDir, 'statuses.json'); return join(this._cacheDir, "statuses.json");
} }
/** /**
* If true, it opens a new browser for each request * If true, it opens a new browser for each request

View File

@ -1,12 +1,12 @@
'use strict'; "use strict";
// Public modules from npm // Public modules from npm
const ky = require('ky-universal').create({ const ky = require("ky-universal").create({
throwHttpErrors: false throwHttpErrors: false,
}); });
// Modules from file // Modules from file
const { F95_BASE_URL } = require('./constants/urls.js'); const { F95_BASE_URL } = require("./constants/urls.js");
/** /**
* @protected * @protected

View File

@ -5,9 +5,9 @@ const {
getGameData, getGameData,
loadF95BaseData, loadF95BaseData,
getUserData, getUserData,
logout logout,
} = require('../app/index.js'); } = require("../app/index.js");
const GameDownload = require('../app/scripts/classes/game-download.js'); const GameDownload = require("../app/scripts/classes/game-download.js");
debug(true); debug(true);
main(); main();