import { inject, injectable } from 'inversify'; import { JSDOM } from 'jsdom'; import { RequestInit, Response } from 'node-fetch'; import { WebCrawlerFormError } from '../error/web-crawler-form-error'; import { WebCrawlerLoginError } from '../error/web-crawler-login-error'; import { IWebCrawler } from '../web-crawler/i-web-crawler'; import { INhentaiApi } from './i-nhentai-api'; const domain = 'nhentai.net'; const url = `https://${domain}/`; const paths = { books: 'g/', login: 'login/', favorites: 'favorites/', }; const usernameInput = 'username_or_email'; const passwordInput = 'password'; interface ILoginMeta { [key: string]: string; } interface ILoginAuth { [usernameInput]: string; [passwordInput]: string; } interface ILoginParams extends ILoginMeta, ILoginAuth {} @injectable() export class NhentaiApi implements INhentaiApi { private webCrawler: IWebCrawler; public constructor(@inject(Symbol.for('web-crawler')) webCrawler: IWebCrawler) { this.webCrawler = webCrawler; } public isLoggedIn(): Promise { return this.webCrawler .fetch(`${url}${paths.favorites}`, { redirect: 'manual' }) .then((res: Response) => res.status === HttpCode.OK); } public login(name: string, password: string): Promise { return this.getLoginMeta() .then((meta: ILoginMeta) => { const loginParams: ILoginParams = { ...meta, ...{ [usernameInput]: name, [passwordInput]: password, }, }; return this.postNHentai(paths.login, { body: encodeURI( Object.keys(loginParams) .map((key: keyof ILoginParams) => `${key}=${loginParams[key]}`) .join('&') ), headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, redirect: 'manual', }); }) .then(() => {}) .catch(() => Promise.reject(new WebCrawlerLoginError())); } private getNHentai(path: string): Promise { return this.webCrawler .fetch(`${url}${path}`) .then((res: Response) => res.text()) .then((text: string) => { const { document } = new JSDOM(text).window; return document; }); } private postNHentai(path: string, requestInit: RequestInit = {}): Promise { const postUrl = `${url}${path}`; return this.webCrawler.fetch(postUrl, { ...requestInit, ...{ headers: { ...requestInit.headers, ...{ Host: domain, Referer: postUrl, }, }, }, method: 'post', }); } private getLoginMeta(): Promise { return this.getNHentai(paths.login).then((document: Document) => { // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < document.forms.length; i++) { const form: HTMLFormElement = document.forms[i]; const valueStore: ILoginMeta = {}; let isLoginForm = false; // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let j = 0; j < form.elements.length; j++) { const input = form.elements[j]; const name = input.getAttribute('name'); if (name === usernameInput || name === passwordInput) { isLoginForm = true; } else if (name) { const value = input.getAttribute('value'); if (value) { valueStore[name] = value; } } } if (isLoginForm) { return valueStore; } } return Promise.reject(new WebCrawlerFormError()); }); } }