[CodeFactor] Apply fixes
							parent
							
								
									ed77fa31f5
								
							
						
					
					
						commit
						8d9d1e11e4
					
				| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/* 
 | 
			
		||||
to use this example, create an .env file 
 | 
			
		||||
/*
 | 
			
		||||
to use this example, create an .env file
 | 
			
		||||
in the project root with the following values:
 | 
			
		||||
 | 
			
		||||
F95_USERNAME = YOUR_USERNAME
 | 
			
		||||
| 
						 | 
				
			
			@ -12,12 +12,12 @@ F95_PASSWORD = YOUR_PASSWORD
 | 
			
		|||
import dotenv from "dotenv";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import { login, 
 | 
			
		||||
    getUserData, 
 | 
			
		||||
    getLatestUpdates, 
 | 
			
		||||
    LatestSearchQuery, 
 | 
			
		||||
    Game, 
 | 
			
		||||
    searchHandiwork, 
 | 
			
		||||
import { login,
 | 
			
		||||
    getUserData,
 | 
			
		||||
    getLatestUpdates,
 | 
			
		||||
    LatestSearchQuery,
 | 
			
		||||
    Game,
 | 
			
		||||
    searchHandiwork,
 | 
			
		||||
    HandiworkSearchQuery
 | 
			
		||||
} from "./index.js";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								src/index.ts
								
								
								
								
							
							
						
						
									
										24
									
								
								src/index.ts
								
								
								
								
							| 
						 | 
				
			
			@ -59,7 +59,7 @@ export function isLogged(): boolean { return shared.isLogged; };
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Log in to the F95Zone platform.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * This **must** be the first operation performed before accessing any other script functions.
 | 
			
		||||
 */
 | 
			
		||||
export async function login(username: string, password: string): Promise<LoginResult> {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ export async function login(username: string, password: string): Promise<LoginRe
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Chek if exists a new version of the handiwork.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 */
 | 
			
		||||
export async function checkIfHandiworkHasUpdate(hw: HandiWork): Promise<boolean> {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,9 +127,9 @@ export async function checkIfHandiworkHasUpdate(hw: HandiWork): Promise<boolean>
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Search for one or more handiworks identified by a specific query.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param {HandiworkSearchQuery} query Parameters used for the search.
 | 
			
		||||
 * @param {Number} limit Maximum number of results. Default: 10
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -137,12 +137,12 @@ export async function searchHandiwork<T extends IBasic>(query: HandiworkSearchQu
 | 
			
		|||
    // Check if the user is logged
 | 
			
		||||
    if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
 | 
			
		||||
 | 
			
		||||
    return await search<T>(query, limit);
 | 
			
		||||
    return search<T>(query, limit);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Given the url, it gets all the information about the handiwork requested.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 */
 | 
			
		||||
export async function getHandiworkFromURL<T extends IBasic>(url: string): Promise<T> {
 | 
			
		||||
| 
						 | 
				
			
			@ -155,14 +155,14 @@ export async function getHandiworkFromURL<T extends IBasic>(url: string): Promis
 | 
			
		|||
    if (!isF95URL(url)) throw new Error(`${url} is not a valid F95Zone URL`);
 | 
			
		||||
    
 | 
			
		||||
    // Get game data
 | 
			
		||||
    return await getHandiworkInformation<T>(url);
 | 
			
		||||
    return getHandiworkInformation<T>(url);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the data of the currently logged in user.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @returns {Promise<UserProfile>} Data of the user currently logged in
 | 
			
		||||
 */
 | 
			
		||||
export async function getUserData(): Promise<UserProfile> {
 | 
			
		||||
| 
						 | 
				
			
			@ -178,9 +178,9 @@ export async function getUserData(): Promise<UserProfile> {
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the latest updated games that match the specified parameters.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param {LatestSearchQuery} query Parameters used for the search.
 | 
			
		||||
 * @param {Number} limit Maximum number of results. Default: 10
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -196,7 +196,7 @@ export async function getLatestUpdates<T extends IBasic>(query: LatestSearchQuer
 | 
			
		|||
 | 
			
		||||
    // Get the data from urls
 | 
			
		||||
    const promiseList = urls.map((u: string) => getHandiworkInformation<T>(u));
 | 
			
		||||
    return await Promise.all(promiseList);
 | 
			
		||||
    return Promise.all(promiseList);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ export default class Thread {
 | 
			
		|||
    public get id() { return this._id; }
 | 
			
		||||
    /**
 | 
			
		||||
     * URL of the thread.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * It may vary depending on any versions of the contained product.
 | 
			
		||||
     */
 | 
			
		||||
    public get url() { return this._url; }
 | 
			
		||||
| 
						 | 
				
			
			@ -85,8 +85,8 @@ export default class Thread {
 | 
			
		|||
    //#endregion Getters
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes an object for mapping a thread. 
 | 
			
		||||
     * 
 | 
			
		||||
     * Initializes an object for mapping a thread.
 | 
			
		||||
     *
 | 
			
		||||
     * The unique ID of the thread must be specified.
 | 
			
		||||
     */
 | 
			
		||||
    constructor(id: number) { this._id = id; }
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ export default class Thread {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * It processes the rating of the thread 
 | 
			
		||||
     * It processes the rating of the thread
 | 
			
		||||
     * starting from the data contained in the JSON+LD tag.
 | 
			
		||||
     */
 | 
			
		||||
    private parseRating(data: TJsonLD): TRating {
 | 
			
		||||
| 
						 | 
				
			
			@ -241,7 +241,7 @@ export default class Thread {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the post in the `index` position with respect to the posts in the thread.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * `index` must be greater or equal to 1.
 | 
			
		||||
     * If the post is not found, `null` is returned.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,7 +149,7 @@ export default class UserProfile extends PlatformUser {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        // Wait for the promises to resolve
 | 
			
		||||
        return await Promise.all(responsePromiseList);
 | 
			
		||||
        return Promise.all(responsePromiseList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ export default class PrefixParser {
 | 
			
		|||
    //#region Private methods
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the key associated with a given value from a dictionary.
 | 
			
		||||
     * @param {Object} object Dictionary to search 
 | 
			
		||||
     * @param {Object} object Dictionary to search
 | 
			
		||||
     * @param {Any} value Value associated with the key
 | 
			
		||||
     * @returns {String|undefined} Key found or undefined
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ export default class PrefixParser {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Search within the platform prefixes for the 
 | 
			
		||||
     * Search within the platform prefixes for the
 | 
			
		||||
     * desired element and return the dictionary that contains it.
 | 
			
		||||
     * @param element Element to search in the prefixes as a key or as a value
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,21 +13,21 @@ import ThreadSearchQuery, { TThreadOrder } from './thread-search-query.js';
 | 
			
		|||
 | 
			
		||||
// Type definitions
 | 
			
		||||
/**
 | 
			
		||||
 * Method of sorting results. Try to unify the two types of 
 | 
			
		||||
 * sorts in the "Latest" section and in the "Thread search" 
 | 
			
		||||
 * section. Being dynamic research, if a sorting type is not 
 | 
			
		||||
 * Method of sorting results. Try to unify the two types of
 | 
			
		||||
 * sorts in the "Latest" section and in the "Thread search"
 | 
			
		||||
 * section. Being dynamic research, if a sorting type is not
 | 
			
		||||
 * available, the replacement sort is chosen.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * `date`: Order based on the latest update
 | 
			
		||||
 *
 | 
			
		||||
 * `likes`: Order based on the number of likes received. Replacement: `replies`.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * `relevance`: Order based on the relevance of the result (or rating).
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * `replies`: Order based on the number of answers to the thread. Replacement: `views`.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * `title`: Order based on the growing alphabetical order of the titles.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * `views`: Order based on the number of visits. Replacement: `replies`.
 | 
			
		||||
 */
 | 
			
		||||
type THandiworkOrder = "date" | "likes" | "relevance" | "replies" | "title" | "views";
 | 
			
		||||
| 
						 | 
				
			
			@ -80,8 +80,8 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
    //#region Public methods
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Select what kind of search should be 
 | 
			
		||||
     * performed based on the properties of 
 | 
			
		||||
     * Select what kind of search should be
 | 
			
		||||
     * performed based on the properties of
 | 
			
		||||
     * the query.
 | 
			
		||||
     */
 | 
			
		||||
    public selectSearchType(): "latest" | "thread" {
 | 
			
		||||
| 
						 | 
				
			
			@ -89,8 +89,8 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
        const MAX_TAGS_LATEST_SEARCH = 5;
 | 
			
		||||
        const DEFAULT_SEARCH_TYPE = "latest";
 | 
			
		||||
 | 
			
		||||
        // If the keywords are set or the number 
 | 
			
		||||
        // of included tags is greather than 5, 
 | 
			
		||||
        // If the keywords are set or the number
 | 
			
		||||
        // of included tags is greather than 5,
 | 
			
		||||
        // we must perform a thread search
 | 
			
		||||
        if (this.keywords || this.includedTags.length > MAX_TAGS_LATEST_SEARCH) return "thread";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +156,7 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
    private castToThread(): ThreadSearchQuery {
 | 
			
		||||
        // Cast the basic query object and copy common values
 | 
			
		||||
        const query: ThreadSearchQuery = new ThreadSearchQuery;
 | 
			
		||||
        Object.keys(this).forEach(key => { 
 | 
			
		||||
        Object.keys(this).forEach(key => {
 | 
			
		||||
            if (query.hasOwnProperty(key)) {
 | 
			
		||||
                query[key] = this[key];
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,15 +29,15 @@ export default class LatestSearchQuery implements IQuery {
 | 
			
		|||
 | 
			
		||||
    public category: TCategory = 'games';
 | 
			
		||||
    /**
 | 
			
		||||
     * Ordering type. 
 | 
			
		||||
     * 
 | 
			
		||||
     * Ordering type.
 | 
			
		||||
     *
 | 
			
		||||
     * Default: `date`.
 | 
			
		||||
     */
 | 
			
		||||
    public order: TLatestOrder = 'date';
 | 
			
		||||
    /**
 | 
			
		||||
     * Date limit in days, to be understood as "less than".
 | 
			
		||||
     * Use `1` to indicate "today" or `null` to indicate "anytime".
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Default: `null`
 | 
			
		||||
     */
 | 
			
		||||
    public date: TDate = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +74,7 @@ export default class LatestSearchQuery implements IQuery {
 | 
			
		|||
        const decoded = decodeURIComponent(url.toString());
 | 
			
		||||
        
 | 
			
		||||
        // Fetch the result
 | 
			
		||||
        return await fetchGETResponse(decoded);
 | 
			
		||||
        return fetchGETResponse(decoded);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public findNearestDate(d: Date): TDate {
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +124,7 @@ export default class LatestSearchQuery implements IQuery {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    private dateDiffInDays(a: Date, b: Date) {
 | 
			
		||||
        const MS_PER_DAY = 1000 * 60 * 60 * 24;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,7 +82,7 @@ export default class ThreadSearchQuery implements IQuery {
 | 
			
		|||
        const params = this.preparePOSTParameters();
 | 
			
		||||
 | 
			
		||||
        // Return the POST response
 | 
			
		||||
        return await fetchPOSTResponse(urls.F95_SEARCH_URL, params);
 | 
			
		||||
        return fetchPOSTResponse(urls.F95_SEARCH_URL, params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //#endregion Public methods
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ export const selectors = {
 | 
			
		|||
 | 
			
		||||
export const GENERIC = {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID of the user currently logged into 
 | 
			
		||||
     * The ID of the user currently logged into
 | 
			
		||||
     * the platform in the attribute `data-user-id`.
 | 
			
		||||
     */
 | 
			
		||||
    CURRENT_USER_ID: "span.avatar[data-user-id]",
 | 
			
		||||
| 
						 | 
				
			
			@ -41,17 +41,17 @@ export const WATCHED_THREAD = {
 | 
			
		|||
     */
 | 
			
		||||
    BODIES: "div.structItem-cell--main",
 | 
			
		||||
    /**
 | 
			
		||||
     * Link element containing the partial URL 
 | 
			
		||||
     * Link element containing the partial URL
 | 
			
		||||
     * of the thread in the `href` attribute.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * It may be followed by the `/unread` segment.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `WATCHED_THREAD.BODIES` selector.
 | 
			
		||||
     */
 | 
			
		||||
    URL: "div > a[data-tp-primary]",
 | 
			
		||||
    /**
 | 
			
		||||
     * Name of the forum to which the thread belongs as text.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `WATCHED_THREAD.BODIES` selector.
 | 
			
		||||
     */
 | 
			
		||||
    FORUM: "div.structItem-cell--main > div.structItem-minor > ul.structItem-parts > li:last-of-type > a",
 | 
			
		||||
| 
						 | 
				
			
			@ -64,19 +64,19 @@ export const WATCHED_THREAD = {
 | 
			
		|||
export const THREAD = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Number of pages in the thread (as text of the element).
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Two identical elements are identified.
 | 
			
		||||
     */
 | 
			
		||||
    LAST_PAGE: "ul.pageNav-main > li:last-child > a",
 | 
			
		||||
    /**
 | 
			
		||||
     * Identify the creator of the thread.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * The ID is contained in the `data-user-id` attribute.
 | 
			
		||||
     */
 | 
			
		||||
    OWNER_ID: "div.uix_headerInner > * a.username[data-user-id]",
 | 
			
		||||
    /**
 | 
			
		||||
     * Contains the creation date of the thread.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * The date is contained in the `datetime` attribute as an ISO string.
 | 
			
		||||
     */
 | 
			
		||||
    CREATION: "div.uix_headerInner > * time",
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ export const THREAD = {
 | 
			
		|||
    TITLE: "h1.p-title-value",
 | 
			
		||||
    /**
 | 
			
		||||
     * JSON containing thread information.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Two different elements are found.
 | 
			
		||||
     */
 | 
			
		||||
    JSONLD: "script[type=\"application/ld+json\"]",
 | 
			
		||||
| 
						 | 
				
			
			@ -106,44 +106,44 @@ export const THREAD = {
 | 
			
		|||
 | 
			
		||||
export const POST = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Unique post number for the current thread. 
 | 
			
		||||
     * 
 | 
			
		||||
     * Unique post number for the current thread.
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    NUMBER: "* ul.message-attribution-opposite > li > a:not([id])[rel=\"nofollow\"]",
 | 
			
		||||
    /**
 | 
			
		||||
     * Unique ID of the post in the F95Zone platform in the `id` attribute.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    ID: "span[id^=\"post\"]",
 | 
			
		||||
    /**
 | 
			
		||||
     * Unique ID of the post author in the `data-user-id` attribute.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    OWNER_ID: "* div.message-cell--user > * a[data-user-id]",
 | 
			
		||||
    /**
 | 
			
		||||
     * Main body of the post where the message written by the user is contained.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    BODY: "* article.message-body > div.bbWrapper",
 | 
			
		||||
    /**
 | 
			
		||||
     * Publication date of the post contained in the `datetime` attribute as an ISO date.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    PUBLISH_DATE: "* div.message-attribution-main > a > time",
 | 
			
		||||
    /**
 | 
			
		||||
     * Last modified date of the post contained in the `datetime` attribute as the ISO date.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    LAST_EDIT: "* div.message-lastEdit > time",
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the element only if the post has been bookmarked.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
     */
 | 
			
		||||
    BOOKMARKED: "* ul.message-attribution-opposite >li > a[title=\"Bookmark\"].is-bookmarked",
 | 
			
		||||
| 
						 | 
				
			
			@ -152,37 +152,37 @@ export const POST = {
 | 
			
		|||
export const MEMBER = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Name of the user.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * It also contains the unique ID of the user in the `data-user-id` attribute.
 | 
			
		||||
     */
 | 
			
		||||
    NAME: "span[class^=\"username\"]",
 | 
			
		||||
    /**
 | 
			
		||||
     * Title of the user in the platform.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * i.e.: Member
 | 
			
		||||
     */
 | 
			
		||||
    TITLE: "span.userTitle",
 | 
			
		||||
    /**
 | 
			
		||||
     * Avatar used by the user.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Source in the attribute `src`.
 | 
			
		||||
     */
 | 
			
		||||
    AVATAR: "span.avatarWrapper > a.avatar > img",
 | 
			
		||||
    /**
 | 
			
		||||
     * User assigned banners.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * The last element is always empty and can be ignored.
 | 
			
		||||
     */
 | 
			
		||||
    BANNERS: "em.userBanner > strong",
 | 
			
		||||
    /**
 | 
			
		||||
     * Date the user joined the platform.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * The date is contained in the `datetime` attribute as an ISO string.
 | 
			
		||||
     */
 | 
			
		||||
    JOINED: "div.uix_memberHeader__extra > div.memberHeader-blurb:nth-child(1) > * time",
 | 
			
		||||
    /**
 | 
			
		||||
     * Last time the user connected to the platform.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * The date is contained in the `datetime` attribute as an ISO string.
 | 
			
		||||
     */
 | 
			
		||||
    LAST_SEEN: "div.uix_memberHeader__extra > div.memberHeader-blurb:nth-child(2) > * time",
 | 
			
		||||
| 
						 | 
				
			
			@ -193,14 +193,14 @@ export const MEMBER = {
 | 
			
		|||
    AMOUNT_DONATED: "div.pairJustifier > dl:nth-child(5) > dd",
 | 
			
		||||
    /**
 | 
			
		||||
     * Button used to follow/unfollow the user.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * If the text is `Unfollow` then the user is followed.
 | 
			
		||||
     * If the text is `Follow` then the user is not followed.
 | 
			
		||||
     */
 | 
			
		||||
    FOLLOWED: "div.memberHeader-buttons > div.buttonGroup:first-child > a[data-sk-follow] > span",
 | 
			
		||||
    /**
 | 
			
		||||
     * Button used to ignore/unignore the user.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * If the text is `Unignore` then the user is ignored.
 | 
			
		||||
     * If the text is `Ignore` then the user is not ignored.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,8 +8,8 @@ export const urls = {
 | 
			
		|||
    F95_LATEST_PHP: "https://f95zone.to/new_latest.php",
 | 
			
		||||
    F95_BOOKMARKS: "https://f95zone.to/account/bookmarks",
 | 
			
		||||
    /**
 | 
			
		||||
     * Add the unique ID of the post to 
 | 
			
		||||
     * get the thread page where the post 
 | 
			
		||||
     * Add the unique ID of the post to
 | 
			
		||||
     * get the thread page where the post
 | 
			
		||||
     * is present.
 | 
			
		||||
     */
 | 
			
		||||
    F95_POSTS: "https://f95zone.to/posts/",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import { urls as f95url } from "../constants/url.js";
 | 
			
		|||
 * You *must* be logged.
 | 
			
		||||
 * @param {LatestSearchQuery} query
 | 
			
		||||
 * Query used for the search
 | 
			
		||||
 * @param {Number} limit 
 | 
			
		||||
 * @param {Number} limit
 | 
			
		||||
 * Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns {Promise<String[]>} URLs of the handiworks
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ interface ILatestResource {
 | 
			
		|||
 | 
			
		||||
//#region Public methods
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the basic data used for game data processing 
 | 
			
		||||
 * Gets the basic data used for game data processing
 | 
			
		||||
 * (such as graphics engines and progress statuses)
 | 
			
		||||
 */
 | 
			
		||||
export default async function fetchPlatformData(): Promise<void> {
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,7 @@ function saveCache(path: string): void {
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * @private
 | 
			
		||||
 * Given the HTML code of the response from the F95Zone, 
 | 
			
		||||
 * Given the HTML code of the response from the F95Zone,
 | 
			
		||||
 * parse it and return the result.
 | 
			
		||||
 */
 | 
			
		||||
function parseLatestPlatformHTML(html: string): ILatestResource{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,6 @@ export default async function executeQuery(query: any, limit: number = 30): Prom
 | 
			
		|||
            "handiwork");
 | 
			
		||||
 | 
			
		||||
    // Fetch and return the urls
 | 
			
		||||
    return await searchMap[key](query, limit);
 | 
			
		||||
    return searchMap[key](query, limit);
 | 
			
		||||
}
 | 
			
		||||
//#endregion
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ export default async function fetchThreadHandiworkURLs(query: ThreadSearchQuery,
 | 
			
		|||
    const response = await query.execute();
 | 
			
		||||
 | 
			
		||||
    // Fetch the results from F95 and return the handiwork urls
 | 
			
		||||
    if (response.isSuccess()) return await fetchResultURLs(response.value.data as string, limit); 
 | 
			
		||||
    if (response.isSuccess()) return fetchResultURLs(response.value.data as string, limit);
 | 
			
		||||
    else throw response.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ import { failure, Result, success } from "./classes/result.js";
 | 
			
		|||
import { GenericAxiosError, InvalidF95Token, UnexpectedResponseContentType } from "./classes/errors.js";
 | 
			
		||||
 | 
			
		||||
// Global variables
 | 
			
		||||
const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " + 
 | 
			
		||||
const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " +
 | 
			
		||||
    "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
axiosCookieJarSupport.default(axios);
 | 
			
		||||
| 
						 | 
				
			
			@ -61,15 +61,15 @@ export async function fetchHTML(url: string): Promise<Result<GenericAxiosError |
 | 
			
		|||
            error: null
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return isHTML ? 
 | 
			
		||||
        return isHTML ?
 | 
			
		||||
            success(response.value.data as string) :
 | 
			
		||||
            failure(unexpectedResponseError);
 | 
			
		||||
    } else return failure(response.value as GenericAxiosError);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * It authenticates to the platform using the credentials 
 | 
			
		||||
 * and token obtained previously. Save cookies on your 
 | 
			
		||||
 * It authenticates to the platform using the credentials
 | 
			
		||||
 * and token obtained previously. Save cookies on your
 | 
			
		||||
 * device after authentication.
 | 
			
		||||
 * @param {module:./classes/credentials.ts:Credentials} credentials Platform access credentials
 | 
			
		||||
 * @param {Boolean} force Specifies whether the request should be forced, ignoring any saved cookies
 | 
			
		||||
| 
						 | 
				
			
			@ -173,7 +173,7 @@ export function isF95URL(url: string): boolean {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks if the string passed by parameter has a 
 | 
			
		||||
 * 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
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +187,7 @@ export function isStringAValidURL(url: string): boolean {
 | 
			
		|||
/**
 | 
			
		||||
 * Check if a particular URL is valid and reachable on the web.
 | 
			
		||||
 * @param {string} url URL to check
 | 
			
		||||
 * @param {boolean} [checkRedirect] 
 | 
			
		||||
 * @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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,7 +87,7 @@ function toUpperCaseArray(a: string[]): string[] {
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if the string `s` is in the dict `a`.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * Case insensitive.
 | 
			
		||||
 */
 | 
			
		||||
function stringInDict(s: string, a: TPrefixDict): boolean {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ function stringInDict(s: string, a: TPrefixDict): boolean {
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert a string to a boolean.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * Check also for `yes`/`no` and `1`/`0`.
 | 
			
		||||
 */
 | 
			
		||||
function stringToBoolean(s: string): boolean {
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +116,7 @@ function stringToBoolean(s: string): boolean {
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the element with the given name or `undefined`.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * Case-insensitive.
 | 
			
		||||
 */
 | 
			
		||||
function getPostElementByName(elements: IPostElement[], name: string): IPostElement | undefined {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +127,7 @@ function getPostElementByName(elements: IPostElement[], name: string): IPostElem
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Parse the post prefixes.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * In particular, it elaborates the following prefixes for games:
 | 
			
		||||
 * `Engine`, `Status`, `Mod`.
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +174,7 @@ function fillWithPrefixes(hw: HandiWork, prefixes: string[]) {
 | 
			
		|||
/**
 | 
			
		||||
 * Compiles a HandiWork object with the data extracted
 | 
			
		||||
 * from the main post of the HandiWork page.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * The values that will be added are:
 | 
			
		||||
 * `Overview`, `OS`, `Language`, `Version`, `Installation`,
 | 
			
		||||
 * `Pages`, `Resolution`, `Lenght`, `Genre`, `Censored`,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,11 +21,11 @@ export interface ILink extends IPostElement {
 | 
			
		|||
 * Given a post of a thread page it extracts the information contained in the body.
 | 
			
		||||
 */
 | 
			
		||||
export function parseF95ThreadPost($: cheerio.Root, post: cheerio.Cheerio): IPostElement[] {
 | 
			
		||||
    // The data is divided between "tag" and "text" elements. 
 | 
			
		||||
    // Simple data is composed of a "tag" element followed 
 | 
			
		||||
    // by a "text" element, while more complex data (contained 
 | 
			
		||||
    // in spoilers) is composed of a "tag" element, followed 
 | 
			
		||||
    // by a text containing only ":" and then by an additional 
 | 
			
		||||
    // The data is divided between "tag" and "text" elements.
 | 
			
		||||
    // Simple data is composed of a "tag" element followed
 | 
			
		||||
    // by a "text" element, while more complex data (contained
 | 
			
		||||
    // in spoilers) is composed of a "tag" element, followed
 | 
			
		||||
    // by a text containing only ":" and then by an additional
 | 
			
		||||
    // "tag" element having as the first term "Spoiler"
 | 
			
		||||
 | 
			
		||||
    // First fetch all the elements in the post
 | 
			
		||||
| 
						 | 
				
			
			@ -44,13 +44,13 @@ export function parseF95ThreadPost($: cheerio.Root, post: cheerio.Cheerio): IPos
 | 
			
		|||
//#region Private methods
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Process a spoiler element by getting its text broken 
 | 
			
		||||
 * Process a spoiler element by getting its text broken
 | 
			
		||||
 * down by any other spoiler elements present.
 | 
			
		||||
 */
 | 
			
		||||
function parseCheerioSpoilerNode($: cheerio.Root, spoiler: cheerio.Cheerio): IPostElement {
 | 
			
		||||
    // A spoiler block is composed of a div with class "bbCodeSpoiler", 
 | 
			
		||||
    // containing a div "bbCodeSpoiler-content" containing, in cascade, 
 | 
			
		||||
    // a div with class "bbCodeBlock--spoiler" and a div with class "bbCodeBlock-content". 
 | 
			
		||||
    // A spoiler block is composed of a div with class "bbCodeSpoiler",
 | 
			
		||||
    // containing a div "bbCodeSpoiler-content" containing, in cascade,
 | 
			
		||||
    // a div with class "bbCodeBlock--spoiler" and a div with class "bbCodeBlock-content".
 | 
			
		||||
    // This last tag contains the required data.
 | 
			
		||||
 | 
			
		||||
    // Local variables
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ function parseCheerioSpoilerNode($: cheerio.Root, spoiler: cheerio.Cheerio): IPo
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if the node passed as a parameter is of text type. 
 | 
			
		||||
 * Check if the node passed as a parameter is of text type.
 | 
			
		||||
 * This also includes formatted nodes (i.e. `<b>`).
 | 
			
		||||
 */
 | 
			
		||||
function isTextNode(node: cheerio.Element): boolean {
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,7 @@ function isTextNode(node: cheerio.Element): boolean {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the text of the node only, excluding child nodes. 
 | 
			
		||||
 * Gets the text of the node only, excluding child nodes.
 | 
			
		||||
 * Also includes formatted text elements (i.e. `<b>`).
 | 
			
		||||
 */
 | 
			
		||||
function getCheerioNonChildrenText(node: cheerio.Cheerio): string {
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +120,7 @@ function getCheerioNonChildrenText(node: cheerio.Cheerio): string {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Process a node and see if it contains a 
 | 
			
		||||
 * Process a node and see if it contains a
 | 
			
		||||
 * link or image. If not, it returns `null`.
 | 
			
		||||
 */
 | 
			
		||||
function parseCheerioLinkNode(element: cheerio.Cheerio): ILink | null {
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +149,7 @@ function parseCheerioLinkNode(element: cheerio.Cheerio): ILink | null {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Collapse an `IPostElement` element with a single subnode 
 | 
			
		||||
 * Collapse an `IPostElement` element with a single subnode
 | 
			
		||||
 * in the `Content` field in case it has no information.
 | 
			
		||||
 */
 | 
			
		||||
function reducePostElement(element: IPostElement): IPostElement {
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +234,7 @@ function parseCheerioNode($: cheerio.Root, node: cheerio.Element, reduce = true)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * It simplifies the `IPostElement` elements by associating 
 | 
			
		||||
 * It simplifies the `IPostElement` elements by associating
 | 
			
		||||
 * the corresponding value to each characterizing element (i.e. author).
 | 
			
		||||
 */
 | 
			
		||||
function parsePostElements(elements: IPostElement[]): IPostElement[] {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue