commit
						5ea465f940
					
				| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
tests
 | 
			
		||||
test
 | 
			
		||||
coverage.lcov
 | 
			
		||||
tsconfig.json
 | 
			
		||||
.dist
 | 
			
		||||
.nyc_output
 | 
			
		||||
.eslintignore
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,84 +0,0 @@
 | 
			
		|||
import { IBasic } from "./scripts/interfaces.js";
 | 
			
		||||
import LoginResult from "./scripts/classes/login-result.js";
 | 
			
		||||
import UserProfile from "./scripts/classes/mapping/user-profile.js";
 | 
			
		||||
import LatestSearchQuery from "./scripts/classes/query/latest-search-query.js";
 | 
			
		||||
import HandiworkSearchQuery from "./scripts/classes/query/handiwork-search-query.js";
 | 
			
		||||
import HandiWork from "./scripts/classes/handiwork/handiwork.js";
 | 
			
		||||
export { default as Animation } from "./scripts/classes/handiwork/animation.js";
 | 
			
		||||
export { default as Asset } from "./scripts/classes/handiwork/asset.js";
 | 
			
		||||
export { default as Comic } from "./scripts/classes/handiwork/comic.js";
 | 
			
		||||
export { default as Game } from "./scripts/classes/handiwork/game.js";
 | 
			
		||||
export { default as Handiwork } from "./scripts/classes/handiwork/handiwork.js";
 | 
			
		||||
export { default as PlatformUser } from "./scripts/classes/mapping/platform-user.js";
 | 
			
		||||
export { default as Post } from "./scripts/classes/mapping/post.js";
 | 
			
		||||
export { default as Thread } from "./scripts/classes/mapping/thread.js";
 | 
			
		||||
export { default as UserProfile } from "./scripts/classes/mapping/user-profile.js";
 | 
			
		||||
export { default as HandiworkSearchQuery } from "./scripts/classes/query/handiwork-search-query.js";
 | 
			
		||||
export { default as LatestSearchQuery } from "./scripts/classes/query/latest-search-query.js";
 | 
			
		||||
export { default as ThreadSearchQuery } from "./scripts/classes/query/thread-search-query.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Set the logger level for module debugging.
 | 
			
		||||
 */
 | 
			
		||||
export declare let loggerLevel: string;
 | 
			
		||||
/**
 | 
			
		||||
 * Indicates whether a user is logged in to the F95Zone platform or not.
 | 
			
		||||
 */
 | 
			
		||||
export declare function isLogged(): boolean;
 | 
			
		||||
/**
 | 
			
		||||
 * Log in to the F95Zone platform.
 | 
			
		||||
 *
 | 
			
		||||
 * This **must** be the first operation performed before accessing any other script functions.
 | 
			
		||||
 *
 | 
			
		||||
 * @param cb2fa
 | 
			
		||||
 * Callback used if two-factor authentication is required for the profile.
 | 
			
		||||
 * It must return he OTP code to use for the login.
 | 
			
		||||
 */
 | 
			
		||||
export declare function login(
 | 
			
		||||
  username: string,
 | 
			
		||||
  password: string,
 | 
			
		||||
  cb2fa?: () => Promise<number>
 | 
			
		||||
): Promise<LoginResult>;
 | 
			
		||||
/**
 | 
			
		||||
 * Chek if exists a new version of the handiwork.
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 */
 | 
			
		||||
export declare 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
 | 
			
		||||
 */
 | 
			
		||||
export declare function searchHandiwork<T extends IBasic>(
 | 
			
		||||
  query: HandiworkSearchQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<T[]>;
 | 
			
		||||
/**
 | 
			
		||||
 * 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 declare function getHandiworkFromURL<T extends IBasic>(url: string): Promise<T>;
 | 
			
		||||
/**
 | 
			
		||||
 * 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 declare 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
 | 
			
		||||
 */
 | 
			
		||||
export declare function getLatestUpdates<T extends IBasic>(
 | 
			
		||||
  query: LatestSearchQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<T[]>;
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										24
									
								
								package.json
								
								
								
								
							
							
						
						
									
										24
									
								
								package.json
								
								
								
								
							| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
{
 | 
			
		||||
  "main": "./app/index.js",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "main": "./src/index.ts",
 | 
			
		||||
  "name": "f95api",
 | 
			
		||||
  "version": "1.9.9",
 | 
			
		||||
  "author": "Millennium Earl",
 | 
			
		||||
  "description": "Unofficial Node JS module for scraping F95Zone platform",
 | 
			
		||||
  "types": "dist/index.d.ts",
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "git+https://github.com/MillenniumEarl/F95API.git"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,11 +24,12 @@
 | 
			
		|||
  ],
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "lint": "eslint . --ext .ts",
 | 
			
		||||
    "prettier-format": "prettier --config .prettierrc '**/*.ts' --write",
 | 
			
		||||
    "ts:compile": "tsc",
 | 
			
		||||
    "ts:definitions": "tsc --declaration --outDir . --emitDeclarationOnly",
 | 
			
		||||
    "prettier-format": "prettier --config .prettierrc '{src,test}/**/*.ts' --write",
 | 
			
		||||
    "compile": "tsc",
 | 
			
		||||
    "test": "nyc --reporter=text mocha --require ts-node/register test/index.ts",
 | 
			
		||||
    "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov -t 38ad72bf-a29d-4c2e-9827-96cbe037afd2",
 | 
			
		||||
    "publish": "npm publish",
 | 
			
		||||
    "run-example": "npm run ts:compile && node ./dist/example.js"
 | 
			
		||||
    "run-example": "npm run compile && node ./dist/example.js"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">=10.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -45,16 +46,17 @@
 | 
			
		|||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/chai": "^4.2.15",
 | 
			
		||||
    "@types/expect": "^24.3.0",
 | 
			
		||||
    "@types/chai-as-promised": "^7.1.3",
 | 
			
		||||
    "@types/inquirer": "^7.3.1",
 | 
			
		||||
    "@types/lodash": "^4.14.168",
 | 
			
		||||
    "@types/luxon": "^1.25.2",
 | 
			
		||||
    "@types/mocha": "^8.2.0",
 | 
			
		||||
    "@types/mocha": "^8.2.1",
 | 
			
		||||
    "@types/node": "^14.14.27",
 | 
			
		||||
    "@types/tough-cookie": "^4.0.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^4.15.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^4.15.0",
 | 
			
		||||
    "chai": "^4.3.3",
 | 
			
		||||
    "chai-as-promised": "^7.1.1",
 | 
			
		||||
    "dotenv": "^8.2.0",
 | 
			
		||||
    "eslint": "^7.21.0",
 | 
			
		||||
    "eslint-config-prettier": "^8.1.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -62,13 +64,11 @@
 | 
			
		|||
    "husky": "^5.1.3",
 | 
			
		||||
    "inquirer": "^8.0.0",
 | 
			
		||||
    "lint-staged": "^10.5.4",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mocha": "^8.3.0",
 | 
			
		||||
    "mocha": "^8.3.1",
 | 
			
		||||
    "nyc": "^15.1.0",
 | 
			
		||||
    "prettier": "^2.2.1",
 | 
			
		||||
    "ts-mocha": "^8.0.0",
 | 
			
		||||
    "ts-node": "^9.1.1",
 | 
			
		||||
    "typescript": "^4.2.0-insiders.20210210"
 | 
			
		||||
    "typescript": "^4.2.3"
 | 
			
		||||
  },
 | 
			
		||||
  "husky": {
 | 
			
		||||
    "hooks": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Represents the credentials used to access the platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class Credentials {
 | 
			
		||||
  /**
 | 
			
		||||
   * Username
 | 
			
		||||
   */
 | 
			
		||||
  username: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Password of the user.
 | 
			
		||||
   */
 | 
			
		||||
  password: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * One time token used during login.
 | 
			
		||||
   */
 | 
			
		||||
  token: string;
 | 
			
		||||
  constructor(username: string, password: string);
 | 
			
		||||
  /**
 | 
			
		||||
   * Fetch and save the token used to log in to F95Zone.
 | 
			
		||||
   */
 | 
			
		||||
  fetchToken(): Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +0,0 @@
 | 
			
		|||
interface IBaseError {
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique identifier of the error.
 | 
			
		||||
   */
 | 
			
		||||
  id: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Error message.
 | 
			
		||||
   */
 | 
			
		||||
  message: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Error to report.
 | 
			
		||||
   */
 | 
			
		||||
  error: Error;
 | 
			
		||||
}
 | 
			
		||||
export declare class GenericAxiosError extends Error implements IBaseError {
 | 
			
		||||
  id: number;
 | 
			
		||||
  message: string;
 | 
			
		||||
  error: Error;
 | 
			
		||||
  constructor(args: IBaseError);
 | 
			
		||||
}
 | 
			
		||||
export declare class UnexpectedResponseContentType extends Error implements IBaseError {
 | 
			
		||||
  id: number;
 | 
			
		||||
  message: string;
 | 
			
		||||
  error: Error;
 | 
			
		||||
  constructor(args: IBaseError);
 | 
			
		||||
}
 | 
			
		||||
export declare class InvalidF95Token extends Error {}
 | 
			
		||||
export declare class UserNotLogged extends Error {}
 | 
			
		||||
export declare class ParameterError extends Error {}
 | 
			
		||||
export {};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
import { TAuthor, IAnimation, TRating, TCategory } from "../../interfaces";
 | 
			
		||||
export default class Animation implements IAnimation {
 | 
			
		||||
  censored: boolean;
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  installation: string;
 | 
			
		||||
  language: string[];
 | 
			
		||||
  lenght: string;
 | 
			
		||||
  pages: string;
 | 
			
		||||
  resolution: string[];
 | 
			
		||||
  authors: TAuthor[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  changelog: string[];
 | 
			
		||||
  cover: string;
 | 
			
		||||
  id: number;
 | 
			
		||||
  lastThreadUpdate: Date;
 | 
			
		||||
  name: string;
 | 
			
		||||
  overview: string;
 | 
			
		||||
  prefixes: string[];
 | 
			
		||||
  rating: TRating;
 | 
			
		||||
  tags: string[];
 | 
			
		||||
  threadPublishingDate: Date;
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
import { TAuthor, IAsset, TRating, TCategory } from "../../interfaces";
 | 
			
		||||
export default class Asset implements IAsset {
 | 
			
		||||
  assetLink: string;
 | 
			
		||||
  associatedAssets: string[];
 | 
			
		||||
  compatibleSoftware: string;
 | 
			
		||||
  includedAssets: string[];
 | 
			
		||||
  officialLinks: string[];
 | 
			
		||||
  sku: string;
 | 
			
		||||
  authors: TAuthor[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  changelog: string[];
 | 
			
		||||
  cover: string;
 | 
			
		||||
  id: number;
 | 
			
		||||
  lastThreadUpdate: Date;
 | 
			
		||||
  name: string;
 | 
			
		||||
  overview: string;
 | 
			
		||||
  prefixes: string[];
 | 
			
		||||
  rating: TRating;
 | 
			
		||||
  tags: string[];
 | 
			
		||||
  threadPublishingDate: Date;
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,19 +0,0 @@
 | 
			
		|||
import { TAuthor, IComic, TRating, TCategory } from "../../interfaces";
 | 
			
		||||
export default class Comic implements IComic {
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  pages: string;
 | 
			
		||||
  resolution: string[];
 | 
			
		||||
  authors: TAuthor[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  changelog: string[];
 | 
			
		||||
  cover: string;
 | 
			
		||||
  id: number;
 | 
			
		||||
  lastThreadUpdate: Date;
 | 
			
		||||
  name: string;
 | 
			
		||||
  overview: string;
 | 
			
		||||
  prefixes: string[];
 | 
			
		||||
  rating: TRating;
 | 
			
		||||
  tags: string[];
 | 
			
		||||
  threadPublishingDate: Date;
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,26 +0,0 @@
 | 
			
		|||
import { TAuthor, TEngine, IGame, TRating, TStatus, TCategory } from "../../interfaces";
 | 
			
		||||
export default class Game implements IGame {
 | 
			
		||||
  censored: boolean;
 | 
			
		||||
  engine: TEngine;
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  installation: string;
 | 
			
		||||
  language: string[];
 | 
			
		||||
  lastRelease: Date;
 | 
			
		||||
  mod: boolean;
 | 
			
		||||
  os: string[];
 | 
			
		||||
  status: TStatus;
 | 
			
		||||
  version: string;
 | 
			
		||||
  authors: TAuthor[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  changelog: string[];
 | 
			
		||||
  cover: string;
 | 
			
		||||
  id: number;
 | 
			
		||||
  lastThreadUpdate: Date;
 | 
			
		||||
  name: string;
 | 
			
		||||
  overview: string;
 | 
			
		||||
  prefixes: string[];
 | 
			
		||||
  rating: TRating;
 | 
			
		||||
  tags: string[];
 | 
			
		||||
  threadPublishingDate: Date;
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,45 +0,0 @@
 | 
			
		|||
import {
 | 
			
		||||
  TAuthor,
 | 
			
		||||
  TRating,
 | 
			
		||||
  IHandiwork,
 | 
			
		||||
  TEngine,
 | 
			
		||||
  TCategory,
 | 
			
		||||
  TStatus
 | 
			
		||||
} from "../../interfaces";
 | 
			
		||||
/**
 | 
			
		||||
 * It represents a generic work, be it a game, a comic, an animation or an asset.
 | 
			
		||||
 */
 | 
			
		||||
export default class HandiWork implements IHandiwork {
 | 
			
		||||
  censored: boolean;
 | 
			
		||||
  engine: TEngine;
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  installation: string;
 | 
			
		||||
  language: string[];
 | 
			
		||||
  lastRelease: Date;
 | 
			
		||||
  mod: boolean;
 | 
			
		||||
  os: string[];
 | 
			
		||||
  status: TStatus;
 | 
			
		||||
  version: string;
 | 
			
		||||
  authors: TAuthor[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  changelog: string[];
 | 
			
		||||
  cover: string;
 | 
			
		||||
  id: number;
 | 
			
		||||
  lastThreadUpdate: Date;
 | 
			
		||||
  name: string;
 | 
			
		||||
  overview: string;
 | 
			
		||||
  prefixes: string[];
 | 
			
		||||
  rating: TRating;
 | 
			
		||||
  tags: string[];
 | 
			
		||||
  threadPublishingDate: Date;
 | 
			
		||||
  url: string;
 | 
			
		||||
  pages: string;
 | 
			
		||||
  resolution: string[];
 | 
			
		||||
  lenght: string;
 | 
			
		||||
  assetLink: string;
 | 
			
		||||
  associatedAssets: string[];
 | 
			
		||||
  compatibleSoftware: string;
 | 
			
		||||
  includedAssets: string[];
 | 
			
		||||
  officialLinks: string[];
 | 
			
		||||
  sku: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Object obtained in response to an attempt to login to the portal.
 | 
			
		||||
 */
 | 
			
		||||
export default class LoginResult {
 | 
			
		||||
  static REQUIRE_2FA: number;
 | 
			
		||||
  static AUTH_SUCCESSFUL: number;
 | 
			
		||||
  static AUTH_SUCCESSFUL_2FA: number;
 | 
			
		||||
  static ALREADY_AUTHENTICATED: number;
 | 
			
		||||
  static UNKNOWN_ERROR: number;
 | 
			
		||||
  static INCORRECT_CREDENTIALS: number;
 | 
			
		||||
  static INCORRECT_2FA_CODE: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Result of the login operation
 | 
			
		||||
   */
 | 
			
		||||
  readonly success: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Code associated with the result of the login operation.
 | 
			
		||||
   */
 | 
			
		||||
  readonly code: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Login response message
 | 
			
		||||
   */
 | 
			
		||||
  readonly message: string;
 | 
			
		||||
  constructor(success: boolean, code: number, message?: string);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,83 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Represents a generic user registered on the platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class PlatformUser {
 | 
			
		||||
  private _id;
 | 
			
		||||
  private _name;
 | 
			
		||||
  private _title;
 | 
			
		||||
  private _banners;
 | 
			
		||||
  private _messages;
 | 
			
		||||
  private _reactionScore;
 | 
			
		||||
  private _points;
 | 
			
		||||
  private _ratingsReceived;
 | 
			
		||||
  private _joined;
 | 
			
		||||
  private _lastSeen;
 | 
			
		||||
  private _followed;
 | 
			
		||||
  private _ignored;
 | 
			
		||||
  private _private;
 | 
			
		||||
  private _avatar;
 | 
			
		||||
  private _amountDonated;
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique user ID.
 | 
			
		||||
   */
 | 
			
		||||
  get id(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Username.
 | 
			
		||||
   */
 | 
			
		||||
  get name(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Title assigned to the user by the platform.
 | 
			
		||||
   */
 | 
			
		||||
  get title(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of banners assigned by the platform.
 | 
			
		||||
   */
 | 
			
		||||
  get banners(): string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of messages written by the user.
 | 
			
		||||
   */
 | 
			
		||||
  get messages(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * @todo Reaction score.
 | 
			
		||||
   */
 | 
			
		||||
  get reactionScore(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * @todo Points.
 | 
			
		||||
   */
 | 
			
		||||
  get points(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of ratings received.
 | 
			
		||||
   */
 | 
			
		||||
  get ratingsReceived(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date of joining the platform.
 | 
			
		||||
   */
 | 
			
		||||
  get joined(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date of the last connection to the platform.
 | 
			
		||||
   */
 | 
			
		||||
  get lastSeen(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates whether the user is followed by the currently logged in user.
 | 
			
		||||
   */
 | 
			
		||||
  get followed(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates whether the user is ignored by the currently logged on user.
 | 
			
		||||
   */
 | 
			
		||||
  get ignored(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates that the profile is private and not viewable by the user.
 | 
			
		||||
   */
 | 
			
		||||
  get private(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * URL of the image used as the user's avatar.
 | 
			
		||||
   */
 | 
			
		||||
  get avatar(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Value of donations made.
 | 
			
		||||
   */
 | 
			
		||||
  get donation(): number;
 | 
			
		||||
  constructor(id?: number);
 | 
			
		||||
  setID(id: number): void;
 | 
			
		||||
  fetch(): Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,53 +0,0 @@
 | 
			
		|||
import PlatformUser from "./platform-user.js";
 | 
			
		||||
import { IPostElement } from "../../scrape-data/post-parse.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a post published by a user on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class Post {
 | 
			
		||||
  private _id;
 | 
			
		||||
  private _number;
 | 
			
		||||
  private _published;
 | 
			
		||||
  private _lastEdit;
 | 
			
		||||
  private _owner;
 | 
			
		||||
  private _bookmarked;
 | 
			
		||||
  private _message;
 | 
			
		||||
  private _body;
 | 
			
		||||
  /**
 | 
			
		||||
   * Represents a post published by a user on the F95Zone platform.
 | 
			
		||||
   */
 | 
			
		||||
  get id(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique ID of the post within the thread in which it is present.
 | 
			
		||||
   */
 | 
			
		||||
  get number(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date the post was first published.
 | 
			
		||||
   */
 | 
			
		||||
  get published(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date the post was last modified.
 | 
			
		||||
   */
 | 
			
		||||
  get lastEdit(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * User who owns the post.
 | 
			
		||||
   */
 | 
			
		||||
  get owner(): PlatformUser;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates whether the post has been bookmarked.
 | 
			
		||||
   */
 | 
			
		||||
  get bookmarked(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Post message text.
 | 
			
		||||
   */
 | 
			
		||||
  get message(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Set of the elements that make up the body of the post.
 | 
			
		||||
   */
 | 
			
		||||
  get body(): IPostElement[];
 | 
			
		||||
  constructor(id: number);
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the post data starting from its unique ID for the entire platform.
 | 
			
		||||
   */
 | 
			
		||||
  fetch(): Promise<void>;
 | 
			
		||||
  private parsePost;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,101 +0,0 @@
 | 
			
		|||
import Post from "./post.js";
 | 
			
		||||
import PlatformUser from "./platform-user.js";
 | 
			
		||||
import { TCategory, TRating } from "../../interfaces.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a generic F95Zone platform thread.
 | 
			
		||||
 */
 | 
			
		||||
export default class Thread {
 | 
			
		||||
  private POST_FOR_PAGE;
 | 
			
		||||
  private _id;
 | 
			
		||||
  private _url;
 | 
			
		||||
  private _title;
 | 
			
		||||
  private _tags;
 | 
			
		||||
  private _prefixes;
 | 
			
		||||
  private _rating;
 | 
			
		||||
  private _owner;
 | 
			
		||||
  private _publication;
 | 
			
		||||
  private _modified;
 | 
			
		||||
  private _category;
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique ID of the thread on the platform.
 | 
			
		||||
   */
 | 
			
		||||
  get id(): number;
 | 
			
		||||
  /**
 | 
			
		||||
   * URL of the thread.
 | 
			
		||||
   *
 | 
			
		||||
   * It may vary depending on any versions of the contained product.
 | 
			
		||||
   */
 | 
			
		||||
  get url(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Thread title.
 | 
			
		||||
   */
 | 
			
		||||
  get title(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Tags associated with the thread.
 | 
			
		||||
   */
 | 
			
		||||
  get tags(): string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Prefixes associated with the thread
 | 
			
		||||
   */
 | 
			
		||||
  get prefixes(): string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Rating assigned to the thread.
 | 
			
		||||
   */
 | 
			
		||||
  get rating(): TRating;
 | 
			
		||||
  /**
 | 
			
		||||
   * Owner of the thread.
 | 
			
		||||
   */
 | 
			
		||||
  get owner(): PlatformUser;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date the thread was first published.
 | 
			
		||||
   */
 | 
			
		||||
  get publication(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date the thread was last modified.
 | 
			
		||||
   */
 | 
			
		||||
  get modified(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Category to which the content of the thread belongs.
 | 
			
		||||
   */
 | 
			
		||||
  get category(): TCategory;
 | 
			
		||||
  /**
 | 
			
		||||
   * Initializes an object for mapping a thread.
 | 
			
		||||
   *
 | 
			
		||||
   * The unique ID of the thread must be specified.
 | 
			
		||||
   */
 | 
			
		||||
  constructor(id: number);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the number of posts to display for the current thread.
 | 
			
		||||
   */
 | 
			
		||||
  private setMaximumPostsForPage;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets all posts on a page.
 | 
			
		||||
   */
 | 
			
		||||
  private parsePostsInPage;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets all posts in the thread.
 | 
			
		||||
   */
 | 
			
		||||
  private fetchPosts;
 | 
			
		||||
  /**
 | 
			
		||||
   * It processes the rating of the thread
 | 
			
		||||
   * starting from the data contained in the JSON+LD tag.
 | 
			
		||||
   */
 | 
			
		||||
  private parseRating;
 | 
			
		||||
  /**
 | 
			
		||||
   * Clean the title of a thread, removing prefixes
 | 
			
		||||
   * and generic elements between square brackets, and
 | 
			
		||||
   * returns the clean title of the work.
 | 
			
		||||
   */
 | 
			
		||||
  private cleanHeadline;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets information about this thread.
 | 
			
		||||
   */
 | 
			
		||||
  fetch(): Promise<void>;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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.
 | 
			
		||||
   */
 | 
			
		||||
  getPost(index: number): Promise<Post | null>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,60 +0,0 @@
 | 
			
		|||
import Post from "./post.js";
 | 
			
		||||
import PlatformUser from "./platform-user.js";
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Class containing the data of the user currently connected to the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class UserProfile extends PlatformUser {
 | 
			
		||||
  private _watched;
 | 
			
		||||
  private _bookmarks;
 | 
			
		||||
  private _alerts;
 | 
			
		||||
  private _conversations;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of followed thread data.
 | 
			
		||||
   */
 | 
			
		||||
  get watched(): IWatchedThread[];
 | 
			
		||||
  /**
 | 
			
		||||
   * List of bookmarked posts.
 | 
			
		||||
   * @todo
 | 
			
		||||
   */
 | 
			
		||||
  get bookmarks(): Post[];
 | 
			
		||||
  /**
 | 
			
		||||
   * List of alerts.
 | 
			
		||||
   * @todo
 | 
			
		||||
   */
 | 
			
		||||
  get alerts(): string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * List of conversations.
 | 
			
		||||
   * @todo
 | 
			
		||||
   */
 | 
			
		||||
  get conversation(): string[];
 | 
			
		||||
  constructor();
 | 
			
		||||
  fetch(): Promise<void>;
 | 
			
		||||
  private fetchUserID;
 | 
			
		||||
  private fetchWatchedThread;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 fetchPages;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets thread data starting from the source code of the page passed by parameter.
 | 
			
		||||
   */
 | 
			
		||||
  private fetchPageThreadElements;
 | 
			
		||||
}
 | 
			
		||||
export {};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,34 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Convert prefixes and platform tags from string to ID and vice versa.
 | 
			
		||||
 */
 | 
			
		||||
export default class PrefixParser {
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the key associated with a given value from a dictionary.
 | 
			
		||||
   * @param {Object} object Dictionary to search
 | 
			
		||||
   * @param {Any} value Value associated with the key
 | 
			
		||||
   * @returns {String|undefined} Key found or undefined
 | 
			
		||||
   */
 | 
			
		||||
  private getKeyByValue;
 | 
			
		||||
  /**
 | 
			
		||||
   * Makes an array of strings uppercase.
 | 
			
		||||
   */
 | 
			
		||||
  private toUpperCaseArray;
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if `dict` contains `value` as a value.
 | 
			
		||||
   */
 | 
			
		||||
  private valueInDict;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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
 | 
			
		||||
   */
 | 
			
		||||
  private searchElementInPrefixes;
 | 
			
		||||
  /**
 | 
			
		||||
   * Convert a list of prefixes to their respective IDs.
 | 
			
		||||
   */
 | 
			
		||||
  prefixesToIDs(prefixes: string[]): number[];
 | 
			
		||||
  /**
 | 
			
		||||
   * It converts a list of IDs into their respective prefixes.
 | 
			
		||||
   */
 | 
			
		||||
  idsToPrefixes(ids: number[]): string[];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,70 +0,0 @@
 | 
			
		|||
import { AxiosResponse } from "axios";
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
			
		||||
import { GenericAxiosError } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
/**
 | 
			
		||||
 * 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`.
 | 
			
		||||
 */
 | 
			
		||||
declare type THandiworkOrder =
 | 
			
		||||
  | "date"
 | 
			
		||||
  | "likes"
 | 
			
		||||
  | "relevance"
 | 
			
		||||
  | "replies"
 | 
			
		||||
  | "title"
 | 
			
		||||
  | "views";
 | 
			
		||||
declare type TExecuteResult = Result<GenericAxiosError, AxiosResponse<any>>;
 | 
			
		||||
export default class HandiworkSearchQuery implements IQuery {
 | 
			
		||||
  static MIN_PAGE: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Keywords to use in the search.
 | 
			
		||||
   */
 | 
			
		||||
  keywords: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * The results must be more recent than the date indicated.
 | 
			
		||||
   */
 | 
			
		||||
  newerThan: Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * The results must be older than the date indicated.
 | 
			
		||||
   */
 | 
			
		||||
  olderThan: Date;
 | 
			
		||||
  includedTags: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Tags to exclude from the search.
 | 
			
		||||
   */
 | 
			
		||||
  excludedTags: string[];
 | 
			
		||||
  includedPrefixes: string[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  /**
 | 
			
		||||
   * Results presentation order.
 | 
			
		||||
   */
 | 
			
		||||
  order: THandiworkOrder;
 | 
			
		||||
  page: number;
 | 
			
		||||
  itype: TQueryInterface;
 | 
			
		||||
  /**
 | 
			
		||||
   * Select what kind of search should be
 | 
			
		||||
   * performed based on the properties of
 | 
			
		||||
   * the query.
 | 
			
		||||
   */
 | 
			
		||||
  selectSearchType(): "latest" | "thread";
 | 
			
		||||
  validate(): boolean;
 | 
			
		||||
  execute(): Promise<TExecuteResult>;
 | 
			
		||||
  cast<T extends IQuery>(type: TQueryInterface): T;
 | 
			
		||||
  private castToLatest;
 | 
			
		||||
  private castToThread;
 | 
			
		||||
}
 | 
			
		||||
export {};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,46 +0,0 @@
 | 
			
		|||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
			
		||||
import { AxiosResponse } from "axios";
 | 
			
		||||
import { GenericAxiosError } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
export declare type TLatestOrder = "date" | "likes" | "views" | "title" | "rating";
 | 
			
		||||
declare type TDate = 365 | 180 | 90 | 30 | 14 | 7 | 3 | 1;
 | 
			
		||||
/**
 | 
			
		||||
 * Query used to search handiwork in the "Latest" tab.
 | 
			
		||||
 */
 | 
			
		||||
export default class LatestSearchQuery implements IQuery {
 | 
			
		||||
  private static MAX_TAGS;
 | 
			
		||||
  private static MIN_PAGE;
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  /**
 | 
			
		||||
   * Ordering type.
 | 
			
		||||
   *
 | 
			
		||||
   * Default: `date`.
 | 
			
		||||
   */
 | 
			
		||||
  order: TLatestOrder;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date limit in days, to be understood as "less than".
 | 
			
		||||
   * Use `1` to indicate "today" or `null` to indicate "anytime".
 | 
			
		||||
   *
 | 
			
		||||
   * Default: `null`
 | 
			
		||||
   */
 | 
			
		||||
  date: TDate;
 | 
			
		||||
  includedTags: string[];
 | 
			
		||||
  includedPrefixes: string[];
 | 
			
		||||
  page: number;
 | 
			
		||||
  itype: TQueryInterface;
 | 
			
		||||
  validate(): boolean;
 | 
			
		||||
  execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>>;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the value (in days) acceptable in the query starting from a generic date.
 | 
			
		||||
   */
 | 
			
		||||
  findNearestDate(d: Date): TDate;
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepare the URL by filling out the GET parameters with the data in the query.
 | 
			
		||||
   */
 | 
			
		||||
  private prepareGETurl;
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  private dateDiffInDays;
 | 
			
		||||
}
 | 
			
		||||
export {};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,55 +0,0 @@
 | 
			
		|||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
			
		||||
import { AxiosResponse } from "axios";
 | 
			
		||||
import { GenericAxiosError } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
export declare type TThreadOrder = "relevance" | "date" | "last_update" | "replies";
 | 
			
		||||
export default class ThreadSearchQuery implements IQuery {
 | 
			
		||||
  static MIN_PAGE: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Keywords to use in the search.
 | 
			
		||||
   */
 | 
			
		||||
  keywords: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates to search by checking only the thread titles and not the content.
 | 
			
		||||
   */
 | 
			
		||||
  onlyTitles: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * The results must be more recent than the date indicated.
 | 
			
		||||
   */
 | 
			
		||||
  newerThan: Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * The results must be older than the date indicated.
 | 
			
		||||
   */
 | 
			
		||||
  olderThan: Date;
 | 
			
		||||
  includedTags: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Tags to exclude from the search.
 | 
			
		||||
   */
 | 
			
		||||
  excludedTags: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Minimum number of answers that the thread must possess.
 | 
			
		||||
   */
 | 
			
		||||
  minimumReplies: number;
 | 
			
		||||
  includedPrefixes: string[];
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  /**
 | 
			
		||||
   * Results presentation order.
 | 
			
		||||
   */
 | 
			
		||||
  order: TThreadOrder;
 | 
			
		||||
  page: number;
 | 
			
		||||
  itype: TQueryInterface;
 | 
			
		||||
  validate(): boolean;
 | 
			
		||||
  execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>>;
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepare the parameters for post request with the data in the query.
 | 
			
		||||
   */
 | 
			
		||||
  private preparePOSTParameters;
 | 
			
		||||
  /**
 | 
			
		||||
   * Convert a date in the YYYY-MM-DD format taking into account the time zone.
 | 
			
		||||
   */
 | 
			
		||||
  private convertShortDate;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the unique ID of the selected category.
 | 
			
		||||
   */
 | 
			
		||||
  private categoryToID;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
export declare type Result<L, A> = Failure<L, A> | Success<L, A>;
 | 
			
		||||
export declare class Failure<L, A> {
 | 
			
		||||
  readonly value: L;
 | 
			
		||||
  constructor(value: L);
 | 
			
		||||
  isFailure(): this is Failure<L, A>;
 | 
			
		||||
  isSuccess(): this is Success<L, A>;
 | 
			
		||||
  applyOnSuccess<B>(_: (a: A) => B): Result<L, B>;
 | 
			
		||||
}
 | 
			
		||||
export declare class Success<L, A> {
 | 
			
		||||
  readonly value: A;
 | 
			
		||||
  constructor(value: A);
 | 
			
		||||
  isFailure(): this is Failure<L, A>;
 | 
			
		||||
  isSuccess(): this is Success<L, A>;
 | 
			
		||||
  applyOnSuccess<B>(func: (a: A) => B): Result<L, B>;
 | 
			
		||||
}
 | 
			
		||||
export declare const failure: <L, A>(l: L) => Result<L, A>;
 | 
			
		||||
export declare const success: <L, A>(a: A) => Result<L, A>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,71 +0,0 @@
 | 
			
		|||
import tough from "tough-cookie";
 | 
			
		||||
export default class Session {
 | 
			
		||||
  /**
 | 
			
		||||
   * Max number of days the session is valid.
 | 
			
		||||
   */
 | 
			
		||||
  private readonly SESSION_TIME;
 | 
			
		||||
  private readonly COOKIEJAR_FILENAME;
 | 
			
		||||
  private _path;
 | 
			
		||||
  private _isMapped;
 | 
			
		||||
  private _created;
 | 
			
		||||
  private _hash;
 | 
			
		||||
  private _token;
 | 
			
		||||
  private _cookieJar;
 | 
			
		||||
  private _cookieJarPath;
 | 
			
		||||
  /**
 | 
			
		||||
   * Path of the session map file on disk.
 | 
			
		||||
   */
 | 
			
		||||
  get path(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates if the session is mapped on disk.
 | 
			
		||||
   */
 | 
			
		||||
  get isMapped(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date of creation of the session.
 | 
			
		||||
   */
 | 
			
		||||
  get created(): Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * MD5 hash of the username and the password.
 | 
			
		||||
   */
 | 
			
		||||
  get hash(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Token used to login to F95Zone.
 | 
			
		||||
   */
 | 
			
		||||
  get token(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Cookie holder.
 | 
			
		||||
   */
 | 
			
		||||
  get cookieJar(): tough.CookieJar;
 | 
			
		||||
  /**
 | 
			
		||||
   * Initializes the session by setting the path for saving information to disk.
 | 
			
		||||
   */
 | 
			
		||||
  constructor(p: string);
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the difference in days between two dates.
 | 
			
		||||
   */
 | 
			
		||||
  private dateDiffInDays;
 | 
			
		||||
  /**
 | 
			
		||||
   * Convert the object to a dictionary serializable in JSON.
 | 
			
		||||
   */
 | 
			
		||||
  private toJSON;
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a new session.
 | 
			
		||||
   */
 | 
			
		||||
  create(username: string, password: string, token: string): void;
 | 
			
		||||
  /**
 | 
			
		||||
   * Save the session to disk.
 | 
			
		||||
   */
 | 
			
		||||
  save(): Promise<void>;
 | 
			
		||||
  /**
 | 
			
		||||
   * Load the session from disk.
 | 
			
		||||
   */
 | 
			
		||||
  load(): Promise<void>;
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete the session from disk.
 | 
			
		||||
   */
 | 
			
		||||
  delete(): Promise<void>;
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if the session is valid.
 | 
			
		||||
   */
 | 
			
		||||
  isValid(username: string, password: string): boolean;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,201 +0,0 @@
 | 
			
		|||
export declare const GENERIC: {
 | 
			
		||||
  /**
 | 
			
		||||
   * The ID of the user currently logged into
 | 
			
		||||
   * the platform in the attribute `data-user-id`.
 | 
			
		||||
   */
 | 
			
		||||
  CURRENT_USER_ID: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Banner containing any error messages as text.
 | 
			
		||||
   */
 | 
			
		||||
  ERROR_BANNER: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Locate the token used for the session.
 | 
			
		||||
   */
 | 
			
		||||
  GET_REQUEST_TOKEN: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Block containing the text of any errors that occurred during the login.
 | 
			
		||||
   */
 | 
			
		||||
  LOGIN_MESSAGE_ERROR: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Locate the script containing the tags and prefixes of the platform content in JSON format.
 | 
			
		||||
   */
 | 
			
		||||
  LATEST_UPDATES_TAGS_SCRIPT: string;
 | 
			
		||||
};
 | 
			
		||||
export declare const WATCHED_THREAD: {
 | 
			
		||||
  /**
 | 
			
		||||
   * List of elements containing the data of the watched threads.
 | 
			
		||||
   */
 | 
			
		||||
  BODIES: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the forum to which the thread belongs as text.
 | 
			
		||||
   *
 | 
			
		||||
   * For use within a `WATCHED_THREAD.BODIES` selector.
 | 
			
		||||
   */
 | 
			
		||||
  FORUM: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Index of the last page available as text.
 | 
			
		||||
   */
 | 
			
		||||
  LAST_PAGE: string;
 | 
			
		||||
};
 | 
			
		||||
export declare const THREAD: {
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of pages in the thread (as text of the element).
 | 
			
		||||
   *
 | 
			
		||||
   * Two identical elements are identified.
 | 
			
		||||
   */
 | 
			
		||||
  LAST_PAGE: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Identify the creator of the thread.
 | 
			
		||||
   *
 | 
			
		||||
   * The ID is contained in the `data-user-id` attribute.
 | 
			
		||||
   */
 | 
			
		||||
  OWNER_ID: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Contains the creation date of the thread.
 | 
			
		||||
   *
 | 
			
		||||
   * The date is contained in the `datetime` attribute as an ISO string.
 | 
			
		||||
   */
 | 
			
		||||
  CREATION: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of tags assigned to the thread.
 | 
			
		||||
   */
 | 
			
		||||
  TAGS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of prefixes assigned to the thread.
 | 
			
		||||
   */
 | 
			
		||||
  PREFIXES: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Thread title.
 | 
			
		||||
   */
 | 
			
		||||
  TITLE: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * JSON containing thread information.
 | 
			
		||||
   *
 | 
			
		||||
   * Two different elements are found.
 | 
			
		||||
   */
 | 
			
		||||
  JSONLD: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Posts on the current page.
 | 
			
		||||
   */
 | 
			
		||||
  POSTS_IN_PAGE: string;
 | 
			
		||||
};
 | 
			
		||||
export declare const THREAD_SEARCH: {
 | 
			
		||||
  /**
 | 
			
		||||
   * Thread title resulting from research.
 | 
			
		||||
   */
 | 
			
		||||
  THREAD_TITLE: string;
 | 
			
		||||
  /**
 | 
			
		||||
   *Thread body resulting from research.
 | 
			
		||||
   */
 | 
			
		||||
  BODY: string;
 | 
			
		||||
};
 | 
			
		||||
export declare const POST: {
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique post number for the current thread.
 | 
			
		||||
   *
 | 
			
		||||
   * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
   */
 | 
			
		||||
  NUMBER: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique ID of the post in the F95Zone platform in the `id` attribute.
 | 
			
		||||
   *
 | 
			
		||||
   * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
   */
 | 
			
		||||
  ID: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique ID of the post author in the `data-user-id` attribute.
 | 
			
		||||
   *
 | 
			
		||||
   * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
   */
 | 
			
		||||
  OWNER_ID: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Main body of the post where the message written by the user is contained.
 | 
			
		||||
   *
 | 
			
		||||
   * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
   */
 | 
			
		||||
  BODY: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the element only if the post has been bookmarked.
 | 
			
		||||
   *
 | 
			
		||||
   * For use within a `THREAD.POSTS_IN_PAGE` selector.
 | 
			
		||||
   */
 | 
			
		||||
  BOOKMARKED: string;
 | 
			
		||||
};
 | 
			
		||||
export declare const MEMBER: {
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the user.
 | 
			
		||||
   *
 | 
			
		||||
   * It also contains the unique ID of the user in the `data-user-id` attribute.
 | 
			
		||||
   */
 | 
			
		||||
  NAME: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Title of the user in the platform.
 | 
			
		||||
   *
 | 
			
		||||
   * i.e.: Member
 | 
			
		||||
   */
 | 
			
		||||
  TITLE: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Avatar used by the user.
 | 
			
		||||
   *
 | 
			
		||||
   * Source in the attribute `src`.
 | 
			
		||||
   */
 | 
			
		||||
  AVATAR: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * User assigned banners.
 | 
			
		||||
   *
 | 
			
		||||
   * The last element is always empty and can be ignored.
 | 
			
		||||
   */
 | 
			
		||||
  BANNERS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Date the user joined the platform.
 | 
			
		||||
   *
 | 
			
		||||
   * The date is contained in the `datetime` attribute as an ISO string.
 | 
			
		||||
   */
 | 
			
		||||
  JOINED: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Last time the user connected to the platform.
 | 
			
		||||
   *
 | 
			
		||||
   * The date is contained in the `datetime` attribute as an ISO string.
 | 
			
		||||
   */
 | 
			
		||||
  LAST_SEEN: string;
 | 
			
		||||
  MESSAGES: string;
 | 
			
		||||
  REACTION_SCORE: string;
 | 
			
		||||
  POINTS: string;
 | 
			
		||||
  RATINGS_RECEIVED: string;
 | 
			
		||||
  AMOUNT_DONATED: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * 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.
 | 
			
		||||
   */
 | 
			
		||||
  IGNORED: string;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,64 +0,0 @@
 | 
			
		|||
export declare const urls: {
 | 
			
		||||
  /**
 | 
			
		||||
   * Page with the list of alerts for the user currently logged.
 | 
			
		||||
   */
 | 
			
		||||
  ALERTS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Basic URL of the platform.
 | 
			
		||||
   */
 | 
			
		||||
  BASE: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Page with the list of favorite posts of the user currently logged.
 | 
			
		||||
   */
 | 
			
		||||
  BOOKMARKS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Page with the list of conversations of the currently logged user.
 | 
			
		||||
   */
 | 
			
		||||
  CONVERSATIONS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * URL of the script used for searching for content
 | 
			
		||||
   * in the "Latest Updates" section of the platform.
 | 
			
		||||
   */
 | 
			
		||||
  LATEST_PHP: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Page with the latest updated platform content.
 | 
			
		||||
   */
 | 
			
		||||
  LATEST_UPDATES: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Page used for user login.
 | 
			
		||||
   */
 | 
			
		||||
  LOGIN: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Page used for entering the OTP code in the case of two-factor authentication.
 | 
			
		||||
   */
 | 
			
		||||
  LOGIN_2FA: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Summary page of users registered on the platform.
 | 
			
		||||
   * Used for the search for a specific member through ID.
 | 
			
		||||
   */
 | 
			
		||||
  MEMBERS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Add the unique ID of the post to
 | 
			
		||||
   * get the thread page where the post
 | 
			
		||||
   * is present.
 | 
			
		||||
   */
 | 
			
		||||
  POSTS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * URL used to send a POST request and change
 | 
			
		||||
   * the number of posts that can be viewed per
 | 
			
		||||
   * page of a specific thread.
 | 
			
		||||
   */
 | 
			
		||||
  POSTS_NUMBER: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * URL used to search the platform by POST request.
 | 
			
		||||
   */
 | 
			
		||||
  SEARCH: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Add the unique ID of the thread to get it's page.
 | 
			
		||||
   */
 | 
			
		||||
  THREADS: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Page with the list of watched threads of the currently logged user.
 | 
			
		||||
   */
 | 
			
		||||
  WATCHED_THREADS: string;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +0,0 @@
 | 
			
		|||
import HandiworkSearchQuery from "../classes/query/handiwork-search-query";
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the URLs of the handiworks that match the passed parameters.
 | 
			
		||||
 * You *must* be logged.
 | 
			
		||||
 * @param {LatestSearchQuery} query
 | 
			
		||||
 * Query used for the search
 | 
			
		||||
 * @param {Number} limit
 | 
			
		||||
 * Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns {Promise<String[]>} URLs of the handiworks
 | 
			
		||||
 */
 | 
			
		||||
export default function fetchHandiworkURLs(
 | 
			
		||||
  query: HandiworkSearchQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<string[]>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
import LatestSearchQuery from "../classes/query/latest-search-query.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the URLs of the latest handiworks that match the passed parameters.
 | 
			
		||||
 *
 | 
			
		||||
 * You *must* be logged.
 | 
			
		||||
 * @param {LatestSearchQuery} query
 | 
			
		||||
 * Query used for the search
 | 
			
		||||
 * @param {Number} limit
 | 
			
		||||
 * Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns {Promise<String[]>} URLs of the handiworks
 | 
			
		||||
 */
 | 
			
		||||
export default function fetchLatestHandiworkURLs(
 | 
			
		||||
  query: LatestSearchQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<string[]>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Gets the basic data used for game data processing
 | 
			
		||||
 * (such as graphics engines and progress statuses)
 | 
			
		||||
 */
 | 
			
		||||
export default function fetchPlatformData(): Promise<void>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
import { IQuery } from "../interfaces.js";
 | 
			
		||||
/**
 | 
			
		||||
 * @param query Query used for the search
 | 
			
		||||
 * @param limit Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns URLs of the fetched games
 | 
			
		||||
 */
 | 
			
		||||
export default function getURLsFromQuery(
 | 
			
		||||
  query: IQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<string[]>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
import ThreadSearchQuery from "../classes/query/thread-search-query.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the URLs of the handiwork' threads that match the passed parameters.
 | 
			
		||||
 *
 | 
			
		||||
 * You *must* be logged.
 | 
			
		||||
 * @param {ThreadSearchQuery} query
 | 
			
		||||
 * Query used for the search
 | 
			
		||||
 * @param {number} limit
 | 
			
		||||
 * Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns {Promise<String[]>} URLs of the handiworks
 | 
			
		||||
 */
 | 
			
		||||
export default function fetchThreadHandiworkURLs(
 | 
			
		||||
  query: ThreadSearchQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<string[]>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,300 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Data relating to an external platform (i.e. Patreon).
 | 
			
		||||
 */
 | 
			
		||||
export declare type TExternalPlatform = {
 | 
			
		||||
  /**
 | 
			
		||||
   * name of the platform.
 | 
			
		||||
   */
 | 
			
		||||
  name: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * link to the platform.
 | 
			
		||||
   */
 | 
			
		||||
  link: string;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * Information about the author of a work.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TAuthor = {
 | 
			
		||||
  /**
 | 
			
		||||
   * Plain name or username of the author.
 | 
			
		||||
   */
 | 
			
		||||
  name: string;
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  platforms: TExternalPlatform[];
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * Information on the evaluation of a work.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TRating = {
 | 
			
		||||
  /**
 | 
			
		||||
   * average value of evaluations.
 | 
			
		||||
   */
 | 
			
		||||
  average: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Best rating received.
 | 
			
		||||
   */
 | 
			
		||||
  best: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of ratings made by users.
 | 
			
		||||
   */
 | 
			
		||||
  count: number;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * List of possible graphics engines used for game development.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TEngine =
 | 
			
		||||
  | "QSP"
 | 
			
		||||
  | "RPGM"
 | 
			
		||||
  | "Unity"
 | 
			
		||||
  | "HTML"
 | 
			
		||||
  | "RAGS"
 | 
			
		||||
  | "Java"
 | 
			
		||||
  | "Ren'Py"
 | 
			
		||||
  | "Flash"
 | 
			
		||||
  | "ADRIFT"
 | 
			
		||||
  | "Others"
 | 
			
		||||
  | "Tads"
 | 
			
		||||
  | "Wolf RPG"
 | 
			
		||||
  | "Unreal Engine"
 | 
			
		||||
  | "WebGL";
 | 
			
		||||
/**
 | 
			
		||||
 * List of possible progress states associated with a game.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TStatus = "Completed" | "Ongoing" | "Abandoned" | "Onhold";
 | 
			
		||||
/**
 | 
			
		||||
 * List of possible categories of a particular work.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TCategory = "games" | "mods" | "comics" | "animations" | "assets";
 | 
			
		||||
/**
 | 
			
		||||
 * Valid names of classes that implement the IQuery interface.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TQueryInterface =
 | 
			
		||||
  | "LatestSearchQuery"
 | 
			
		||||
  | "ThreadSearchQuery"
 | 
			
		||||
  | "HandiworkSearchQuery";
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of values defined for each
 | 
			
		||||
 * handiwork on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export interface IBasic {
 | 
			
		||||
  /**
 | 
			
		||||
   * Authors of the work.
 | 
			
		||||
   */
 | 
			
		||||
  authors: TAuthor[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Category of the work..
 | 
			
		||||
   */
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of changes of the work for each version.
 | 
			
		||||
   */
 | 
			
		||||
  changelog: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * link to the cover image of the work.
 | 
			
		||||
   */
 | 
			
		||||
  cover: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique ID of the work on the platform.
 | 
			
		||||
   */
 | 
			
		||||
  id: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Last update of the opera thread.
 | 
			
		||||
   */
 | 
			
		||||
  lastThreadUpdate: Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Plain name of the work (without tags and/or prefixes)
 | 
			
		||||
   */
 | 
			
		||||
  name: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Work description
 | 
			
		||||
   */
 | 
			
		||||
  overview: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of prefixes associated with the work.
 | 
			
		||||
   */
 | 
			
		||||
  prefixes: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Evaluation of the work by the users of the platform.
 | 
			
		||||
   */
 | 
			
		||||
  rating: TRating;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of tags associated with the work.
 | 
			
		||||
   */
 | 
			
		||||
  tags: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Date of publication of the thread associated with the work.
 | 
			
		||||
   */
 | 
			
		||||
  threadPublishingDate: Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * URL to the work's official conversation on the F95Zone portal.
 | 
			
		||||
   */
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of values representing a game present on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export interface IGame extends IBasic {
 | 
			
		||||
  /**
 | 
			
		||||
   * Specify whether the work has censorship
 | 
			
		||||
   * measures regarding NSFW scenes
 | 
			
		||||
   */
 | 
			
		||||
  censored: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Graphics engine used for game development.
 | 
			
		||||
   */
 | 
			
		||||
  engine: TEngine;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of genres associated with the work.
 | 
			
		||||
   */
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Author's Guide to Installation.
 | 
			
		||||
   */
 | 
			
		||||
  installation: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of available languages.
 | 
			
		||||
   */
 | 
			
		||||
  language: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Last time the work underwent updates.
 | 
			
		||||
   */
 | 
			
		||||
  lastRelease: Date;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates that this item represents a mod.
 | 
			
		||||
   */
 | 
			
		||||
  mod: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of OS for which the work is compatible.
 | 
			
		||||
   */
 | 
			
		||||
  os: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates the progress of a game.
 | 
			
		||||
   */
 | 
			
		||||
  status: TStatus;
 | 
			
		||||
  /**
 | 
			
		||||
   * Version of the work.
 | 
			
		||||
   */
 | 
			
		||||
  version: string;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of values representing a comic present on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export interface IComic extends IBasic {
 | 
			
		||||
  /**
 | 
			
		||||
   * List of genres associated with the work.
 | 
			
		||||
   */
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of pages or elements that make up the work.
 | 
			
		||||
   */
 | 
			
		||||
  pages: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of resolutions available for the work.
 | 
			
		||||
   */
 | 
			
		||||
  resolution: string[];
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of values representing an animation present on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export interface IAnimation extends IBasic {
 | 
			
		||||
  /**
 | 
			
		||||
   * Specify whether the work has censorship
 | 
			
		||||
   * measures regarding NSFW scenes
 | 
			
		||||
   */
 | 
			
		||||
  censored: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of genres associated with the work.
 | 
			
		||||
   */
 | 
			
		||||
  genre: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Author's Guide to Installation.
 | 
			
		||||
   */
 | 
			
		||||
  installation: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of available languages.
 | 
			
		||||
   */
 | 
			
		||||
  language: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Length of the animation.
 | 
			
		||||
   */
 | 
			
		||||
  lenght: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of pages or elements that make up the work.
 | 
			
		||||
   */
 | 
			
		||||
  pages: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of resolutions available for the work.
 | 
			
		||||
   */
 | 
			
		||||
  resolution: string[];
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of values representing an asset present on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export interface IAsset extends IBasic {
 | 
			
		||||
  /**
 | 
			
		||||
   * External URL of the asset.
 | 
			
		||||
   */
 | 
			
		||||
  assetLink: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of URLs of assets associated with the work
 | 
			
		||||
   * (for example same collection).
 | 
			
		||||
   */
 | 
			
		||||
  associatedAssets: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Software compatible with the work.
 | 
			
		||||
   */
 | 
			
		||||
  compatibleSoftware: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of assets url included in the work or used to develop it.
 | 
			
		||||
   */
 | 
			
		||||
  includedAssets: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * List of official links of the work, external to the platform.
 | 
			
		||||
   */
 | 
			
		||||
  officialLinks: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Unique SKU value of the work.
 | 
			
		||||
   */
 | 
			
		||||
  sku: string;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of values extrapolated from the
 | 
			
		||||
 * F95 platform representing a particular work.
 | 
			
		||||
 */
 | 
			
		||||
export interface IHandiwork extends IGame, IComic, IAnimation, IAsset {}
 | 
			
		||||
export interface IQuery {
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the implemented interface.
 | 
			
		||||
   */
 | 
			
		||||
  itype: TQueryInterface;
 | 
			
		||||
  /**
 | 
			
		||||
   * Category of items to search among.
 | 
			
		||||
   */
 | 
			
		||||
  category: TCategory;
 | 
			
		||||
  /**
 | 
			
		||||
   * Tags to be include in the search.
 | 
			
		||||
   * Max. 5 tags
 | 
			
		||||
   */
 | 
			
		||||
  includedTags: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Prefixes to include in the search.
 | 
			
		||||
   */
 | 
			
		||||
  includedPrefixes: string[];
 | 
			
		||||
  /**
 | 
			
		||||
   * Index of the page to be obtained.
 | 
			
		||||
   * Between 1 and infinity.
 | 
			
		||||
   */
 | 
			
		||||
  page: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Verify that the query values are valid.
 | 
			
		||||
   */
 | 
			
		||||
  validate(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * Search with the data in the query and returns the result.
 | 
			
		||||
   *
 | 
			
		||||
   * If the query is invalid it throws an exception.
 | 
			
		||||
   */
 | 
			
		||||
  execute(): any;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,85 +0,0 @@
 | 
			
		|||
import { AxiosResponse } from "axios";
 | 
			
		||||
import LoginResult from "./classes/login-result.js";
 | 
			
		||||
import { Result } from "./classes/result.js";
 | 
			
		||||
import { GenericAxiosError, UnexpectedResponseContentType } from "./classes/errors.js";
 | 
			
		||||
import Credentials from "./classes/credentials.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the HTML code of a page.
 | 
			
		||||
 */
 | 
			
		||||
export declare function fetchHTML(
 | 
			
		||||
  url: string
 | 
			
		||||
): Promise<Result<GenericAxiosError | UnexpectedResponseContentType, string>>;
 | 
			
		||||
/**
 | 
			
		||||
 * 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 {Boolean} force Specifies whether the request should be forced, ignoring any saved cookies
 | 
			
		||||
 * @returns {Promise<LoginResult>} Result of the operation
 | 
			
		||||
 */
 | 
			
		||||
export declare function authenticate(
 | 
			
		||||
  credentials: Credentials,
 | 
			
		||||
  force?: boolean
 | 
			
		||||
): Promise<LoginResult>;
 | 
			
		||||
/**
 | 
			
		||||
 * Send an OTP code if the login procedure requires it.
 | 
			
		||||
 * @param code OTP code.
 | 
			
		||||
 * @param token Unique token for the session associated with the credentials in use.
 | 
			
		||||
 * @param trustedDevice If the device in use is trusted, 2FA authentication is not required for 30 days.
 | 
			
		||||
 */
 | 
			
		||||
export declare function send2faCode(
 | 
			
		||||
  code: number,
 | 
			
		||||
  token: string,
 | 
			
		||||
  trustedDevice?: boolean
 | 
			
		||||
): Promise<Result<GenericAxiosError, LoginResult>>;
 | 
			
		||||
/**
 | 
			
		||||
 * Obtain the token used to authenticate the user to the platform.
 | 
			
		||||
 */
 | 
			
		||||
export declare function getF95Token(): Promise<string>;
 | 
			
		||||
/**
 | 
			
		||||
 * Performs a GET request to a specific URL and returns the response.
 | 
			
		||||
 */
 | 
			
		||||
export declare function fetchGETResponse(
 | 
			
		||||
  url: string
 | 
			
		||||
): Promise<Result<GenericAxiosError, AxiosResponse<any>>>;
 | 
			
		||||
/**
 | 
			
		||||
 * Performs a POST request through axios.
 | 
			
		||||
 * @param url URL to request
 | 
			
		||||
 * @param params List of value pairs to send with the request
 | 
			
		||||
 * @param force If `true`, the request ignores the sending of cookies already present on the device.
 | 
			
		||||
 */
 | 
			
		||||
export declare function fetchPOSTResponse(
 | 
			
		||||
  url: string,
 | 
			
		||||
  params: {
 | 
			
		||||
    [s: string]: string;
 | 
			
		||||
  },
 | 
			
		||||
  force?: boolean
 | 
			
		||||
): Promise<Result<GenericAxiosError, AxiosResponse<any>>>;
 | 
			
		||||
/**
 | 
			
		||||
 * Enforces the scheme of the URL is https and returns the new URL.
 | 
			
		||||
 */
 | 
			
		||||
export declare function enforceHttpsUrl(url: string): string;
 | 
			
		||||
/**
 | 
			
		||||
 * Check if the url belongs to the domain of the F95 platform.
 | 
			
		||||
 */
 | 
			
		||||
export declare function isF95URL(url: string): boolean;
 | 
			
		||||
/**
 | 
			
		||||
 * Checks if the string passed by parameter has a
 | 
			
		||||
 * properly formatted and valid path to a URL (HTTP/HTTPS).
 | 
			
		||||
 */
 | 
			
		||||
export declare 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]
 | 
			
		||||
 * If true, the function will consider redirects a violation and return false.
 | 
			
		||||
 * Default: false
 | 
			
		||||
 * @returns {Promise<Boolean>} true if the URL exists, false otherwise
 | 
			
		||||
 */
 | 
			
		||||
export declare function urlExists(url: string, checkRedirect?: boolean): Promise<boolean>;
 | 
			
		||||
/**
 | 
			
		||||
 * 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
 | 
			
		||||
 */
 | 
			
		||||
export declare function getUrlRedirect(url: string): Promise<string>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
import Thread from "../classes/mapping/thread.js";
 | 
			
		||||
import { IBasic } from "../interfaces.js";
 | 
			
		||||
export declare function getHandiworkInformation<T extends IBasic>(
 | 
			
		||||
  url: string
 | 
			
		||||
): Promise<T>;
 | 
			
		||||
export declare function getHandiworkInformation<T extends IBasic>(
 | 
			
		||||
  url: string
 | 
			
		||||
): Promise<T>;
 | 
			
		||||
/**
 | 
			
		||||
 * Gets information of a particular handiwork from its thread.
 | 
			
		||||
 *
 | 
			
		||||
 * If you don't want to specify the object type, use `HandiWork`.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo It does not currently support assets.
 | 
			
		||||
 */
 | 
			
		||||
export default function getHandiworkInformation<T extends IBasic>(
 | 
			
		||||
  arg: string | Thread
 | 
			
		||||
): Promise<T>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
/// <reference types="cheerio" />
 | 
			
		||||
/**
 | 
			
		||||
 * Represents information contained in a JSON+LD tag.
 | 
			
		||||
 */
 | 
			
		||||
export declare type TJsonLD = {
 | 
			
		||||
  [s: string]: string | TJsonLD;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * Extracts and processes the JSON-LD values of the page.
 | 
			
		||||
 * @param {cheerio.Cheerio} body Page `body` selector
 | 
			
		||||
 * @returns {TJsonLD[]} List of data obtained from the page
 | 
			
		||||
 */
 | 
			
		||||
export declare function getJSONLD(body: cheerio.Cheerio): TJsonLD;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
/// <reference types="cheerio" />
 | 
			
		||||
export interface IPostElement {
 | 
			
		||||
  type: "Empty" | "Text" | "Link" | "Image" | "Spoiler";
 | 
			
		||||
  name: string;
 | 
			
		||||
  text: string;
 | 
			
		||||
  content: IPostElement[];
 | 
			
		||||
}
 | 
			
		||||
export interface ILink extends IPostElement {
 | 
			
		||||
  type: "Image" | "Link";
 | 
			
		||||
  href: string;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * Given a post of a thread page it extracts the information contained in the body.
 | 
			
		||||
 */
 | 
			
		||||
export declare function parseF95ThreadPost(
 | 
			
		||||
  $: cheerio.Root,
 | 
			
		||||
  post: cheerio.Cheerio
 | 
			
		||||
): IPostElement[];
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
import { IBasic, IQuery } from "./interfaces.js";
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the handiworks that match the passed parameters.
 | 
			
		||||
 * You *must* be logged.
 | 
			
		||||
 * @param {Number} limit
 | 
			
		||||
 * Maximum number of items to get. Default: 30
 | 
			
		||||
 */
 | 
			
		||||
export default function search<T extends IBasic>(
 | 
			
		||||
  query: IQuery,
 | 
			
		||||
  limit?: number
 | 
			
		||||
): Promise<T[]>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,40 +0,0 @@
 | 
			
		|||
import log4js from "log4js";
 | 
			
		||||
import Session from "./classes/session.js";
 | 
			
		||||
export declare type TPrefixDict = {
 | 
			
		||||
  [n: number]: string;
 | 
			
		||||
};
 | 
			
		||||
declare type TPrefixKey = "engines" | "statuses" | "tags" | "others";
 | 
			
		||||
/**
 | 
			
		||||
 * Class containing variables shared between modules.
 | 
			
		||||
 */
 | 
			
		||||
export default abstract class Shared {
 | 
			
		||||
  private static _isLogged;
 | 
			
		||||
  private static _prefixes;
 | 
			
		||||
  private static _logger;
 | 
			
		||||
  private static _session;
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates whether a user is logged in to the F95Zone platform or not.
 | 
			
		||||
   */
 | 
			
		||||
  static get isLogged(): boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * List of platform prefixes and tags.
 | 
			
		||||
   */
 | 
			
		||||
  static get prefixes(): {
 | 
			
		||||
    [s: string]: TPrefixDict;
 | 
			
		||||
  };
 | 
			
		||||
  /**
 | 
			
		||||
   * Logger object used to write to both file and console.
 | 
			
		||||
   */
 | 
			
		||||
  static get logger(): log4js.Logger;
 | 
			
		||||
  /**
 | 
			
		||||
   * Path to the cache used by this module wich contains engines, statuses, tags...
 | 
			
		||||
   */
 | 
			
		||||
  static get cachePath(): string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Session on the F95Zone platform.
 | 
			
		||||
   */
 | 
			
		||||
  static get session(): Session;
 | 
			
		||||
  static setPrefixPair(key: TPrefixKey, val: TPrefixDict): void;
 | 
			
		||||
  static setIsLogged(val: boolean): void;
 | 
			
		||||
}
 | 
			
		||||
export {};
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ import {
 | 
			
		|||
  Game,
 | 
			
		||||
  searchHandiwork,
 | 
			
		||||
  HandiworkSearchQuery
 | 
			
		||||
} from "./index.js";
 | 
			
		||||
} from "./index";
 | 
			
		||||
 | 
			
		||||
// Configure the .env reader
 | 
			
		||||
dotenv.config();
 | 
			
		||||
| 
						 | 
				
			
			@ -103,14 +103,11 @@ async function main() {
 | 
			
		|||
    const searchResult = await searchHandiwork<Game>(query, 1);
 | 
			
		||||
 | 
			
		||||
    // No game found
 | 
			
		||||
    if (searchResult.length === 0) {
 | 
			
		||||
      console.log(`No data found for '${gamename}'\n`);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Extract first game
 | 
			
		||||
    const gamedata = searchResult.shift();
 | 
			
		||||
    const authors = gamedata.authors.map((a, idx) => a.name).join(", ");
 | 
			
		||||
    console.log(`Found: ${gamedata.name} (${gamedata.version}) by ${authors}\n`);
 | 
			
		||||
    if (searchResult.length !== 0) {
 | 
			
		||||
      // Extract first game
 | 
			
		||||
      const gamedata = searchResult.shift();
 | 
			
		||||
      const authors = gamedata.authors.map((a, idx) => a.name).join(", ");
 | 
			
		||||
      console.log(`Found: ${gamedata.name} (${gamedata.version}) by ${authors}\n`);
 | 
			
		||||
    } else console.log(`No data found for '${gamename}'\n`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										78
									
								
								src/index.ts
								
								
								
								
							
							
						
						
									
										78
									
								
								src/index.ts
								
								
								
								
							| 
						 | 
				
			
			@ -6,50 +6,41 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import shared from "./scripts/shared.js";
 | 
			
		||||
import search from "./scripts/search.js";
 | 
			
		||||
import {
 | 
			
		||||
  authenticate,
 | 
			
		||||
  urlExists,
 | 
			
		||||
  isF95URL,
 | 
			
		||||
  send2faCode
 | 
			
		||||
} from "./scripts/network-helper.js";
 | 
			
		||||
import fetchLatestHandiworkURLs from "./scripts/fetch-data/fetch-latest.js";
 | 
			
		||||
import fetchPlatformData from "./scripts/fetch-data/fetch-platform-data.js";
 | 
			
		||||
import getHandiworkInformation from "./scripts/scrape-data/handiwork-parse.js";
 | 
			
		||||
import { IBasic } from "./scripts/interfaces.js";
 | 
			
		||||
import shared from "./scripts/shared";
 | 
			
		||||
import search from "./scripts/search";
 | 
			
		||||
import { authenticate, urlExists, isF95URL, send2faCode } from "./scripts/network-helper";
 | 
			
		||||
import fetchLatestHandiworkURLs from "./scripts/fetch-data/fetch-latest";
 | 
			
		||||
import fetchPlatformData from "./scripts/fetch-data/fetch-platform-data";
 | 
			
		||||
import getHandiworkInformation from "./scripts/scrape-data/handiwork-parse";
 | 
			
		||||
import { IBasic } from "./scripts/interfaces";
 | 
			
		||||
 | 
			
		||||
// Classes from file
 | 
			
		||||
import Credentials from "./scripts/classes/credentials.js";
 | 
			
		||||
import LoginResult from "./scripts/classes/login-result.js";
 | 
			
		||||
import UserProfile from "./scripts/classes/mapping/user-profile.js";
 | 
			
		||||
import LatestSearchQuery from "./scripts/classes/query/latest-search-query.js";
 | 
			
		||||
import HandiworkSearchQuery from "./scripts/classes/query/handiwork-search-query.js";
 | 
			
		||||
import HandiWork from "./scripts/classes/handiwork/handiwork.js";
 | 
			
		||||
import { UserNotLogged } from "./scripts/classes/errors.js";
 | 
			
		||||
 | 
			
		||||
//#region Global variables
 | 
			
		||||
 | 
			
		||||
const USER_NOT_LOGGED = "User not authenticated, unable to continue";
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
import Credentials from "./scripts/classes/credentials";
 | 
			
		||||
import LoginResult from "./scripts/classes/login-result";
 | 
			
		||||
import UserProfile from "./scripts/classes/mapping/user-profile";
 | 
			
		||||
import LatestSearchQuery from "./scripts/classes/query/latest-search-query";
 | 
			
		||||
import HandiworkSearchQuery from "./scripts/classes/query/handiwork-search-query";
 | 
			
		||||
import HandiWork from "./scripts/classes/handiwork/handiwork";
 | 
			
		||||
import { UserNotLogged, USER_NOT_LOGGED } from "./scripts/classes/errors";
 | 
			
		||||
 | 
			
		||||
//#region Re-export classes
 | 
			
		||||
 | 
			
		||||
export { default as Animation } from "./scripts/classes/handiwork/animation.js";
 | 
			
		||||
export { default as Asset } from "./scripts/classes/handiwork/asset.js";
 | 
			
		||||
export { default as Comic } from "./scripts/classes/handiwork/comic.js";
 | 
			
		||||
export { default as Game } from "./scripts/classes/handiwork/game.js";
 | 
			
		||||
export { default as Handiwork } from "./scripts/classes/handiwork/handiwork.js";
 | 
			
		||||
export { default as PrefixParser } from "./scripts/classes/prefix-parser";
 | 
			
		||||
 | 
			
		||||
export { default as PlatformUser } from "./scripts/classes/mapping/platform-user.js";
 | 
			
		||||
export { default as Post } from "./scripts/classes/mapping/post.js";
 | 
			
		||||
export { default as Thread } from "./scripts/classes/mapping/thread.js";
 | 
			
		||||
export { default as UserProfile } from "./scripts/classes/mapping/user-profile.js";
 | 
			
		||||
export { default as Animation } from "./scripts/classes/handiwork/animation";
 | 
			
		||||
export { default as Asset } from "./scripts/classes/handiwork/asset";
 | 
			
		||||
export { default as Comic } from "./scripts/classes/handiwork/comic";
 | 
			
		||||
export { default as Game } from "./scripts/classes/handiwork/game";
 | 
			
		||||
export { default as Handiwork } from "./scripts/classes/handiwork/handiwork";
 | 
			
		||||
 | 
			
		||||
export { default as HandiworkSearchQuery } from "./scripts/classes/query/handiwork-search-query.js";
 | 
			
		||||
export { default as LatestSearchQuery } from "./scripts/classes/query/latest-search-query.js";
 | 
			
		||||
export { default as ThreadSearchQuery } from "./scripts/classes/query/thread-search-query.js";
 | 
			
		||||
export { default as PlatformUser } from "./scripts/classes/mapping/platform-user";
 | 
			
		||||
export { default as Post } from "./scripts/classes/mapping/post";
 | 
			
		||||
export { default as Thread } from "./scripts/classes/mapping/thread";
 | 
			
		||||
export { default as UserProfile } from "./scripts/classes/mapping/user-profile";
 | 
			
		||||
 | 
			
		||||
export { default as HandiworkSearchQuery } from "./scripts/classes/query/handiwork-search-query";
 | 
			
		||||
export { default as LatestSearchQuery } from "./scripts/classes/query/latest-search-query";
 | 
			
		||||
export { default as ThreadSearchQuery } from "./scripts/classes/query/thread-search-query";
 | 
			
		||||
 | 
			
		||||
//#endregion Re-export classes
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +127,19 @@ export async function login(
 | 
			
		|||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Close the currently open session.
 | 
			
		||||
 *
 | 
			
		||||
 * You **must** be logged in to the portal before calling this method.
 | 
			
		||||
 */
 | 
			
		||||
export async function logout(): Promise<void> {
 | 
			
		||||
  // Check if the user is logged
 | 
			
		||||
  if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
 | 
			
		||||
 | 
			
		||||
  await shared.session.delete();
 | 
			
		||||
  shared.setIsLogged(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Chek if exists a new version of the handiwork.
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import { getF95Token } from "../network-helper.js";
 | 
			
		||||
import { getF95Token } from "../network-helper";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the credentials used to access the platform.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,11 @@ interface IBaseError {
 | 
			
		|||
  error: Error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const USER_NOT_LOGGED = "User not authenticated, unable to continue";
 | 
			
		||||
export const INVALID_USER_ID = "Invalid user ID";
 | 
			
		||||
export const INVALID_POST_ID = "Invalid post ID";
 | 
			
		||||
export const INVALID_THREAD_ID = "Invalid thread ID";
 | 
			
		||||
 | 
			
		||||
export class GenericAxiosError extends Error implements IBaseError {
 | 
			
		||||
  id: number;
 | 
			
		||||
  message: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,4 +55,6 @@ export class InvalidF95Token extends Error {}
 | 
			
		|||
 | 
			
		||||
export class UserNotLogged extends Error {}
 | 
			
		||||
 | 
			
		||||
export class InvalidID extends Error {}
 | 
			
		||||
 | 
			
		||||
export class ParameterError extends Error {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,17 +7,20 @@
 | 
			
		|||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import cheerio from "cheerio";
 | 
			
		||||
import luxon from "luxon";
 | 
			
		||||
import { DateTime } from "luxon";
 | 
			
		||||
 | 
			
		||||
// Modules from files
 | 
			
		||||
import { urls } from "../../constants/url.js";
 | 
			
		||||
import { fetchHTML } from "../../network-helper.js";
 | 
			
		||||
import { GENERIC, MEMBER } from "../../constants/css-selector.js";
 | 
			
		||||
import { urls } from "../../constants/url";
 | 
			
		||||
import { fetchHTML } from "../../network-helper";
 | 
			
		||||
import { GENERIC, MEMBER } from "../../constants/css-selector";
 | 
			
		||||
import shared from "../../shared";
 | 
			
		||||
import { InvalidID, INVALID_USER_ID, UserNotLogged, USER_NOT_LOGGED } from "../errors";
 | 
			
		||||
import { ILazy } from "../../interfaces";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a generic user registered on the platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class PlatformUser {
 | 
			
		||||
export default class PlatformUser implements ILazy {
 | 
			
		||||
  //#region Fields
 | 
			
		||||
 | 
			
		||||
  private _id: number;
 | 
			
		||||
| 
						 | 
				
			
			@ -140,57 +143,73 @@ export default class PlatformUser {
 | 
			
		|||
  //#region Public methods
 | 
			
		||||
 | 
			
		||||
  public setID(id: number): void {
 | 
			
		||||
    // Check ID
 | 
			
		||||
    if (!id || id < 1) throw new InvalidID(INVALID_USER_ID);
 | 
			
		||||
 | 
			
		||||
    this._id = id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async fetch(): Promise<void> {
 | 
			
		||||
    // Check login
 | 
			
		||||
    if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
 | 
			
		||||
 | 
			
		||||
    // Check ID
 | 
			
		||||
    if (!this.id && this.id < 1) throw new Error("Invalid user ID");
 | 
			
		||||
    if (!this.id || this.id < 1) throw new InvalidID(INVALID_USER_ID);
 | 
			
		||||
 | 
			
		||||
    // Prepare the URL
 | 
			
		||||
    const url = new URL(this.id.toString(), `${urls.MEMBERS}/`).toString();
 | 
			
		||||
 | 
			
		||||
    // Fetch the page
 | 
			
		||||
    const htmlResponse = await fetchHTML(url);
 | 
			
		||||
 | 
			
		||||
    if (htmlResponse.isSuccess()) {
 | 
			
		||||
      // Prepare cheerio
 | 
			
		||||
      const $ = cheerio.load(htmlResponse.value);
 | 
			
		||||
 | 
			
		||||
      // Check if the profile is private
 | 
			
		||||
      this._private =
 | 
			
		||||
        $(GENERIC.ERROR_BANNER)?.text().trim() ===
 | 
			
		||||
        "This member limits who may view their full profile.";
 | 
			
		||||
 | 
			
		||||
      if (!this._private) {
 | 
			
		||||
        // Parse the elements
 | 
			
		||||
        this._name = $(MEMBER.NAME).text();
 | 
			
		||||
        this._title = $(MEMBER.TITLE).text();
 | 
			
		||||
        this._banners = $(MEMBER.BANNERS)
 | 
			
		||||
          .toArray()
 | 
			
		||||
          .map((el, idx) => $(el).text().trim())
 | 
			
		||||
          .filter((el) => el);
 | 
			
		||||
        this._avatar = $(MEMBER.AVATAR).attr("src");
 | 
			
		||||
        this._followed = $(MEMBER.FOLLOWED).text() === "Unfollow";
 | 
			
		||||
        this._ignored = $(MEMBER.IGNORED).text() === "Unignore";
 | 
			
		||||
        this._messages = parseInt($(MEMBER.MESSAGES).text(), 10);
 | 
			
		||||
        this._reactionScore = parseInt($(MEMBER.REACTION_SCORE).text(), 10);
 | 
			
		||||
        this._points = parseInt($(MEMBER.POINTS).text(), 10);
 | 
			
		||||
        this._ratingsReceived = parseInt($(MEMBER.RATINGS_RECEIVED).text(), 10);
 | 
			
		||||
 | 
			
		||||
        // Parse date
 | 
			
		||||
        const joined = $(MEMBER.JOINED)?.attr("datetime");
 | 
			
		||||
        if (luxon.DateTime.fromISO(joined).isValid) this._joined = new Date(joined);
 | 
			
		||||
 | 
			
		||||
        const lastSeen = $(MEMBER.LAST_SEEN)?.attr("datetime");
 | 
			
		||||
        if (luxon.DateTime.fromISO(lastSeen).isValid) this._joined = new Date(lastSeen);
 | 
			
		||||
 | 
			
		||||
        // Parse donation
 | 
			
		||||
        const donation = $(MEMBER.AMOUNT_DONATED)?.text().replace("$", "");
 | 
			
		||||
        this._amountDonated = donation ? parseInt(donation, 10) : 0;
 | 
			
		||||
      }
 | 
			
		||||
    } else throw htmlResponse.value;
 | 
			
		||||
    const response = await fetchHTML(url);
 | 
			
		||||
    const result = response.applyOnSuccess((html) => this.elaborateResponse(html));
 | 
			
		||||
    if (result.isFailure()) throw response.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //#endregion Public method
 | 
			
		||||
  //#endregion Public methods
 | 
			
		||||
 | 
			
		||||
  //#region Private methods
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process the HTML code received as
 | 
			
		||||
   * an answer and gets the data contained in it.
 | 
			
		||||
   */
 | 
			
		||||
  private elaborateResponse(html: string): void {
 | 
			
		||||
    // Prepare cheerio
 | 
			
		||||
    const $ = cheerio.load(html);
 | 
			
		||||
 | 
			
		||||
    // Check if the profile is private
 | 
			
		||||
    this._private =
 | 
			
		||||
      $(GENERIC.ERROR_BANNER)?.text().trim() ===
 | 
			
		||||
      "This member limits who may view their full profile.";
 | 
			
		||||
 | 
			
		||||
    if (!this._private) {
 | 
			
		||||
      // Parse the elements
 | 
			
		||||
      this._name = $(MEMBER.NAME).text();
 | 
			
		||||
      this._title = $(MEMBER.TITLE).text();
 | 
			
		||||
      this._banners = $(MEMBER.BANNERS)
 | 
			
		||||
        .toArray()
 | 
			
		||||
        .map((el, idx) => $(el).text().trim())
 | 
			
		||||
        .filter((el) => el);
 | 
			
		||||
      this._avatar = $(MEMBER.AVATAR).attr("src");
 | 
			
		||||
      this._followed = $(MEMBER.FOLLOWED).text() === "Unfollow";
 | 
			
		||||
      this._ignored = $(MEMBER.IGNORED).text() === "Unignore";
 | 
			
		||||
      this._messages = parseInt($(MEMBER.MESSAGES).text(), 10);
 | 
			
		||||
      this._reactionScore = parseInt($(MEMBER.REACTION_SCORE).text(), 10);
 | 
			
		||||
      this._points = parseInt($(MEMBER.POINTS).text(), 10);
 | 
			
		||||
      this._ratingsReceived = parseInt($(MEMBER.RATINGS_RECEIVED).text(), 10);
 | 
			
		||||
 | 
			
		||||
      // Parse date
 | 
			
		||||
      const joined = $(MEMBER.JOINED)?.attr("datetime");
 | 
			
		||||
      if (DateTime.fromISO(joined).isValid) this._joined = new Date(joined);
 | 
			
		||||
 | 
			
		||||
      const lastSeen = $(MEMBER.LAST_SEEN)?.attr("datetime");
 | 
			
		||||
      if (DateTime.fromISO(lastSeen).isValid) this._joined = new Date(lastSeen);
 | 
			
		||||
 | 
			
		||||
      // Parse donation
 | 
			
		||||
      const donation = $(MEMBER.AMOUNT_DONATED)?.text().replace("$", "");
 | 
			
		||||
      this._amountDonated = donation ? parseInt(donation, 10) : 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //#endregion Private methods
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,16 +9,19 @@
 | 
			
		|||
import cheerio from "cheerio";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import PlatformUser from "./platform-user.js";
 | 
			
		||||
import { IPostElement, parseF95ThreadPost } from "../../scrape-data/post-parse.js";
 | 
			
		||||
import { POST, THREAD } from "../../constants/css-selector.js";
 | 
			
		||||
import { urls } from "../../constants/url.js";
 | 
			
		||||
import { fetchHTML } from "../../network-helper.js";
 | 
			
		||||
import PlatformUser from "./platform-user";
 | 
			
		||||
import { IPostElement, parseF95ThreadPost } from "../../scrape-data/post-parse";
 | 
			
		||||
import { POST, THREAD } from "../../constants/css-selector";
 | 
			
		||||
import { urls } from "../../constants/url";
 | 
			
		||||
import { fetchHTML } from "../../network-helper";
 | 
			
		||||
import shared from "../../shared";
 | 
			
		||||
import { InvalidID, INVALID_POST_ID, UserNotLogged, USER_NOT_LOGGED } from "../errors";
 | 
			
		||||
import { ILazy } from "../../interfaces";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a post published by a user on the F95Zone platform.
 | 
			
		||||
 */
 | 
			
		||||
export default class Post {
 | 
			
		||||
export default class Post implements ILazy {
 | 
			
		||||
  //#region Fields
 | 
			
		||||
 | 
			
		||||
  private _id: number;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,33 +98,46 @@ export default class Post {
 | 
			
		|||
   * Gets the post data starting from its unique ID for the entire platform.
 | 
			
		||||
   */
 | 
			
		||||
  public async fetch(): Promise<void> {
 | 
			
		||||
    // Check login
 | 
			
		||||
    if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
 | 
			
		||||
 | 
			
		||||
    // Check ID
 | 
			
		||||
    if (!this.id || this.id < 1) throw new InvalidID(INVALID_POST_ID);
 | 
			
		||||
 | 
			
		||||
    // Fetch HTML page containing the post
 | 
			
		||||
    const url = new URL(this.id.toString(), urls.POSTS).toString();
 | 
			
		||||
    const htmlResponse = await fetchHTML(url);
 | 
			
		||||
    const response = await fetchHTML(url);
 | 
			
		||||
 | 
			
		||||
    if (htmlResponse.isSuccess()) {
 | 
			
		||||
      // Load cheerio and find post
 | 
			
		||||
      const $ = cheerio.load(htmlResponse.value);
 | 
			
		||||
 | 
			
		||||
      const post = $(THREAD.POSTS_IN_PAGE)
 | 
			
		||||
        .toArray()
 | 
			
		||||
        .find((el, idx) => {
 | 
			
		||||
          // Fetch the ID and check if it is what we are searching
 | 
			
		||||
          const sid: string = $(el).find(POST.ID).attr("id").replace("post-", "");
 | 
			
		||||
          const id = parseInt(sid, 10);
 | 
			
		||||
 | 
			
		||||
          if (id === this.id) return el;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      // Finally parse the post
 | 
			
		||||
      await this.parsePost($, $(post));
 | 
			
		||||
    } else throw htmlResponse.value;
 | 
			
		||||
    if (response.isSuccess()) await this.elaborateResponse(response.value);
 | 
			
		||||
    else throw response.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //#endregion Public methods
 | 
			
		||||
 | 
			
		||||
  //#region Private methods
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process the HTML code received as
 | 
			
		||||
   * an answer and gets the data contained in it.
 | 
			
		||||
   */
 | 
			
		||||
  private async elaborateResponse(html: string) {
 | 
			
		||||
    // Load cheerio and find post
 | 
			
		||||
    const $ = cheerio.load(html);
 | 
			
		||||
 | 
			
		||||
    const post = $(THREAD.POSTS_IN_PAGE)
 | 
			
		||||
      .toArray()
 | 
			
		||||
      .find((el, idx) => {
 | 
			
		||||
        // Fetch the ID and check if it is what we are searching
 | 
			
		||||
        const sid: string = $(el).find(POST.ID).attr("id").replace("post-", "");
 | 
			
		||||
        const id = parseInt(sid, 10);
 | 
			
		||||
 | 
			
		||||
        if (id === this.id) return el;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    // Finally parse the post
 | 
			
		||||
    await this.parsePost($, $(post));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async parsePost($: cheerio.Root, post: cheerio.Cheerio): Promise<void> {
 | 
			
		||||
    // Find post's ID
 | 
			
		||||
    const sid: string = post.find(POST.ID).attr("id").replace("post-", "");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,28 +7,30 @@
 | 
			
		|||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import cheerio from "cheerio";
 | 
			
		||||
import luxon from "luxon";
 | 
			
		||||
import { DateTime } from "luxon";
 | 
			
		||||
 | 
			
		||||
// Modules from files
 | 
			
		||||
import Post from "./post.js";
 | 
			
		||||
import PlatformUser from "./platform-user.js";
 | 
			
		||||
import { TCategory, TRating } from "../../interfaces.js";
 | 
			
		||||
import { urls } from "../../constants/url.js";
 | 
			
		||||
import { POST, THREAD } from "../../constants/css-selector.js";
 | 
			
		||||
import { fetchHTML, fetchPOSTResponse } from "../../network-helper.js";
 | 
			
		||||
import Shared from "../../shared.js";
 | 
			
		||||
import Post from "./post";
 | 
			
		||||
import PlatformUser from "./platform-user";
 | 
			
		||||
import { ILazy, TCategory, TRating } from "../../interfaces";
 | 
			
		||||
import { urls } from "../../constants/url";
 | 
			
		||||
import { POST, THREAD } from "../../constants/css-selector";
 | 
			
		||||
import { fetchHTML, fetchPOSTResponse } from "../../network-helper";
 | 
			
		||||
import Shared from "../../shared";
 | 
			
		||||
import {
 | 
			
		||||
  GenericAxiosError,
 | 
			
		||||
  InvalidID,
 | 
			
		||||
  INVALID_THREAD_ID,
 | 
			
		||||
  ParameterError,
 | 
			
		||||
  UnexpectedResponseContentType
 | 
			
		||||
} from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
import { getJSONLD, TJsonLD } from "../../scrape-data/json-ld.js";
 | 
			
		||||
  UserNotLogged,
 | 
			
		||||
  USER_NOT_LOGGED
 | 
			
		||||
} from "../errors";
 | 
			
		||||
import { getJSONLD, TJsonLD } from "../../scrape-data/json-ld";
 | 
			
		||||
import shared from "../../shared";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a generic F95Zone platform thread.
 | 
			
		||||
 */
 | 
			
		||||
export default class Thread {
 | 
			
		||||
export default class Thread implements ILazy {
 | 
			
		||||
  //#region Fields
 | 
			
		||||
 | 
			
		||||
  private POST_FOR_PAGE = 20;
 | 
			
		||||
| 
						 | 
				
			
			@ -162,42 +164,6 @@ export default class Thread {
 | 
			
		|||
    return posts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets all posts in the thread.
 | 
			
		||||
   */
 | 
			
		||||
  private async fetchPosts(pages: number): Promise<Post[]> {
 | 
			
		||||
    // Local variables
 | 
			
		||||
    type TFetchResult = Promise<
 | 
			
		||||
      Result<GenericAxiosError | UnexpectedResponseContentType, string>
 | 
			
		||||
    >;
 | 
			
		||||
    const htmlPromiseList: TFetchResult[] = [];
 | 
			
		||||
    const fetchedPosts: Post[] = [];
 | 
			
		||||
 | 
			
		||||
    // Fetch posts for every page in the thread
 | 
			
		||||
    for (let i = 1; i <= pages; i++) {
 | 
			
		||||
      // Prepare the URL
 | 
			
		||||
      const url = new URL(`page-${i}`, `${this.url}/`).toString();
 | 
			
		||||
 | 
			
		||||
      // Fetch the HTML source
 | 
			
		||||
      const htmlResponse = fetchHTML(url);
 | 
			
		||||
      htmlPromiseList.push(htmlResponse);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Wait for all the pages to load
 | 
			
		||||
    const responses = await Promise.all(htmlPromiseList);
 | 
			
		||||
 | 
			
		||||
    // Scrape the pages
 | 
			
		||||
    for (const response of responses) {
 | 
			
		||||
      if (response.isSuccess()) {
 | 
			
		||||
        const posts = this.parsePostsInPage(response.value);
 | 
			
		||||
        fetchedPosts.push(...posts);
 | 
			
		||||
      } else throw response.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sorts the list of posts
 | 
			
		||||
    return fetchedPosts.sort((a, b) => (a.id > b.id ? 1 : b.id > a.id ? -1 : 0));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * It processes the rating of the thread
 | 
			
		||||
   * starting from the data contained in the JSON+LD tag.
 | 
			
		||||
| 
						 | 
				
			
			@ -229,6 +195,36 @@ export default class Thread {
 | 
			
		|||
    return name.trim();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process the HTML code received as
 | 
			
		||||
   * an answer and gets the data contained in it.
 | 
			
		||||
   */
 | 
			
		||||
  private async elaborateResponse(html: string) {
 | 
			
		||||
    // Load the HTML
 | 
			
		||||
    const $ = cheerio.load(html);
 | 
			
		||||
 | 
			
		||||
    // Fetch data from selectors
 | 
			
		||||
    const ownerID = $(THREAD.OWNER_ID).attr("data-user-id");
 | 
			
		||||
    const tagArray = $(THREAD.TAGS).toArray();
 | 
			
		||||
    const prefixArray = $(THREAD.PREFIXES).toArray();
 | 
			
		||||
    const JSONLD = getJSONLD($("body"));
 | 
			
		||||
    const published = JSONLD["datePublished"] as string;
 | 
			
		||||
    const modified = JSONLD["dateModified"] as string;
 | 
			
		||||
 | 
			
		||||
    // Parse the thread's data
 | 
			
		||||
    this._title = this.cleanHeadline(JSONLD["headline"] as string);
 | 
			
		||||
    this._tags = tagArray.map((el) => $(el).text().trim());
 | 
			
		||||
    this._prefixes = prefixArray.map((el) => $(el).text().trim());
 | 
			
		||||
    this._owner = new PlatformUser(parseInt(ownerID, 10));
 | 
			
		||||
    await this._owner.fetch();
 | 
			
		||||
    this._rating = this.parseRating(JSONLD);
 | 
			
		||||
    this._category = JSONLD["articleSection"] as TCategory;
 | 
			
		||||
 | 
			
		||||
    // Validate the dates
 | 
			
		||||
    if (DateTime.fromISO(modified).isValid) this._modified = new Date(modified);
 | 
			
		||||
    if (DateTime.fromISO(published).isValid) this._publication = new Date(published);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //#endregion Private methods
 | 
			
		||||
 | 
			
		||||
  //#region Public methods
 | 
			
		||||
| 
						 | 
				
			
			@ -237,38 +233,19 @@ export default class Thread {
 | 
			
		|||
   * Gets information about this thread.
 | 
			
		||||
   */
 | 
			
		||||
  public async fetch(): Promise<void> {
 | 
			
		||||
    // Check login
 | 
			
		||||
    if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
 | 
			
		||||
 | 
			
		||||
    // Check ID
 | 
			
		||||
    if (!this.id || this.id < 1) throw new InvalidID(INVALID_THREAD_ID);
 | 
			
		||||
 | 
			
		||||
    // Prepare the url
 | 
			
		||||
    this._url = new URL(this.id.toString(), urls.THREADS).toString();
 | 
			
		||||
 | 
			
		||||
    // Fetch the HTML source
 | 
			
		||||
    const htmlResponse = await fetchHTML(this.url);
 | 
			
		||||
 | 
			
		||||
    if (htmlResponse.isSuccess()) {
 | 
			
		||||
      // Load the HTML
 | 
			
		||||
      const $ = cheerio.load(htmlResponse.value);
 | 
			
		||||
 | 
			
		||||
      // Fetch data from selectors
 | 
			
		||||
      const ownerID = $(THREAD.OWNER_ID).attr("data-user-id");
 | 
			
		||||
      const tagArray = $(THREAD.TAGS).toArray();
 | 
			
		||||
      const prefixArray = $(THREAD.PREFIXES).toArray();
 | 
			
		||||
      const JSONLD = getJSONLD($("body"));
 | 
			
		||||
      const published = JSONLD["datePublished"] as string;
 | 
			
		||||
      const modified = JSONLD["dateModified"] as string;
 | 
			
		||||
 | 
			
		||||
      // Parse the thread's data
 | 
			
		||||
      this._title = this.cleanHeadline(JSONLD["headline"] as string);
 | 
			
		||||
      this._tags = tagArray.map((el) => $(el).text().trim());
 | 
			
		||||
      this._prefixes = prefixArray.map((el) => $(el).text().trim());
 | 
			
		||||
      this._owner = new PlatformUser(parseInt(ownerID, 10));
 | 
			
		||||
      await this._owner.fetch();
 | 
			
		||||
      this._rating = this.parseRating(JSONLD);
 | 
			
		||||
      this._category = JSONLD["articleSection"] as TCategory;
 | 
			
		||||
 | 
			
		||||
      // Validate the dates
 | 
			
		||||
      if (luxon.DateTime.fromISO(modified).isValid) this._modified = new Date(modified);
 | 
			
		||||
      if (luxon.DateTime.fromISO(published).isValid)
 | 
			
		||||
        this._publication = new Date(published);
 | 
			
		||||
    } else throw htmlResponse.value;
 | 
			
		||||
    const response = await fetchHTML(this.url);
 | 
			
		||||
    if (response.isSuccess()) await this.elaborateResponse(response.value);
 | 
			
		||||
    else throw response.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,13 +9,19 @@
 | 
			
		|||
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 { GENERIC, WATCHED_THREAD } from "../../constants/css-selector.js";
 | 
			
		||||
import { fetchHTML } from "../../network-helper.js";
 | 
			
		||||
import { GenericAxiosError, UnexpectedResponseContentType } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
import Post from "./post";
 | 
			
		||||
import PlatformUser from "./platform-user";
 | 
			
		||||
import { urls } from "../../constants/url";
 | 
			
		||||
import { GENERIC, WATCHED_THREAD } from "../../constants/css-selector";
 | 
			
		||||
import { fetchHTML } from "../../network-helper";
 | 
			
		||||
import {
 | 
			
		||||
  GenericAxiosError,
 | 
			
		||||
  UnexpectedResponseContentType,
 | 
			
		||||
  UserNotLogged,
 | 
			
		||||
  USER_NOT_LOGGED
 | 
			
		||||
} from "../errors";
 | 
			
		||||
import { Result } from "../result";
 | 
			
		||||
import shared from "../../shared";
 | 
			
		||||
 | 
			
		||||
// Interfaces
 | 
			
		||||
interface IWatchedThread {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +94,9 @@ export default class UserProfile extends PlatformUser {
 | 
			
		|||
  //#region Public methods
 | 
			
		||||
 | 
			
		||||
  public async fetch(): Promise<void> {
 | 
			
		||||
    // Check login
 | 
			
		||||
    if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
 | 
			
		||||
 | 
			
		||||
    // First get the user ID and set it
 | 
			
		||||
    const id = await this.fetchUserID();
 | 
			
		||||
    super.setID(id);
 | 
			
		||||
| 
						 | 
				
			
			@ -103,31 +112,39 @@ export default class UserProfile extends PlatformUser {
 | 
			
		|||
 | 
			
		||||
  //#region Private methods
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the ID of the user currently logged.
 | 
			
		||||
   */
 | 
			
		||||
  private async fetchUserID(): Promise<number> {
 | 
			
		||||
    // Local variables
 | 
			
		||||
    const url = new URL(urls.BASE).toString();
 | 
			
		||||
 | 
			
		||||
    // fetch and parse page
 | 
			
		||||
    const htmlResponse = await fetchHTML(url);
 | 
			
		||||
    if (htmlResponse.isSuccess()) {
 | 
			
		||||
    // Fetch and parse page
 | 
			
		||||
    const response = await fetchHTML(url);
 | 
			
		||||
    const result = response.applyOnSuccess((html) => {
 | 
			
		||||
      // Load page with cheerio
 | 
			
		||||
      const $ = cheerio.load(htmlResponse.value);
 | 
			
		||||
      const $ = cheerio.load(html);
 | 
			
		||||
 | 
			
		||||
      const sid = $(GENERIC.CURRENT_USER_ID).attr("data-user-id").trim();
 | 
			
		||||
      return parseInt(sid, 10);
 | 
			
		||||
    } else throw htmlResponse.value;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (result.isFailure()) throw result.value;
 | 
			
		||||
    else return result.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the list of threads followed by the user currently logged.
 | 
			
		||||
   */
 | 
			
		||||
  private async fetchWatchedThread(): Promise<IWatchedThread[]> {
 | 
			
		||||
    // Prepare and fetch URL
 | 
			
		||||
    const url = new URL(urls.WATCHED_THREADS);
 | 
			
		||||
    url.searchParams.set("unread", "0");
 | 
			
		||||
 | 
			
		||||
    const htmlResponse = await fetchHTML(url.toString());
 | 
			
		||||
 | 
			
		||||
    if (htmlResponse.isSuccess()) {
 | 
			
		||||
    const response = await fetchHTML(url.toString());
 | 
			
		||||
    const result = response.applyOnSuccess(async (html) => {
 | 
			
		||||
      // Load page in cheerio
 | 
			
		||||
      const $ = cheerio.load(htmlResponse.value);
 | 
			
		||||
      const $ = cheerio.load(html);
 | 
			
		||||
 | 
			
		||||
      // Fetch the pages
 | 
			
		||||
      const lastPage = parseInt($(WATCHED_THREAD.LAST_PAGE).text().trim(), 10);
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +156,10 @@ export default class UserProfile extends PlatformUser {
 | 
			
		|||
      });
 | 
			
		||||
 | 
			
		||||
      return [].concat(...watchedThreads);
 | 
			
		||||
    } else throw htmlResponse.value;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (result.isFailure()) throw result.value;
 | 
			
		||||
    else return result.value as Promise<IWatchedThread[]>;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import shared, { TPrefixDict } from "../shared.js";
 | 
			
		||||
import shared, { TPrefixDict } from "../shared";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert prefixes and platform tags from string to ID and vice versa.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,16 +5,16 @@
 | 
			
		|||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
import { AxiosResponse } from "axios";
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import validator from "class-validator";
 | 
			
		||||
import { IsInt, Min, validateSync } from "class-validator";
 | 
			
		||||
import { AxiosResponse } from "axios";
 | 
			
		||||
 | 
			
		||||
// Module from files
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
			
		||||
import { GenericAxiosError } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
import LatestSearchQuery, { TLatestOrder } from "./latest-search-query.js";
 | 
			
		||||
import ThreadSearchQuery, { TThreadOrder } from "./thread-search-query.js";
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces";
 | 
			
		||||
import { GenericAxiosError } from "../errors";
 | 
			
		||||
import { Result } from "../result";
 | 
			
		||||
import LatestSearchQuery, { TLatestOrder } from "./latest-search-query";
 | 
			
		||||
import ThreadSearchQuery, { TThreadOrder } from "./thread-search-query";
 | 
			
		||||
 | 
			
		||||
// Type definitions
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +70,10 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
   * Results presentation order.
 | 
			
		||||
   */
 | 
			
		||||
  public order: THandiworkOrder = "relevance";
 | 
			
		||||
  @validator.IsInt({
 | 
			
		||||
  @IsInt({
 | 
			
		||||
    message: "$property expect an integer, received $value"
 | 
			
		||||
  })
 | 
			
		||||
  @validator.Min(HandiworkSearchQuery.MIN_PAGE, {
 | 
			
		||||
  @Min(HandiworkSearchQuery.MIN_PAGE, {
 | 
			
		||||
    message: "The minimum $property value must be $constraint1, received $value"
 | 
			
		||||
  })
 | 
			
		||||
  public page = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  public validate(): boolean {
 | 
			
		||||
    return validator.validateSync(this).length === 0;
 | 
			
		||||
    return validateSync(this).length === 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async execute(): Promise<TExecuteResult> {
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +112,7 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
 | 
			
		||||
    // Check if the query is valid
 | 
			
		||||
    if (!this.validate()) {
 | 
			
		||||
      throw new Error(`Invalid query: ${validator.validateSync(this).join("\n")}`);
 | 
			
		||||
      throw new Error(`Invalid query: ${validateSync(this).join("\n")}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Convert the query
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +144,7 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
    // Cast the basic query object and copy common values
 | 
			
		||||
    const query: LatestSearchQuery = new LatestSearchQuery();
 | 
			
		||||
    Object.keys(this).forEach((key) => {
 | 
			
		||||
      if (query.hasOwnProperty(key)) {
 | 
			
		||||
      if (Object.prototype.hasOwnProperty.call(query, key)) {
 | 
			
		||||
        query[key] = this[key];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +165,7 @@ export default class HandiworkSearchQuery implements IQuery {
 | 
			
		|||
    // Cast the basic query object and copy common values
 | 
			
		||||
    const query: ThreadSearchQuery = new ThreadSearchQuery();
 | 
			
		||||
    Object.keys(this).forEach((key) => {
 | 
			
		||||
      if (query.hasOwnProperty(key)) {
 | 
			
		||||
      if (Object.prototype.hasOwnProperty.call(query, key)) {
 | 
			
		||||
        query[key] = this[key];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,16 +6,16 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import validator from "class-validator";
 | 
			
		||||
import { ArrayMaxSize, IsInt, Min, validateSync } from "class-validator";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import { urls } from "../../constants/url.js";
 | 
			
		||||
import PrefixParser from "../prefix-parser.js";
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
			
		||||
import { fetchGETResponse } from "../../network-helper.js";
 | 
			
		||||
import { urls } from "../../constants/url";
 | 
			
		||||
import PrefixParser from "../prefix-parser";
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces";
 | 
			
		||||
import { fetchGETResponse } from "../../network-helper";
 | 
			
		||||
import { AxiosResponse } from "axios";
 | 
			
		||||
import { GenericAxiosError } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
import { GenericAxiosError } from "../errors";
 | 
			
		||||
import { Result } from "../result";
 | 
			
		||||
 | 
			
		||||
// Type definitions
 | 
			
		||||
export type TLatestOrder = "date" | "likes" | "views" | "title" | "rating";
 | 
			
		||||
| 
						 | 
				
			
			@ -49,16 +49,16 @@ export default class LatestSearchQuery implements IQuery {
 | 
			
		|||
   */
 | 
			
		||||
  public date: TDate = null;
 | 
			
		||||
 | 
			
		||||
  @validator.ArrayMaxSize(LatestSearchQuery.MAX_TAGS, {
 | 
			
		||||
  @ArrayMaxSize(LatestSearchQuery.MAX_TAGS, {
 | 
			
		||||
    message: "Too many tags: $value instead of $constraint1"
 | 
			
		||||
  })
 | 
			
		||||
  public includedTags: string[] = [];
 | 
			
		||||
  public includedPrefixes: string[] = [];
 | 
			
		||||
 | 
			
		||||
  @validator.IsInt({
 | 
			
		||||
  @IsInt({
 | 
			
		||||
    message: "$property expect an integer, received $value"
 | 
			
		||||
  })
 | 
			
		||||
  @validator.Min(LatestSearchQuery.MIN_PAGE, {
 | 
			
		||||
  @Min(LatestSearchQuery.MIN_PAGE, {
 | 
			
		||||
    message: "The minimum $property value must be $constraint1, received $value"
 | 
			
		||||
  })
 | 
			
		||||
  public page = LatestSearchQuery.MIN_PAGE;
 | 
			
		||||
| 
						 | 
				
			
			@ -69,13 +69,13 @@ export default class LatestSearchQuery implements IQuery {
 | 
			
		|||
  //#region Public methods
 | 
			
		||||
 | 
			
		||||
  public validate(): boolean {
 | 
			
		||||
    return validator.validateSync(this).length === 0;
 | 
			
		||||
    return validateSync(this).length === 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
 | 
			
		||||
    // Check if the query is valid
 | 
			
		||||
    if (!this.validate()) {
 | 
			
		||||
      throw new Error(`Invalid query: ${validator.validateSync(this).join("\n")}`);
 | 
			
		||||
      throw new Error(`Invalid query: ${validateSync(this).join("\n")}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prepare the URL
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,17 +6,17 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import validator from "class-validator";
 | 
			
		||||
import { IsInt, Min, validateSync } from "class-validator";
 | 
			
		||||
 | 
			
		||||
// Module from files
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
			
		||||
import { urls } from "../../constants/url.js";
 | 
			
		||||
import PrefixParser from "./../prefix-parser.js";
 | 
			
		||||
import { fetchPOSTResponse } from "../../network-helper.js";
 | 
			
		||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces";
 | 
			
		||||
import { urls } from "../../constants/url";
 | 
			
		||||
import PrefixParser from "./../prefix-parser";
 | 
			
		||||
import { fetchPOSTResponse } from "../../network-helper";
 | 
			
		||||
import { AxiosResponse } from "axios";
 | 
			
		||||
import { GenericAxiosError } from "../errors.js";
 | 
			
		||||
import { Result } from "../result.js";
 | 
			
		||||
import Shared from "../../shared.js";
 | 
			
		||||
import { GenericAxiosError } from "../errors";
 | 
			
		||||
import { Result } from "../result";
 | 
			
		||||
import Shared from "../../shared";
 | 
			
		||||
 | 
			
		||||
// Type definitions
 | 
			
		||||
export type TThreadOrder = "relevance" | "date" | "last_update" | "replies";
 | 
			
		||||
| 
						 | 
				
			
			@ -61,10 +61,10 @@ export default class ThreadSearchQuery implements IQuery {
 | 
			
		|||
   * Results presentation order.
 | 
			
		||||
   */
 | 
			
		||||
  public order: TThreadOrder = "relevance";
 | 
			
		||||
  @validator.IsInt({
 | 
			
		||||
  @IsInt({
 | 
			
		||||
    message: "$property expect an integer, received $value"
 | 
			
		||||
  })
 | 
			
		||||
  @validator.Min(ThreadSearchQuery.MIN_PAGE, {
 | 
			
		||||
  @Min(ThreadSearchQuery.MIN_PAGE, {
 | 
			
		||||
    message: "The minimum $property value must be $constraint1, received $value"
 | 
			
		||||
  })
 | 
			
		||||
  public page = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -75,13 +75,13 @@ export default class ThreadSearchQuery implements IQuery {
 | 
			
		|||
  //#region Public methods
 | 
			
		||||
 | 
			
		||||
  public validate(): boolean {
 | 
			
		||||
    return validator.validateSync(this).length === 0;
 | 
			
		||||
    return validateSync(this).length === 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
 | 
			
		||||
    // Check if the query is valid
 | 
			
		||||
    if (!this.validate()) {
 | 
			
		||||
      throw new Error(`Invalid query: ${validator.validateSync(this).join("\n")}`);
 | 
			
		||||
      throw new Error(`Invalid query: ${validateSync(this).join("\n")}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Define the POST parameters
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ export default class Session {
 | 
			
		|||
  /**
 | 
			
		||||
   * Max number of days the session is valid.
 | 
			
		||||
   */
 | 
			
		||||
  private readonly SESSION_TIME: number = 3;
 | 
			
		||||
  private readonly SESSION_TIME: number = 1;
 | 
			
		||||
  private readonly COOKIEJAR_FILENAME: string = "f95cookiejar.json";
 | 
			
		||||
  private _path: string;
 | 
			
		||||
  private _isMapped: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,7 @@ export default class Session {
 | 
			
		|||
    const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
 | 
			
		||||
    const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
 | 
			
		||||
 | 
			
		||||
    return Math.floor((utc2 - utc1) / MS_PER_DAY);
 | 
			
		||||
    return Math.abs(Math.floor((utc2 - utc1) / MS_PER_DAY));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,8 @@
 | 
			
		|||
import HandiworkSearchQuery from "../classes/query/handiwork-search-query";
 | 
			
		||||
import LatestSearchQuery from "../classes/query/latest-search-query";
 | 
			
		||||
import ThreadSearchQuery from "../classes/query/thread-search-query";
 | 
			
		||||
import fetchLatestHandiworkURLs from "./fetch-latest.js";
 | 
			
		||||
import fetchThreadHandiworkURLs from "./fetch-thread.js";
 | 
			
		||||
import fetchLatestHandiworkURLs from "./fetch-latest";
 | 
			
		||||
import fetchThreadHandiworkURLs from "./fetch-thread";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the URLs of the handiworks that match the passed parameters.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,8 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import LatestSearchQuery from "../classes/query/latest-search-query.js";
 | 
			
		||||
import { urls } from "../constants/url.js";
 | 
			
		||||
import LatestSearchQuery from "../classes/query/latest-search-query";
 | 
			
		||||
import { urls } from "../constants/url";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the URLs of the latest handiworks that match the passed parameters.
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ import { urls } from "../constants/url.js";
 | 
			
		|||
 */
 | 
			
		||||
export default async function fetchLatestHandiworkURLs(
 | 
			
		||||
  query: LatestSearchQuery,
 | 
			
		||||
  limit = 30
 | 
			
		||||
  limit: number = 30
 | 
			
		||||
): Promise<string[]> {
 | 
			
		||||
  // Local variables
 | 
			
		||||
  const shallowQuery: LatestSearchQuery = Object.assign(new LatestSearchQuery(), query);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,10 +12,10 @@ import { readFileSync, writeFileSync, existsSync } from "fs";
 | 
			
		|||
import cheerio from "cheerio";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import shared, { TPrefixDict } from "../shared.js";
 | 
			
		||||
import { urls as f95url } from "../constants/url.js";
 | 
			
		||||
import { GENERIC } from "../constants/css-selector.js";
 | 
			
		||||
import { fetchHTML } from "../network-helper.js";
 | 
			
		||||
import shared, { TPrefixDict } from "../shared";
 | 
			
		||||
import { urls as f95url } from "../constants/url";
 | 
			
		||||
import { GENERIC } from "../constants/css-selector";
 | 
			
		||||
import { fetchHTML } from "../network-helper";
 | 
			
		||||
 | 
			
		||||
//#region Interface definitions
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ interface ICategoryResource {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the set of tags present on the platform-
 | 
			
		||||
 * Represents the set of tags present on the platform.
 | 
			
		||||
 */
 | 
			
		||||
interface ILatestResource {
 | 
			
		||||
  prefixes: { [s: string]: ICategoryResource[] };
 | 
			
		||||
| 
						 | 
				
			
			@ -58,18 +58,20 @@ export default async function fetchPlatformData(): Promise<void> {
 | 
			
		|||
  // Check if the data are cached
 | 
			
		||||
  if (!readCache(shared.cachePath)) {
 | 
			
		||||
    // Load the HTML
 | 
			
		||||
    const html = await fetchHTML(f95url.LATEST_UPDATES);
 | 
			
		||||
    const response = await fetchHTML(f95url.LATEST_UPDATES);
 | 
			
		||||
 | 
			
		||||
    // Parse data
 | 
			
		||||
    if (html.isSuccess()) {
 | 
			
		||||
      const data = parseLatestPlatformHTML(html.value);
 | 
			
		||||
    const result = response.applyOnSuccess((html) => {
 | 
			
		||||
      const data = parseLatestPlatformHTML(html);
 | 
			
		||||
 | 
			
		||||
      // Assign data
 | 
			
		||||
      assignLatestPlatformData(data);
 | 
			
		||||
 | 
			
		||||
      // Cache data
 | 
			
		||||
      saveCache(shared.cachePath);
 | 
			
		||||
    } else throw html.value;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (result.isFailure()) throw result.value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -140,12 +142,19 @@ function assignLatestPlatformData(data: ILatestResource): void {
 | 
			
		|||
      // Prepare the dict
 | 
			
		||||
      const dict: TPrefixDict = {};
 | 
			
		||||
 | 
			
		||||
      for (const e of res.prefixes) {
 | 
			
		||||
        dict[e.id] = e.name.replace("'", "'");
 | 
			
		||||
      }
 | 
			
		||||
      // Assign values
 | 
			
		||||
      res.prefixes.map((e) => (dict[e.id] = e.name.replace("'", "'")));
 | 
			
		||||
 | 
			
		||||
      // Save the property
 | 
			
		||||
      scrapedData[res.name] = dict;
 | 
			
		||||
      // Merge the dicts ("Other"/"Status" field)
 | 
			
		||||
      if (scrapedData[res.name]) {
 | 
			
		||||
        const newKeys = Object.keys(dict)
 | 
			
		||||
          .map((k) => parseInt(k, 10))
 | 
			
		||||
          .filter((k) => !scrapedData[res.name][k]);
 | 
			
		||||
 | 
			
		||||
        newKeys.map((k) => (scrapedData[res.name][k] = dict[k]));
 | 
			
		||||
      }
 | 
			
		||||
      // Assign the property
 | 
			
		||||
      else scrapedData[res.name] = dict;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,40 +0,0 @@
 | 
			
		|||
// Copyright (c) 2021 MillenniumEarl
 | 
			
		||||
//
 | 
			
		||||
// This software is released under the MIT License.
 | 
			
		||||
// https://opensource.org/licenses/MIT
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Modules from files
 | 
			
		||||
import fetchHandiworkURLs from "./fetch-handiwork.js";
 | 
			
		||||
import fetchLatestHandiworkURLs from "./fetch-latest.js";
 | 
			
		||||
import fetchThreadHandiworkURLs from "./fetch-thread.js";
 | 
			
		||||
import HandiworkSearchQuery from "../classes/query/handiwork-search-query.js";
 | 
			
		||||
import LatestSearchQuery from "../classes/query/latest-search-query.js";
 | 
			
		||||
import ThreadSearchQuery from "../classes/query/thread-search-query.js";
 | 
			
		||||
import { IQuery } from "../interfaces.js";
 | 
			
		||||
 | 
			
		||||
//#region Public methods
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param query Query used for the search
 | 
			
		||||
 * @param limit Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns URLs of the fetched games
 | 
			
		||||
 */
 | 
			
		||||
export default async function getURLsFromQuery(
 | 
			
		||||
  query: IQuery,
 | 
			
		||||
  limit = 30
 | 
			
		||||
): Promise<string[]> {
 | 
			
		||||
  switch (query.itype) {
 | 
			
		||||
    case "HandiworkSearchQuery":
 | 
			
		||||
      return fetchHandiworkURLs(query as HandiworkSearchQuery, limit);
 | 
			
		||||
    case "LatestSearchQuery":
 | 
			
		||||
      return fetchLatestHandiworkURLs(query as LatestSearchQuery, limit);
 | 
			
		||||
    case "ThreadSearchQuery":
 | 
			
		||||
      return fetchThreadHandiworkURLs(query as ThreadSearchQuery, limit);
 | 
			
		||||
    default:
 | 
			
		||||
      throw Error(`Invalid query type: ${query.itype}`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
| 
						 | 
				
			
			@ -9,10 +9,10 @@
 | 
			
		|||
import cheerio from "cheerio";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import shared from "../shared.js";
 | 
			
		||||
import { THREAD_SEARCH } from "../constants/css-selector.js";
 | 
			
		||||
import { urls as f95urls } from "../constants/url.js";
 | 
			
		||||
import ThreadSearchQuery from "../classes/query/thread-search-query.js";
 | 
			
		||||
import shared from "../shared";
 | 
			
		||||
import { THREAD_SEARCH } from "../constants/css-selector";
 | 
			
		||||
import { urls as f95urls } from "../constants/url";
 | 
			
		||||
import ThreadSearchQuery from "../classes/query/thread-search-query";
 | 
			
		||||
 | 
			
		||||
//#region Public methods
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -316,3 +316,15 @@ export interface IQuery {
 | 
			
		|||
   */
 | 
			
		||||
  execute(): any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * It represents an object that obtains the data
 | 
			
		||||
 * only on the explicit request of the user and
 | 
			
		||||
 * only after its establishment.
 | 
			
		||||
 */
 | 
			
		||||
export interface ILazy {
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the data relating to the object.
 | 
			
		||||
   */
 | 
			
		||||
  fetch(): Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,21 +11,20 @@ import cheerio from "cheerio";
 | 
			
		|||
import axiosCookieJarSupport from "axios-cookiejar-support";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import shared from "./shared.js";
 | 
			
		||||
import { urls } from "./constants/url.js";
 | 
			
		||||
import { GENERIC } from "./constants/css-selector.js";
 | 
			
		||||
import LoginResult from "./classes/login-result.js";
 | 
			
		||||
import { failure, Result, success } from "./classes/result.js";
 | 
			
		||||
import shared from "./shared";
 | 
			
		||||
import { urls } from "./constants/url";
 | 
			
		||||
import { GENERIC } from "./constants/css-selector";
 | 
			
		||||
import LoginResult from "./classes/login-result";
 | 
			
		||||
import { failure, Result, success } from "./classes/result";
 | 
			
		||||
import {
 | 
			
		||||
  GenericAxiosError,
 | 
			
		||||
  InvalidF95Token,
 | 
			
		||||
  UnexpectedResponseContentType
 | 
			
		||||
} from "./classes/errors.js";
 | 
			
		||||
import Credentials from "./classes/credentials.js";
 | 
			
		||||
} from "./classes/errors";
 | 
			
		||||
import Credentials from "./classes/credentials";
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
// Configure axios to use the cookie jar
 | 
			
		||||
axiosCookieJarSupport.default(axios);
 | 
			
		||||
axiosCookieJarSupport(axios);
 | 
			
		||||
 | 
			
		||||
// Global variables
 | 
			
		||||
const userAgent =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,18 +6,14 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import luxon from "luxon";
 | 
			
		||||
import { DateTime } from "luxon";
 | 
			
		||||
 | 
			
		||||
// Modules from files
 | 
			
		||||
import HandiWork from "../classes/handiwork/handiwork.js";
 | 
			
		||||
import Thread from "../classes/mapping/thread.js";
 | 
			
		||||
import { IBasic, TAuthor, TEngine, TExternalPlatform, TStatus } from "../interfaces.js";
 | 
			
		||||
import shared, { TPrefixDict } from "../shared.js";
 | 
			
		||||
import { ILink, IPostElement } from "./post-parse.js";
 | 
			
		||||
 | 
			
		||||
export async function getHandiworkInformation<T extends IBasic>(url: string): Promise<T>;
 | 
			
		||||
 | 
			
		||||
export async function getHandiworkInformation<T extends IBasic>(url: string): Promise<T>;
 | 
			
		||||
import HandiWork from "../classes/handiwork/handiwork";
 | 
			
		||||
import Thread from "../classes/mapping/thread";
 | 
			
		||||
import { IBasic, TAuthor, TEngine, TExternalPlatform, TStatus } from "../interfaces";
 | 
			
		||||
import shared, { TPrefixDict } from "../shared";
 | 
			
		||||
import { ILink, IPostElement } from "./post-parse";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets information of a particular handiwork from its thread.
 | 
			
		||||
| 
						 | 
				
			
			@ -226,7 +222,7 @@ function fillWithPostData(hw: HandiWork, elements: IPostElement[]) {
 | 
			
		|||
 | 
			
		||||
  // Fill the dates
 | 
			
		||||
  const releaseDate = getPostElementByName(elements, "release date")?.text;
 | 
			
		||||
  if (luxon.DateTime.fromISO(releaseDate).isValid) hw.lastRelease = new Date(releaseDate);
 | 
			
		||||
  if (DateTime.fromISO(releaseDate).isValid) hw.lastRelease = new Date(releaseDate);
 | 
			
		||||
 | 
			
		||||
  //#region Convert the author
 | 
			
		||||
  const authorElement =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,8 @@
 | 
			
		|||
import cheerio from "cheerio";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import shared from "../shared.js";
 | 
			
		||||
import { THREAD } from "../constants/css-selector.js";
 | 
			
		||||
import shared from "../shared";
 | 
			
		||||
import { THREAD } from "../constants/css-selector";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents information contained in a JSON+LD tag.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,16 +3,19 @@
 | 
			
		|||
// This software is released under the MIT License.
 | 
			
		||||
// https://opensource.org/licenses/MIT
 | 
			
		||||
 | 
			
		||||
/* eslint-disable @typescript-eslint/no-inferrable-types */
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import { IBasic, IQuery } from "./interfaces.js";
 | 
			
		||||
import getHandiworkInformation from "./scrape-data/handiwork-parse.js";
 | 
			
		||||
import getURLsFromQuery from "./fetch-data/fetch-query.js";
 | 
			
		||||
import { IBasic, IQuery } from "./interfaces";
 | 
			
		||||
import getHandiworkInformation from "./scrape-data/handiwork-parse";
 | 
			
		||||
import { HandiworkSearchQuery, LatestSearchQuery, ThreadSearchQuery } from "..";
 | 
			
		||||
import fetchHandiworkURLs from "./fetch-data/fetch-handiwork";
 | 
			
		||||
import fetchLatestHandiworkURLs from "./fetch-data/fetch-latest";
 | 
			
		||||
import fetchThreadHandiworkURLs from "./fetch-data/fetch-thread";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the handiworks that match the passed parameters.
 | 
			
		||||
 *
 | 
			
		||||
 * You *must* be logged.
 | 
			
		||||
 * @param {Number} limit
 | 
			
		||||
 * Maximum number of items to get. Default: 30
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +32,25 @@ export default async function search<T extends IBasic>(
 | 
			
		|||
 | 
			
		||||
  return Promise.all(results);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#region Private methods
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param query Query used for the search
 | 
			
		||||
 * @param limit Maximum number of items to get. Default: 30
 | 
			
		||||
 * @returns URLs of the fetched games
 | 
			
		||||
 */
 | 
			
		||||
async function getURLsFromQuery(query: IQuery, limit = 30): Promise<string[]> {
 | 
			
		||||
  switch (query.itype) {
 | 
			
		||||
    case "HandiworkSearchQuery":
 | 
			
		||||
      return fetchHandiworkURLs(query as HandiworkSearchQuery, limit);
 | 
			
		||||
    case "LatestSearchQuery":
 | 
			
		||||
      return fetchLatestHandiworkURLs(query as LatestSearchQuery, limit);
 | 
			
		||||
    case "ThreadSearchQuery":
 | 
			
		||||
      return fetchThreadHandiworkURLs(query as ThreadSearchQuery, limit);
 | 
			
		||||
    default:
 | 
			
		||||
      throw Error(`Invalid query type: ${query.itype}`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#endregion Private methods
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ import { join } from "path";
 | 
			
		|||
import log4js from "log4js";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import Session from "./classes/session.js";
 | 
			
		||||
import Session from "./classes/session";
 | 
			
		||||
 | 
			
		||||
// Types declaration
 | 
			
		||||
export type TPrefixDict = { [n: number]: string };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public module from npm
 | 
			
		||||
import { expect } from "chai";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import Credentials from "../../src/scripts/classes/credentials";
 | 
			
		||||
 | 
			
		||||
export function suite(): void {
 | 
			
		||||
  it("Check token formatting", async function testValidToken() {
 | 
			
		||||
    // Token example:
 | 
			
		||||
    // 1604309951,0338213c00fcbd894fd9415e6ba08403
 | 
			
		||||
    // 1604309986,ebdb75502337699381f0f55c86353555
 | 
			
		||||
    // 1604310008,2d50d55808e5ec3a157ec01953da9d26
 | 
			
		||||
 | 
			
		||||
    // Fetch token (is a GET request, we don't need the credentials)
 | 
			
		||||
    const cred = new Credentials(null, null);
 | 
			
		||||
    await cred.fetchToken();
 | 
			
		||||
 | 
			
		||||
    // Parse token for assert
 | 
			
		||||
    const splitted = cred.token.split(",");
 | 
			
		||||
    const unique = splitted[0];
 | 
			
		||||
    const hash = splitted[1];
 | 
			
		||||
    expect(splitted.length).to.be.equal(2, "The token consists of two parts");
 | 
			
		||||
 | 
			
		||||
    // Check type of parts
 | 
			
		||||
    expect(isNumeric(unique)).to.be.true;
 | 
			
		||||
    expect(isNumeric(hash)).to.be.false;
 | 
			
		||||
 | 
			
		||||
    // The second part is most probably the MD5 hash of something
 | 
			
		||||
    expect(hash.length).to.be.equal(32, "Hash should have 32 hex chars");
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#region Private methods
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if a string is a number.
 | 
			
		||||
 * @author Jeremy
 | 
			
		||||
 * @see https://preview.tinyurl.com/y46jqwkt
 | 
			
		||||
 */
 | 
			
		||||
function isNumeric(num: any): boolean {
 | 
			
		||||
  const isNan = isNaN(num as number);
 | 
			
		||||
  const isNum = typeof num === "number";
 | 
			
		||||
  const isValidString = typeof num === "string" && num.trim() !== "";
 | 
			
		||||
 | 
			
		||||
  return (isNum || isValidString) && !isNan;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public module from npm
 | 
			
		||||
import chai from "chai";
 | 
			
		||||
import chaiAsPromised from "chai-as-promised";
 | 
			
		||||
import { INVALID_USER_ID, USER_NOT_LOGGED } from "../../../src/scripts/classes/errors";
 | 
			
		||||
 | 
			
		||||
// Module from files
 | 
			
		||||
import { PlatformUser } from "../../../src";
 | 
			
		||||
import Shared from "../../../src/scripts/shared";
 | 
			
		||||
 | 
			
		||||
chai.use(chaiAsPromised);
 | 
			
		||||
const { expect } = chai;
 | 
			
		||||
 | 
			
		||||
export function suite(): void {
 | 
			
		||||
  it("Set invalid ID", function setInvalidID() {
 | 
			
		||||
    const user = new PlatformUser();
 | 
			
		||||
    expect(user.setID(-1)).to.be.rejectedWith(INVALID_USER_ID);
 | 
			
		||||
    expect(user.setID(null)).to.be.rejectedWith(INVALID_USER_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch platform user without ID", async function fetchWithoutID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const user = new PlatformUser();
 | 
			
		||||
    await expect(user.fetch()).to.be.rejectedWith(INVALID_USER_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch platform user with null ID", async function fetchWithNullID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const user = new PlatformUser(null);
 | 
			
		||||
    await expect(user.fetch()).to.be.rejectedWith(INVALID_USER_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch platform user with invalid ID", async function fetchWithInvalidID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const user = new PlatformUser(-1);
 | 
			
		||||
    await expect(user.fetch()).to.be.rejectedWith(INVALID_USER_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch platform user without authentication", async function fetchWithoutAuth() {
 | 
			
		||||
    Shared.setIsLogged(false);
 | 
			
		||||
    const user = new PlatformUser(1234);
 | 
			
		||||
    await expect(user.fetch()).to.be.rejectedWith(USER_NOT_LOGGED);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public module from npm
 | 
			
		||||
import chai from "chai";
 | 
			
		||||
import chaiAsPromised from "chai-as-promised";
 | 
			
		||||
import { INVALID_POST_ID, USER_NOT_LOGGED } from "../../../src/scripts/classes/errors";
 | 
			
		||||
 | 
			
		||||
// Module from files
 | 
			
		||||
import { Post } from "../../../src";
 | 
			
		||||
import Shared from "../../../src/scripts/shared";
 | 
			
		||||
 | 
			
		||||
chai.use(chaiAsPromised);
 | 
			
		||||
const { expect } = chai;
 | 
			
		||||
 | 
			
		||||
export function suite(): void {
 | 
			
		||||
  it("Fetch post with null ID", async function fetchWithNullID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const post = new Post(null);
 | 
			
		||||
    await expect(post.fetch()).to.be.rejectedWith(INVALID_POST_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch post with invalid ID", async function fetchWithInvalidID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const post = new Post(-1);
 | 
			
		||||
    await expect(post.fetch()).to.be.rejectedWith(INVALID_POST_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch post without authentication", async function fetchWithoutAuth() {
 | 
			
		||||
    Shared.setIsLogged(false);
 | 
			
		||||
    const post = new Post(1234);
 | 
			
		||||
    await expect(post.fetch()).to.be.rejectedWith(USER_NOT_LOGGED);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public module from npm
 | 
			
		||||
import chai from "chai";
 | 
			
		||||
import chaiAsPromised from "chai-as-promised";
 | 
			
		||||
import { INVALID_THREAD_ID, USER_NOT_LOGGED } from "../../../src/scripts/classes/errors";
 | 
			
		||||
 | 
			
		||||
// Module from files
 | 
			
		||||
import { Thread } from "../../../src";
 | 
			
		||||
import Shared from "../../../src/scripts/shared";
 | 
			
		||||
 | 
			
		||||
chai.use(chaiAsPromised);
 | 
			
		||||
const { expect } = chai;
 | 
			
		||||
 | 
			
		||||
export function suite(): void {
 | 
			
		||||
  it("Fetch thread with invalid ID", async function fetchWithInvalidID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const thread = new Thread(-1);
 | 
			
		||||
    await expect(thread.fetch()).to.be.rejectedWith(INVALID_THREAD_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch thread with null ID", async function fetchWithNullID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const thread = new Thread(null);
 | 
			
		||||
    await expect(thread.fetch()).to.be.rejectedWith(INVALID_THREAD_ID);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch thread without authentication", async function fetchWithoutAuth() {
 | 
			
		||||
    Shared.setIsLogged(false);
 | 
			
		||||
    const thread = new Thread(1234);
 | 
			
		||||
    await expect(thread.fetch()).to.be.rejectedWith(USER_NOT_LOGGED);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Fetch post with invalid ID", async function fetchWithInvalidID() {
 | 
			
		||||
    Shared.setIsLogged(true);
 | 
			
		||||
    const thread = new Thread(-1);
 | 
			
		||||
    await expect(thread.getPost(0)).to.be.rejectedWith(
 | 
			
		||||
      "Index must be greater or equal than 1"
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public module from npm
 | 
			
		||||
import chai from "chai";
 | 
			
		||||
import chaiAsPromised from "chai-as-promised";
 | 
			
		||||
import { INVALID_USER_ID, USER_NOT_LOGGED } from "../../../src/scripts/classes/errors";
 | 
			
		||||
 | 
			
		||||
// Module from files
 | 
			
		||||
import { UserProfile } from "../../../src";
 | 
			
		||||
import Shared from "../../../src/scripts/shared";
 | 
			
		||||
 | 
			
		||||
chai.use(chaiAsPromised);
 | 
			
		||||
const { expect } = chai;
 | 
			
		||||
 | 
			
		||||
export function suite(): void {
 | 
			
		||||
  it("Fetch profile without authentication", async function fetchWithoutAuth() {
 | 
			
		||||
    Shared.setIsLogged(false);
 | 
			
		||||
    const up = new UserProfile();
 | 
			
		||||
    await expect(up.fetch()).to.be.rejectedWith(USER_NOT_LOGGED);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public module from npm
 | 
			
		||||
import { expect } from "chai";
 | 
			
		||||
import dotenv from "dotenv";
 | 
			
		||||
 | 
			
		||||
// Modules from file
 | 
			
		||||
import { login, PrefixParser } from "../../src/index";
 | 
			
		||||
 | 
			
		||||
// Configure the .env reader
 | 
			
		||||
dotenv.config();
 | 
			
		||||
 | 
			
		||||
// Global variables
 | 
			
		||||
const USERNAME = process.env.F95_USERNAME;
 | 
			
		||||
const PASSWORD = process.env.F95_PASSWORD;
 | 
			
		||||
 | 
			
		||||
export function suite(): void {
 | 
			
		||||
  //#region Setup
 | 
			
		||||
 | 
			
		||||
  before(async function beforeAll() {
 | 
			
		||||
    await login(USERNAME, PASSWORD);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //#endregion Setup
 | 
			
		||||
 | 
			
		||||
  it("Parse prefixes", async function testPrefixParser() {
 | 
			
		||||
    // Create a new parser
 | 
			
		||||
    const parser = new PrefixParser();
 | 
			
		||||
 | 
			
		||||
    // Test values
 | 
			
		||||
    const testIDs = [103, 225, 44, 13, 2, 7, 22];
 | 
			
		||||
    const testPrefixes = [
 | 
			
		||||
      "corruption",
 | 
			
		||||
      "pregnancy",
 | 
			
		||||
      "slave",
 | 
			
		||||
      "VN",
 | 
			
		||||
      "RPGM",
 | 
			
		||||
      "Ren'Py",
 | 
			
		||||
      "Abandoned"
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Parse values
 | 
			
		||||
    const ids = parser.prefixesToIDs(testPrefixes);
 | 
			
		||||
    const tags = parser.idsToPrefixes(ids);
 | 
			
		||||
 | 
			
		||||
    // Assert equality
 | 
			
		||||
    expect(testPrefixes).to.be.deep.equal(tags, "The tags must be the same");
 | 
			
		||||
    expect(testIDs).to.be.deep.equal(ids, "The IDs must be the same");
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Public modules from npm
 | 
			
		||||
import dotenv from "dotenv";
 | 
			
		||||
import inquirer from "inquirer";
 | 
			
		||||
 | 
			
		||||
// Modulee from files
 | 
			
		||||
import { login } from "../src/index";
 | 
			
		||||
import LoginResult from "../src/scripts/classes/login-result";
 | 
			
		||||
 | 
			
		||||
// Configure the .env reader
 | 
			
		||||
dotenv.config();
 | 
			
		||||
 | 
			
		||||
export async function auth(): Promise<LoginResult> {
 | 
			
		||||
  return login(process.env.F95_USERNAME, process.env.F95_PASSWORD, insert2faCode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#region Private methods
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ask the user to enter the OTP code
 | 
			
		||||
 * necessary to authenticate on the server.
 | 
			
		||||
 */
 | 
			
		||||
async function insert2faCode(): Promise<number> {
 | 
			
		||||
  const questions = [
 | 
			
		||||
    {
 | 
			
		||||
      type: "input",
 | 
			
		||||
      name: "code",
 | 
			
		||||
      message: "Insert 2FA code:"
 | 
			
		||||
    }
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  // Prompt the user to insert the code
 | 
			
		||||
  const answers = await inquirer.prompt(questions);
 | 
			
		||||
  return answers.code as number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#endregion Private methods
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Import suites
 | 
			
		||||
import { suite as credentials } from "./classes/credentials";
 | 
			
		||||
import { suite as prefixParser } from "./classes/prefix-parser";
 | 
			
		||||
import { suite as platformUser } from "./classes/mapping/platform-user";
 | 
			
		||||
import { suite as post } from "./classes/mapping/post";
 | 
			
		||||
import { suite as thread } from "./classes/mapping/thread";
 | 
			
		||||
import { suite as userProfile } from "./classes/mapping/user-profile";
 | 
			
		||||
 | 
			
		||||
describe("Test basic function", function testBasic() {
 | 
			
		||||
  //#region Set-up
 | 
			
		||||
 | 
			
		||||
  this.timeout(30000); // All tests in this suite get 30 seconds before timeout
 | 
			
		||||
 | 
			
		||||
  //#endregion Set-up
 | 
			
		||||
 | 
			
		||||
  // describe("Test network helper", network.bind(this));
 | 
			
		||||
  describe("Test Credentials", credentials.bind(this));
 | 
			
		||||
  describe("Test PrefixParser", prefixParser.bind(this));
 | 
			
		||||
  describe("Test PlatformUser", platformUser.bind(this));
 | 
			
		||||
  describe("Test Post", post.bind(this));
 | 
			
		||||
  describe("Test Thread", thread.bind(this));
 | 
			
		||||
  describe("Test UserProfile", userProfile.bind(this));
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
        "experimentalDecorators": true,
 | 
			
		||||
        "outDir": "./dist",
 | 
			
		||||
        "allowJs": true,
 | 
			
		||||
        "module": "es6",
 | 
			
		||||
        "module": "commonjs",
 | 
			
		||||
        "target": "es5",
 | 
			
		||||
        "moduleResolution": "node",
 | 
			
		||||
        "esModuleInterop": true,
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
        "incremental": true,
 | 
			
		||||
        "sourceMap": true,
 | 
			
		||||
        "alwaysStrict": true,
 | 
			
		||||
        "declaration": true,
 | 
			
		||||
    },
 | 
			
		||||
    "include": [
 | 
			
		||||
        "./src/**/*.ts"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue