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