Implement class
							parent
							
								
									bd967d5a8c
								
							
						
					
					
						commit
						e88c808986
					
				| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import cheerio from "cheerio";
 | 
			
		||||
 | 
			
		||||
// Modules from files
 | 
			
		||||
import Post from "./post.js";
 | 
			
		||||
import PlatformUser from "./platform-user.js";
 | 
			
		||||
import { urls } from "../../constants/url.js";
 | 
			
		||||
import { WATCHED_THREAD } from "../../constants/css-selector.js";
 | 
			
		||||
import { fetchHTML } from "../../network-helper.js";
 | 
			
		||||
import { GenericAxiosError, UnexpectedResponseContentType } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
 | 
			
		||||
// Interfaces
 | 
			
		||||
interface IWatchedThread {
 | 
			
		||||
    /**
 | 
			
		||||
     * URL of the thread
 | 
			
		||||
     */
 | 
			
		||||
    url: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates whether the thread has any unread posts.
 | 
			
		||||
     */
 | 
			
		||||
    unread: boolean,
 | 
			
		||||
    /**
 | 
			
		||||
     * Specifies the forum to which the thread belongs.
 | 
			
		||||
     */
 | 
			
		||||
    forum: string,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Types
 | 
			
		||||
type TFetchResult = Result<GenericAxiosError | UnexpectedResponseContentType, string>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class containing the data of the user currently connected to the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class UserProfile extends PlatformUser {
 | 
			
		||||
 | 
			
		||||
    //#region Fields
 | 
			
		||||
 | 
			
		||||
    private _watched: IWatchedThread[] = [];
 | 
			
		||||
    private _bookmarks: Post[] = [];
 | 
			
		||||
    private _alerts: string[] = [];
 | 
			
		||||
    private _conversations: string[];
 | 
			
		||||
 | 
			
		||||
    //#endregion Fields
 | 
			
		||||
    
 | 
			
		||||
    //#region Getters
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * List of followed thread data.
 | 
			
		||||
     */
 | 
			
		||||
    public get watched() { return this._watched; }
 | 
			
		||||
    /**
 | 
			
		||||
     * List of bookmarked posts.
 | 
			
		||||
     * @todo
 | 
			
		||||
     */
 | 
			
		||||
    public get bookmarks() { return this._bookmarks; }
 | 
			
		||||
    /**
 | 
			
		||||
     * List of alerts.
 | 
			
		||||
     * @todo
 | 
			
		||||
     */
 | 
			
		||||
    public get alerts() { return this._alerts; }
 | 
			
		||||
    /**
 | 
			
		||||
     * List of conversations.
 | 
			
		||||
     * @todo
 | 
			
		||||
     */
 | 
			
		||||
    public get conversation() { return this._conversations; }
 | 
			
		||||
 | 
			
		||||
    //#endregion Getters
 | 
			
		||||
 | 
			
		||||
    //#region Public methods
 | 
			
		||||
 | 
			
		||||
    public async fetch() {
 | 
			
		||||
        // First fetch the basic data
 | 
			
		||||
        await super.fetch();
 | 
			
		||||
 | 
			
		||||
        // Now fetch the watched threads
 | 
			
		||||
        this._watched = await this.fetchWatchedThread();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //#endregion Public methods
 | 
			
		||||
 | 
			
		||||
    //#region Private methods
 | 
			
		||||
 | 
			
		||||
    private async fetchWatchedThread(): Promise<IWatchedThread[]> {
 | 
			
		||||
        // Prepare and fetch URL
 | 
			
		||||
        const url = new URL(urls.F95_WATCHED_THREADS);
 | 
			
		||||
        url.searchParams.set("unread", "0");
 | 
			
		||||
 | 
			
		||||
        const htmlResponse = await fetchHTML(url.toString());
 | 
			
		||||
 | 
			
		||||
        if (htmlResponse.isSuccess()) {
 | 
			
		||||
            // Load page in cheerio
 | 
			
		||||
            const $ = cheerio.load(htmlResponse.value);
 | 
			
		||||
 | 
			
		||||
            // Fetch the pages
 | 
			
		||||
            const lastPage = parseInt($(WATCHED_THREAD.LAST_PAGE).text().trim());
 | 
			
		||||
            const pages = await this.fetchPages(url, lastPage);
 | 
			
		||||
            
 | 
			
		||||
            const watchedThreads = pages.map((r, idx) => {
 | 
			
		||||
                const elements = r.applyOnSuccess(this.fetchPageThreadElements);
 | 
			
		||||
                if (elements.isSuccess()) return elements.value;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return [].concat(...watchedThreads);
 | 
			
		||||
        } else throw htmlResponse.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the pages containing the thread data.
 | 
			
		||||
     * @param url Base URL to use for scraping a page
 | 
			
		||||
     * @param n Total number of pages
 | 
			
		||||
     * @param s Page to start from
 | 
			
		||||
     */
 | 
			
		||||
    private async fetchPages(url: URL, n: number, s: number = 1): Promise<TFetchResult[]> {
 | 
			
		||||
        // Local variables
 | 
			
		||||
        const responsePromiseList: Promise<TFetchResult>[] = [];
 | 
			
		||||
 | 
			
		||||
        // Fetch the page' HTML
 | 
			
		||||
        for (let page = s; page <= n; page++) {
 | 
			
		||||
            // Set the page URL
 | 
			
		||||
            url.searchParams.set("page", page.toString());
 | 
			
		||||
 | 
			
		||||
            // Fetch HTML but not wait for it
 | 
			
		||||
            const promise = fetchHTML(url.toString())
 | 
			
		||||
            responsePromiseList.push(promise);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Wait for the promises to resolve
 | 
			
		||||
        return await Promise.all(responsePromiseList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets thread data starting from the source code of the page passed by parameter.
 | 
			
		||||
     */
 | 
			
		||||
    private fetchPageThreadElements(html: string): IWatchedThread[] {
 | 
			
		||||
        // Local variables
 | 
			
		||||
        const $ = cheerio.load(html);
 | 
			
		||||
 | 
			
		||||
        return $(WATCHED_THREAD.BODIES).map((idx, el) => {
 | 
			
		||||
            // Parse the URL
 | 
			
		||||
            const partialURL = $(el).find(WATCHED_THREAD.URL).attr("href");
 | 
			
		||||
            const url = new URL(partialURL.replace("unread", ""), `${urls.F95_BASE_URL}`).toString();
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                url: url.toString(),
 | 
			
		||||
                unread: partialURL.endsWith("unread"),
 | 
			
		||||
                forum: $(el).find(WATCHED_THREAD.FORUM).text().trim()
 | 
			
		||||
            } as IWatchedThread;
 | 
			
		||||
        }).get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //#endregion Private methods
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue