From b2df5df7409a83a3130b31569817d62e2c3b951d Mon Sep 17 00:00:00 2001 From: "samuele.berlusconi" Date: Wed, 21 Oct 2020 15:31:11 +0200 Subject: [PATCH] Fixed timeout errors (probably), fixed missing preview for games --- README.md | 7 + app/index.js | 195 +++++++++--------- app/scripts/classes/game-info.js | 20 +- app/scripts/classes/login-result.js | 6 +- .../{css-selectors.js => css-selector.js} | 0 app/scripts/constants/{urls.js => url.js} | 0 app/scripts/game-scraper.js | 83 +++++--- app/scripts/game-searcher.js | 37 ++-- app/scripts/shared.js | 62 +++--- app/scripts/{urls-helper.js => url-helper.js} | 2 +- package-lock.json | 72 ++++++- package.json | 3 +- test/index-test.js | 42 ++++ test/user-test.js | 15 +- 14 files changed, 336 insertions(+), 208 deletions(-) rename app/scripts/constants/{css-selectors.js => css-selector.js} (100%) rename app/scripts/constants/{urls.js => url.js} (100%) rename app/scripts/{urls-helper.js => url-helper.js} (97%) diff --git a/README.md b/README.md index 4e0fa7f..9a094a9 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,10 @@ # F95API Unofficial Node JS module for scraping F95Zone platform + +# Guidelines for errors ++ If you can, return a meaningful value ++ Return `null` only if the function should return a complex object (including strings) ++ Return an empty array if the function should return an array ++ Return `false`, `-1` when the function should retrn `boolean` or `number` ++ Throw an exception only if it is an error or if a wrong value could mess up the functioning of the library diff --git a/app/index.js b/app/index.js index f7beedb..9c1add9 100644 --- a/app/index.js +++ b/app/index.js @@ -5,13 +5,9 @@ const fs = require("fs"); // Modules from file const shared = require("./scripts/shared.js"); -const constURLs = require("./scripts/constants/urls.js"); -const selectors = require("./scripts/constants/css-selectors.js"); -const { - isStringAValidURL, - urlExists, - isF95URL, -} = require("./scripts/urls-helper.js"); +const urlK = require("./scripts/constants/url.js"); +const selectorK = require("./scripts/constants/css-selector.js"); +const urlHelper = require("./scripts/url-helper.js"); const scraper = require("./scripts/game-scraper.js"); const { prepareBrowser, @@ -37,6 +33,9 @@ module.exports.UserData = UserData; */ module.exports.debug = function (value) { shared.debug = value; + + // Configure logger + shared.logger.level = value ? "debug" : "OFF"; }; /** * @public @@ -86,6 +85,7 @@ module.exports.setChromiumPath = function (value) { //#region Global variables var _browser = null; +const USER_NOT_LOGGED = "User not authenticated, unable to continue"; //#endregion //#region Export methods @@ -99,27 +99,22 @@ var _browser = null; */ module.exports.login = async function (username, password) { if (shared.isLogged) { - if (shared.debug) console.log("Already logged in"); - const result = new LoginResult(); - result.success = true; - result.message = "Already logged in"; + shared.logger.info("Already logged in"); + const result = new LoginResult(true, "Already logged in"); return result; } // If cookies are loaded, use them to authenticate shared.cookies = loadCookies(); if (shared.cookies !== null) { - if (shared.debug) console.log("Valid session, no need to re-authenticate"); + shared.logger.info("Valid session, no need to re-authenticate"); shared.isLogged = true; - const result = new LoginResult(); - result.success = true; - result.message = "Logged with cookies"; + const result = new LoginResult(true, "Logged with cookies"); return result; } // Else, log in throught browser - if (shared.debug) - console.log("No saved sessions or expired session, login on the platform"); + shared.logger.info("No saved sessions or expired session, login on the platform"); if (_browser === null && !shared.isolation) _browser = await prepareBrowser(); const browser = shared.isolation ? await prepareBrowser() : _browser; @@ -130,9 +125,9 @@ module.exports.login = async function (username, password) { if (result.success) { // Reload cookies shared.cookies = loadCookies(); - if (shared.debug) console.log("User logged in through the platform"); + shared.logger.info("User logged in through the platform"); } else { - console.warn("Error during authentication: " + result.message); + shared.logger.warn("Error during authentication: " + result.message); } if (shared.isolation) await browser.close(); return result; @@ -146,11 +141,11 @@ module.exports.login = async function (username, password) { */ module.exports.loadF95BaseData = async function () { if (!shared.isLogged || !shared.cookies) { - console.warn("User not authenticated, unable to continue"); + shared.logger.warn(USER_NOT_LOGGED); return false; } - if (shared.debug) console.log("Loading base data..."); + shared.logger.info("Loading base data..."); // Prepare a new web page if (_browser === null && !shared.isolation) _browser = await prepareBrowser(); @@ -160,30 +155,31 @@ module.exports.loadF95BaseData = async function () { await page.setCookie(...shared.cookies); // Set cookies to avoid login // Go to latest update page and wait for it to load - await page.goto(constURLs.F95_LATEST_UPDATES, { + await page.goto(urlK.F95_LATEST_UPDATES, { waitUntil: shared.WAIT_STATEMENT, }); // Obtain engines (disk/online) - await page.waitForSelector(selectors.ENGINE_ID_SELECTOR); + await page.waitForSelector(selectorK.ENGINE_ID_SELECTOR); shared.engines = await loadValuesFromLatestPage( page, shared.enginesCachePath, - selectors.ENGINE_ID_SELECTOR, + selectorK.ENGINE_ID_SELECTOR, "engines" ); // Obtain statuses (disk/online) - await page.waitForSelector(selectors.STATUS_ID_SELECTOR); + await page.waitForSelector(selectorK.STATUS_ID_SELECTOR); shared.statuses = await loadValuesFromLatestPage( page, shared.statusesCachePath, - selectors.STATUS_ID_SELECTOR, + selectorK.STATUS_ID_SELECTOR, "statuses" ); + await page.close(); if (shared.isolation) await browser.close(); - if (shared.debug) console.log("Base data loaded"); + shared.logger.info("Base data loaded"); return true; }; /** @@ -195,13 +191,13 @@ module.exports.loadF95BaseData = async function () { */ module.exports.chekIfGameHasUpdate = async function (info) { if (!shared.isLogged || !shared.cookies) { - console.warn("user not authenticated, unable to continue"); - return info.version; + shared.logger.warn(USER_NOT_LOGGED); + return false; } // F95 change URL at every game update, // so if the URL is different an update is available - const exists = await urlExists(info.f95url, true); + const exists = await urlHelper.urlExists(info.f95url, true); if (!exists) return true; // Parse version from title @@ -221,11 +217,11 @@ module.exports.chekIfGameHasUpdate = async function (info) { * @param {String} name Name of the game searched * @param {Boolean} includeMods Indicates whether to also take mods into account when searching * @returns {Promise} List of information obtained where each item corresponds to - * an identified game (in the case of homonymy). If no games were found, null is returned + * an identified game (in the case of homonymy of titles) */ module.exports.getGameData = async function (name, includeMods) { if (!shared.isLogged || !shared.cookies) { - console.warn("user not authenticated, unable to continue"); + shared.logger.warn(USER_NOT_LOGGED); return null; } @@ -244,10 +240,12 @@ module.exports.getGameData = async function (name, includeMods) { // Filter for mods const result = []; for (const info of await Promise.all(promiseList)) { - // Skip mods if not required + // Ignore empty results if (!info) continue; + // Skip mods if not required if (info.isMod && !includeMods) continue; - else result.push(info); + // Else save data + result.push(info); } if (shared.isolation) await browser.close(); @@ -262,13 +260,14 @@ module.exports.getGameData = async function (name, includeMods) { */ module.exports.getGameDataFromURL = async function (url) { if (!shared.isLogged || !shared.cookies) { - console.warn("user not authenticated, unable to continue"); + shared.logger.warn(USER_NOT_LOGGED); return null; } // Check URL - if (!urlExists(url)) return null; - if (!isF95URL(url)) throw new Error(url + " is not a valid F95Zone URL"); + const exists = await urlHelper.urlExists(url); + if (!exists) throw new URIError(url + " is not a valid URL"); + if (!urlHelper.isF95URL(url)) throw new Error(url + " is not a valid F95Zone URL"); // Gets the search results of the game being searched for if (_browser === null && !shared.isolation) _browser = await prepareBrowser(); @@ -284,11 +283,11 @@ module.exports.getGameDataFromURL = async function (url) { * @public * Gets the data of the currently logged in user. * You **must** be logged in to the portal before calling this method. - * @returns {Promise} Data of the user currently logged in or null if an error arise + * @returns {Promise} Data of the user currently logged in */ module.exports.getUserData = async function () { if (!shared.isLogged || !shared.cookies) { - console.warn("user not authenticated, unable to continue"); + shared.logger.warn(USER_NOT_LOGGED); return null; } @@ -297,29 +296,31 @@ module.exports.getUserData = async function () { const browser = shared.isolation ? await prepareBrowser() : _browser; const page = await preparePage(browser); // Set new isolated page await page.setCookie(...shared.cookies); // Set cookies to avoid login - await page.goto(constURLs.F95_BASE_URL); // Go to base page + await page.goto(urlK.F95_BASE_URL); // Go to base page // Explicitly wait for the required items to load - await page.waitForSelector(selectors.USERNAME_ELEMENT); - await page.waitForSelector(selectors.AVATAR_PIC); + await Promise.all([ + page.waitForSelector(selectorK.USERNAME_ELEMENT), + page.waitForSelector(selectorK.AVATAR_PIC), + ]); const threads = getUserWatchedGameThreads(browser); const username = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).innerText, - selectors.USERNAME_ELEMENT + selectorK.USERNAME_ELEMENT ); const avatarSrc = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).getAttribute("src"), - selectors.AVATAR_PIC + selectorK.AVATAR_PIC ); const ud = new UserData(); ud.username = username; - ud.avatarSrc = isStringAValidURL(avatarSrc) ? avatarSrc : null; + ud.avatarSrc = urlHelper.isStringAValidURL(avatarSrc) ? avatarSrc : null; ud.watchedThreads = await threads; await page.close(); @@ -334,9 +335,11 @@ module.exports.getUserData = async function () { */ module.exports.logout = async function () { if (!shared.isLogged || !shared.cookies) { - console.warn("user not authenticated, unable to continue"); + shared.logger.warn(USER_NOT_LOGGED); return; } + + // Logout shared.isLogged = false; // Gracefully close shared browser @@ -390,10 +393,7 @@ function isCookieExpired(cookie) { const expirationDate = new Date(expirationUnixTimestamp * 1000); if (expirationDate < Date.now()) { - if (shared.debug) - console.log( - "Cookie " + cookie.name + " expired, you need to re-authenticate" - ); + shared.logger.warn("Cookie " + cookie.name + " expired, you need to re-authenticate"); expiredCookies = true; } } @@ -421,15 +421,14 @@ async function loadValuesFromLatestPage( elementRequested ) { // 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..."); + shared.logger.info("Load " + elementRequested + " from disk..."); if (fs.existsSync(path)) { const valueJSON = fs.readFileSync(path); return JSON.parse(valueJSON); } // Otherwise, connect and download the data from the portal - if (shared.debug) - console.log("No " + elementRequested + " cached, downloading..."); + shared.logger.info("No " + elementRequested + " cached, downloading..."); const values = await getValuesFromLatestPage( page, selector, @@ -449,7 +448,7 @@ async function loadValuesFromLatestPage( * @return {Promise} List of uppercase strings indicating the textual values of the elements identified by the selector */ async function getValuesFromLatestPage(page, selector, logMessage) { - if (shared.debug) console.log(logMessage); + shared.logger.info(logMessage); const result = []; const elements = await page.$$(selector); @@ -477,65 +476,63 @@ async function getValuesFromLatestPage(page, selector, logMessage) { */ async function loginF95(browser, username, password) { const page = await preparePage(browser); // Set new isolated page - await page.goto(constURLs.F95_LOGIN_URL); // Go to login page + await page.goto(urlK.F95_LOGIN_URL); // Go to login page // Explicitly wait for the required items to load await Promise.all([ - page.waitForSelector(selectors.USERNAME_INPUT), - page.waitForSelector(selectors.PASSWORD_INPUT), - page.waitForSelector(selectors.LOGIN_BUTTON), + page.waitForSelector(selectorK.USERNAME_INPUT), + page.waitForSelector(selectorK.PASSWORD_INPUT), + page.waitForSelector(selectorK.LOGIN_BUTTON), ]); - await page.type(selectors.USERNAME_INPUT, username); // Insert username - await page.type(selectors.PASSWORD_INPUT, password); // Insert password + await page.type(selectorK.USERNAME_INPUT, username); // Insert username + await page.type(selectorK.PASSWORD_INPUT, password); // Insert password await Promise.all([ - page.click(selectors.LOGIN_BUTTON), // Click on the login button + page.click(selectorK.LOGIN_BUTTON), // Click on the login button page.waitForNavigation({ waitUntil: shared.WAIT_STATEMENT, }), // Wait for page to load ]); // Prepare result - const result = new LoginResult(); + let message = ""; // Check if the user is logged in - result.success = await page.evaluate( + let success = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector) !== null, - selectors.AVATAR_INFO + selectorK.AVATAR_INFO ); + let errorMessageExists = await page.evaluate( + /* istanbul ignore next */ + (selector) => + document.querySelector(selector) !== null, + selectorK.LOGIN_MESSAGE_ERROR + ) + // Save cookies to avoid re-auth - if (result.success) { + if (success) { const c = await page.cookies(); fs.writeFileSync(shared.cookiesCachePath, JSON.stringify(c)); - result.message = "Authentication successful"; - } else if ( - // Obtain the error message - await page.evaluate( - /* istanbul ignore next */ (selector) => - document.querySelector(selector) !== null, - selectors.LOGIN_MESSAGE_ERROR - ) - ) { + message = "Authentication successful"; + } else if (errorMessageExists) { const errorMessage = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).innerText, - selectors.LOGIN_MESSAGE_ERROR + selectorK.LOGIN_MESSAGE_ERROR ); if (errorMessage === "Incorrect password. Please try again.") { - result.message = "Incorrect password"; - } else if ( - errorMessage === - 'The requested user "' + username + '" could not be found.' - ) { - result.message = "Incorrect username"; - } else result.message = errorMessage; - } else result.message = "Unknown error"; + message = "Incorrect password"; + } else if (errorMessage ==='The requested user \'' + username + '\' could not be found.') { + // The escaped quotes are important! + message = "Incorrect username"; + } else message = errorMessage; + } else message = "Unknown error"; await page.close(); // Close the page - return result; + return new LoginResult(success, message); } /** * @private @@ -545,39 +542,37 @@ async function loginF95(browser, username, password) { */ async function getUserWatchedGameThreads(browser) { const page = await preparePage(browser); // Set new isolated page - await page.goto(constURLs.F95_WATCHED_THREADS); // Go to the thread page + await page.goto(urlK.F95_WATCHED_THREADS); // Go to the thread page // Explicitly wait for the required items to load - await page.waitForSelector(selectors.WATCHED_THREAD_FILTER_POPUP_BUTTON); + await page.waitForSelector(selectorK.WATCHED_THREAD_FILTER_POPUP_BUTTON); // Show the popup await Promise.all([ - page.click(selectors.WATCHED_THREAD_FILTER_POPUP_BUTTON), - page.waitForSelector(selectors.UNREAD_THREAD_CHECKBOX), - page.waitForSelector(selectors.ONLY_GAMES_THREAD_OPTION), - page.waitForSelector(selectors.FILTER_THREADS_BUTTON), + page.click(selectorK.WATCHED_THREAD_FILTER_POPUP_BUTTON), + page.waitForSelector(selectorK.UNREAD_THREAD_CHECKBOX), + page.waitForSelector(selectorK.ONLY_GAMES_THREAD_OPTION), + page.waitForSelector(selectorK.FILTER_THREADS_BUTTON), ]); // Set the filters await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).removeAttribute("checked"), - selectors.UNREAD_THREAD_CHECKBOX + selectorK.UNREAD_THREAD_CHECKBOX ); // Also read the threads already read // Filter the threads - await Promise.all([ - page.click(selectors.ONLY_GAMES_THREAD_OPTION), - page.click(selectors.FILTER_THREADS_BUTTON), - page.waitForSelector(selectors.WATCHED_THREAD_URLS), - ]); + await page.click(selectorK.ONLY_GAMES_THREAD_OPTION); + await page.click(selectorK.FILTER_THREADS_BUTTON); + await page.waitForSelector(selectorK.WATCHED_THREAD_URLS); // Get the threads urls const urls = []; let nextPageExists = false; do { // Get all the URLs - for (const handle of await page.$$(selectors.WATCHED_THREAD_URLS)) { + for (const handle of await page.$$(selectorK.WATCHED_THREAD_URLS)) { const src = await page.evaluate( /* istanbul ignore next */ (element) => element.href, handle @@ -589,13 +584,13 @@ async function getUserWatchedGameThreads(browser) { nextPageExists = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector), - selectors.WATCHED_THREAD_NEXT_PAGE + selectorK.WATCHED_THREAD_NEXT_PAGE ); // Click to next page if (nextPageExists) { - await page.click(selectors.WATCHED_THREAD_NEXT_PAGE); - await page.waitForSelector(selectors.WATCHED_THREAD_URLS); + await page.click(selectorK.WATCHED_THREAD_NEXT_PAGE); + await page.waitForSelector(selectorK.WATCHED_THREAD_URLS); } } while (nextPageExists); diff --git a/app/scripts/classes/game-info.js b/app/scripts/classes/game-info.js index f13aedf..0c9f03e 100644 --- a/app/scripts/classes/game-info.js +++ b/app/scripts/classes/game-info.js @@ -1,7 +1,5 @@ "use strict"; -const UNKNOWN = "Unknown"; - class GameInfo { constructor() { //#region Properties @@ -9,12 +7,12 @@ class GameInfo { * Game name * @type String */ - this.name = UNKNOWN; + this.name = null; /** * Game author * @type String */ - this.author = UNKNOWN; + this.author = null; /** * URL to the game's official conversation on the F95Zone portal * @type String @@ -24,7 +22,7 @@ class GameInfo { * Game description * @type String */ - this.overview = UNKNOWN; + this.overview = null; /** * List of tags associated with the game * @type String[] @@ -34,12 +32,12 @@ class GameInfo { * Graphics engine used for game development * @type String */ - this.engine = UNKNOWN; + this.engine = null; /** * Progress of the game * @type String */ - this.status = UNKNOWN; + this.status = null; /** * Game description image URL * @type String @@ -49,17 +47,17 @@ class GameInfo { * Game version * @type String */ - this.version = UNKNOWN; + this.version = null; /** * Last time the game underwent updates * @type String */ - this.lastUpdate = UNKNOWN; + this.lastUpdate = null; /** * Last time the local copy of the game was run * @type String */ - this.lastPlayed = UNKNOWN; + this.lastPlayed = null; /** * Specifies if the game is original or a mod * @type Boolean @@ -74,7 +72,7 @@ class GameInfo { * Directory containing the local copy of the game * @type String */ - this.gameDir = UNKNOWN; + this.gameDir = null; /** * Information on game file download links, * including information on hosting platforms diff --git a/app/scripts/classes/login-result.js b/app/scripts/classes/login-result.js index 15eb311..5eb9646 100644 --- a/app/scripts/classes/login-result.js +++ b/app/scripts/classes/login-result.js @@ -4,17 +4,17 @@ * Object obtained in response to an attempt to login to the portal. */ class LoginResult { - constructor() { + constructor(success, message) { /** * Result of the login operation * @type Boolean */ - this.success = false; + this.success = success; /** * Login response message * @type String */ - this.message = ""; + this.message = message; } } module.exports = LoginResult; diff --git a/app/scripts/constants/css-selectors.js b/app/scripts/constants/css-selector.js similarity index 100% rename from app/scripts/constants/css-selectors.js rename to app/scripts/constants/css-selector.js diff --git a/app/scripts/constants/urls.js b/app/scripts/constants/url.js similarity index 100% rename from app/scripts/constants/urls.js rename to app/scripts/constants/url.js diff --git a/app/scripts/game-scraper.js b/app/scripts/game-scraper.js index 3079d82..9b4c2d5 100644 --- a/app/scripts/game-scraper.js +++ b/app/scripts/game-scraper.js @@ -6,11 +6,12 @@ const puppeteer = require("puppeteer"); // skipcq: JS-0128 // Modules from file const shared = require("./shared.js"); -const selectors = require("./constants/css-selectors.js"); +const selectorK = require("./constants/css-selector.js"); const { preparePage } = require("./puppeteer-helper.js"); const GameDownload = require("./classes/game-download.js"); const GameInfo = require("./classes/game-info.js"); -const urlsHelper = require("./urls-helper.js"); +const urlHelper = require("./url-helper.js"); +const { TimeoutError } = require("ky-universal"); /** * @protected @@ -18,16 +19,15 @@ const urlsHelper = require("./urls-helper.js"); * @param {puppeteer.Browser} browser Browser object used for navigation * @param {String} url URL (String) of the game/mod to extract data from * @return {Promise} Complete information about the game you are - * looking for or null if the URL doesn't exists + * looking for */ module.exports.getGameInfo = async function (browser, url) { - if (shared.debug) console.log("Obtaining game info"); + shared.logger.info("Obtaining game info"); // Verify the correctness of the URL - if (!urlsHelper.isF95URL(url)) - throw new Error(url + " is not a valid F95Zone URL"); - const exists = await urlsHelper.urlExists(url); - if (!exists) return null; + const exists = await urlHelper.urlExists(url); + if (!exists) throw new URIError(url + " is not a valid URL"); + if (!urlHelper.isF95URL(url)) throw new Error(url + " is not a valid F95Zone URL"); const page = await preparePage(browser); // Set new isolated page await page.setCookie(...shared.cookies); // Set cookies to avoid login @@ -41,7 +41,7 @@ module.exports.getGameInfo = async function (browser, url) { const title = getGameTitle(page); const author = getGameAuthor(page); const tags = getGameTags(page); - const redirectUrl = urlsHelper.getUrlRedirect(url); + const redirectUrl = urlHelper.getUrlRedirect(url); info = await parsePrefixes(page, info); // Fill status/engines/isMod const structuredText = await getMainPostStructuredText(page); const overview = getOverview(structuredText, info.isMod); @@ -60,7 +60,7 @@ module.exports.getGameInfo = async function (browser, url) { ? parsedInfos.UPDATED : parsedInfos.THREAD_UPDATED; info.previewSource = await previewSource; - info.changelog = (await changelog) || "Unknown changelog"; + info.changelog = await changelog; //let downloadData = getGameDownloadLink(page); //info.downloadInfo = await downloadData; @@ -70,7 +70,7 @@ module.exports.getGameInfo = async function (browser, url) { * keep the links for downloading the games. */ await page.close(); // Close the page - if (shared.debug) console.log("Founded data for " + info.name); + shared.logger.info("Founded data for " + info.name); return info; }; @@ -91,7 +91,7 @@ module.exports.getGameVersionFromTitle = async function (browser, info) { const titleHTML = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).innerHTML, - selectors.GAME_TITLE + selectorK.GAME_TITLE ); const title = HTMLParser.parse(titleHTML).childNodes.pop().rawText; @@ -129,7 +129,7 @@ function getOverview(text, isMod) { */ async function getMainPostStructuredText(page) { // Gets the first post, where are listed all the game's informations - const post = (await page.$$(selectors.THREAD_POSTS))[0]; + const post = (await page.$$(selectorK.THREAD_POSTS))[0]; // The info are plain text so we need to parse the HTML code const bodyHTML = await page.evaluate( @@ -151,7 +151,7 @@ async function getGameAuthor(page) { const titleHTML = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).innerHTML, - selectors.GAME_TITLE + selectorK.GAME_TITLE ); const structuredTitle = HTMLParser.parse(titleHTML); @@ -180,7 +180,7 @@ function parseConversationPage(text) { // Create pair key/value const splitted = line.split(":"); - const key = splitted[0].trim().toUpperCase().replaceAll(" ", "_"); // Uppercase to avoid mismatch + const key = splitted[0].trim().toUpperCase().replace(/ /g, "_"); // Uppercase to avoid mismatch const value = splitted[1].trim(); // Add pair to the dict if valid @@ -194,23 +194,24 @@ function parseConversationPage(text) { * @private * Gets the URL of the image used as a preview for the game in the conversation. * @param {puppeteer.Page} page Page containing the URL to be extrapolated - * @returns {Promise} URL (String) of the image or null if failed to get it + * @returns {Promise} URL (String) of the image or a empty string if failed to get it */ async function getGamePreviewSource(page) { + await page.waitForSelector(selectorK.GAME_IMAGES); const src = await page.evaluate( /* istanbul ignore next */ (selector) => { // Get the firs image available const img = document.querySelector(selector); - + if (img) return img.getAttribute("src"); else return null; }, - selectors.GAME_IMAGES + selectorK.GAME_IMAGES ); // Check if the URL is valid - return urlsHelper.isStringAValidURL(src) ? src : null; + return urlHelper.isStringAValidURL(src) ? src : ""; } /** @@ -224,7 +225,7 @@ async function getGameTitle(page) { const titleHTML = await page.evaluate( /* istanbul ignore next */ (selector) => document.querySelector(selector).innerHTML, - selectors.GAME_TITLE + selectorK.GAME_TITLE ); const structuredTitle = HTMLParser.parse(titleHTML); @@ -244,7 +245,7 @@ async function getGameTags(page) { const tags = []; // Get the game tags - for (const handle of await page.$$(selectors.GAME_TAGS)) { + for (const handle of await page.$$(selectorK.GAME_TAGS)) { const tag = await page.evaluate( /* istanbul ignore next */ (element) => element.innerText, @@ -268,7 +269,7 @@ async function parsePrefixes(page, info) { // The 'Ongoing' status is not specified, only 'Abandoned'/'OnHold'/'Complete' info.status = "Ongoing"; - for (const handle of await page.$$(selectors.GAME_TITLE_PREFIXES)) { + for (const handle of await page.$$(selectorK.GAME_TITLE_PREFIXES)) { const value = await page.evaluate( /* istanbul ignore next */ (element) => element.innerText, @@ -277,10 +278,10 @@ async function parsePrefixes(page, info) { // Clean the prefix const prefix = value.toUpperCase().replace("[", "").replace("]", "").trim(); - + // Getting infos... - if (shared.statuses.includes(prefix)) info.status = prefix; - else if (shared.engines.includes(prefix)) info.engine = prefix; + if (shared.statuses.includes(prefix)) info.status = capitalize(prefix); + else if (shared.engines.includes(prefix)) info.engine = capitalize(prefix); // This is not a game but a mod else if (prefix === MOD_PREFIX) info.isMod = true; } @@ -291,22 +292,26 @@ async function parsePrefixes(page, info) { * @private * Get the last changelog available for the game. * @param {puppeteer.Page} page Page containing the changelog - * @returns {Promise} Changelog for the last version or null if no changelog is found + * @returns {Promise} Changelog for the last version or a empty string if no changelog is found */ async function getLastChangelog(page) { // Gets the first post, where are listed all the game's informations - const post = (await page.$$(selectors.THREAD_POSTS))[0]; + const post = (await page.$$(selectorK.THREAD_POSTS))[0]; - const spoiler = await post.$(selectors.THREAD_LAST_CHANGELOG); - if (!spoiler) return null; + const spoiler = await post.$(selectorK.THREAD_LAST_CHANGELOG); + if (!spoiler) return ""; const changelogHTML = await page.evaluate( /* istanbul ignore next */ (e) => e.innerText, spoiler ); - const parsedText = HTMLParser.parse(changelogHTML).structuredText; - return parsedText.replace("Spoiler", "").trim(); + let parsedText = HTMLParser.parse(changelogHTML).structuredText; + + // Clean the text + if (parsedText.startsWith("Spoiler")) parsedText = parsedText.replace("Spoiler", ""); + if (parsedText.startsWith(":")) parsedText = parsedText.replace(":", ""); + return parsedText.trim(); } /** @@ -314,8 +319,10 @@ async function getLastChangelog(page) { * Get game download links for different platforms. * @param {puppeteer.Page} page Page containing the links to be extrapolated * @returns {Promise} List of objects used for game download + * @deprecated */ // skipcq: JS-0128 +/* istanbul ignore next */ async function getGameDownloadLink(page) { // Most used hosting platforms const hostingPlatforms = [ @@ -332,7 +339,7 @@ async function getGameDownloadLink(page) { const platformOS = ["WIN", "LINUX", "MAC", "ALL"]; // Gets the which contains the download links - const temp = await page.$$(selectors.DOWNLOAD_LINKS_CONTAINER); + const temp = await page.$$(selectorK.DOWNLOAD_LINKS_CONTAINER); if (temp.length === 0) return []; // Look for the container that contains the links @@ -384,7 +391,9 @@ async function getGameDownloadLink(page) { * It can only be *WIN/LINUX/MAC/ALL* * @param {String} text HTML string to extract links from * @returns {GameDownload[]} List of game download links for the selected platform + * @deprecated */ +/* istanbul ignore next */ function extractGameHostingData(platform, text) { const PLATFORM_BOLD_OPEN = ""; const CONTAINER_SPAN_CLOSE = ""; @@ -427,7 +436,7 @@ function extractGameHostingData(platform, text) { endIndex = tag.indexOf(HREF_END, startIndex); const link = tag.substring(startIndex, endIndex); - if (urlsHelper.isStringAValidURL(link)) { + if (urlHelper.isStringAValidURL(link)) { const gd = new GameDownload(); gd.hosting = hosting.toUpperCase(); gd.link = link; @@ -438,4 +447,12 @@ function extractGameHostingData(platform, text) { } return downloadData; } + +/** + * Capitalize a string + * @param {String} string + */ +function capitalize(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} //#endregion Private methods diff --git a/app/scripts/game-searcher.js b/app/scripts/game-searcher.js index c5d8366..a6c7339 100644 --- a/app/scripts/game-searcher.js +++ b/app/scripts/game-searcher.js @@ -5,10 +5,10 @@ const puppeteer = require("puppeteer"); // skipcq: JS-0128 // Modules from file const shared = require("./shared.js"); -const constURLs = require("./constants/urls.js"); -const selectors = require("./constants/css-selectors.js"); +const urlK = require("./constants/url.js"); +const selectorK = require("./constants/css-selector.js"); const { preparePage } = require("./puppeteer-helper.js"); -const { isF95URL } = require("./urls-helper.js"); +const { isF95URL } = require("./url-helper.js"); /** * @protected @@ -18,41 +18,41 @@ const { isF95URL } = require("./urls-helper.js"); * @returns {Promise} List of URL of possible games obtained from the preliminary research on the F95 portal */ module.exports.getSearchGameResults = async function (browser, gamename) { - if (shared.debug) console.log("Searching " + gamename + " on F95Zone"); + shared.logger.info("Searching " + gamename + " on F95Zone"); const page = await preparePage(browser); // Set new isolated page await page.setCookie(...shared.cookies); // Set cookies to avoid login - await page.goto(constURLs.F95_SEARCH_URL, { + await page.goto(urlK.F95_SEARCH_URL, { waitUntil: shared.WAIT_STATEMENT, }); // Go to the search form and wait for it // Explicitly wait for the required items to load await Promise.all([ - page.waitForSelector(selectors.SEARCH_FORM_TEXTBOX), - page.waitForSelector(selectors.TITLE_ONLY_CHECKBOX), - page.waitForSelector(selectors.SEARCH_BUTTON), + page.waitForSelector(selectorK.SEARCH_FORM_TEXTBOX), + page.waitForSelector(selectorK.TITLE_ONLY_CHECKBOX), + page.waitForSelector(selectorK.SEARCH_BUTTON), ]); - await page.type(selectors.SEARCH_FORM_TEXTBOX, gamename); // Type the game we desire + await page.type(selectorK.SEARCH_FORM_TEXTBOX, gamename); // Type the game we desire + await page.click(selectorK.TITLE_ONLY_CHECKBOX); // Select only the thread with the game in the titles await Promise.all([ - page.click(selectors.TITLE_ONLY_CHECKBOX), // Select only the thread with the game in the titles - page.click(selectors.SEARCH_BUTTON), // Execute search + page.click(selectorK.SEARCH_BUTTON), // Execute search page.waitForNavigation({ waitUntil: shared.WAIT_STATEMENT, }), // Wait for page to load ]); // Select all conversation titles - const resultsThread = await page.$$(selectors.SEARCH_THREADS_RESULTS_BODY); + const resultsThread = await page.$$(selectorK.SEARCH_THREADS_RESULTS_BODY); // For each element found extract the info about the conversation - if (shared.debug) console.log("Extracting info from conversations"); + shared.logger.info("Extracting info from conversations"); const results = []; for (const element of resultsThread) { const gameUrl = await getOnlyGameThreads(page, element); if (gameUrl !== null) results.push(gameUrl); } - if (shared.debug) console.log("Find " + results.length + " conversations"); + shared.logger.info("Find " + results.length + " conversations"); await page.close(); // Close the page return results; @@ -68,8 +68,8 @@ module.exports.getSearchGameResults = async function (browser, gamename) { */ async function getOnlyGameThreads(page, divHandle) { // Obtain the elements containing the basic information - const titleHandle = await divHandle.$(selectors.THREAD_TITLE); - const forumHandle = await divHandle.$(selectors.SEARCH_THREADS_MEMBERSHIP); + const titleHandle = await divHandle.$(selectorK.THREAD_TITLE); + const forumHandle = await divHandle.$(selectorK.SEARCH_THREADS_MEMBERSHIP); // Get the forum where the thread was posted const forum = await getMembershipForum(page, forumHandle); @@ -120,12 +120,13 @@ async function getThreadURL(page, handle) { handle ); - // Some game already have a full URL + // Some game already have a full URL... if (isF95URL(relativeURLThread)) return relativeURLThread; + // ... else compose the URL and return const urlThread = new URL( relativeURLThread, - constURLs.F95_BASE_URL + urlK.F95_BASE_URL ).toString(); return urlThread; } diff --git a/app/scripts/shared.js b/app/scripts/shared.js index 2b15887..156d02c 100644 --- a/app/scripts/shared.js +++ b/app/scripts/shared.js @@ -3,6 +3,8 @@ // Core modules const { join } = require("path"); +const log4js = require("log4js"); + /** * Class containing variables shared between modules. */ @@ -12,27 +14,27 @@ class Shared { * Shows log messages and other useful functions for module debugging. * @type Boolean */ - static _debug = false; + static #_debug = false; /** * Indicates whether a user is logged in to the F95Zone platform or not. * @type Boolean */ - static _isLogged = false; + static #_isLogged = false; /** * List of cookies obtained from the F95Zone platform. * @type Object[] */ - static _cookies = null; + static #_cookies = null; /** * List of possible game engines used for development. * @type String[] */ - static _engines = null; + static #_engines = null; /** * List of possible development statuses that a game can assume. * @type String[] */ - static _statuses = null; + static #_statuses = null; /** * Wait instruction for the browser created by puppeteer. * @type String @@ -42,13 +44,18 @@ class Shared { * Path to the directory to save the cache generated by the API. * @type String */ - static _cacheDir = "./f95cache"; + static #_cacheDir = "./f95cache"; /** * If true, it opens a new browser for each request to * the F95Zone platform, otherwise it reuses the same. * @type Boolean */ - static _isolation = false; + static #_isolation = false; + /** + * Logger object used to write to both file and console. + * @type log4js.Logger + */ + static #_logger = log4js.getLogger(); //#endregion Properties //#region Getters @@ -57,63 +64,63 @@ class Shared { * @returns {Boolean} */ static get debug() { - return this._debug; + return this.#_debug; } /** * Indicates whether a user is logged in to the F95Zone platform or not. * @returns {Boolean} */ static get isLogged() { - return this._isLogged; + return this.#_isLogged; } /** * List of cookies obtained from the F95Zone platform. * @returns {Object[]} */ static get cookies() { - return this._cookies; + return this.#_cookies; } /** * List of possible game engines used for development. * @returns {String[]} */ static get engines() { - return this._engines; + return this.#_engines; } /** * List of possible development states that a game can assume. * @returns {String[]} */ static get statuses() { - return this._statuses; + return this.#_statuses; } /** * Directory to save the API cache. * @returns {String} */ static get cacheDir() { - return this._cacheDir; + return this.#_cacheDir; } /** * Path to the F95 platform cache. * @returns {String} */ static get cookiesCachePath() { - return join(this._cacheDir, "cookies.json"); + return join(this.#_cacheDir, "cookies.json"); } /** * Path to the game engine cache. * @returns {String} */ static get enginesCachePath() { - return join(this._cacheDir, "engines.json"); + return join(this.#_cacheDir, "engines.json"); } /** * Path to the cache of possible game states. * @returns {String} */ 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 @@ -121,37 +128,44 @@ class Shared { * @returns {Boolean} */ static get isolation() { - return this._isolation; + return this.#_isolation; + } + /** + * Logger object used to write to both file and console. + * @returns {log4js.Logger} + */ + static get logger() { + return this.#_logger; } //#endregion Getters //#region Setters static set cookies(val) { - this._cookies = val; + this.#_cookies = val; } static set engines(val) { - this._engines = val; + this.#_engines = val; } static set statuses(val) { - this._statuses = val; + this.#_statuses = val; } static set cacheDir(val) { - this._cacheDir = val; + this.#_cacheDir = val; } static set debug(val) { - this._debug = val; + this.#_debug = val; } static set isLogged(val) { - this._isLogged = val; + this.#_isLogged = val; } static set isolation(val) { - this._isolation = val; + this.#_isolation = val; } //#endregion Setters } diff --git a/app/scripts/urls-helper.js b/app/scripts/url-helper.js similarity index 97% rename from app/scripts/urls-helper.js rename to app/scripts/url-helper.js index 69b9612..5669ac3 100644 --- a/app/scripts/urls-helper.js +++ b/app/scripts/url-helper.js @@ -6,7 +6,7 @@ const ky = require("ky-universal").create({ }); // Modules from file -const { F95_BASE_URL } = require("./constants/urls.js"); +const { F95_BASE_URL } = require("./constants/url.js"); /** * @protected diff --git a/package-lock.json b/package-lock.json index df6ec70..061057e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "f95api", - "version": "1.2.4", + "version": "1.3.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -560,6 +560,11 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" }, + "date-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==" + }, "debug": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", @@ -768,6 +773,11 @@ "is-buffer": "~2.0.3" } }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" + }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -789,6 +799,16 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -870,8 +890,7 @@ "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "growl": { "version": "1.10.5", @@ -1239,6 +1258,14 @@ "minimist": "^1.2.5" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "ky": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/ky/-/ky-0.24.0.tgz", @@ -1334,6 +1361,18 @@ } } }, + "log4js": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "requires": { + "date-format": "^3.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.1", + "rfdc": "^1.1.4", + "streamroller": "^2.2.4" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1919,6 +1958,11 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "rfdc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", + "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2010,6 +2054,23 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "streamroller": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "requires": { + "date-format": "^2.1.0", + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==" + } + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -2170,6 +2231,11 @@ "through": "^2.3.8" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 687dfd3..68fd8ed 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "main": "./app/index.js", "name": "f95api", - "version": "1.3.4", + "version": "1.3.5", "author": { "name": "Millennium Earl" }, @@ -33,6 +33,7 @@ "dependencies": { "ky": "^0.24.0", "ky-universal": "^0.8.2", + "log4js": "^6.3.0", "node-html-parser": "^1.2.21", "puppeteer": "^5.3.1" }, diff --git a/test/index-test.js b/test/index-test.js index 74b312b..b8ce53e 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -1,5 +1,6 @@ "use strict"; +const urlHelper = require("../app/scripts/url-helper.js"); const expect = require("chai").expect; const F95API = require("../app/index"); const fs = require("fs"); @@ -142,6 +143,8 @@ describe("Search game data", function () { }); //#endregion Set-up + let testGame = null; + it("Search game when logged", async function () { const loginResult = await F95API.login(USERNAME, PASSWORD); expect(loginResult.success).to.be.true; @@ -162,11 +165,17 @@ describe("Search game data", function () { expect(result.isMod, "Should be false").to.be.false; expect(result.engine).to.equal("REN'PY"); expect(result.previewSource).to.equal(src); // Could be null -> Why sometimes doesn't get the image? + testGame = Object.assign({}, result); }); it("Search game when not logged", async function () { const result = await F95API.getGameData("Kingdom of Deception", false); expect(result, "Without being logged should return null").to.be.null; }); + it("Test game serialization", function() { + let json = JSON.stringify(testGame); + let parsedGameInfo = JSON.parse(json); + expect(parsedGameInfo).to.be.equal(testGame); + }); }); describe("Load user data", function () { @@ -230,3 +239,36 @@ describe("Check game update", function () { expect(update).to.be.true; }); }); + +describe("Test url-helper", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + //#endregion Set-up + + it("Check if URL exists", async function () { + // Check generic URLs... + let exists = urlHelper.urlExists("https://www.google.com/"); + expect(exists).to.be.true; + + exists = urlHelper.urlExists("www.google.com"); + expect(exists).to.be.true; + + exists = urlHelper.urlExists("https://www.google/"); + expect(exists).to.be.false; + + // Now check for more specific URLs (with redirect)... + exists = urlHelper.urlExists("https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/"); + expect(exists).to.be.true; + + exists = urlHelper.urlExists("https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/", true); + expect(exists).to.be.false; + }); + + it("Check if URL belong to the platform", async function () { + let belong = urlHelper.isF95URL("https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/"); + expect(belong).to.be.true; + + belong = urlHelper.isF95URL("https://www.google/"); + expect(belong).to.be.false; + }); +}); diff --git a/test/user-test.js b/test/user-test.js index 07717e7..60152ae 100644 --- a/test/user-test.js +++ b/test/user-test.js @@ -1,4 +1,3 @@ -const { join } = require("path"); const { debug, login, @@ -7,18 +6,16 @@ const { getUserData, logout, } = require("../app/index.js"); -const GameDownload = require("../app/scripts/classes/game-download.js"); debug(true); main(); -//downloadGameMEGA(); async function main() { const loginResult = await login("MillenniumEarl", "f9vTcRNuvxj4YpK"); if (loginResult.success) { await loadF95BaseData(); - const gameData = await getGameData("queen's brothel", false); + const gameData = await getGameData("kingdom of deception", false); console.log(gameData); // let userData = await getUserData(); @@ -26,13 +23,3 @@ async function main() { } logout(); } - -async function downloadGameMEGA() { - const gd = new GameDownload(); - gd.hosting = "NOPY"; - gd.link = - "https://f95zone.to/masked/mega.nz/2733/1470797/4O5LKwMx4ZSlw0QYTVMP0uDK660/hoobTb44f0IKx7Yio2SE2w/loX_px2vLRyNRQCnkNn5U7nnQe7jGmpEVERvH1tk7RjAFkQbs2kH_vCK6zVmRDuqiypPoIx358MNHHCd3QCdVvEsClSiAq4rwjK0r_ruXIs"; - const savepath = join(__dirname, "Kingdom_of_Deception-pc0.10.8.zip"); - const result = await gd.download(savepath); - console.log(result); -}