commit
5ea465f940
|
@ -1,5 +1,6 @@
|
||||||
tests
|
test
|
||||||
coverage.lcov
|
coverage.lcov
|
||||||
|
tsconfig.json
|
||||||
.dist
|
.dist
|
||||||
.nyc_output
|
.nyc_output
|
||||||
.eslintignore
|
.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",
|
"main": "./src/index.ts",
|
||||||
"type": "module",
|
|
||||||
"name": "f95api",
|
"name": "f95api",
|
||||||
"version": "1.9.9",
|
"version": "1.9.9",
|
||||||
"author": "Millennium Earl",
|
"author": "Millennium Earl",
|
||||||
"description": "Unofficial Node JS module for scraping F95Zone platform",
|
"description": "Unofficial Node JS module for scraping F95Zone platform",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/MillenniumEarl/F95API.git"
|
"url": "git+https://github.com/MillenniumEarl/F95API.git"
|
||||||
|
@ -24,11 +24,12 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
"prettier-format": "prettier --config .prettierrc '**/*.ts' --write",
|
"prettier-format": "prettier --config .prettierrc '{src,test}/**/*.ts' --write",
|
||||||
"ts:compile": "tsc",
|
"compile": "tsc",
|
||||||
"ts:definitions": "tsc --declaration --outDir . --emitDeclarationOnly",
|
"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",
|
"publish": "npm publish",
|
||||||
"run-example": "npm run ts:compile && node ./dist/example.js"
|
"run-example": "npm run compile && node ./dist/example.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0"
|
"node": ">=10.0"
|
||||||
|
@ -45,16 +46,17 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.2.15",
|
"@types/chai": "^4.2.15",
|
||||||
"@types/expect": "^24.3.0",
|
"@types/chai-as-promised": "^7.1.3",
|
||||||
"@types/inquirer": "^7.3.1",
|
"@types/inquirer": "^7.3.1",
|
||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/luxon": "^1.25.2",
|
"@types/luxon": "^1.25.2",
|
||||||
"@types/mocha": "^8.2.0",
|
"@types/mocha": "^8.2.1",
|
||||||
"@types/node": "^14.14.27",
|
"@types/node": "^14.14.27",
|
||||||
"@types/tough-cookie": "^4.0.0",
|
"@types/tough-cookie": "^4.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||||
"@typescript-eslint/parser": "^4.15.0",
|
"@typescript-eslint/parser": "^4.15.0",
|
||||||
"chai": "^4.3.3",
|
"chai": "^4.3.3",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"eslint": "^7.21.0",
|
"eslint": "^7.21.0",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"eslint-config-prettier": "^8.1.0",
|
||||||
|
@ -62,13 +64,11 @@
|
||||||
"husky": "^5.1.3",
|
"husky": "^5.1.3",
|
||||||
"inquirer": "^8.0.0",
|
"inquirer": "^8.0.0",
|
||||||
"lint-staged": "^10.5.4",
|
"lint-staged": "^10.5.4",
|
||||||
"lodash": "^4.17.21",
|
"mocha": "^8.3.1",
|
||||||
"mocha": "^8.3.0",
|
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"ts-mocha": "^8.0.0",
|
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.2.0-insiders.20210210"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"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,
|
Game,
|
||||||
searchHandiwork,
|
searchHandiwork,
|
||||||
HandiworkSearchQuery
|
HandiworkSearchQuery
|
||||||
} from "./index.js";
|
} from "./index";
|
||||||
|
|
||||||
// Configure the .env reader
|
// Configure the .env reader
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
@ -103,14 +103,11 @@ async function main() {
|
||||||
const searchResult = await searchHandiwork<Game>(query, 1);
|
const searchResult = await searchHandiwork<Game>(query, 1);
|
||||||
|
|
||||||
// No game found
|
// No game found
|
||||||
if (searchResult.length === 0) {
|
if (searchResult.length !== 0) {
|
||||||
console.log(`No data found for '${gamename}'\n`);
|
// Extract first game
|
||||||
continue;
|
const gamedata = searchResult.shift();
|
||||||
}
|
const authors = gamedata.authors.map((a, idx) => a.name).join(", ");
|
||||||
|
console.log(`Found: ${gamedata.name} (${gamedata.version}) by ${authors}\n`);
|
||||||
// Extract first game
|
} else console.log(`No data found for '${gamename}'\n`);
|
||||||
const gamedata = searchResult.shift();
|
|
||||||
const authors = gamedata.authors.map((a, idx) => a.name).join(", ");
|
|
||||||
console.log(`Found: ${gamedata.name} (${gamedata.version}) by ${authors}\n`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
78
src/index.ts
78
src/index.ts
|
@ -6,50 +6,41 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import shared from "./scripts/shared.js";
|
import shared from "./scripts/shared";
|
||||||
import search from "./scripts/search.js";
|
import search from "./scripts/search";
|
||||||
import {
|
import { authenticate, urlExists, isF95URL, send2faCode } from "./scripts/network-helper";
|
||||||
authenticate,
|
import fetchLatestHandiworkURLs from "./scripts/fetch-data/fetch-latest";
|
||||||
urlExists,
|
import fetchPlatformData from "./scripts/fetch-data/fetch-platform-data";
|
||||||
isF95URL,
|
import getHandiworkInformation from "./scripts/scrape-data/handiwork-parse";
|
||||||
send2faCode
|
import { IBasic } from "./scripts/interfaces";
|
||||||
} 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";
|
|
||||||
|
|
||||||
// Classes from file
|
// Classes from file
|
||||||
import Credentials from "./scripts/classes/credentials.js";
|
import Credentials from "./scripts/classes/credentials";
|
||||||
import LoginResult from "./scripts/classes/login-result.js";
|
import LoginResult from "./scripts/classes/login-result";
|
||||||
import UserProfile from "./scripts/classes/mapping/user-profile.js";
|
import UserProfile from "./scripts/classes/mapping/user-profile";
|
||||||
import LatestSearchQuery from "./scripts/classes/query/latest-search-query.js";
|
import LatestSearchQuery from "./scripts/classes/query/latest-search-query";
|
||||||
import HandiworkSearchQuery from "./scripts/classes/query/handiwork-search-query.js";
|
import HandiworkSearchQuery from "./scripts/classes/query/handiwork-search-query";
|
||||||
import HandiWork from "./scripts/classes/handiwork/handiwork.js";
|
import HandiWork from "./scripts/classes/handiwork/handiwork";
|
||||||
import { UserNotLogged } from "./scripts/classes/errors.js";
|
import { UserNotLogged, USER_NOT_LOGGED } from "./scripts/classes/errors";
|
||||||
|
|
||||||
//#region Global variables
|
|
||||||
|
|
||||||
const USER_NOT_LOGGED = "User not authenticated, unable to continue";
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Re-export classes
|
//#region Re-export classes
|
||||||
|
|
||||||
export { default as Animation } from "./scripts/classes/handiwork/animation.js";
|
export { default as PrefixParser } from "./scripts/classes/prefix-parser";
|
||||||
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 Animation } from "./scripts/classes/handiwork/animation";
|
||||||
export { default as Post } from "./scripts/classes/mapping/post.js";
|
export { default as Asset } from "./scripts/classes/handiwork/asset";
|
||||||
export { default as Thread } from "./scripts/classes/mapping/thread.js";
|
export { default as Comic } from "./scripts/classes/handiwork/comic";
|
||||||
export { default as UserProfile } from "./scripts/classes/mapping/user-profile.js";
|
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 PlatformUser } from "./scripts/classes/mapping/platform-user";
|
||||||
export { default as LatestSearchQuery } from "./scripts/classes/query/latest-search-query.js";
|
export { default as Post } from "./scripts/classes/mapping/post";
|
||||||
export { default as ThreadSearchQuery } from "./scripts/classes/query/thread-search-query.js";
|
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
|
//#endregion Re-export classes
|
||||||
|
|
||||||
|
@ -136,6 +127,19 @@ export async function login(
|
||||||
return result;
|
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.
|
* Chek if exists a new version of the handiwork.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import { getF95Token } from "../network-helper.js";
|
import { getF95Token } from "../network-helper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the credentials used to access the platform.
|
* Represents the credentials used to access the platform.
|
||||||
|
|
|
@ -20,6 +20,11 @@ interface IBaseError {
|
||||||
error: Error;
|
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 {
|
export class GenericAxiosError extends Error implements IBaseError {
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
@ -50,4 +55,6 @@ export class InvalidF95Token extends Error {}
|
||||||
|
|
||||||
export class UserNotLogged extends Error {}
|
export class UserNotLogged extends Error {}
|
||||||
|
|
||||||
|
export class InvalidID extends Error {}
|
||||||
|
|
||||||
export class ParameterError extends Error {}
|
export class ParameterError extends Error {}
|
||||||
|
|
|
@ -7,17 +7,20 @@
|
||||||
|
|
||||||
// Public modules from npm
|
// Public modules from npm
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
import luxon from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
// Modules from files
|
// Modules from files
|
||||||
import { urls } from "../../constants/url.js";
|
import { urls } from "../../constants/url";
|
||||||
import { fetchHTML } from "../../network-helper.js";
|
import { fetchHTML } from "../../network-helper";
|
||||||
import { GENERIC, MEMBER } from "../../constants/css-selector.js";
|
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.
|
* Represents a generic user registered on the platform.
|
||||||
*/
|
*/
|
||||||
export default class PlatformUser {
|
export default class PlatformUser implements ILazy {
|
||||||
//#region Fields
|
//#region Fields
|
||||||
|
|
||||||
private _id: number;
|
private _id: number;
|
||||||
|
@ -140,57 +143,73 @@ export default class PlatformUser {
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
|
|
||||||
public setID(id: number): void {
|
public setID(id: number): void {
|
||||||
|
// Check ID
|
||||||
|
if (!id || id < 1) throw new InvalidID(INVALID_USER_ID);
|
||||||
|
|
||||||
this._id = id;
|
this._id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetch(): Promise<void> {
|
public async fetch(): Promise<void> {
|
||||||
|
// Check login
|
||||||
|
if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
|
||||||
|
|
||||||
// Check ID
|
// 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
|
// Prepare the URL
|
||||||
const url = new URL(this.id.toString(), `${urls.MEMBERS}/`).toString();
|
const url = new URL(this.id.toString(), `${urls.MEMBERS}/`).toString();
|
||||||
|
|
||||||
// Fetch the page
|
// Fetch the page
|
||||||
const htmlResponse = await fetchHTML(url);
|
const response = await fetchHTML(url);
|
||||||
|
const result = response.applyOnSuccess((html) => this.elaborateResponse(html));
|
||||||
if (htmlResponse.isSuccess()) {
|
if (result.isFailure()) throw response.value;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#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";
|
import cheerio from "cheerio";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import PlatformUser from "./platform-user.js";
|
import PlatformUser from "./platform-user";
|
||||||
import { IPostElement, parseF95ThreadPost } from "../../scrape-data/post-parse.js";
|
import { IPostElement, parseF95ThreadPost } from "../../scrape-data/post-parse";
|
||||||
import { POST, THREAD } from "../../constants/css-selector.js";
|
import { POST, THREAD } from "../../constants/css-selector";
|
||||||
import { urls } from "../../constants/url.js";
|
import { urls } from "../../constants/url";
|
||||||
import { fetchHTML } from "../../network-helper.js";
|
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.
|
* Represents a post published by a user on the F95Zone platform.
|
||||||
*/
|
*/
|
||||||
export default class Post {
|
export default class Post implements ILazy {
|
||||||
//#region Fields
|
//#region Fields
|
||||||
|
|
||||||
private _id: number;
|
private _id: number;
|
||||||
|
@ -95,33 +98,46 @@ export default class Post {
|
||||||
* Gets the post data starting from its unique ID for the entire platform.
|
* Gets the post data starting from its unique ID for the entire platform.
|
||||||
*/
|
*/
|
||||||
public async fetch(): Promise<void> {
|
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
|
// Fetch HTML page containing the post
|
||||||
const url = new URL(this.id.toString(), urls.POSTS).toString();
|
const url = new URL(this.id.toString(), urls.POSTS).toString();
|
||||||
const htmlResponse = await fetchHTML(url);
|
const response = await fetchHTML(url);
|
||||||
|
|
||||||
if (htmlResponse.isSuccess()) {
|
if (response.isSuccess()) await this.elaborateResponse(response.value);
|
||||||
// Load cheerio and find post
|
else throw response.value;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion Public methods
|
//#endregion Public methods
|
||||||
|
|
||||||
//#region Private 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> {
|
private async parsePost($: cheerio.Root, post: cheerio.Cheerio): Promise<void> {
|
||||||
// Find post's ID
|
// Find post's ID
|
||||||
const sid: string = post.find(POST.ID).attr("id").replace("post-", "");
|
const sid: string = post.find(POST.ID).attr("id").replace("post-", "");
|
||||||
|
|
|
@ -7,28 +7,30 @@
|
||||||
|
|
||||||
// Public modules from npm
|
// Public modules from npm
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
import luxon from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
// Modules from files
|
// Modules from files
|
||||||
import Post from "./post.js";
|
import Post from "./post";
|
||||||
import PlatformUser from "./platform-user.js";
|
import PlatformUser from "./platform-user";
|
||||||
import { TCategory, TRating } from "../../interfaces.js";
|
import { ILazy, TCategory, TRating } from "../../interfaces";
|
||||||
import { urls } from "../../constants/url.js";
|
import { urls } from "../../constants/url";
|
||||||
import { POST, THREAD } from "../../constants/css-selector.js";
|
import { POST, THREAD } from "../../constants/css-selector";
|
||||||
import { fetchHTML, fetchPOSTResponse } from "../../network-helper.js";
|
import { fetchHTML, fetchPOSTResponse } from "../../network-helper";
|
||||||
import Shared from "../../shared.js";
|
import Shared from "../../shared";
|
||||||
import {
|
import {
|
||||||
GenericAxiosError,
|
InvalidID,
|
||||||
|
INVALID_THREAD_ID,
|
||||||
ParameterError,
|
ParameterError,
|
||||||
UnexpectedResponseContentType
|
UserNotLogged,
|
||||||
} from "../errors.js";
|
USER_NOT_LOGGED
|
||||||
import { Result } from "../result.js";
|
} from "../errors";
|
||||||
import { getJSONLD, TJsonLD } from "../../scrape-data/json-ld.js";
|
import { getJSONLD, TJsonLD } from "../../scrape-data/json-ld";
|
||||||
|
import shared from "../../shared";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a generic F95Zone platform thread.
|
* Represents a generic F95Zone platform thread.
|
||||||
*/
|
*/
|
||||||
export default class Thread {
|
export default class Thread implements ILazy {
|
||||||
//#region Fields
|
//#region Fields
|
||||||
|
|
||||||
private POST_FOR_PAGE = 20;
|
private POST_FOR_PAGE = 20;
|
||||||
|
@ -162,42 +164,6 @@ export default class Thread {
|
||||||
return posts;
|
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
|
* It processes the rating of the thread
|
||||||
* starting from the data contained in the JSON+LD tag.
|
* starting from the data contained in the JSON+LD tag.
|
||||||
|
@ -229,6 +195,36 @@ export default class Thread {
|
||||||
return name.trim();
|
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
|
//#endregion Private methods
|
||||||
|
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
|
@ -237,38 +233,19 @@ export default class Thread {
|
||||||
* Gets information about this thread.
|
* Gets information about this thread.
|
||||||
*/
|
*/
|
||||||
public async fetch(): Promise<void> {
|
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
|
// Prepare the url
|
||||||
this._url = new URL(this.id.toString(), urls.THREADS).toString();
|
this._url = new URL(this.id.toString(), urls.THREADS).toString();
|
||||||
|
|
||||||
// Fetch the HTML source
|
// Fetch the HTML source
|
||||||
const htmlResponse = await fetchHTML(this.url);
|
const response = await fetchHTML(this.url);
|
||||||
|
if (response.isSuccess()) await this.elaborateResponse(response.value);
|
||||||
if (htmlResponse.isSuccess()) {
|
else throw response.value;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,13 +9,19 @@
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
|
|
||||||
// Modules from files
|
// Modules from files
|
||||||
import Post from "./post.js";
|
import Post from "./post";
|
||||||
import PlatformUser from "./platform-user.js";
|
import PlatformUser from "./platform-user";
|
||||||
import { urls } from "../../constants/url.js";
|
import { urls } from "../../constants/url";
|
||||||
import { GENERIC, WATCHED_THREAD } from "../../constants/css-selector.js";
|
import { GENERIC, WATCHED_THREAD } from "../../constants/css-selector";
|
||||||
import { fetchHTML } from "../../network-helper.js";
|
import { fetchHTML } from "../../network-helper";
|
||||||
import { GenericAxiosError, UnexpectedResponseContentType } from "../errors.js";
|
import {
|
||||||
import { Result } from "../result.js";
|
GenericAxiosError,
|
||||||
|
UnexpectedResponseContentType,
|
||||||
|
UserNotLogged,
|
||||||
|
USER_NOT_LOGGED
|
||||||
|
} from "../errors";
|
||||||
|
import { Result } from "../result";
|
||||||
|
import shared from "../../shared";
|
||||||
|
|
||||||
// Interfaces
|
// Interfaces
|
||||||
interface IWatchedThread {
|
interface IWatchedThread {
|
||||||
|
@ -88,6 +94,9 @@ export default class UserProfile extends PlatformUser {
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
|
|
||||||
public async fetch(): Promise<void> {
|
public async fetch(): Promise<void> {
|
||||||
|
// Check login
|
||||||
|
if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
|
||||||
|
|
||||||
// First get the user ID and set it
|
// First get the user ID and set it
|
||||||
const id = await this.fetchUserID();
|
const id = await this.fetchUserID();
|
||||||
super.setID(id);
|
super.setID(id);
|
||||||
|
@ -103,31 +112,39 @@ export default class UserProfile extends PlatformUser {
|
||||||
|
|
||||||
//#region Private methods
|
//#region Private methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the ID of the user currently logged.
|
||||||
|
*/
|
||||||
private async fetchUserID(): Promise<number> {
|
private async fetchUserID(): Promise<number> {
|
||||||
// Local variables
|
// Local variables
|
||||||
const url = new URL(urls.BASE).toString();
|
const url = new URL(urls.BASE).toString();
|
||||||
|
|
||||||
// fetch and parse page
|
// Fetch and parse page
|
||||||
const htmlResponse = await fetchHTML(url);
|
const response = await fetchHTML(url);
|
||||||
if (htmlResponse.isSuccess()) {
|
const result = response.applyOnSuccess((html) => {
|
||||||
// Load page with cheerio
|
// Load page with cheerio
|
||||||
const $ = cheerio.load(htmlResponse.value);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
const sid = $(GENERIC.CURRENT_USER_ID).attr("data-user-id").trim();
|
const sid = $(GENERIC.CURRENT_USER_ID).attr("data-user-id").trim();
|
||||||
return parseInt(sid, 10);
|
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[]> {
|
private async fetchWatchedThread(): Promise<IWatchedThread[]> {
|
||||||
// Prepare and fetch URL
|
// Prepare and fetch URL
|
||||||
const url = new URL(urls.WATCHED_THREADS);
|
const url = new URL(urls.WATCHED_THREADS);
|
||||||
url.searchParams.set("unread", "0");
|
url.searchParams.set("unread", "0");
|
||||||
|
|
||||||
const htmlResponse = await fetchHTML(url.toString());
|
const response = await fetchHTML(url.toString());
|
||||||
|
const result = response.applyOnSuccess(async (html) => {
|
||||||
if (htmlResponse.isSuccess()) {
|
|
||||||
// Load page in cheerio
|
// Load page in cheerio
|
||||||
const $ = cheerio.load(htmlResponse.value);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
// Fetch the pages
|
// Fetch the pages
|
||||||
const lastPage = parseInt($(WATCHED_THREAD.LAST_PAGE).text().trim(), 10);
|
const lastPage = parseInt($(WATCHED_THREAD.LAST_PAGE).text().trim(), 10);
|
||||||
|
@ -139,7 +156,10 @@ export default class UserProfile extends PlatformUser {
|
||||||
});
|
});
|
||||||
|
|
||||||
return [].concat(...watchedThreads);
|
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";
|
"use strict";
|
||||||
|
|
||||||
// Modules from file
|
// 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.
|
* Convert prefixes and platform tags from string to ID and vice versa.
|
||||||
|
|
|
@ -5,16 +5,16 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { AxiosResponse } from "axios";
|
|
||||||
// Public modules from npm
|
// Public modules from npm
|
||||||
import validator from "class-validator";
|
import { IsInt, Min, validateSync } from "class-validator";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
// Module from files
|
// Module from files
|
||||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
|
import { IQuery, TCategory, TQueryInterface } from "../../interfaces";
|
||||||
import { GenericAxiosError } from "../errors.js";
|
import { GenericAxiosError } from "../errors";
|
||||||
import { Result } from "../result.js";
|
import { Result } from "../result";
|
||||||
import LatestSearchQuery, { TLatestOrder } from "./latest-search-query.js";
|
import LatestSearchQuery, { TLatestOrder } from "./latest-search-query";
|
||||||
import ThreadSearchQuery, { TThreadOrder } from "./thread-search-query.js";
|
import ThreadSearchQuery, { TThreadOrder } from "./thread-search-query";
|
||||||
|
|
||||||
// Type definitions
|
// Type definitions
|
||||||
/**
|
/**
|
||||||
|
@ -70,10 +70,10 @@ export default class HandiworkSearchQuery implements IQuery {
|
||||||
* Results presentation order.
|
* Results presentation order.
|
||||||
*/
|
*/
|
||||||
public order: THandiworkOrder = "relevance";
|
public order: THandiworkOrder = "relevance";
|
||||||
@validator.IsInt({
|
@IsInt({
|
||||||
message: "$property expect an integer, received $value"
|
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"
|
message: "The minimum $property value must be $constraint1, received $value"
|
||||||
})
|
})
|
||||||
public page = 1;
|
public page = 1;
|
||||||
|
@ -103,7 +103,7 @@ export default class HandiworkSearchQuery implements IQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
public validate(): boolean {
|
public validate(): boolean {
|
||||||
return validator.validateSync(this).length === 0;
|
return validateSync(this).length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async execute(): Promise<TExecuteResult> {
|
public async execute(): Promise<TExecuteResult> {
|
||||||
|
@ -112,7 +112,7 @@ export default class HandiworkSearchQuery implements IQuery {
|
||||||
|
|
||||||
// Check if the query is valid
|
// Check if the query is valid
|
||||||
if (!this.validate()) {
|
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
|
// Convert the query
|
||||||
|
@ -144,7 +144,7 @@ export default class HandiworkSearchQuery implements IQuery {
|
||||||
// Cast the basic query object and copy common values
|
// Cast the basic query object and copy common values
|
||||||
const query: LatestSearchQuery = new LatestSearchQuery();
|
const query: LatestSearchQuery = new LatestSearchQuery();
|
||||||
Object.keys(this).forEach((key) => {
|
Object.keys(this).forEach((key) => {
|
||||||
if (query.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(query, key)) {
|
||||||
query[key] = this[key];
|
query[key] = this[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -165,7 +165,7 @@ export default class HandiworkSearchQuery implements IQuery {
|
||||||
// Cast the basic query object and copy common values
|
// Cast the basic query object and copy common values
|
||||||
const query: ThreadSearchQuery = new ThreadSearchQuery();
|
const query: ThreadSearchQuery = new ThreadSearchQuery();
|
||||||
Object.keys(this).forEach((key) => {
|
Object.keys(this).forEach((key) => {
|
||||||
if (query.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(query, key)) {
|
||||||
query[key] = this[key];
|
query[key] = this[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,16 +6,16 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Public modules from npm
|
// Public modules from npm
|
||||||
import validator from "class-validator";
|
import { ArrayMaxSize, IsInt, Min, validateSync } from "class-validator";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import { urls } from "../../constants/url.js";
|
import { urls } from "../../constants/url";
|
||||||
import PrefixParser from "../prefix-parser.js";
|
import PrefixParser from "../prefix-parser";
|
||||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
|
import { IQuery, TCategory, TQueryInterface } from "../../interfaces";
|
||||||
import { fetchGETResponse } from "../../network-helper.js";
|
import { fetchGETResponse } from "../../network-helper";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { GenericAxiosError } from "../errors.js";
|
import { GenericAxiosError } from "../errors";
|
||||||
import { Result } from "../result.js";
|
import { Result } from "../result";
|
||||||
|
|
||||||
// Type definitions
|
// Type definitions
|
||||||
export type TLatestOrder = "date" | "likes" | "views" | "title" | "rating";
|
export type TLatestOrder = "date" | "likes" | "views" | "title" | "rating";
|
||||||
|
@ -49,16 +49,16 @@ export default class LatestSearchQuery implements IQuery {
|
||||||
*/
|
*/
|
||||||
public date: TDate = null;
|
public date: TDate = null;
|
||||||
|
|
||||||
@validator.ArrayMaxSize(LatestSearchQuery.MAX_TAGS, {
|
@ArrayMaxSize(LatestSearchQuery.MAX_TAGS, {
|
||||||
message: "Too many tags: $value instead of $constraint1"
|
message: "Too many tags: $value instead of $constraint1"
|
||||||
})
|
})
|
||||||
public includedTags: string[] = [];
|
public includedTags: string[] = [];
|
||||||
public includedPrefixes: string[] = [];
|
public includedPrefixes: string[] = [];
|
||||||
|
|
||||||
@validator.IsInt({
|
@IsInt({
|
||||||
message: "$property expect an integer, received $value"
|
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"
|
message: "The minimum $property value must be $constraint1, received $value"
|
||||||
})
|
})
|
||||||
public page = LatestSearchQuery.MIN_PAGE;
|
public page = LatestSearchQuery.MIN_PAGE;
|
||||||
|
@ -69,13 +69,13 @@ export default class LatestSearchQuery implements IQuery {
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
|
|
||||||
public validate(): boolean {
|
public validate(): boolean {
|
||||||
return validator.validateSync(this).length === 0;
|
return validateSync(this).length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
|
public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
|
||||||
// Check if the query is valid
|
// Check if the query is valid
|
||||||
if (!this.validate()) {
|
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
|
// Prepare the URL
|
||||||
|
|
|
@ -6,17 +6,17 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Public modules from npm
|
// Public modules from npm
|
||||||
import validator from "class-validator";
|
import { IsInt, Min, validateSync } from "class-validator";
|
||||||
|
|
||||||
// Module from files
|
// Module from files
|
||||||
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
|
import { IQuery, TCategory, TQueryInterface } from "../../interfaces";
|
||||||
import { urls } from "../../constants/url.js";
|
import { urls } from "../../constants/url";
|
||||||
import PrefixParser from "./../prefix-parser.js";
|
import PrefixParser from "./../prefix-parser";
|
||||||
import { fetchPOSTResponse } from "../../network-helper.js";
|
import { fetchPOSTResponse } from "../../network-helper";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { GenericAxiosError } from "../errors.js";
|
import { GenericAxiosError } from "../errors";
|
||||||
import { Result } from "../result.js";
|
import { Result } from "../result";
|
||||||
import Shared from "../../shared.js";
|
import Shared from "../../shared";
|
||||||
|
|
||||||
// Type definitions
|
// Type definitions
|
||||||
export type TThreadOrder = "relevance" | "date" | "last_update" | "replies";
|
export type TThreadOrder = "relevance" | "date" | "last_update" | "replies";
|
||||||
|
@ -61,10 +61,10 @@ export default class ThreadSearchQuery implements IQuery {
|
||||||
* Results presentation order.
|
* Results presentation order.
|
||||||
*/
|
*/
|
||||||
public order: TThreadOrder = "relevance";
|
public order: TThreadOrder = "relevance";
|
||||||
@validator.IsInt({
|
@IsInt({
|
||||||
message: "$property expect an integer, received $value"
|
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"
|
message: "The minimum $property value must be $constraint1, received $value"
|
||||||
})
|
})
|
||||||
public page = 1;
|
public page = 1;
|
||||||
|
@ -75,13 +75,13 @@ export default class ThreadSearchQuery implements IQuery {
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
|
|
||||||
public validate(): boolean {
|
public validate(): boolean {
|
||||||
return validator.validateSync(this).length === 0;
|
return validateSync(this).length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
|
public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
|
||||||
// Check if the query is valid
|
// Check if the query is valid
|
||||||
if (!this.validate()) {
|
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
|
// Define the POST parameters
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default class Session {
|
||||||
/**
|
/**
|
||||||
* Max number of days the session is valid.
|
* 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 readonly COOKIEJAR_FILENAME: string = "f95cookiejar.json";
|
||||||
private _path: string;
|
private _path: string;
|
||||||
private _isMapped: boolean;
|
private _isMapped: boolean;
|
||||||
|
@ -106,7 +106,7 @@ export default class Session {
|
||||||
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
|
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
|
||||||
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.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 HandiworkSearchQuery from "../classes/query/handiwork-search-query";
|
||||||
import LatestSearchQuery from "../classes/query/latest-search-query";
|
import LatestSearchQuery from "../classes/query/latest-search-query";
|
||||||
import ThreadSearchQuery from "../classes/query/thread-search-query";
|
import ThreadSearchQuery from "../classes/query/thread-search-query";
|
||||||
import fetchLatestHandiworkURLs from "./fetch-latest.js";
|
import fetchLatestHandiworkURLs from "./fetch-latest";
|
||||||
import fetchThreadHandiworkURLs from "./fetch-thread.js";
|
import fetchThreadHandiworkURLs from "./fetch-thread";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the URLs of the handiworks that match the passed parameters.
|
* Gets the URLs of the handiworks that match the passed parameters.
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import LatestSearchQuery from "../classes/query/latest-search-query.js";
|
import LatestSearchQuery from "../classes/query/latest-search-query";
|
||||||
import { urls } from "../constants/url.js";
|
import { urls } from "../constants/url";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the URLs of the latest handiworks that match the passed parameters.
|
* 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(
|
export default async function fetchLatestHandiworkURLs(
|
||||||
query: LatestSearchQuery,
|
query: LatestSearchQuery,
|
||||||
limit = 30
|
limit: number = 30
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
// Local variables
|
// Local variables
|
||||||
const shallowQuery: LatestSearchQuery = Object.assign(new LatestSearchQuery(), query);
|
const shallowQuery: LatestSearchQuery = Object.assign(new LatestSearchQuery(), query);
|
||||||
|
|
|
@ -12,10 +12,10 @@ import { readFileSync, writeFileSync, existsSync } from "fs";
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import shared, { TPrefixDict } from "../shared.js";
|
import shared, { TPrefixDict } from "../shared";
|
||||||
import { urls as f95url } from "../constants/url.js";
|
import { urls as f95url } from "../constants/url";
|
||||||
import { GENERIC } from "../constants/css-selector.js";
|
import { GENERIC } from "../constants/css-selector";
|
||||||
import { fetchHTML } from "../network-helper.js";
|
import { fetchHTML } from "../network-helper";
|
||||||
|
|
||||||
//#region Interface definitions
|
//#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 {
|
interface ILatestResource {
|
||||||
prefixes: { [s: string]: ICategoryResource[] };
|
prefixes: { [s: string]: ICategoryResource[] };
|
||||||
|
@ -58,18 +58,20 @@ export default async function fetchPlatformData(): Promise<void> {
|
||||||
// Check if the data are cached
|
// Check if the data are cached
|
||||||
if (!readCache(shared.cachePath)) {
|
if (!readCache(shared.cachePath)) {
|
||||||
// Load the HTML
|
// Load the HTML
|
||||||
const html = await fetchHTML(f95url.LATEST_UPDATES);
|
const response = await fetchHTML(f95url.LATEST_UPDATES);
|
||||||
|
|
||||||
// Parse data
|
// Parse data
|
||||||
if (html.isSuccess()) {
|
const result = response.applyOnSuccess((html) => {
|
||||||
const data = parseLatestPlatformHTML(html.value);
|
const data = parseLatestPlatformHTML(html);
|
||||||
|
|
||||||
// Assign data
|
// Assign data
|
||||||
assignLatestPlatformData(data);
|
assignLatestPlatformData(data);
|
||||||
|
|
||||||
// Cache data
|
// Cache data
|
||||||
saveCache(shared.cachePath);
|
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
|
// Prepare the dict
|
||||||
const dict: TPrefixDict = {};
|
const dict: TPrefixDict = {};
|
||||||
|
|
||||||
for (const e of res.prefixes) {
|
// Assign values
|
||||||
dict[e.id] = e.name.replace("'", "'");
|
res.prefixes.map((e) => (dict[e.id] = e.name.replace("'", "'")));
|
||||||
}
|
|
||||||
|
|
||||||
// Save the property
|
// Merge the dicts ("Other"/"Status" field)
|
||||||
scrapedData[res.name] = dict;
|
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";
|
import cheerio from "cheerio";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import shared from "../shared.js";
|
import shared from "../shared";
|
||||||
import { THREAD_SEARCH } from "../constants/css-selector.js";
|
import { THREAD_SEARCH } from "../constants/css-selector";
|
||||||
import { urls as f95urls } from "../constants/url.js";
|
import { urls as f95urls } from "../constants/url";
|
||||||
import ThreadSearchQuery from "../classes/query/thread-search-query.js";
|
import ThreadSearchQuery from "../classes/query/thread-search-query";
|
||||||
|
|
||||||
//#region Public methods
|
//#region Public methods
|
||||||
|
|
||||||
|
|
|
@ -316,3 +316,15 @@ export interface IQuery {
|
||||||
*/
|
*/
|
||||||
execute(): any;
|
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";
|
import axiosCookieJarSupport from "axios-cookiejar-support";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import shared from "./shared.js";
|
import shared from "./shared";
|
||||||
import { urls } from "./constants/url.js";
|
import { urls } from "./constants/url";
|
||||||
import { GENERIC } from "./constants/css-selector.js";
|
import { GENERIC } from "./constants/css-selector";
|
||||||
import LoginResult from "./classes/login-result.js";
|
import LoginResult from "./classes/login-result";
|
||||||
import { failure, Result, success } from "./classes/result.js";
|
import { failure, Result, success } from "./classes/result";
|
||||||
import {
|
import {
|
||||||
GenericAxiosError,
|
GenericAxiosError,
|
||||||
InvalidF95Token,
|
InvalidF95Token,
|
||||||
UnexpectedResponseContentType
|
UnexpectedResponseContentType
|
||||||
} from "./classes/errors.js";
|
} from "./classes/errors";
|
||||||
import Credentials from "./classes/credentials.js";
|
import Credentials from "./classes/credentials";
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
// Configure axios to use the cookie jar
|
// Configure axios to use the cookie jar
|
||||||
axiosCookieJarSupport.default(axios);
|
axiosCookieJarSupport(axios);
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
const userAgent =
|
const userAgent =
|
||||||
|
|
|
@ -6,18 +6,14 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Public modules from npm
|
// Public modules from npm
|
||||||
import luxon from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
// Modules from files
|
// Modules from files
|
||||||
import HandiWork from "../classes/handiwork/handiwork.js";
|
import HandiWork from "../classes/handiwork/handiwork";
|
||||||
import Thread from "../classes/mapping/thread.js";
|
import Thread from "../classes/mapping/thread";
|
||||||
import { IBasic, TAuthor, TEngine, TExternalPlatform, TStatus } from "../interfaces.js";
|
import { IBasic, TAuthor, TEngine, TExternalPlatform, TStatus } from "../interfaces";
|
||||||
import shared, { TPrefixDict } from "../shared.js";
|
import shared, { TPrefixDict } from "../shared";
|
||||||
import { ILink, IPostElement } from "./post-parse.js";
|
import { ILink, IPostElement } from "./post-parse";
|
||||||
|
|
||||||
export async function getHandiworkInformation<T extends IBasic>(url: string): Promise<T>;
|
|
||||||
|
|
||||||
export async function getHandiworkInformation<T extends IBasic>(url: string): Promise<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets information of a particular handiwork from its thread.
|
* Gets information of a particular handiwork from its thread.
|
||||||
|
@ -226,7 +222,7 @@ function fillWithPostData(hw: HandiWork, elements: IPostElement[]) {
|
||||||
|
|
||||||
// Fill the dates
|
// Fill the dates
|
||||||
const releaseDate = getPostElementByName(elements, "release date")?.text;
|
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
|
//#region Convert the author
|
||||||
const authorElement =
|
const authorElement =
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import shared from "../shared.js";
|
import shared from "../shared";
|
||||||
import { THREAD } from "../constants/css-selector.js";
|
import { THREAD } from "../constants/css-selector";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents information contained in a JSON+LD tag.
|
* Represents information contained in a JSON+LD tag.
|
||||||
|
|
|
@ -3,16 +3,19 @@
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-inferrable-types */
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import { IBasic, IQuery } from "./interfaces.js";
|
import { IBasic, IQuery } from "./interfaces";
|
||||||
import getHandiworkInformation from "./scrape-data/handiwork-parse.js";
|
import getHandiworkInformation from "./scrape-data/handiwork-parse";
|
||||||
import getURLsFromQuery from "./fetch-data/fetch-query.js";
|
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.
|
* Gets the handiworks that match the passed parameters.
|
||||||
|
*
|
||||||
* You *must* be logged.
|
* You *must* be logged.
|
||||||
* @param {Number} limit
|
* @param {Number} limit
|
||||||
* Maximum number of items to get. Default: 30
|
* Maximum number of items to get. Default: 30
|
||||||
|
@ -29,3 +32,25 @@ export default async function search<T extends IBasic>(
|
||||||
|
|
||||||
return Promise.all(results);
|
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";
|
import log4js from "log4js";
|
||||||
|
|
||||||
// Modules from file
|
// Modules from file
|
||||||
import Session from "./classes/session.js";
|
import Session from "./classes/session";
|
||||||
|
|
||||||
// Types declaration
|
// Types declaration
|
||||||
export type TPrefixDict = { [n: number]: string };
|
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,
|
"experimentalDecorators": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"module": "es6",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
|
"declaration": true,
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*.ts"
|
"./src/**/*.ts"
|
||||||
|
|
Loading…
Reference in New Issue