Format code with prettier
This commit fixes the style issues introduced in ca2a59e according to the output
from prettier.
Details: https://deepsource.io/gh/MillenniumEarl/F95API/transform/e30a87a8-5cf4-49aa-a78e-f15831f22583/
			
			
				pull/5/head
			
			
		
							parent
							
								
									ca2a59e0c3
								
							
						
					
					
						commit
						a52a8e32f4
					
				
							
								
								
									
										742
									
								
								app/index.js
								
								
								
								
							
							
						
						
									
										742
									
								
								app/index.js
								
								
								
								
							| 
						 | 
				
			
			@ -1,26 +1,24 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Core modules
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
const urlExist = require('url-exist');
 | 
			
		||||
const urlExist = require("url-exist");
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
const shared = require('./scripts/shared.js');
 | 
			
		||||
const constURLs = require('./scripts/costants/urls.js');
 | 
			
		||||
const constSelectors = require('./scripts/costants/css-selectors.js');
 | 
			
		||||
const shared = require("./scripts/shared.js");
 | 
			
		||||
const constURLs = require("./scripts/costants/urls.js");
 | 
			
		||||
const constSelectors = require("./scripts/costants/css-selectors.js");
 | 
			
		||||
const { isStringAValidURL } = require("./scripts/urls-helper.js");
 | 
			
		||||
const gameScraper = require("./scripts/game-scraper.js");
 | 
			
		||||
const {
 | 
			
		||||
    isStringAValidURL
 | 
			
		||||
} = require('./scripts/urls-helper.js');
 | 
			
		||||
const gameScraper = require('./scripts/game-scraper.js');
 | 
			
		||||
const {
 | 
			
		||||
    prepareBrowser,
 | 
			
		||||
    preparePage
 | 
			
		||||
} = require('./scripts/puppeteer-helper.js');
 | 
			
		||||
const GameInfo = require('./scripts/classes/game-info.js');
 | 
			
		||||
const LoginResult = require('./scripts/classes/login-result.js');
 | 
			
		||||
const UserData = require('./scripts/classes/user-data.js');
 | 
			
		||||
  prepareBrowser,
 | 
			
		||||
  preparePage,
 | 
			
		||||
} = require("./scripts/puppeteer-helper.js");
 | 
			
		||||
const GameInfo = require("./scripts/classes/game-info.js");
 | 
			
		||||
const LoginResult = require("./scripts/classes/login-result.js");
 | 
			
		||||
const UserData = require("./scripts/classes/user-data.js");
 | 
			
		||||
 | 
			
		||||
//#region Expose classes
 | 
			
		||||
module.exports.GameInfo = GameInfo;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,15 +32,15 @@ module.exports.UserData = UserData;
 | 
			
		|||
 * @param {Boolean} value
 | 
			
		||||
 */
 | 
			
		||||
module.exports.debug = function (value) {
 | 
			
		||||
    shared.debug = value;
 | 
			
		||||
}
 | 
			
		||||
  shared.debug = value;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Indicates whether a user is logged in to the F95Zone platform or not.
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
module.exports.isLogged = function () {
 | 
			
		||||
    return shared.isLogged;
 | 
			
		||||
  return shared.isLogged;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
| 
						 | 
				
			
			@ -50,28 +48,28 @@ module.exports.isLogged = function () {
 | 
			
		|||
 * to the F95Zone platform, otherwise it reuses the same.
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
module.exports.setIsolation = function(value) {
 | 
			
		||||
    shared.isolation = value;
 | 
			
		||||
}
 | 
			
		||||
module.exports.setIsolation = function (value) {
 | 
			
		||||
  shared.isolation = value;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Path to the cache directory
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
module.exports.getCacheDir = function() {
 | 
			
		||||
    return shared.cacheDir;
 | 
			
		||||
}
 | 
			
		||||
module.exports.getCacheDir = function () {
 | 
			
		||||
  return shared.cacheDir;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Set path to the cache directory
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
module.exports.setCacheDir = function(value) {
 | 
			
		||||
    shared.cacheDir = value;
 | 
			
		||||
module.exports.setCacheDir = function (value) {
 | 
			
		||||
  shared.cacheDir = value;
 | 
			
		||||
 | 
			
		||||
    // Create directory if it doesn't exist
 | 
			
		||||
    if (!fs.existsSync(shared.cacheDir)) fs.mkdirSync(shared.cacheDir);
 | 
			
		||||
}
 | 
			
		||||
  // Create directory if it doesn't exist
 | 
			
		||||
  if (!fs.existsSync(shared.cacheDir)) fs.mkdirSync(shared.cacheDir);
 | 
			
		||||
};
 | 
			
		||||
//#endregion Exposed properties
 | 
			
		||||
 | 
			
		||||
//#region Global variables
 | 
			
		||||
| 
						 | 
				
			
			@ -88,48 +86,49 @@ var _browser = null;
 | 
			
		|||
 * @returns {Promise<LoginResult>} Result of the operation
 | 
			
		||||
 */
 | 
			
		||||
module.exports.login = async function (username, password) {
 | 
			
		||||
    if (shared.isLogged) {
 | 
			
		||||
        if (shared.debug) console.log("Already logged in");
 | 
			
		||||
        let result = new LoginResult();
 | 
			
		||||
        result.success = true;
 | 
			
		||||
        result.message = '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.isLogged = true;
 | 
			
		||||
        let result = new LoginResult();
 | 
			
		||||
        result.success = true;
 | 
			
		||||
        result.message = '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');
 | 
			
		||||
 | 
			
		||||
    let browser = null;
 | 
			
		||||
    if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
    else {
 | 
			
		||||
        if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
        browser = _browser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result = await loginF95(browser, username, password);
 | 
			
		||||
    shared.isLogged = result.success;
 | 
			
		||||
 | 
			
		||||
    if (result.success) {
 | 
			
		||||
        // Reload cookies
 | 
			
		||||
        shared.cookies = loadCookies();
 | 
			
		||||
        if (shared.debug) console.log('User logged in through the platform');
 | 
			
		||||
    } else {
 | 
			
		||||
        console.warn('Error during authentication: ' + result.message);
 | 
			
		||||
    }
 | 
			
		||||
    if (shared.isolation) await browser.close();
 | 
			
		||||
  if (shared.isLogged) {
 | 
			
		||||
    if (shared.debug) console.log("Already logged in");
 | 
			
		||||
    let result = new LoginResult();
 | 
			
		||||
    result.success = true;
 | 
			
		||||
    result.message = "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.isLogged = true;
 | 
			
		||||
    let result = new LoginResult();
 | 
			
		||||
    result.success = true;
 | 
			
		||||
    result.message = "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");
 | 
			
		||||
 | 
			
		||||
  let browser = null;
 | 
			
		||||
  if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
  else {
 | 
			
		||||
    if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
    browser = _browser;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let result = await loginF95(browser, username, password);
 | 
			
		||||
  shared.isLogged = result.success;
 | 
			
		||||
 | 
			
		||||
  if (result.success) {
 | 
			
		||||
    // Reload cookies
 | 
			
		||||
    shared.cookies = loadCookies();
 | 
			
		||||
    if (shared.debug) console.log("User logged in through the platform");
 | 
			
		||||
  } else {
 | 
			
		||||
    console.warn("Error during authentication: " + result.message);
 | 
			
		||||
  }
 | 
			
		||||
  if (shared.isolation) await browser.close();
 | 
			
		||||
  return result;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * This method loads the main data from the F95 portal
 | 
			
		||||
| 
						 | 
				
			
			@ -138,47 +137,51 @@ module.exports.login = async function (username, password) {
 | 
			
		|||
 * @returns {Promise<Boolean>} Result of the operation
 | 
			
		||||
 */
 | 
			
		||||
module.exports.loadF95BaseData = async function () {
 | 
			
		||||
    if (!shared.isLogged) {
 | 
			
		||||
        console.warn('User not authenticated, unable to continue');
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
  if (!shared.isLogged) {
 | 
			
		||||
    console.warn("User not authenticated, unable to continue");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (shared.debug) console.log('Loading base data...');
 | 
			
		||||
  if (shared.debug) console.log("Loading base data...");
 | 
			
		||||
 | 
			
		||||
    // Prepare a new web page
 | 
			
		||||
    let browser = null;
 | 
			
		||||
    if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
    else {
 | 
			
		||||
        if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
        browser = _browser;
 | 
			
		||||
    }
 | 
			
		||||
  // Prepare a new web page
 | 
			
		||||
  let browser = null;
 | 
			
		||||
  if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
  else {
 | 
			
		||||
    if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
    browser = _browser;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    let page = await preparePage(browser); // Set new isolated page
 | 
			
		||||
    await page.setCookie(...shared.cookies); // Set cookies to avoid login
 | 
			
		||||
  let page = await preparePage(browser); // Set new isolated page
 | 
			
		||||
  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, {
 | 
			
		||||
        waitUntil: shared.WAIT_STATEMENT
 | 
			
		||||
    });
 | 
			
		||||
  // Go to latest update page and wait for it to load
 | 
			
		||||
  await page.goto(constURLs.F95_LATEST_UPDATES, {
 | 
			
		||||
    waitUntil: shared.WAIT_STATEMENT,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    // Obtain engines (disc/online)
 | 
			
		||||
    await page.waitForSelector(constSelectors.ENGINE_ID_SELECTOR);
 | 
			
		||||
    shared.engines = await loadValuesFromLatestPage(page,
 | 
			
		||||
        shared.enginesCachePath,
 | 
			
		||||
        constSelectors.ENGINE_ID_SELECTOR,
 | 
			
		||||
        'engines');
 | 
			
		||||
  // Obtain engines (disc/online)
 | 
			
		||||
  await page.waitForSelector(constSelectors.ENGINE_ID_SELECTOR);
 | 
			
		||||
  shared.engines = await loadValuesFromLatestPage(
 | 
			
		||||
    page,
 | 
			
		||||
    shared.enginesCachePath,
 | 
			
		||||
    constSelectors.ENGINE_ID_SELECTOR,
 | 
			
		||||
    "engines"
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    // Obtain statuses (disc/online)
 | 
			
		||||
    await page.waitForSelector(constSelectors.STATUS_ID_SELECTOR);
 | 
			
		||||
    shared.statuses = await loadValuesFromLatestPage(page,
 | 
			
		||||
        shared.statusesCachePath,
 | 
			
		||||
        constSelectors.STATUS_ID_SELECTOR,
 | 
			
		||||
        'statuses');
 | 
			
		||||
  // Obtain statuses (disc/online)
 | 
			
		||||
  await page.waitForSelector(constSelectors.STATUS_ID_SELECTOR);
 | 
			
		||||
  shared.statuses = await loadValuesFromLatestPage(
 | 
			
		||||
    page,
 | 
			
		||||
    shared.statusesCachePath,
 | 
			
		||||
    constSelectors.STATUS_ID_SELECTOR,
 | 
			
		||||
    "statuses"
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    if (shared.isolation) await browser.close();
 | 
			
		||||
    if (shared.debug) console.log('Base data loaded');
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
  if (shared.isolation) await browser.close();
 | 
			
		||||
  if (shared.debug) console.log("Base data loaded");
 | 
			
		||||
  return true;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Returns the currently online version of the specified game.
 | 
			
		||||
| 
						 | 
				
			
			@ -187,17 +190,17 @@ module.exports.loadF95BaseData = async function () {
 | 
			
		|||
 * @returns {Promise<String>} Currently online version of the specified game
 | 
			
		||||
 */
 | 
			
		||||
module.exports.getGameVersion = async function (info) {
 | 
			
		||||
    if (!shared.isLogged) {
 | 
			
		||||
        console.warn('user not authenticated, unable to continue');
 | 
			
		||||
        return info.version;
 | 
			
		||||
    }
 | 
			
		||||
  if (!shared.isLogged) {
 | 
			
		||||
    console.warn("user not authenticated, unable to continue");
 | 
			
		||||
    return info.version;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    let urlExists = await urlExist(info.f95url.toString());
 | 
			
		||||
  let urlExists = await urlExist(info.f95url.toString());
 | 
			
		||||
 | 
			
		||||
    // F95 change URL at every game update, so if the URL is the same no update is available
 | 
			
		||||
    if (urlExists) return info.version;
 | 
			
		||||
    else return await module.exports.getGameData(info.name, info.isMod).version;
 | 
			
		||||
}
 | 
			
		||||
  // F95 change URL at every game update, so if the URL is the same no update is available
 | 
			
		||||
  if (urlExists) return info.version;
 | 
			
		||||
  else return await module.exports.getGameData(info.name, info.isMod).version;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Starting from the name, it gets all the information about the game you are looking for.
 | 
			
		||||
| 
						 | 
				
			
			@ -208,38 +211,38 @@ module.exports.getGameVersion = async function (info) {
 | 
			
		|||
 * an identified game (in the case of homonymy). If no games were found, null is returned
 | 
			
		||||
 */
 | 
			
		||||
module.exports.getGameData = async function (name, includeMods) {
 | 
			
		||||
    if (!shared.isLogged) {
 | 
			
		||||
        console.warn('user not authenticated, unable to continue');
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
  if (!shared.isLogged) {
 | 
			
		||||
    console.warn("user not authenticated, unable to continue");
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    // Gets the search results of the game being searched for
 | 
			
		||||
    let browser = null;
 | 
			
		||||
    if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
    else {
 | 
			
		||||
        if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
        browser = _browser;
 | 
			
		||||
    }
 | 
			
		||||
    let urlList = await getSearchGameResults(browser, name);
 | 
			
		||||
  // Gets the search results of the game being searched for
 | 
			
		||||
  let browser = null;
 | 
			
		||||
  if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
  else {
 | 
			
		||||
    if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
    browser = _browser;
 | 
			
		||||
  }
 | 
			
		||||
  let urlList = await getSearchGameResults(browser, name);
 | 
			
		||||
 | 
			
		||||
    // Process previous partial results
 | 
			
		||||
    let promiseList = [];
 | 
			
		||||
    for (let url of urlList) {
 | 
			
		||||
        // Start looking for information
 | 
			
		||||
        promiseList.push(gameScraper.getGameInfo(browser, url));
 | 
			
		||||
    }
 | 
			
		||||
  // Process previous partial results
 | 
			
		||||
  let promiseList = [];
 | 
			
		||||
  for (let url of urlList) {
 | 
			
		||||
    // Start looking for information
 | 
			
		||||
    promiseList.push(gameScraper.getGameInfo(browser, url));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    // Filter for mods
 | 
			
		||||
    let result = [];
 | 
			
		||||
    for (let info of await Promise.all(promiseList)) {
 | 
			
		||||
        // Skip mods if not required
 | 
			
		||||
        if (info.isMod && !includeMods) continue;
 | 
			
		||||
        else result.push(info);
 | 
			
		||||
    }
 | 
			
		||||
  // Filter for mods
 | 
			
		||||
  let result = [];
 | 
			
		||||
  for (let info of await Promise.all(promiseList)) {
 | 
			
		||||
    // Skip mods if not required
 | 
			
		||||
    if (info.isMod && !includeMods) continue;
 | 
			
		||||
    else result.push(info);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (shared.isolation) await browser.close();
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
  if (shared.isolation) await browser.close();
 | 
			
		||||
  return result;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Gets the data of the currently logged in user.
 | 
			
		||||
| 
						 | 
				
			
			@ -247,58 +250,62 @@ module.exports.getGameData = async function (name, includeMods) {
 | 
			
		|||
 * @returns {Promise<UserData>} Data of the user currently logged in or null if an error arise
 | 
			
		||||
 */
 | 
			
		||||
module.exports.getUserData = async function () {
 | 
			
		||||
    if (!shared.isLogged) {
 | 
			
		||||
        console.warn('user not authenticated, unable to continue');
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
  if (!shared.isLogged) {
 | 
			
		||||
    console.warn("user not authenticated, unable to continue");
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    // Prepare a new web page
 | 
			
		||||
    let browser = null;
 | 
			
		||||
    if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
    else {
 | 
			
		||||
        if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
        browser = _browser;
 | 
			
		||||
    }
 | 
			
		||||
    let 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
 | 
			
		||||
  // Prepare a new web page
 | 
			
		||||
  let browser = null;
 | 
			
		||||
  if (shared.isolation) browser = await prepareBrowser();
 | 
			
		||||
  else {
 | 
			
		||||
    if (_browser === null) _browser = await prepareBrowser();
 | 
			
		||||
    browser = _browser;
 | 
			
		||||
  }
 | 
			
		||||
  let 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
 | 
			
		||||
 | 
			
		||||
    // Explicitly wait for the required items to load
 | 
			
		||||
    await page.waitForSelector(constSelectors.USERNAME_ELEMENT);
 | 
			
		||||
    await page.waitForSelector(constSelectors.AVATAR_PIC);
 | 
			
		||||
  // Explicitly wait for the required items to load
 | 
			
		||||
  await page.waitForSelector(constSelectors.USERNAME_ELEMENT);
 | 
			
		||||
  await page.waitForSelector(constSelectors.AVATAR_PIC);
 | 
			
		||||
 | 
			
		||||
    let threads = getUserWatchedGameThreads(browser);
 | 
			
		||||
  let threads = getUserWatchedGameThreads(browser);
 | 
			
		||||
 | 
			
		||||
    let username = await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
        document.querySelector(selector).innerText,
 | 
			
		||||
        constSelectors.USERNAME_ELEMENT);
 | 
			
		||||
  let username = await page.evaluate(
 | 
			
		||||
    /* istanbul ignore next */ (selector) =>
 | 
			
		||||
      document.querySelector(selector).innerText,
 | 
			
		||||
    constSelectors.USERNAME_ELEMENT
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    let avatarSrc = await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
        document.querySelector(selector).getAttribute('src'),
 | 
			
		||||
        constSelectors.AVATAR_PIC);
 | 
			
		||||
  let avatarSrc = await page.evaluate(
 | 
			
		||||
    /* istanbul ignore next */ (selector) =>
 | 
			
		||||
      document.querySelector(selector).getAttribute("src"),
 | 
			
		||||
    constSelectors.AVATAR_PIC
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    let ud = new UserData();
 | 
			
		||||
    ud.username = username;
 | 
			
		||||
    ud.avatarSrc = isStringAValidURL(avatarSrc) ? new URL(avatarSrc) : null;
 | 
			
		||||
    ud.watchedThreads = await threads;
 | 
			
		||||
  let ud = new UserData();
 | 
			
		||||
  ud.username = username;
 | 
			
		||||
  ud.avatarSrc = isStringAValidURL(avatarSrc) ? new URL(avatarSrc) : null;
 | 
			
		||||
  ud.watchedThreads = await threads;
 | 
			
		||||
 | 
			
		||||
    await page.close();
 | 
			
		||||
    if (shared.isolation) await browser.close();
 | 
			
		||||
  await page.close();
 | 
			
		||||
  if (shared.isolation) await browser.close();
 | 
			
		||||
 | 
			
		||||
    return ud;
 | 
			
		||||
}
 | 
			
		||||
  return ud;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 * Logout from the current user.
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 */
 | 
			
		||||
module.exports.logout = function() {
 | 
			
		||||
    if (!shared.isLogged) {
 | 
			
		||||
        console.warn('user not authenticated, unable to continue');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    shared.isLogged = false;
 | 
			
		||||
}
 | 
			
		||||
module.exports.logout = function () {
 | 
			
		||||
  if (!shared.isLogged) {
 | 
			
		||||
    console.warn("user not authenticated, unable to continue");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  shared.isLogged = false;
 | 
			
		||||
};
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region Private methods
 | 
			
		||||
| 
						 | 
				
			
			@ -311,21 +318,20 @@ module.exports.logout = function() {
 | 
			
		|||
 * @return {object[]} List of dictionaries or null if cookies don't exist
 | 
			
		||||
 */
 | 
			
		||||
function loadCookies() {
 | 
			
		||||
    // Check the existence of the cookie file
 | 
			
		||||
    if (fs.existsSync(shared.cookiesCachePath)) {
 | 
			
		||||
        // Read cookies
 | 
			
		||||
        let cookiesJSON = fs.readFileSync(shared.cookiesCachePath);
 | 
			
		||||
        let cookies = JSON.parse(cookiesJSON);
 | 
			
		||||
  // Check the existence of the cookie file
 | 
			
		||||
  if (fs.existsSync(shared.cookiesCachePath)) {
 | 
			
		||||
    // Read cookies
 | 
			
		||||
    let cookiesJSON = fs.readFileSync(shared.cookiesCachePath);
 | 
			
		||||
    let cookies = JSON.parse(cookiesJSON);
 | 
			
		||||
 | 
			
		||||
        // Check if the cookies have expired
 | 
			
		||||
        for (let cookie of cookies) {
 | 
			
		||||
            if (isCookieExpired(cookie)) return null;
 | 
			
		||||
        }
 | 
			
		||||
    // Check if the cookies have expired
 | 
			
		||||
    for (let cookie of cookies) {
 | 
			
		||||
      if (isCookieExpired(cookie)) return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        // Cookies loaded and verified
 | 
			
		||||
        return cookies;
 | 
			
		||||
 | 
			
		||||
    } else return null;
 | 
			
		||||
    // Cookies loaded and verified
 | 
			
		||||
    return cookies;
 | 
			
		||||
  } else return null;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * @private
 | 
			
		||||
| 
						 | 
				
			
			@ -334,23 +340,26 @@ function loadCookies() {
 | 
			
		|||
 * @returns {Boolean} true if the cookie has expired, false otherwise
 | 
			
		||||
 */
 | 
			
		||||
function isCookieExpired(cookie) {
 | 
			
		||||
    // Local variables
 | 
			
		||||
    let expiredCookies = false;
 | 
			
		||||
  // Local variables
 | 
			
		||||
  let expiredCookies = false;
 | 
			
		||||
 | 
			
		||||
    // Ignore cookies that never expire 
 | 
			
		||||
    let expirationUnixTimestamp = cookie['expire'];
 | 
			
		||||
  // Ignore cookies that never expire
 | 
			
		||||
  let expirationUnixTimestamp = cookie["expire"];
 | 
			
		||||
 | 
			
		||||
    if (expirationUnixTimestamp !== '-1') {
 | 
			
		||||
        // Convert UNIX epoch timestamp to normal Date
 | 
			
		||||
        let expirationDate = new Date(expirationUnixTimestamp * 1000);
 | 
			
		||||
  if (expirationUnixTimestamp !== "-1") {
 | 
			
		||||
    // Convert UNIX epoch timestamp to normal Date
 | 
			
		||||
    let expirationDate = new Date(expirationUnixTimestamp * 1000);
 | 
			
		||||
 | 
			
		||||
        if (expirationDate < Date.now()) {
 | 
			
		||||
            if (shared.debug) console.log('Cookie ' + cookie['name'] + ' expired, you need to re-authenticate');
 | 
			
		||||
            expiredCookies = true;
 | 
			
		||||
        }
 | 
			
		||||
    if (expirationDate < Date.now()) {
 | 
			
		||||
      if (shared.debug)
 | 
			
		||||
        console.log(
 | 
			
		||||
          "Cookie " + cookie["name"] + " expired, you need to re-authenticate"
 | 
			
		||||
        );
 | 
			
		||||
      expiredCookies = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return expiredCookies;
 | 
			
		||||
  return expiredCookies;
 | 
			
		||||
}
 | 
			
		||||
//#endregion Cookies functions
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -366,19 +375,29 @@ function isCookieExpired(cookie) {
 | 
			
		|||
 * @param {String} elementRequested Required element (engines or states) used to detail log messages
 | 
			
		||||
 * @returns {Promise<String[]>} List of required values in uppercase
 | 
			
		||||
 */
 | 
			
		||||
async function loadValuesFromLatestPage(page, path, selector, 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...');
 | 
			
		||||
    if (fs.existsSync(path)) {
 | 
			
		||||
        let valueJSON = fs.readFileSync(path);
 | 
			
		||||
        return JSON.parse(valueJSON);
 | 
			
		||||
    }
 | 
			
		||||
async function loadValuesFromLatestPage(
 | 
			
		||||
  page,
 | 
			
		||||
  path,
 | 
			
		||||
  selector,
 | 
			
		||||
  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...");
 | 
			
		||||
  if (fs.existsSync(path)) {
 | 
			
		||||
    let 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...');
 | 
			
		||||
    let values = await getValuesFromLatestPage(page, selector, 'Getting ' + elementRequested + ' from page');
 | 
			
		||||
    fs.writeFileSync(path, JSON.stringify(values));
 | 
			
		||||
    return values;
 | 
			
		||||
  // Otherwise, connect and download the data from the portal
 | 
			
		||||
  if (shared.debug)
 | 
			
		||||
    console.log("No " + elementRequested + " cached, downloading...");
 | 
			
		||||
  let values = await getValuesFromLatestPage(
 | 
			
		||||
    page,
 | 
			
		||||
    selector,
 | 
			
		||||
    "Getting " + elementRequested + " from page"
 | 
			
		||||
  );
 | 
			
		||||
  fs.writeFileSync(path, JSON.stringify(values));
 | 
			
		||||
  return values;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * @private
 | 
			
		||||
| 
						 | 
				
			
			@ -391,18 +410,20 @@ async function loadValuesFromLatestPage(page, path, selector, elementRequested)
 | 
			
		|||
 * @return {Promise<String[]>} 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);
 | 
			
		||||
  if (shared.debug) console.log(logMessage);
 | 
			
		||||
 | 
			
		||||
    let result = [];
 | 
			
		||||
    let elements = await page.$$(selector);
 | 
			
		||||
  let result = [];
 | 
			
		||||
  let elements = await page.$$(selector);
 | 
			
		||||
 | 
			
		||||
    for (let element of elements) {
 | 
			
		||||
        let text = await element.evaluate( /* istanbul ignore next */ e => e.innerText);
 | 
			
		||||
  for (let element of elements) {
 | 
			
		||||
    let text = await element.evaluate(
 | 
			
		||||
      /* istanbul ignore next */ (e) => e.innerText
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
        // Save as upper text for better match if used in query
 | 
			
		||||
        result.push(text.toUpperCase());
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
    // Save as upper text for better match if used in query
 | 
			
		||||
    result.push(text.toUpperCase());
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -416,52 +437,62 @@ async function getValuesFromLatestPage(page, selector, logMessage) {
 | 
			
		|||
 * @returns {Promise<LoginResult>} Result of the operation
 | 
			
		||||
 */
 | 
			
		||||
async function loginF95(browser, username, password) {
 | 
			
		||||
    let page = await preparePage(browser); // Set new isolated page
 | 
			
		||||
    await page.goto(constURLs.F95_LOGIN_URL); // Go to login page
 | 
			
		||||
  let page = await preparePage(browser); // Set new isolated page
 | 
			
		||||
  await page.goto(constURLs.F95_LOGIN_URL); // Go to login page
 | 
			
		||||
 | 
			
		||||
    // Explicitly wait for the required items to load
 | 
			
		||||
    await page.waitForSelector(constSelectors.USERNAME_INPUT);
 | 
			
		||||
    await page.waitForSelector(constSelectors.PASSWORD_INPUT);
 | 
			
		||||
    await page.waitForSelector(constSelectors.LOGIN_BUTTON);
 | 
			
		||||
    await page.type(constSelectors.USERNAME_INPUT, username); // Insert username
 | 
			
		||||
    await page.type(constSelectors.PASSWORD_INPUT, password); // Insert password
 | 
			
		||||
    await page.click(constSelectors.LOGIN_BUTTON); // Click on the login button
 | 
			
		||||
    await page.waitForNavigation({
 | 
			
		||||
        waitUntil: shared.WAIT_STATEMENT
 | 
			
		||||
    }); // Wait for page to load
 | 
			
		||||
  // Explicitly wait for the required items to load
 | 
			
		||||
  await page.waitForSelector(constSelectors.USERNAME_INPUT);
 | 
			
		||||
  await page.waitForSelector(constSelectors.PASSWORD_INPUT);
 | 
			
		||||
  await page.waitForSelector(constSelectors.LOGIN_BUTTON);
 | 
			
		||||
  await page.type(constSelectors.USERNAME_INPUT, username); // Insert username
 | 
			
		||||
  await page.type(constSelectors.PASSWORD_INPUT, password); // Insert password
 | 
			
		||||
  await page.click(constSelectors.LOGIN_BUTTON); // Click on the login button
 | 
			
		||||
  await page.waitForNavigation({
 | 
			
		||||
    waitUntil: shared.WAIT_STATEMENT,
 | 
			
		||||
  }); // Wait for page to load
 | 
			
		||||
 | 
			
		||||
    // Prepare result
 | 
			
		||||
    let result = new LoginResult();
 | 
			
		||||
  // Prepare result
 | 
			
		||||
  let result = new LoginResult();
 | 
			
		||||
 | 
			
		||||
    // Check if the user is logged in
 | 
			
		||||
    result.success = await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
  // Check if the user is logged in
 | 
			
		||||
  result.success = await page.evaluate(
 | 
			
		||||
    /* istanbul ignore next */ (selector) =>
 | 
			
		||||
      document.querySelector(selector) !== null,
 | 
			
		||||
    constSelectors.AVATAR_INFO
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Save cookies to avoid re-auth
 | 
			
		||||
  if (result.success) {
 | 
			
		||||
    let c = await page.cookies();
 | 
			
		||||
    fs.writeFileSync(shared.cookiesCachePath, JSON.stringify(c));
 | 
			
		||||
    result.message = "Authentication successful";
 | 
			
		||||
  }
 | 
			
		||||
  // Obtain the error message
 | 
			
		||||
  else if (
 | 
			
		||||
    await page.evaluate(
 | 
			
		||||
      /* istanbul ignore next */ (selector) =>
 | 
			
		||||
        document.querySelector(selector) !== null,
 | 
			
		||||
        constSelectors.AVATAR_INFO);
 | 
			
		||||
      constSelectors.LOGIN_MESSAGE_ERROR
 | 
			
		||||
    )
 | 
			
		||||
  ) {
 | 
			
		||||
    let errorMessage = await page.evaluate(
 | 
			
		||||
      /* istanbul ignore next */ (selector) =>
 | 
			
		||||
        document.querySelector(selector).innerText,
 | 
			
		||||
      constSelectors.LOGIN_MESSAGE_ERROR
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Save cookies to avoid re-auth
 | 
			
		||||
    if (result.success) {
 | 
			
		||||
        let c = await page.cookies();
 | 
			
		||||
        fs.writeFileSync(shared.cookiesCachePath, JSON.stringify(c));
 | 
			
		||||
        result.message = 'Authentication successful';
 | 
			
		||||
    }
 | 
			
		||||
    // Obtain the error message
 | 
			
		||||
    else if (await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
            document.querySelector(selector) !== null,
 | 
			
		||||
            constSelectors.LOGIN_MESSAGE_ERROR)) {
 | 
			
		||||
        let errorMessage = await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
            document.querySelector(selector).innerText,
 | 
			
		||||
            constSelectors.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";
 | 
			
		||||
 | 
			
		||||
        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";
 | 
			
		||||
 | 
			
		||||
    await page.close(); // Close the page
 | 
			
		||||
    return result;
 | 
			
		||||
  await page.close(); // Close the page
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * @private
 | 
			
		||||
| 
						 | 
				
			
			@ -470,55 +501,60 @@ async function loginF95(browser, username, password) {
 | 
			
		|||
 * @returns {Promise<URL[]>} URL list
 | 
			
		||||
 */
 | 
			
		||||
async function getUserWatchedGameThreads(browser) {
 | 
			
		||||
    let page = await preparePage(browser); // Set new isolated page
 | 
			
		||||
    await page.goto(constURLs.F95_WATCHED_THREADS); // Go to the thread page
 | 
			
		||||
  let page = await preparePage(browser); // Set new isolated page
 | 
			
		||||
  await page.goto(constURLs.F95_WATCHED_THREADS); // Go to the thread page
 | 
			
		||||
 | 
			
		||||
    // Explicitly wait for the required items to load
 | 
			
		||||
    await page.waitForSelector(constSelectors.WATCHED_THREAD_FILTER_POPUP_BUTTON);
 | 
			
		||||
  // Explicitly wait for the required items to load
 | 
			
		||||
  await page.waitForSelector(constSelectors.WATCHED_THREAD_FILTER_POPUP_BUTTON);
 | 
			
		||||
 | 
			
		||||
    // Show the popup
 | 
			
		||||
    await page.click(constSelectors.WATCHED_THREAD_FILTER_POPUP_BUTTON);
 | 
			
		||||
    await page.waitForSelector(constSelectors.UNREAD_THREAD_CHECKBOX);
 | 
			
		||||
    await page.waitForSelector(constSelectors.ONLY_GAMES_THREAD_OPTION);
 | 
			
		||||
    await page.waitForSelector(constSelectors.FILTER_THREADS_BUTTON);
 | 
			
		||||
  // Show the popup
 | 
			
		||||
  await page.click(constSelectors.WATCHED_THREAD_FILTER_POPUP_BUTTON);
 | 
			
		||||
  await page.waitForSelector(constSelectors.UNREAD_THREAD_CHECKBOX);
 | 
			
		||||
  await page.waitForSelector(constSelectors.ONLY_GAMES_THREAD_OPTION);
 | 
			
		||||
  await page.waitForSelector(constSelectors.FILTER_THREADS_BUTTON);
 | 
			
		||||
 | 
			
		||||
    // Set the filters
 | 
			
		||||
    await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
        document.querySelector(selector).removeAttribute('checked'),
 | 
			
		||||
        constSelectors.UNREAD_THREAD_CHECKBOX); // Also read the threads already read
 | 
			
		||||
  // Set the filters
 | 
			
		||||
  await page.evaluate(
 | 
			
		||||
    /* istanbul ignore next */ (selector) =>
 | 
			
		||||
      document.querySelector(selector).removeAttribute("checked"),
 | 
			
		||||
    constSelectors.UNREAD_THREAD_CHECKBOX
 | 
			
		||||
  ); // Also read the threads already read
 | 
			
		||||
 | 
			
		||||
    await page.click(constSelectors.ONLY_GAMES_THREAD_OPTION);
 | 
			
		||||
  await page.click(constSelectors.ONLY_GAMES_THREAD_OPTION);
 | 
			
		||||
 | 
			
		||||
    // Filter the threads
 | 
			
		||||
    await page.click(constSelectors.FILTER_THREADS_BUTTON);
 | 
			
		||||
    await page.waitForSelector(constSelectors.WATCHED_THREAD_URLS);
 | 
			
		||||
  // Filter the threads
 | 
			
		||||
  await page.click(constSelectors.FILTER_THREADS_BUTTON);
 | 
			
		||||
  await page.waitForSelector(constSelectors.WATCHED_THREAD_URLS);
 | 
			
		||||
 | 
			
		||||
    // Get the threads urls
 | 
			
		||||
    let urls = [];
 | 
			
		||||
    let nextPageExists = false;
 | 
			
		||||
    do {
 | 
			
		||||
        // Get all the URLs
 | 
			
		||||
        for (let handle of await page.$$(constSelectors.WATCHED_THREAD_URLS)) {
 | 
			
		||||
            let src = await page.evaluate( /* istanbul ignore next */ (element) => element.href, handle);
 | 
			
		||||
            // If 'unread' is left, it will redirect to the last unread post
 | 
			
		||||
            let url = new URL(src.replace('/unread', ''));
 | 
			
		||||
            urls.push(url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        nextPageExists = await page.evaluate( /* istanbul ignore next */ (selector) =>
 | 
			
		||||
            document.querySelector(selector),
 | 
			
		||||
            constSelectors.WATCHED_THREAD_NEXT_PAGE);
 | 
			
		||||
 | 
			
		||||
        // Click to next page
 | 
			
		||||
        if (nextPageExists) {
 | 
			
		||||
            await page.click(constSelectors.WATCHED_THREAD_NEXT_PAGE);
 | 
			
		||||
            await page.waitForSelector(constSelectors.WATCHED_THREAD_URLS);
 | 
			
		||||
        }
 | 
			
		||||
  // Get the threads urls
 | 
			
		||||
  let urls = [];
 | 
			
		||||
  let nextPageExists = false;
 | 
			
		||||
  do {
 | 
			
		||||
    // Get all the URLs
 | 
			
		||||
    for (let handle of await page.$$(constSelectors.WATCHED_THREAD_URLS)) {
 | 
			
		||||
      let src = await page.evaluate(
 | 
			
		||||
        /* istanbul ignore next */ (element) => element.href,
 | 
			
		||||
        handle
 | 
			
		||||
      );
 | 
			
		||||
      // If 'unread' is left, it will redirect to the last unread post
 | 
			
		||||
      let url = new URL(src.replace("/unread", ""));
 | 
			
		||||
      urls.push(url);
 | 
			
		||||
    }
 | 
			
		||||
    while (nextPageExists);
 | 
			
		||||
 | 
			
		||||
    await page.close();
 | 
			
		||||
    return urls;
 | 
			
		||||
    nextPageExists = await page.evaluate(
 | 
			
		||||
      /* istanbul ignore next */ (selector) => document.querySelector(selector),
 | 
			
		||||
      constSelectors.WATCHED_THREAD_NEXT_PAGE
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Click to next page
 | 
			
		||||
    if (nextPageExists) {
 | 
			
		||||
      await page.click(constSelectors.WATCHED_THREAD_NEXT_PAGE);
 | 
			
		||||
      await page.waitForSelector(constSelectors.WATCHED_THREAD_URLS);
 | 
			
		||||
    }
 | 
			
		||||
  } while (nextPageExists);
 | 
			
		||||
 | 
			
		||||
  await page.close();
 | 
			
		||||
  return urls;
 | 
			
		||||
}
 | 
			
		||||
//#endregion User
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -531,42 +567,42 @@ async function getUserWatchedGameThreads(browser) {
 | 
			
		|||
 * @returns {Promise<URL[]>} List of URL of possible games  obtained from the preliminary research on the F95 portal
 | 
			
		||||
 */
 | 
			
		||||
async function getSearchGameResults(browser, gamename) {
 | 
			
		||||
    if (shared.debug) console.log('Searching ' + gamename + ' on F95Zone');
 | 
			
		||||
  if (shared.debug) console.log("Searching " + gamename + " on F95Zone");
 | 
			
		||||
 | 
			
		||||
    let 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, {
 | 
			
		||||
        waitUntil: shared.WAIT_STATEMENT
 | 
			
		||||
    }); // Go to the search form and wait for it
 | 
			
		||||
  let 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, {
 | 
			
		||||
    waitUntil: shared.WAIT_STATEMENT,
 | 
			
		||||
  }); // Go to the search form and wait for it
 | 
			
		||||
 | 
			
		||||
    // Explicitly wait for the required items to load
 | 
			
		||||
    await page.waitForSelector(constSelectors.SEARCH_FORM_TEXTBOX);
 | 
			
		||||
    await page.waitForSelector(constSelectors.TITLE_ONLY_CHECKBOX);
 | 
			
		||||
    await page.waitForSelector(constSelectors.SEARCH_BUTTON);
 | 
			
		||||
  // Explicitly wait for the required items to load
 | 
			
		||||
  await page.waitForSelector(constSelectors.SEARCH_FORM_TEXTBOX);
 | 
			
		||||
  await page.waitForSelector(constSelectors.TITLE_ONLY_CHECKBOX);
 | 
			
		||||
  await page.waitForSelector(constSelectors.SEARCH_BUTTON);
 | 
			
		||||
 | 
			
		||||
    await page.type(constSelectors.SEARCH_FORM_TEXTBOX, gamename) // Type the game we desire
 | 
			
		||||
    await page.click(constSelectors.TITLE_ONLY_CHECKBOX) // Select only the thread with the game in the titles
 | 
			
		||||
    await page.click(constSelectors.SEARCH_BUTTON); // Execute search
 | 
			
		||||
    await page.waitForNavigation({
 | 
			
		||||
        waitUntil: shared.WAIT_STATEMENT
 | 
			
		||||
    }); // Wait for page to load
 | 
			
		||||
  await page.type(constSelectors.SEARCH_FORM_TEXTBOX, gamename); // Type the game we desire
 | 
			
		||||
  await page.click(constSelectors.TITLE_ONLY_CHECKBOX); // Select only the thread with the game in the titles
 | 
			
		||||
  await page.click(constSelectors.SEARCH_BUTTON); // Execute search
 | 
			
		||||
  await page.waitForNavigation({
 | 
			
		||||
    waitUntil: shared.WAIT_STATEMENT,
 | 
			
		||||
  }); // Wait for page to load
 | 
			
		||||
 | 
			
		||||
    // Select all conversation titles
 | 
			
		||||
    let threadTitleList = await page.$$(constSelectors.THREAD_TITLE);
 | 
			
		||||
  // Select all conversation titles
 | 
			
		||||
  let threadTitleList = await page.$$(constSelectors.THREAD_TITLE);
 | 
			
		||||
 | 
			
		||||
    // For each title extract the info about the conversation
 | 
			
		||||
    if (shared.debug) console.log('Extracting info from conversation titles');
 | 
			
		||||
    let results = [];
 | 
			
		||||
    for (let title of threadTitleList) {
 | 
			
		||||
        let gameUrl = await getOnlyGameThreads(page, title);
 | 
			
		||||
  // For each title extract the info about the conversation
 | 
			
		||||
  if (shared.debug) console.log("Extracting info from conversation titles");
 | 
			
		||||
  let results = [];
 | 
			
		||||
  for (let title of threadTitleList) {
 | 
			
		||||
    let gameUrl = await getOnlyGameThreads(page, title);
 | 
			
		||||
 | 
			
		||||
        // Append the game's informations
 | 
			
		||||
        if (gameUrl !== null) results.push(gameUrl);
 | 
			
		||||
    }
 | 
			
		||||
    if (shared.debug) console.log('Find ' + results.length + ' conversations');
 | 
			
		||||
    await page.close(); // Close the page
 | 
			
		||||
    // Append the game's informations
 | 
			
		||||
    if (gameUrl !== null) results.push(gameUrl);
 | 
			
		||||
  }
 | 
			
		||||
  if (shared.debug) console.log("Find " + results.length + " conversations");
 | 
			
		||||
  await page.close(); // Close the page
 | 
			
		||||
 | 
			
		||||
    return results;
 | 
			
		||||
  return results;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * @private
 | 
			
		||||
| 
						 | 
				
			
			@ -576,22 +612,28 @@ async function getSearchGameResults(browser, gamename) {
 | 
			
		|||
 * @return {Promise<URL>} URL of the game/mod
 | 
			
		||||
 */
 | 
			
		||||
async function getOnlyGameThreads(page, titleHandle) {
 | 
			
		||||
    const GAME_RECOMMENDATION_PREFIX = 'RECOMMENDATION';
 | 
			
		||||
  const GAME_RECOMMENDATION_PREFIX = "RECOMMENDATION";
 | 
			
		||||
 | 
			
		||||
    // Get the URL of the thread from the title
 | 
			
		||||
    let relativeURLThread = await page.evaluate( /* istanbul ignore next */ (element) => element.querySelector('a').href, titleHandle);
 | 
			
		||||
    let url = new URL(relativeURLThread, constURLs.F95_BASE_URL);
 | 
			
		||||
  // Get the URL of the thread from the title
 | 
			
		||||
  let relativeURLThread = await page.evaluate(
 | 
			
		||||
    /* istanbul ignore next */ (element) => element.querySelector("a").href,
 | 
			
		||||
    titleHandle
 | 
			
		||||
  );
 | 
			
		||||
  let url = new URL(relativeURLThread, constURLs.F95_BASE_URL);
 | 
			
		||||
 | 
			
		||||
    // Parse prefixes to ignore game recommendation
 | 
			
		||||
    for (let element of await titleHandle.$$('span[dir="auto"]')) {
 | 
			
		||||
        // Elaborate the prefixes
 | 
			
		||||
        let prefix = await page.evaluate( /* istanbul ignore next */ element => element.textContent.toUpperCase(), element);
 | 
			
		||||
        prefix = prefix.replace('[', '').replace(']', '');
 | 
			
		||||
  // Parse prefixes to ignore game recommendation
 | 
			
		||||
  for (let element of await titleHandle.$$('span[dir="auto"]')) {
 | 
			
		||||
    // Elaborate the prefixes
 | 
			
		||||
    let prefix = await page.evaluate(
 | 
			
		||||
      /* istanbul ignore next */ (element) => element.textContent.toUpperCase(),
 | 
			
		||||
      element
 | 
			
		||||
    );
 | 
			
		||||
    prefix = prefix.replace("[", "").replace("]", "");
 | 
			
		||||
 | 
			
		||||
        // This is not a game nor a mod, we can exit
 | 
			
		||||
        if (prefix === GAME_RECOMMENDATION_PREFIX) return null;
 | 
			
		||||
    }
 | 
			
		||||
    return url;
 | 
			
		||||
    // This is not a game nor a mod, we can exit
 | 
			
		||||
    if (prefix === GAME_RECOMMENDATION_PREFIX) return null;
 | 
			
		||||
  }
 | 
			
		||||
  return url;
 | 
			
		||||
}
 | 
			
		||||
//#endregion Game search
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
"use strict"
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
const expect = require("chai").expect;
 | 
			
		||||
const F95API = require("../app/index");
 | 
			
		||||
| 
						 | 
				
			
			@ -15,166 +15,167 @@ const FAKE_PASSWORD = "fake_password";
 | 
			
		|||
F95API.setIsolation(true);
 | 
			
		||||
 | 
			
		||||
describe("Login without cookies", function () {
 | 
			
		||||
    //#region Set-up
 | 
			
		||||
    this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
  this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
 | 
			
		||||
    beforeEach("Remove all cookies", function () {
 | 
			
		||||
        // Runs before each test in this block
 | 
			
		||||
        if (fs.existsSync(COOKIES_SAVE_PATH)) fs.unlinkSync(COOKIES_SAVE_PATH);
 | 
			
		||||
        F95API.logout();
 | 
			
		||||
    });
 | 
			
		||||
    //#endregion Set-up
 | 
			
		||||
  beforeEach("Remove all cookies", function () {
 | 
			
		||||
    // Runs before each test in this block
 | 
			
		||||
    if (fs.existsSync(COOKIES_SAVE_PATH)) fs.unlinkSync(COOKIES_SAVE_PATH);
 | 
			
		||||
    F95API.logout();
 | 
			
		||||
  });
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
    it("Test with valid credentials", async function () {
 | 
			
		||||
        const result = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
        expect(result.success).to.be.true;
 | 
			
		||||
        expect(result.message).equal("Authentication successful");
 | 
			
		||||
    });
 | 
			
		||||
    it("Test with invalid username", async function () {
 | 
			
		||||
        const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD);
 | 
			
		||||
        expect(result.success).to.be.false;
 | 
			
		||||
        expect(result.message).to.equal("Incorrect username");
 | 
			
		||||
    });
 | 
			
		||||
    it("Test with invalid password", async function () {
 | 
			
		||||
        const result = await F95API.login(USERNAME, FAKE_PASSWORD);
 | 
			
		||||
        expect(result.success).to.be.false;
 | 
			
		||||
        expect(result.message).to.equal("Incorrect password");
 | 
			
		||||
    });
 | 
			
		||||
    it("Test with invalid credentials", async function () {
 | 
			
		||||
        const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD);
 | 
			
		||||
        expect(result.success).to.be.false;
 | 
			
		||||
        expect(result.message).to.equal("Incorrect username"); // It should first check the username
 | 
			
		||||
    });
 | 
			
		||||
  it("Test with valid credentials", async function () {
 | 
			
		||||
    const result = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
    expect(result.success).to.be.true;
 | 
			
		||||
    expect(result.message).equal("Authentication successful");
 | 
			
		||||
  });
 | 
			
		||||
  it("Test with invalid username", async function () {
 | 
			
		||||
    const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD);
 | 
			
		||||
    expect(result.success).to.be.false;
 | 
			
		||||
    expect(result.message).to.equal("Incorrect username");
 | 
			
		||||
  });
 | 
			
		||||
  it("Test with invalid password", async function () {
 | 
			
		||||
    const result = await F95API.login(USERNAME, FAKE_PASSWORD);
 | 
			
		||||
    expect(result.success).to.be.false;
 | 
			
		||||
    expect(result.message).to.equal("Incorrect password");
 | 
			
		||||
  });
 | 
			
		||||
  it("Test with invalid credentials", async function () {
 | 
			
		||||
    const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD);
 | 
			
		||||
    expect(result.success).to.be.false;
 | 
			
		||||
    expect(result.message).to.equal("Incorrect username"); // It should first check the username
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Login with cookies", function () {
 | 
			
		||||
    //#region Set-up
 | 
			
		||||
    this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
  this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
 | 
			
		||||
    before("Log in to create cookies then logout", async function () {
 | 
			
		||||
        // Runs once before the first test in this block
 | 
			
		||||
        if (!fs.existsSync(COOKIES_SAVE_PATH)) await F95API.login(USERNAME, PASSWORD); // Download cookies
 | 
			
		||||
        F95API.logout();
 | 
			
		||||
    });
 | 
			
		||||
    //#endregion Set-up
 | 
			
		||||
  before("Log in to create cookies then logout", async function () {
 | 
			
		||||
    // Runs once before the first test in this block
 | 
			
		||||
    if (!fs.existsSync(COOKIES_SAVE_PATH))
 | 
			
		||||
      await F95API.login(USERNAME, PASSWORD); // Download cookies
 | 
			
		||||
    F95API.logout();
 | 
			
		||||
  });
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
    it("Test with valid credentials", async function () {
 | 
			
		||||
        const result = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
        expect(result.success).to.be.true;
 | 
			
		||||
        expect(result.message).equal("Logged with cookies");
 | 
			
		||||
    });
 | 
			
		||||
  it("Test with valid credentials", async function () {
 | 
			
		||||
    const result = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
    expect(result.success).to.be.true;
 | 
			
		||||
    expect(result.message).equal("Logged with cookies");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Load base data without cookies", function () {
 | 
			
		||||
    //#region Set-up
 | 
			
		||||
    this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
  this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
 | 
			
		||||
    before("Delete cache if exists", function () {
 | 
			
		||||
        // Runs once before the first test in this block
 | 
			
		||||
        if (fs.existsSync(ENGINES_SAVE_PATH)) fs.unlinkSync(ENGINES_SAVE_PATH);
 | 
			
		||||
        if (fs.existsSync(STATUSES_SAVE_PATH)) fs.unlinkSync(STATUSES_SAVE_PATH);
 | 
			
		||||
    });
 | 
			
		||||
    //#endregion Set-up
 | 
			
		||||
  before("Delete cache if exists", function () {
 | 
			
		||||
    // Runs once before the first test in this block
 | 
			
		||||
    if (fs.existsSync(ENGINES_SAVE_PATH)) fs.unlinkSync(ENGINES_SAVE_PATH);
 | 
			
		||||
    if (fs.existsSync(STATUSES_SAVE_PATH)) fs.unlinkSync(STATUSES_SAVE_PATH);
 | 
			
		||||
  });
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
    it("With login", async function () {
 | 
			
		||||
        let loginResult = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
        expect(loginResult.success).to.be.true;
 | 
			
		||||
  it("With login", async function () {
 | 
			
		||||
    let loginResult = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
    expect(loginResult.success).to.be.true;
 | 
			
		||||
 | 
			
		||||
        let result = await F95API.loadF95BaseData();
 | 
			
		||||
    let result = await F95API.loadF95BaseData();
 | 
			
		||||
 | 
			
		||||
        let enginesCacheExists = fs.existsSync(ENGINES_SAVE_PATH);
 | 
			
		||||
        let statusesCacheExists = fs.existsSync(STATUSES_SAVE_PATH);
 | 
			
		||||
    let enginesCacheExists = fs.existsSync(ENGINES_SAVE_PATH);
 | 
			
		||||
    let statusesCacheExists = fs.existsSync(STATUSES_SAVE_PATH);
 | 
			
		||||
 | 
			
		||||
        expect(result).to.be.true;
 | 
			
		||||
        expect(enginesCacheExists).to.be.true;
 | 
			
		||||
        expect(statusesCacheExists).to.be.true;
 | 
			
		||||
    });
 | 
			
		||||
    expect(result).to.be.true;
 | 
			
		||||
    expect(enginesCacheExists).to.be.true;
 | 
			
		||||
    expect(statusesCacheExists).to.be.true;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    it("Without login", async function () {
 | 
			
		||||
        F95API.logout();
 | 
			
		||||
        let result = await F95API.loadF95BaseData();
 | 
			
		||||
        expect(result).to.be.false;
 | 
			
		||||
    });
 | 
			
		||||
  it("Without login", async function () {
 | 
			
		||||
    F95API.logout();
 | 
			
		||||
    let result = await F95API.loadF95BaseData();
 | 
			
		||||
    expect(result).to.be.false;
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Search game data", function () {
 | 
			
		||||
    //#region Set-up
 | 
			
		||||
    this.timeout(60000); // All tests in this suite get 60 seconds before timeout
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
  this.timeout(60000); // All tests in this suite get 60 seconds before timeout
 | 
			
		||||
 | 
			
		||||
    beforeEach("Prepare API", function () {
 | 
			
		||||
        // Runs once before the first test in this block
 | 
			
		||||
        F95API.logout();
 | 
			
		||||
    });
 | 
			
		||||
    //#endregion Set-up
 | 
			
		||||
  beforeEach("Prepare API", function () {
 | 
			
		||||
    // Runs once before the first test in this block
 | 
			
		||||
    F95API.logout();
 | 
			
		||||
  });
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
    it("Search game when logged", async function () {
 | 
			
		||||
        const loginResult = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
        expect(loginResult.success).to.be.true;
 | 
			
		||||
  it("Search game when logged", async function () {
 | 
			
		||||
    const loginResult = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
    expect(loginResult.success).to.be.true;
 | 
			
		||||
 | 
			
		||||
        const loadResult = await F95API.loadF95BaseData();
 | 
			
		||||
        expect(loadResult).to.be.true;
 | 
			
		||||
    const loadResult = await F95API.loadF95BaseData();
 | 
			
		||||
    expect(loadResult).to.be.true;
 | 
			
		||||
 | 
			
		||||
        // This test depend on the data on F95Zone at 
 | 
			
		||||
        // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/
 | 
			
		||||
        const result = (await F95API.getGameData("Kingdom of Deception", false))[0];
 | 
			
		||||
        let src = "https://attachments.f95zone.to/2018/09/162821_f9nXfwF.png";
 | 
			
		||||
    // This test depend on the data on F95Zone at
 | 
			
		||||
    // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/
 | 
			
		||||
    const result = (await F95API.getGameData("Kingdom of Deception", false))[0];
 | 
			
		||||
    let src = "https://attachments.f95zone.to/2018/09/162821_f9nXfwF.png";
 | 
			
		||||
 | 
			
		||||
        // Test only the main information
 | 
			
		||||
        expect(result.name).to.equal("Kingdom of Deception");
 | 
			
		||||
        expect(result.author).to.equal("Hreinn Games");
 | 
			
		||||
        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?
 | 
			
		||||
    });
 | 
			
		||||
    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;
 | 
			
		||||
    });
 | 
			
		||||
    // Test only the main information
 | 
			
		||||
    expect(result.name).to.equal("Kingdom of Deception");
 | 
			
		||||
    expect(result.author).to.equal("Hreinn Games");
 | 
			
		||||
    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?
 | 
			
		||||
  });
 | 
			
		||||
  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;
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Load user data", function () {
 | 
			
		||||
    //#region Set-up
 | 
			
		||||
    this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
    //#endregion Set-up
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
  this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
    it("Retrieve when logged", async function () {
 | 
			
		||||
        // Login
 | 
			
		||||
        await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
  it("Retrieve when logged", async function () {
 | 
			
		||||
    // Login
 | 
			
		||||
    await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
 | 
			
		||||
        // Then retrieve user data
 | 
			
		||||
        let data = await F95API.getUserData();
 | 
			
		||||
    // Then retrieve user data
 | 
			
		||||
    let data = await F95API.getUserData();
 | 
			
		||||
 | 
			
		||||
        expect(data).to.exist;
 | 
			
		||||
        expect(data.username).to.equal(USERNAME);
 | 
			
		||||
    });
 | 
			
		||||
    it("Retrieve when not logged", async function () {
 | 
			
		||||
        // Logout
 | 
			
		||||
        F95API.logout();
 | 
			
		||||
    expect(data).to.exist;
 | 
			
		||||
    expect(data.username).to.equal(USERNAME);
 | 
			
		||||
  });
 | 
			
		||||
  it("Retrieve when not logged", async function () {
 | 
			
		||||
    // Logout
 | 
			
		||||
    F95API.logout();
 | 
			
		||||
 | 
			
		||||
        // Try to retrieve user data
 | 
			
		||||
        let data = await F95API.getUserData();
 | 
			
		||||
    // Try to retrieve user data
 | 
			
		||||
    let data = await F95API.getUserData();
 | 
			
		||||
 | 
			
		||||
        expect(data).to.be.null;
 | 
			
		||||
    });
 | 
			
		||||
    expect(data).to.be.null;
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("Check game version", function () {
 | 
			
		||||
    //#region Set-up
 | 
			
		||||
    this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
    //#endregion Set-up
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
  this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
    it("Get game version", async function () {
 | 
			
		||||
        const loginResult = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
        expect(loginResult.success).to.be.true;
 | 
			
		||||
  it("Get game version", async function () {
 | 
			
		||||
    const loginResult = await F95API.login(USERNAME, PASSWORD);
 | 
			
		||||
    expect(loginResult.success).to.be.true;
 | 
			
		||||
 | 
			
		||||
        const loadResult = await F95API.loadF95BaseData();
 | 
			
		||||
        expect(loadResult).to.be.true;
 | 
			
		||||
    const loadResult = await F95API.loadF95BaseData();
 | 
			
		||||
    expect(loadResult).to.be.true;
 | 
			
		||||
 | 
			
		||||
        // This test depend on the data on F95Zone at 
 | 
			
		||||
        // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/
 | 
			
		||||
        const result = (await F95API.getGameData("Kingdom of Deception", false))[0];
 | 
			
		||||
    // This test depend on the data on F95Zone at
 | 
			
		||||
    // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/
 | 
			
		||||
    const result = (await F95API.getGameData("Kingdom of Deception", false))[0];
 | 
			
		||||
 | 
			
		||||
        let version = await F95API.getGameVersion(result);
 | 
			
		||||
        expect(version).to.be.equal(result.version);
 | 
			
		||||
    });
 | 
			
		||||
    let version = await F95API.getGameVersion(result);
 | 
			
		||||
    expect(version).to.be.equal(result.version);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue