Adapt to new Result class
							parent
							
								
									20e3d863ed
								
							
						
					
					
						commit
						138a112e96
					
				| 
						 | 
					@ -53,13 +53,15 @@ export default async function fetchPlatformData(): Promise<void> {
 | 
				
			||||||
        const html = await fetchHTML(f95url.F95_LATEST_UPDATES);
 | 
					        const html = await fetchHTML(f95url.F95_LATEST_UPDATES);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Parse data
 | 
					        // Parse data
 | 
				
			||||||
        const data = parseLatestPlatformHTML(html);
 | 
					        if (html.isSuccess()) {
 | 
				
			||||||
 | 
					            const data = parseLatestPlatformHTML(html.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Assign data
 | 
					            // Assign data
 | 
				
			||||||
        assignLatestPlatformData(data);
 | 
					            assignLatestPlatformData(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Cache data
 | 
					            // Cache data
 | 
				
			||||||
        saveCache(shared.cachePath);
 | 
					            saveCache(shared.cachePath);
 | 
				
			||||||
 | 
					        } else throw html.value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
//#endregion Public methods
 | 
					//#endregion Public methods
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,18 +42,21 @@ async function fetchResultURLs(url: string, limit: number = 30): Promise<string[
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fetch HTML and prepare Cheerio
 | 
					    // Fetch HTML and prepare Cheerio
 | 
				
			||||||
    const html = await fetchHTML(url);
 | 
					    const html = await fetchHTML(url);
 | 
				
			||||||
    const $ = cheerio.load(html);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Here we get all the DIV that are the body of the various query results
 | 
					    if (html.isSuccess()) {
 | 
				
			||||||
    const results = $("body").find(f95Selector.GS_RESULT_BODY);
 | 
					        const $ = cheerio.load(html.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Than we extract the URLs
 | 
					        // Here we get all the DIV that are the body of the various query results
 | 
				
			||||||
    const urls = results.slice(0, limit).map((idx, el) => {
 | 
					        const results = $("body").find(f95Selector.GS_RESULT_BODY);
 | 
				
			||||||
        const elementSelector = $(el);
 | 
					 | 
				
			||||||
        return extractLinkFromResult(elementSelector);
 | 
					 | 
				
			||||||
    }).get();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return urls;
 | 
					        // Than we extract the URLs
 | 
				
			||||||
 | 
					        const urls = results.slice(0, limit).map((idx, el) => {
 | 
				
			||||||
 | 
					            const elementSelector = $(el);
 | 
				
			||||||
 | 
					            return extractLinkFromResult(elementSelector);
 | 
				
			||||||
 | 
					        }).get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return urls;
 | 
				
			||||||
 | 
					    } else throw html.value;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,8 @@ import { urls as f95url } from "./constants/url.js";
 | 
				
			||||||
import { selectors as f95selector } from "./constants/css-selector.js";
 | 
					import { selectors as f95selector } from "./constants/css-selector.js";
 | 
				
			||||||
import LoginResult from "./classes/login-result.js";
 | 
					import LoginResult from "./classes/login-result.js";
 | 
				
			||||||
import credentials from "./classes/credentials.js";
 | 
					import credentials from "./classes/credentials.js";
 | 
				
			||||||
 | 
					import { failure, Result, success } from "./classes/result.js";
 | 
				
			||||||
 | 
					import { GenericAxiosError, InvalidF95Token, UnexpectedResponseContentType } from "./classes/errors.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Global variables
 | 
					// 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) " + 
 | 
				
			||||||
| 
						 | 
					@ -31,26 +33,23 @@ const commonConfig = {
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Gets the HTML code of a page.
 | 
					 * Gets the HTML code of a page.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function fetchHTML(url: string): Promise<string|null> {
 | 
					export async function fetchHTML(url: string): Promise<Result<GenericAxiosError | UnexpectedResponseContentType, string>> {
 | 
				
			||||||
    // Local variables
 | 
					 | 
				
			||||||
    let returnValue = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Fetch the response of the platform
 | 
					    // Fetch the response of the platform
 | 
				
			||||||
    const response = await fetchGETResponse(url);
 | 
					    const response = await fetchGETResponse(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Manage response
 | 
					    if (response.isSuccess()) {
 | 
				
			||||||
    /* istambul ignore next */
 | 
					        const isHTML = response.value["content-type"].includes("text/html");
 | 
				
			||||||
    if (!response) {
 | 
					 | 
				
			||||||
        shared.logger.warn(`Unable to fetch HTML for ${url}`);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /* istambul ignore next */
 | 
					 | 
				
			||||||
    else if (!response.headers["content-type"].includes("text/html")) {
 | 
					 | 
				
			||||||
        // The response is not a HTML page
 | 
					 | 
				
			||||||
        shared.logger.warn(`The ${url} returned a ${response.headers["content-type"]} response`);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    returnValue = response.data;
 | 
					        const unexpectedResponseError = new UnexpectedResponseContentType({
 | 
				
			||||||
    return returnValue;
 | 
					            id: 2,
 | 
				
			||||||
 | 
					            message: `Expected HTML but received ${response.value["content-type"]}`,
 | 
				
			||||||
 | 
					            error: null
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return isHTML ? 
 | 
				
			||||||
 | 
					            success(response.value.data as string) :
 | 
				
			||||||
 | 
					            failure(unexpectedResponseError);
 | 
				
			||||||
 | 
					    } else return failure(response.value as GenericAxiosError);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -63,7 +62,7 @@ export async function fetchHTML(url: string): Promise<string|null> {
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function authenticate(credentials: credentials, force: boolean = false): Promise<LoginResult> {
 | 
					export async function authenticate(credentials: credentials, force: boolean = false): Promise<LoginResult> {
 | 
				
			||||||
    shared.logger.info(`Authenticating with user ${credentials.username}`);
 | 
					    shared.logger.info(`Authenticating with user ${credentials.username}`);
 | 
				
			||||||
    if (!credentials.token) throw new Error(`Invalid token for auth: ${credentials.token}`);
 | 
					    if (!credentials.token) throw new InvalidF95Token(`Invalid token for auth: ${credentials.token}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Secure the URL
 | 
					    // Secure the URL
 | 
				
			||||||
    const secureURL = enforceHttpsUrl(f95url.F95_LOGIN_URL);
 | 
					    const secureURL = enforceHttpsUrl(f95url.F95_LOGIN_URL);
 | 
				
			||||||
| 
						 | 
					@ -104,43 +103,43 @@ export async function authenticate(credentials: credentials, force: boolean = fa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Obtain the token used to authenticate the user to the platform.
 | 
					 * Obtain the token used to authenticate the user to the platform.
 | 
				
			||||||
 * @returns {Promise<String>} Token or `null` if an error arise
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function getF95Token(): Promise<string|null> {
 | 
					export async function getF95Token() {
 | 
				
			||||||
    // Fetch the response of the platform
 | 
					    // Fetch the response of the platform
 | 
				
			||||||
    const response = await fetchGETResponse(f95url.F95_LOGIN_URL);
 | 
					    const response = await fetchGETResponse(f95url.F95_LOGIN_URL);
 | 
				
			||||||
    /* istambul ignore next */
 | 
					 | 
				
			||||||
    if (!response) {
 | 
					 | 
				
			||||||
        shared.logger.warn("Unable to get the token for the session");
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The response is a HTML page, we need to find the <input> with name "_xfToken"
 | 
					    if (response.isSuccess()) {
 | 
				
			||||||
    const $ = cheerio.load(response.data as string);
 | 
					        // The response is a HTML page, we need to find the <input> with name "_xfToken"
 | 
				
			||||||
    return $("body").find(f95selector.GET_REQUEST_TOKEN).attr("value");
 | 
					        const $ = cheerio.load(response.value.data as string);
 | 
				
			||||||
 | 
					        return $("body").find(f95selector.GET_REQUEST_TOKEN).attr("value");
 | 
				
			||||||
 | 
					    } else throw response.value;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//#region Utility methods
 | 
					//#region Utility methods
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Performs a GET request to a specific URL and returns the response.
 | 
					 * Performs a GET request to a specific URL and returns the response.
 | 
				
			||||||
 * If the request generates an error (for example 400) `null` is returned.
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function fetchGETResponse(url: string): Promise<AxiosResponse<unknown>> {
 | 
					export async function fetchGETResponse(url: string): Promise<Result<GenericAxiosError, AxiosResponse<any>>>{
 | 
				
			||||||
    // Secure the URL
 | 
					    // Secure the URL
 | 
				
			||||||
    const secureURL = enforceHttpsUrl(url);
 | 
					    const secureURL = enforceHttpsUrl(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        // Fetch and return the response
 | 
					        // Fetch and return the response
 | 
				
			||||||
        return await axios.get(secureURL, commonConfig);
 | 
					        const response = await axios.get(secureURL, commonConfig);
 | 
				
			||||||
 | 
					        return success(response);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
        shared.logger.error(`Error ${e.message} occurred while trying to fetch ${secureURL}`);
 | 
					        shared.logger.error(`Error ${e.message} occurred while trying to fetch ${secureURL}`);
 | 
				
			||||||
        return null;
 | 
					        const genericError = new GenericAxiosError({
 | 
				
			||||||
 | 
					            id: 1,
 | 
				
			||||||
 | 
					            message:`Error ${e.message} occurred while trying to fetch ${secureURL}`,
 | 
				
			||||||
 | 
					            error: e
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return failure(genericError);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Enforces the scheme of the URL is https and returns the new URL.
 | 
					 * 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
 | 
					 * @returns {String} Secure URL or `null` if the argument is not a string
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function enforceHttpsUrl(url: string): string {
 | 
					export function enforceHttpsUrl(url: string): string {
 | 
				
			||||||
| 
						 | 
					@ -149,12 +148,9 @@ export function enforceHttpsUrl(url: string): string {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Check if the url belongs to the domain of the F95 platform.
 | 
					 * 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
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function isF95URL(url: string): boolean {
 | 
					export function isF95URL(url: string): boolean {
 | 
				
			||||||
    if (url.toString().startsWith(f95url.F95_BASE_URL)) return true;
 | 
					    return url.toString().startsWith(f95url.F95_BASE_URL);
 | 
				
			||||||
    else return false;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -167,8 +163,7 @@ export function isStringAValidURL(url: string): boolean {
 | 
				
			||||||
    // Many thanks to Daveo at StackOverflow (https://preview.tinyurl.com/y2f2e2pc)
 | 
					    // 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 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);
 | 
					    const regex = new RegExp(expression);
 | 
				
			||||||
    if (url.match(regex)) return true;
 | 
					    return url.match(regex).length > 0;
 | 
				
			||||||
    else return false;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,25 +24,26 @@ export async function getPostInformation<T extends IBasic>(url: string): Promise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fetch HTML and prepare Cheerio
 | 
					    // Fetch HTML and prepare Cheerio
 | 
				
			||||||
    const html = await fetchHTML(url);
 | 
					    const html = await fetchHTML(url);
 | 
				
			||||||
    if (!html) return null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const $ = cheerio.load(html);
 | 
					    if (html.isSuccess()) {
 | 
				
			||||||
    const body = $("body");
 | 
					        const $ = cheerio.load(html.value);
 | 
				
			||||||
    const mainPost = $(f95Selector.GS_POSTS).first();
 | 
					        const body = $("body");
 | 
				
			||||||
 | 
					        const mainPost = $(f95Selector.GS_POSTS).first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Extract data
 | 
					        // Extract data
 | 
				
			||||||
    const postData = parseCheerioMainPost($, mainPost);
 | 
					        const postData = parseCheerioMainPost($, mainPost);
 | 
				
			||||||
    const TJsonLD = getJSONLD(body);
 | 
					        const TJsonLD = getJSONLD(body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fill in the HandiWork element with the information obtained
 | 
					        // Fill in the HandiWork element with the information obtained
 | 
				
			||||||
    const hw: HandiWork = {} as HandiWork;
 | 
					        const hw: HandiWork = {} as HandiWork;
 | 
				
			||||||
    fillWithJSONLD(hw, TJsonLD);
 | 
					        fillWithJSONLD(hw, TJsonLD);
 | 
				
			||||||
    fillWithPostData(hw, postData);
 | 
					        fillWithPostData(hw, postData);
 | 
				
			||||||
    fillWithPrefixes(hw, body);
 | 
					        fillWithPrefixes(hw, body);
 | 
				
			||||||
    hw.tags = extractTags(body);
 | 
					        hw.tags = extractTags(body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shared.logger.info(`Founded data for ${hw.name}`);
 | 
					        shared.logger.info(`Founded data for ${hw.name}`);
 | 
				
			||||||
    return <T><unknown>hw;
 | 
					        return <T><unknown>hw;
 | 
				
			||||||
 | 
					    } else throw html.value;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
//#endregion Public methods
 | 
					//#endregion Public methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,20 +36,22 @@ async function fetchUsernameAndAvatar(): Promise<{ [s: string]: string; }> {
 | 
				
			||||||
    // Fetch page
 | 
					    // Fetch page
 | 
				
			||||||
    const html = await fetchHTML(f95url.F95_BASE_URL);
 | 
					    const html = await fetchHTML(f95url.F95_BASE_URL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Load HTML response
 | 
					    if (html.isSuccess()) {
 | 
				
			||||||
    const $ = cheerio.load(html);
 | 
					        // Load HTML response
 | 
				
			||||||
    const body = $("body");
 | 
					        const $ = cheerio.load(html.value);
 | 
				
			||||||
 | 
					        const body = $("body");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fetch username
 | 
					        // Fetch username
 | 
				
			||||||
    const username = body.find(f95Selector.UD_USERNAME_ELEMENT).first().text().trim();
 | 
					        const username = body.find(f95Selector.UD_USERNAME_ELEMENT).first().text().trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fetch user avatar image source
 | 
					        // Fetch user avatar image source
 | 
				
			||||||
    const source = body.find(f95Selector.UD_AVATAR_PIC).first().attr("src");
 | 
					        const source = body.find(f95Selector.UD_AVATAR_PIC).first().attr("src");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					        return {
 | 
				
			||||||
        username,
 | 
					            username,
 | 
				
			||||||
        source
 | 
					            source
 | 
				
			||||||
    };
 | 
					        };
 | 
				
			||||||
 | 
					    } else throw html.value;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -73,16 +75,18 @@ async function fetchWatchedGameThreadURLs(): Promise<string[]> {
 | 
				
			||||||
        // Fetch page
 | 
					        // Fetch page
 | 
				
			||||||
        const html = await fetchHTML(currentURL);
 | 
					        const html = await fetchHTML(currentURL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Load HTML response
 | 
					        if (html.isSuccess()) {
 | 
				
			||||||
        const $ = cheerio.load(html);
 | 
					            // Load HTML response
 | 
				
			||||||
        const body = $("body");
 | 
					            const $ = cheerio.load(html.value);
 | 
				
			||||||
 | 
					            const body = $("body");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Find the URLs
 | 
					            // Find the URLs
 | 
				
			||||||
        const urls = fetchPageURLs(body);
 | 
					            const urls = fetchPageURLs(body);
 | 
				
			||||||
        watchedGameThreadURLs.push(...urls);
 | 
					            watchedGameThreadURLs.push(...urls);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Find the next page (if any)
 | 
					            // Find the next page (if any)
 | 
				
			||||||
        currentURL = fetchNextPageURL(body);
 | 
					            currentURL = fetchNextPageURL(body);
 | 
				
			||||||
 | 
					        } else throw html.value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    while (currentURL);
 | 
					    while (currentURL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue