Translate scripts

pull/73/head
MillenniumEarl 2021-02-15 22:26:18 +01:00
parent 52264a487a
commit e508149257
7 changed files with 144 additions and 180 deletions

View File

@ -9,10 +9,10 @@ F95_PASSWORD = YOUR_PASSWORD
"use strict";
// Public modules from npm
const dotenv = require("dotenv");
import dotenv from "dotenv";
// Modules from file
const F95API = require("./index.js");
import { login, getUserData, getLatestUpdates, getGameData} from "./index";
// Configure the .env reader
dotenv.config();
@ -29,16 +29,16 @@ async function main() {
// Log in the platform
console.log("Authenticating...");
const result = await F95API.login(process.env.F95_USERNAME, process.env.F95_PASSWORD);
const result = await login(process.env.F95_USERNAME, process.env.F95_PASSWORD);
console.log(`Authentication result: ${result.message}\n`);
// Get user data
console.log("Fetching user data...");
const userdata = await F95API.getUserData();
const userdata = await getUserData();
console.log(`${userdata.username} follows ${userdata.watchedGameThreads.length} threads\n`);
// Get latest game update
const latestUpdates = await F95API.getLatestUpdates({
const latestUpdates = await getLatestUpdates({
tags: ["3d game"]
}, 1);
console.log(`"${latestUpdates[0].name}" was the last "3d game" tagged game to be updated\n`);
@ -46,7 +46,7 @@ async function main() {
// Get game data
for(const gamename of gameList) {
console.log(`Searching '${gamename}'...`);
const found = await F95API.getGameData(gamename, false);
const found = await getGameData(gamename, false);
// If no game is found
if (found.length === 0) {

View File

@ -1,20 +1,20 @@
"use strict";
// Modules from file
const shared = require("./scripts/shared.js");
const networkHelper = require("./scripts/network-helper.js");
const scraper = require("./scripts/scraper.js");
const searcher = require("./scripts/searcher.js");
const uScraper = require("./scripts/user-scraper.js");
const latestFetch = require("./scripts/latest-fetch.js");
import shared from "./scripts/shared.js";
import { authenticate, urlExists, isF95URL } from "./scripts/network-helper.js";
import { getGameInfo } from "./scripts/scraper.js";
import { searchGame, searchMod } from "./scripts/searcher.js";
import { getUserData as retrieveUserData } from "./scripts/user-scraper.js";
import { fetchLatest } from "./scripts/latest-fetch.js";
const fetchPlatformData = require("./scripts/platform-data.js").fetchPlatformData;
// Classes from file
const Credentials = require("./scripts/classes/credentials.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");
const PrefixParser = require("./scripts/classes/prefix-parser.js");
import Credentials from "./scripts/classes/credentials.js";
import GameInfo from "./scripts/classes/game-info.js";
import LoginResult from "./scripts/classes/login-result.js";
import UserData from "./scripts/classes/user-data.js";
import PrefixParser from "./scripts/classes/prefix-parser.js";
//#region Global variables
const USER_NOT_LOGGED = "User not authenticated, unable to continue";
@ -35,26 +35,22 @@ module.exports.PrefixParser = PrefixParser;
/* istambul ignore next */
module.exports.loggerLevel = shared.logger.level;
exports.loggerLevel = "warn"; // By default log only the warn messages
/**
* @public
* Indicates whether a user is logged in to the F95Zone platform or not.
* @returns {String}
*/
module.exports.isLogged = function isLogged() {
export function isLogged(): boolean {
return shared.isLogged;
};
//#endregion Export properties
//#region Export methods
/**
* @public
* Log in to the F95Zone platform.
* This **must** be the first operation performed before accessing any other script functions.
* @param {String} username Username used for login
* @param {String} password Password used for login
* @returns {Promise<LoginResult>} Result of the operation
*/
module.exports.login = async function (username, password) {
export async function login(username: string, password: string): Promise<LoginResult> {
/* istanbul ignore next */
if (shared.isLogged) {
shared.logger.info(`${username} already authenticated`);
@ -66,8 +62,8 @@ module.exports.login = async function (username, password) {
await creds.fetchToken();
shared.logger.trace(`Authentication for ${username}`);
const result = await networkHelper.authenticate(creds);
shared.isLogged = result.success;
const result = await authenticate(creds);
shared.setIsLogged(result.success);
// Load platform data
if (result.success) await fetchPlatformData();
@ -80,13 +76,12 @@ module.exports.login = async function (username, password) {
};
/**
* @public
* Chek if exists a new version of the game.
* You **must** be logged in to the portal before calling this method.
* @param {GameInfo} info Information about the game to get the version for
* @returns {Promise<Boolean>} true if an update is available, false otherwise
*/
module.exports.checkIfGameHasUpdate = async function (info) {
export async function checkIfGameHasUpdate(info: GameInfo): Promise<boolean> {
/* istanbul ignore next */
if (!shared.isLogged) {
shared.logger.warn(USER_NOT_LOGGED);
@ -95,11 +90,11 @@ module.exports.checkIfGameHasUpdate = async function (info) {
// F95 change URL at every game update,
// so if the URL is different an update is available
const exists = await networkHelper.urlExists(info.url, true);
const exists = await urlExists(info.url, true);
if (!exists) return true;
// Parse version from title
const onlineInfo = await scraper.getGameInfo(info.url);
const onlineInfo = await getGameInfo(info.url);
const onlineVersion = onlineInfo.version;
// Compare the versions
@ -107,7 +102,6 @@ module.exports.checkIfGameHasUpdate = async function (info) {
};
/**
* @public
* Starting from the name, it gets all the information about the game you are looking for.
* You **must** be logged in to the portal before calling this method.
* @param {String} name Name of the game searched
@ -115,7 +109,7 @@ module.exports.checkIfGameHasUpdate = async function (info) {
* @returns {Promise<GameInfo[]>} List of information obtained where each item corresponds to
* an identified game (in the case of homonymy of titles)
*/
module.exports.getGameData = async function (name, mod) {
export async function getGameData (name: string, mod: boolean): Promise<GameInfo[]> {
/* istanbul ignore next */
if (!shared.isLogged) {
shared.logger.warn(USER_NOT_LOGGED);
@ -124,27 +118,26 @@ module.exports.getGameData = async function (name, mod) {
// Gets the search results of the game/mod being searched for
const urls = mod ?
await searcher.searchMod(name) :
await searcher.searchGame(name);
await searchMod(name) :
await searchGame(name);
// Process previous partial results
const results = [];
for (const url of urls) {
// Start looking for information
const info = await scraper.getGameInfo(url);
const info = await getGameInfo(url);
if (info) results.push(info);
}
return results;
};
/**
* @public
* Starting from the url, it gets all the information about the game you are looking for.
* You **must** be logged in to the portal before calling this method.
* @param {String} url URL of the game to obtain information of
* @returns {Promise<GameInfo>} Information about the game. If no game was found, null is returned
*/
module.exports.getGameDataFromURL = async function (url) {
export async function getGameDataFromURL(url: string): Promise<GameInfo> {
/* istanbul ignore next */
if (!shared.isLogged) {
shared.logger.warn(USER_NOT_LOGGED);
@ -152,32 +145,30 @@ module.exports.getGameDataFromURL = async function (url) {
}
// Check URL validity
const exists = await networkHelper.urlExists(url);
const exists = await urlExists(url);
if (!exists) throw new URIError(`${url} is not a valid URL`);
if (!networkHelper.isF95URL(url)) throw new Error(`${url} is not a valid F95Zone URL`);
if (!isF95URL(url)) throw new Error(`${url} is not a valid F95Zone URL`);
// Get game data
return await scraper.getGameInfo(url);
return await getGameInfo(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<UserData>} Data of the user currently logged in
*/
module.exports.getUserData = async function () {
export async function getUserData(): Promise<UserData> {
/* istanbul ignore next */
if (!shared.isLogged) {
shared.logger.warn(USER_NOT_LOGGED);
return null;
}
return await uScraper.getUserData();
return await retrieveUserData();
};
/**
* @public
* Gets the latest updated games that match the specified parameters.
* You **must** be logged in to the portal before calling this method.
* @param {Object} args
@ -196,7 +187,7 @@ module.exports.getUserData = async function () {
* @param {Number} limit Maximum number of results
* @returns {Promise<GameInfo[]>} List of games
*/
module.exports.getLatestUpdates = async function(args, limit) {
export async function getLatestUpdates(args, limit: number): Promise<GameInfo[]> {
// Check limit value
if(limit <= 0) throw new Error("limit must be greater than 0");
@ -217,22 +208,20 @@ module.exports.getLatestUpdates = async function(args, limit) {
sort: args.sorting ? args.sorting : "date",
date: filterDate,
};
const urls = await latestFetch.fetchLatest(query, limit);
const urls = await fetchLatest(query, limit);
// Get the gamedata from urls
const promiseList = urls.map(u => exports.getGameDataFromURL(u));
const promiseList = urls.map((u: string) => exports.getGameDataFromURL(u));
return await Promise.all(promiseList);
};
//#endregion
//#region Private Methods
/**
* @private
* Given an array of numbers, get the nearest value for a given `value`.
* @param {Number[]} array List of default values
* @param {Number} value Value to search
*/
function getNearestValueFromArray(array, value) {
function getNearestValueFromArray(array: number[], value: number) {
// Script taken from:
// https://www.gavsblog.com/blog/find-closest-number-in-array-javascript
array.sort((a, b) => {

View File

@ -1,8 +1,9 @@
"use strict";
// Modules from file
import { fetchGETResponse } from "./network-helper.js";
import { fetchGETResponse } from "./network-helper";
import SearchQuery from "./classes/search-query";
import { urls as f95url } from "./constants/url";
/**
* @public

View File

@ -1,16 +1,17 @@
"use strict";
// Public modules from npm
const axios = require("axios").default;
const cheerio = require("cheerio");
const axiosCookieJarSupport = require("axios-cookiejar-support").default;
const tough = require("tough-cookie");
import axios, { AxiosResponse } from "axios";
import cheerio from "cheerio";
import axiosCookieJarSupport from "axios-cookiejar-support";
import tough from "tough-cookie";
// Modules from file
const shared = require("./shared.js");
const f95url = require("./constants/url.js");
const f95selector = require("./constants/css-selector.js");
const LoginResult = require("./classes/login-result.js");
import shared from "./shared";
import { urls as f95url } from "./constants/url";
import { selectors as f95selector } from "./constants/css-selector";
import LoginResult from "./classes/login-result";
import credentials from "./classes/credentials";
// Global variables
const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " +
@ -27,12 +28,10 @@ const commonConfig = {
};
/**
* @protected
* Gets the HTML code of a page.
* @param {String} url URL to fetch
* @returns {Promise<String>} HTML code or `null` if an error arise
*/
module.exports.fetchHTML = async function (url) {
export async function fetchHTML(url: string): Promise<string|null> {
// Local variables
let returnValue = null;
@ -52,18 +51,17 @@ module.exports.fetchHTML = async function (url) {
returnValue = response.data;
return returnValue;
};
}
/**
* @protected
* It authenticates to the platform using the credentials
* and token obtained previously. Save cookies on your
* device after authentication.
* @param {Credentials} credentials Platform access credentials
* @param {module:./classes/credentials.ts:Credentials} credentials Platform access credentials
* @param {Boolean} force Specifies whether the request should be forced, ignoring any saved cookies
* @returns {Promise<LoginResult>} Result of the operation
*/
module.exports.authenticate = async function (credentials, force) {
export async function authenticate(credentials: credentials, force: boolean = false): Promise<LoginResult> {
shared.logger.info(`Authenticating with user ${credentials.username}`);
if (!credentials.token) throw new Error(`Invalid token for auth: ${credentials.token}`);
@ -108,7 +106,7 @@ module.exports.authenticate = async function (credentials, force) {
* Obtain the token used to authenticate the user to the platform.
* @returns {Promise<String>} Token or `null` if an error arise
*/
module.exports.getF95Token = async function() {
export async function getF95Token(): Promise<string|null> {
// Fetch the response of the platform
const response = await exports.fetchGETResponse(f95url.F95_LOGIN_URL);
/* istambul ignore next */
@ -119,18 +117,15 @@ module.exports.getF95Token = async function() {
// The response is a HTML page, we need to find the <input> with name "_xfToken"
const $ = cheerio.load(response.data);
const token = $("body").find(f95selector.GET_REQUEST_TOKEN).attr("value");
return token;
};
return $("body").find(f95selector.GET_REQUEST_TOKEN).attr("value");
}
//#region Utility methods
/**
* @protected
* Performs a GET request to a specific URL and returns the response.
* If the request generates an error (for example 400) `null` is returned.
* @param {String} url
*/
module.exports.fetchGETResponse = async function(url) {
export async function fetchGETResponse(url: string): Promise<AxiosResponse<unknown>> {
// Secure the URL
const secureURL = exports.enforceHttpsUrl(url);
@ -141,37 +136,34 @@ module.exports.fetchGETResponse = async function(url) {
shared.logger.error(`Error ${e.message} occurred while trying to fetch ${secureURL}`);
return null;
}
};
}
/**
* @protected
* Enforces the scheme of the URL is https and returns the new URL.
* @param {String} url
* @returns {String} Secure URL or `null` if the argument is not a string
*/
module.exports.enforceHttpsUrl = function (url) {
export function enforceHttpsUrl(url: string): string {
return exports.isStringAValidURL(url) ? url.replace(/^(https?:)?\/\//, "https://") : null;
};
/**
* @protected
* Check if the url belongs to the domain of the F95 platform.
* @param {String} url URL to check
* @returns {Boolean} true if the url belongs to the domain, false otherwise
*/
module.exports.isF95URL = function (url) {
export function isF95URL(url: string): boolean {
if (url.toString().startsWith(f95url.F95_BASE_URL)) return true;
else return false;
};
/**
* @protected
* Checks if the string passed by parameter has a
* properly formatted and valid path to a URL (HTTP/HTTPS).
* @param {String} url String to check for correctness
* @returns {Boolean} true if the string is a valid URL, false otherwise
*/
module.exports.isStringAValidURL = function (url) {
export function isStringAValidURL(url: string): boolean {
// Many thanks to Daveo at StackOverflow (https://preview.tinyurl.com/y2f2e2pc)
const expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
const regex = new RegExp(expression);
@ -180,20 +172,19 @@ module.exports.isStringAValidURL = function (url) {
};
/**
* @protected
* Check if a particular URL is valid and reachable on the web.
* @param {String} url URL to check
* @param {Boolean} [checkRedirect]
* @param {string} url URL to check
* @param {boolean} [checkRedirect]
* If true, the function will consider redirects a violation and return false.
* Default: false
* @returns {Promise<Boolean>} true if the URL exists, false otherwise
*/
module.exports.urlExists = async function (url, checkRedirect = false) {
export async function urlExists(url: string, checkRedirect: boolean = false): Promise<boolean> {
// Local variables
let valid = false;
if (exports.isStringAValidURL(url)) {
valid = await _axiosUrlExists(url);
valid = await axiosUrlExists(url);
if (valid && checkRedirect) {
const redirectUrl = await exports.getUrlRedirect(url);
@ -202,18 +193,17 @@ module.exports.urlExists = async function (url, checkRedirect = false) {
}
return valid;
};
}
/**
* @protected
* Check if the URL has a redirect to another page.
* @param {String} url URL to check for redirect
* @returns {Promise<String>} Redirect URL or the passed URL
*/
module.exports.getUrlRedirect = async function (url) {
export async function getUrlRedirect(url: string): Promise<string> {
const response = await axios.head(url);
return response.config.url;
};
}
//#endregion Utility methods
//#region Private methods
@ -222,12 +212,12 @@ module.exports.getUrlRedirect = async function (url) {
* Check with Axios if a URL exists.
* @param {String} url
*/
async function _axiosUrlExists(url) {
async function axiosUrlExists(url: string): Promise<boolean> {
// Local variables
let valid = false;
try {
const response = await axios.head(url, {timeout: 3000});
valid = response && !/4\d\d/.test(response.status);
valid = response && !/4\d\d/.test(response.status.toString());
} catch (error) {
if (error.code === "ENOTFOUND") valid = false;
else if (error.code === "ETIMEDOUT") valid = false;

View File

@ -1,23 +1,22 @@
"use strict";
// Public modules from npm
const cheerio = require("cheerio");
const {DateTime} = require("luxon");
import cheerio from "cheerio";
import { DateTime } from "luxon";
// Modules from file
const { fetchHTML, getUrlRedirect } = require("./network-helper.js");
const shared = require("./shared.js");
const GameInfo = require("./classes/game-info.js");
const f95Selector = require("./constants/css-selector.js");
import { fetchHTML, getUrlRedirect } from "./network-helper.js";
import shared from "./shared.js";
import GameInfo from "./classes/game-info.js";
import { selectors as f95Selector} from "./constants/css-selector.js";
/**
* @protected
* Get information from the game's main page.
* @param {String} url URL of the game/mod to extract data from
* @return {Promise<GameInfo>} Complete information about the game you are
* looking for or `null` if is impossible to parse information
*/
module.exports.getGameInfo = async function (url) {
export async function getGameInfo(url: string): Promise<GameInfo|null> {
shared.logger.info("Obtaining game info");
// Fetch HTML and prepare Cheerio
@ -39,7 +38,7 @@ module.exports.getGameInfo = async function (url) {
if(!structuredData) return null;
const parsedInfos = parseMainPostText(structuredData.description);
const overview = getOverview(structuredData.description, prefixesData.mod);
const overview = getOverview(structuredData.description, prefixesData.mod as boolean);
// Obtain the updated URL
const redirectUrl = await getUrlRedirect(url);
@ -49,23 +48,23 @@ module.exports.getGameInfo = async function (url) {
info.id = extractIDFromURL(url);
info.name = titleData.name;
info.author = titleData.author;
info.isMod = prefixesData.mod;
info.engine = prefixesData.engine;
info.status = prefixesData.status;
info.isMod = prefixesData.mod as boolean;
info.engine = prefixesData.engine as string;
info.status = prefixesData.status as string;
info.tags = tags;
info.url = redirectUrl;
info.language = parsedInfos.Language;
info.language = parsedInfos.Language as unknown as string;
info.overview = overview;
info.supportedOS = parsedInfos.SupportedOS;
info.censored = parsedInfos.Censored;
info.lastUpdate = parsedInfos.LastUpdate;
info.supportedOS = parsedInfos.SupportedOS as string[];
info.censored = parsedInfos.Censored as unknown as boolean;
info.lastUpdate = parsedInfos.LastUpdate as Date;
info.previewSrc = src;
info.changelog = changelog;
info.version = titleData.version;
shared.logger.info(`Founded data for ${info.name}`);
return info;
};
}
//#region Private methods
/**
@ -75,7 +74,7 @@ module.exports.getGameInfo = async function (url) {
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {Object.<string, object>} Dictionary of values with keys `engine`, `status`, `mod`
*/
function parseGamePrefixes(body) {
function parseGamePrefixes(body: cheerio.Cheerio): { [s: string]: string | boolean; } {
shared.logger.trace("Parsing prefixes...");
// Local variables
@ -86,10 +85,9 @@ function parseGamePrefixes(body) {
// Obtain the title prefixes
const prefixeElements = body.find(f95Selector.GT_TITLE_PREFIXES);
const $ = cheerio.load([].concat(body));
prefixeElements.each(function parseGamePrefix(idx, el) {
// Obtain the prefix text
let prefix = $(el).text().trim();
let prefix = cheerio(el).text().trim();
// Remove the square brackets
prefix = prefix.replace("[", "").replace("]", "");
@ -100,8 +98,8 @@ function parseGamePrefixes(body) {
else if (isMod(prefix)) mod = true;
});
// If the status is not set, then the game in in development (Ongoing)
status = !status ? "Ongoing" : status; // status ?? "Ongoing";
// If the status is not set, then the game is in development (Ongoing)
status = status ?? "Ongoing";
return {
engine,
@ -116,7 +114,7 @@ function parseGamePrefixes(body) {
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {Object.<string, string>} Dictionary of values with keys `name`, `author`, `version`
*/
function extractInfoFromTitle(body) {
function extractInfoFromTitle(body: cheerio.Cheerio): { [s: string]: string; } {
shared.logger.trace("Extracting information from title...");
const title = body
.find(f95Selector.GT_TITLE)
@ -163,14 +161,14 @@ function extractInfoFromTitle(body) {
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {String[]} List of tags
*/
function extractTags(body) {
function extractTags(body: cheerio.Cheerio): string[] {
shared.logger.trace("Extracting tags...");
// Get the game tags
const tagResults = body.find(f95Selector.GT_TAGS);
const $ = cheerio.load([].concat(body));
return tagResults.map(function parseGameTags(idx, el) {
return $(el).text().trim();
return cheerio(el).text().trim();
}).get();
}
@ -180,7 +178,7 @@ function extractTags(body) {
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {String} URL of the image
*/
function extractPreviewSource(body) {
function extractPreviewSource(body: cheerio.Cheerio): string {
shared.logger.trace("Extracting image preview source...");
const image = body.find(f95Selector.GT_IMAGES);
@ -196,7 +194,7 @@ function extractPreviewSource(body) {
* @param {cheerio.Cheerio} mainPost main post selector
* @returns {String} Changelog of the last version or `null` if no changelog is fetched
*/
function extractChangelog(mainPost) {
function extractChangelog(mainPost: cheerio.Cheerio): string|null {
shared.logger.trace("Extracting last changelog...");
// Obtain the changelog for ALL the versions
@ -229,10 +227,17 @@ function extractChangelog(mainPost) {
* @param {String} text Structured text of the post
* @returns {Object.<string, object>} Dictionary of information
*/
function parseMainPostText(text) {
function parseMainPostText(text: string): { [s: string]: object; } {
shared.logger.trace("Parsing main post raw text...");
const data = {};
interface DataFormat {
CENSORED: string,
UPDATED: string,
THREAD_UPDATED: string,
OS: string,
LANGUAGE: string
}
const data = {} as DataFormat;
// The information searched in the game post are one per line
const splittedText = text.split("\n");
@ -275,7 +280,7 @@ function parseMainPostText(text) {
// Usually the string is something like "Windows, Linux, Mac"
const splitted = data.OS.split(",");
splitted.forEach(function (os) {
splitted.forEach(function (os: string) {
listOS.push(os.trim());
});
@ -296,13 +301,11 @@ function parseMainPostText(text) {
}
/**
* @private
* Parse a JSON-LD element.
* @param {cheerio.Element} element
*/
function parseScriptTag(element) {
function parseJSONLD(element: cheerio.Element) {
// Get the element HTML
const html = cheerio.load([].concat(element)).html().trim();
const html = cheerio.load(element).html().trim();
// Obtain the JSON-LD
const data = html
@ -310,10 +313,7 @@ function parseScriptTag(element) {
.replace("</script>", "");
// Convert the string to an object
const json = JSON.parse(data);
// Return only the data of the game
if (json["@type"] === "Book") return json;
return JSON.parse(data);
}
/**
@ -322,15 +322,15 @@ function parseScriptTag(element) {
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {Object.<string, string>} JSON-LD or `null` if no valid JSON is found
*/
function extractStructuredData(body) {
function extractStructuredData(body: cheerio.Cheerio): { [s: string]: string; } {
shared.logger.trace("Extracting JSON-LD data...");
// Fetch the JSON-LD data
const structuredDataElements = body.find(f95Selector.GT_JSONLD);
// Parse the data
const json = structuredDataElements.map((idx, el) => parseScriptTag(el)).get();
return json.lenght !== 0 ? json[0] : null;
const json = structuredDataElements.map((idx, el) => parseJSONLD(el)).get();
return json.length !== 0 ? json[0] : null;
}
/**
@ -341,7 +341,7 @@ function extractStructuredData(body) {
* @param {Boolean} mod Specify if it is a game or a mod
* @returns {String} Game description
*/
function getOverview(text, mod) {
function getOverview(text: string, mod: boolean): string {
shared.logger.trace("Extracting game overview...");
// Get overview (different parsing for game and mod)
@ -355,7 +355,7 @@ function getOverview(text, mod) {
* @param {String} prefix Prefix to check
* @return {Boolean}
*/
function isEngine(prefix) {
function isEngine(prefix: string): boolean {
const engines = toUpperCaseArray(Object.values(shared.engines));
return engines.includes(prefix.toUpperCase());
}
@ -366,7 +366,7 @@ function isEngine(prefix) {
* @param {String} prefix Prefix to check
* @return {Boolean}
*/
function isStatus(prefix) {
function isStatus(prefix: string): boolean {
const statuses = toUpperCaseArray(Object.values(shared.statuses));
return statuses.includes(prefix.toUpperCase());
}
@ -377,7 +377,7 @@ function isStatus(prefix) {
* @param {String} prefix Prefix to check
* @return {Boolean}
*/
function isMod(prefix) {
function isMod(prefix: string): boolean {
const modPrefixes = ["MOD", "CHEAT MOD"];
return modPrefixes.includes(prefix.toUpperCase());
}
@ -388,7 +388,7 @@ function isMod(prefix) {
* @param {String} url Game's URL
* @return {Number} Game's ID
*/
function extractIDFromURL(url) {
function extractIDFromURL(url: string): number {
// URL are in the format https://f95zone.to/threads/GAMENAME-VERSION-DEVELOPER.ID/
// or https://f95zone.to/threads/ID/
const match = url.match(/([0-9]+)(?=\/|\b)(?!-|\.)/);
@ -403,13 +403,13 @@ function extractIDFromURL(url) {
* Makes an array of strings uppercase.
* @param {String[]} a
*/
function toUpperCaseArray(a) {
function toUpperCaseArray(a: string[]) {
/**
* Makes a string uppercase.
* @param {String} s
* @returns {String}
*/
function toUpper(s) {
function toUpper(s: string): string {
return s.toUpperCase();
}
return a.map(toUpper);

View File

@ -1,22 +1,20 @@
"use strict";
// Public modules from npm
const cheerio = require("cheerio");
import cheerio from "cheerio";
// Modules from file
const { fetchHTML } = require("./network-helper.js");
const shared = require("./shared.js");
const f95Selector = require("./constants/css-selector.js");
const { F95_BASE_URL } = require("./constants/url.js");
import { fetchHTML } from "./network-helper.js";
import shared from "./shared.js";
import { selectors as f95Selector } from "./constants/css-selector.js";
import { urls as f95urls } from "./constants/url.js";
//#region Public methods
/**
* @protected
* Search for a game on F95Zone and return a list of URLs, one for each search result.
* @param {String} name Game name
* @returns {Promise<String[]>} URLs of results
*/
module.exports.searchGame = async function (name) {
export async function searchGame(name: string): Promise<string[]> {
shared.logger.info(`Searching games with name ${name}`);
// Replace the whitespaces with +
@ -30,12 +28,10 @@ module.exports.searchGame = async function (name) {
};
/**
* @protected
* Search for a mod on F95Zone and return a list of URLs, one for each search result.
* @param {String} name Mod name
* @returns {Promise<String[]>} URLs of results
*/
module.exports.searchMod = async function (name) {
export async function searchMod(name: string): Promise<string[]> {
shared.logger.info(`Searching mods with name ${name}`);
// Replace the whitespaces with +
@ -51,12 +47,10 @@ module.exports.searchMod = async function (name) {
//#region Private methods
/**
* @private
* Gets the URLs of the threads resulting from the F95Zone search.
* @param {String} url Search URL
* @return {Promise<String[]>} List of URLs
*/
async function fetchResultURLs(url) {
async function fetchResultURLs(url: string): Promise<string[]> {
shared.logger.trace(`Fetching ${url}...`);
// Fetch HTML and prepare Cheerio
@ -76,12 +70,11 @@ async function fetchResultURLs(url) {
}
/**
* @private
* Look for the URL to the thread referenced by the item.
* @param {cheerio.Cheerio} selector Element to search
* @returns {String} URL to thread
*/
function extractLinkFromResult(selector) {
function extractLinkFromResult(selector: cheerio.Cheerio): string {
shared.logger.trace("Extracting thread link from result...");
const partialLink = selector
@ -90,6 +83,6 @@ function extractLinkFromResult(selector) {
.trim();
// Compose and return the URL
return new URL(partialLink, F95_BASE_URL).toString();
return new URL(partialLink, f95urls.F95_BASE_URL).toString();
}
//#endregion Private methods

View File

@ -1,20 +1,18 @@
"use strict";
// Public modules from npm
const cheerio = require("cheerio");
import cheerio from "cheerio";
// Modules from file
const networkHelper = require("./network-helper.js");
const f95Selector = require("./constants/css-selector.js");
const f95url = require("./constants/url.js");
const UserData = require("./classes/user-data.js");
import { fetchHTML } from "./network-helper.js";
import { selectors as f95Selector } from "./constants/css-selector.js";
import { urls as f95url } from "./constants/url.js";
import UserData from "./classes/user-data.js";
/**
* @protected
* Gets user data, such as username, url of watched threads, and profile picture url.
* @return {Promise<UserData>} User data
*/
module.exports.getUserData = async function() {
export async function getUserData(): Promise<UserData> {
// Fetch data
const data = await fetchUsernameAndAvatar();
const urls = await fetchWatchedGameThreadURLs();
@ -30,15 +28,13 @@ module.exports.getUserData = async function() {
//#region Private methods
/**
* @private
* It connects to the page and extracts the name
* of the currently logged in user and the URL
* of their profile picture.
* @return {Promise<Object.<string, string>>}
*/
async function fetchUsernameAndAvatar() {
async function fetchUsernameAndAvatar(): Promise<{ [s: string]: string; }> {
// Fetch page
const html = await networkHelper.fetchHTML(f95url.F95_BASE_URL);
const html = await fetchHTML(f95url.F95_BASE_URL);
// Load HTML response
const $ = cheerio.load(html);
@ -57,11 +53,10 @@ async function fetchUsernameAndAvatar() {
}
/**
* @private
* Gets the list of URLs of game threads watched by the user.
* @returns {Promise<String[]>} List of URLs
*/
async function fetchWatchedGameThreadURLs() {
async function fetchWatchedGameThreadURLs(): Promise<string[]> {
// Local variables
const watchedGameThreadURLs = [];
@ -76,7 +71,7 @@ async function fetchWatchedGameThreadURLs() {
do {
// Fetch page
const html = await networkHelper.fetchHTML(currentURL);
const html = await fetchHTML(currentURL);
// Load HTML response
const $ = cheerio.load(html);
@ -95,17 +90,15 @@ async function fetchWatchedGameThreadURLs() {
}
/**
* @private
* Gets the URLs of the watched threads on the page.
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {String[]}
*/
function fetchPageURLs(body) {
function fetchPageURLs(body: cheerio.Cheerio): string[] {
const elements = body.find(f95Selector.WT_URLS);
return elements.map(function extractURLs(idx, e) {
// Obtain the link (replace "unread" only for the unread threads)
const partialLink = e.attribs.href.replace("unread", "");
const partialLink = cheerio(e).attr("href").replace("unread", "");
// Compose and return the URL
return new URL(partialLink, f95url.F95_BASE_URL).toString();
@ -113,13 +106,11 @@ function fetchPageURLs(body) {
}
/**
* @private
* Gets the URL of the next page containing the watched threads
* or `null` if that page does not exist.
* @param {cheerio.Cheerio} body Page `body` selector
* @returns {String}
*/
function fetchNextPageURL(body) {
function fetchNextPageURL(body: cheerio.Cheerio): string {
const element = body.find(f95Selector.WT_NEXT_PAGE).first();
// No element found