diff --git a/.prettierrc.yml b/.prettierrc.yml index 194b624..aba85ec 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -7,4 +7,4 @@ printWidth: 80 overrides: - files: '*.svelte' options: - parser: vue + parser: html diff --git a/package-lock.json b/package-lock.json index 52cebce..4daec3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6318,6 +6318,24 @@ "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } } }, "request-promise-core": { @@ -6338,6 +6356,18 @@ "request-promise-core": "1.1.2", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } } }, "require-directory": { @@ -7424,21 +7454,14 @@ } }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { diff --git a/package.json b/package.json index 8d1e7ac..5d2adc5 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,9 @@ }, "dependencies": {}, "devDependencies": { + "@types/jsdom": "^12.2.3", + "@types/node-fetch": "^2.3.7", + "@types/tough-cookie": "^2.3.5", "@types/webpack": "^4.4.32", "electron": "^5.0.5", "electron-rebuild": "^1.8.5", @@ -32,10 +35,13 @@ "gulp-cli": "^2.2.0", "gulp-sourcemaps": "^2.6.5", "gulp-typescript": "^5.0.1", + "jsdom": "^15.1.1", + "node-fetch": "^2.6.0", "prettier": "^1.18.2", "sqlite3": "^4.0.9", "svelte": "^3.5.1", "svelte-loader": "^2.13.4", + "tough-cookie": "^3.0.1", "ts-loader": "^6.0.3", "tslint": "^5.17.0", "tslint-config-prettier": "^1.18.0", diff --git a/src/declarations/electron.d.ts b/src/declarations/electron.d.ts new file mode 100644 index 0000000..02be2a0 --- /dev/null +++ b/src/declarations/electron.d.ts @@ -0,0 +1,8 @@ +import WebContents = Electron.WebContents; + +declare type IpcEvent = { + frameId: number; + preventDefault: () => void; + reply: (channel: string, ...args: any) => void; + sender: WebContents; +}; diff --git a/src/declarations/global.d.ts b/src/declarations/svelte.d.ts similarity index 100% rename from src/declarations/global.d.ts rename to src/declarations/svelte.d.ts diff --git a/src/main.ts b/src/main.ts index f376ecd..cc6d64f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,7 +6,7 @@ import session from './main/services/session'; let mainWindow: Electron.BrowserWindow; -async function createWindow() { +async function createWindow(): Promise { session.init(); // Create the browser window. diff --git a/src/main/controllers/api.ts b/src/main/controllers/api.ts index ba064e4..729a633 100644 --- a/src/main/controllers/api.ts +++ b/src/main/controllers/api.ts @@ -1,17 +1,13 @@ import { ipcMain } from 'electron'; -import WebContents = Electron.WebContents; +import nhentai from './../services/nhentai-crawler'; -ipcMain.on( - IpcChannels.Credentials, - ( - event: { - frameId: number; - preventDefault: () => void; - reply: (channel: string, ...args: any) => void; - sender: WebContents; - }, - ...args: any - ) => { - event.reply(IpcChannels.Pong, args); - } -); +ipcMain.on(IpcChannels.Credentials, (event: IpcEvent, args: ICredentials) => { + nhentai + .login(args.name, args.password) + .then(() => { + console.log('success'); + }) + .catch(() => { + console.log('fail'); + }); +}); diff --git a/src/main/entities/library/book.ts b/src/main/entities/library/book.ts index b3a8613..51fb722 100644 --- a/src/main/entities/library/book.ts +++ b/src/main/entities/library/book.ts @@ -8,7 +8,7 @@ import { Tag } from './tag'; @Entity() export class Book extends MultiNamed { - @OneToMany(() => Copy, copy => copy.original, { + @OneToMany(() => Copy, (copy: Copy) => copy.original, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/main/entities/library/copy-type.ts b/src/main/entities/library/copy-type.ts index bac597a..5f7f20e 100644 --- a/src/main/entities/library/copy-type.ts +++ b/src/main/entities/library/copy-type.ts @@ -11,7 +11,7 @@ const enum CopyTypes { @Entity() export class CopyType extends Base { - @ManyToOne(() => Copy, copy => copy.types, { + @ManyToOne(() => Copy, (copy: Copy) => copy.types, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/main/entities/library/copy.ts b/src/main/entities/library/copy.ts index 5190f32..6b265ce 100644 --- a/src/main/entities/library/copy.ts +++ b/src/main/entities/library/copy.ts @@ -15,7 +15,7 @@ import { Translator } from './translator'; @Entity() export class Copy extends Base { - @ManyToOne(() => Book, book => book.copies, { + @ManyToOne(() => Book, (book: Book) => book.copies, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', @@ -25,7 +25,7 @@ export class Copy extends Base { @Column({ nullable: false, default: false }) public favorited: boolean; - @OneToMany(() => CopyType, copyType => copyType.copy, { + @OneToMany(() => CopyType, (copyType: CopyType) => copyType.copy, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/main/entities/library/site.ts b/src/main/entities/library/site.ts index 70ed718..4ed1bd6 100644 --- a/src/main/entities/library/site.ts +++ b/src/main/entities/library/site.ts @@ -4,7 +4,7 @@ import { Source } from './source'; @Entity() export class Site extends MultiNamed { - @OneToMany(() => Source, source => source.site, { + @OneToMany(() => Source, (source: Source) => source.site, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/main/entities/library/source.ts b/src/main/entities/library/source.ts index 280a52f..ec83569 100644 --- a/src/main/entities/library/source.ts +++ b/src/main/entities/library/source.ts @@ -11,7 +11,7 @@ export class Source extends Base { }) public uri: string; - @ManyToOne(() => Site, site => site.sources, { + @ManyToOne(() => Site, (site: Site) => site.sources, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/main/services/database.ts b/src/main/services/database.ts index 37a21f7..d95eeb3 100644 --- a/src/main/services/database.ts +++ b/src/main/services/database.ts @@ -3,7 +3,7 @@ import { Connection, createConnection } from 'typeorm'; let connection: Connection; -function init() { +function init(): void { initConnection(); } @@ -11,10 +11,10 @@ function initConnection(): void { // createConnection method will automatically read connection options // from your ormconfig file or environment variables createConnection('library') - .then(c => { + .then((c: Connection) => { connection = c; }) - .catch(reason => { + .catch((reason: any) => { throw reason; }); } diff --git a/src/main/services/nhentai-crawler.ts b/src/main/services/nhentai-crawler.ts index 4a452e3..d7f7871 100644 --- a/src/main/services/nhentai-crawler.ts +++ b/src/main/services/nhentai-crawler.ts @@ -1,3 +1,8 @@ +import { JSDOM } from 'jsdom'; +import { RequestInit, Response } from 'node-fetch'; +import RenaiError, { Errors } from '../../types/error'; +import fetch from './web-crawler'; + const url = 'https://nhentai.net/'; const paths = { @@ -6,39 +11,88 @@ const paths = { favorites: 'favorites/', }; -// @ts-ignore -let loginPassword: string; -// @ts-ignore -let loginName: string; +const usernameInput = 'username_or_email'; +const passwordInput = 'password'; -function fetchNHentai(path: string): Promise { - return fetch(`${url}${path}`, { - credentials: 'include', - }) - .then(res => { - console.log(res); +interface ILoginMeta { + [key: string]: string; +} + +interface ILoginAuth { + [usernameInput]: string; + [passwordInput]: string; +} + +interface ILoginParams extends ILoginMeta, ILoginAuth {} + +function login(name: string, password: string): Promise { + return getLoginMeta() + .then((meta: ILoginMeta) => { + const loginParams: ILoginParams = { + ...meta, + ...{ + // tslint:disable-next-line: object-literal-sort-keys + username_or_email: name, + password, + }, + }; + + return postNHentai(paths.login, { + body: encodeURI( + Object.keys(loginParams) + .map((key: keyof ILoginParams) => `${key}=${loginParams[key]}`) + .join('&') + ), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + }) + .then(() => {}) + .catch(() => Promise.reject(new RenaiError(Errors.ELOGINFAIL))); +} + +function getNHentai(path: string): Promise { + return fetch(`${url}${path}`) + .then((res: Response) => { return res.text(); }) - .then(text => { - const parser = new DOMParser(); - return parser.parseFromString(text, 'text/html'); + .then((text: string) => { + const { document } = new JSDOM(text).window; + return document; }); } -function fetchLogin(): void { - fetchNHentai(paths.login) - .then(() => true) - .catch(e => { - console.error(e); - }); +function postNHentai(path: string, init: RequestInit = {}): Promise { + return fetch(`${url}${path}`, { ...init, ...{ method: 'post' } }); } -function setLoginCredentials(name: string, password: string): void { - loginName = name; - loginPassword = password; +function getLoginMeta(): Promise { + return getNHentai(paths.login).then((document: Document) => { + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < document.forms.length; i++) { + const form: HTMLFormElement = document.forms[i]; + const valueStore: ILoginMeta = {}; + let isLoginForm = false; + // tslint:disable-next-line: 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) { + valueStore[name] = input.getAttribute('value'); + } + } + if (isLoginForm) { + return valueStore; + } + } + return Promise.reject(new RenaiError(Errors.ENOLOGIN)); + }); } export default { - setLoginCredentials, - fetchLogin, + login, }; diff --git a/src/main/services/web-crawler.ts b/src/main/services/web-crawler.ts new file mode 100644 index 0000000..1688df8 --- /dev/null +++ b/src/main/services/web-crawler.ts @@ -0,0 +1,27 @@ +import nodeFetch, { RequestInit, Response } from 'node-fetch'; +import { Cookie, CookieJar } from 'tough-cookie'; + +const cookieJar: CookieJar = new CookieJar(); + +function fetch(url: string, init: RequestInit = {}): Promise { + const headers: HeadersInit = {}; + cookieJar.getCookiesSync(url).forEach((cookie: Cookie) => { + headers[cookie.key] = cookie.value; + }); + const cookiedInit = { + ...init, + ...{ headers: { ...init.headers, ...headers } }, + }; + return nodeFetch(url, cookiedInit).then((res: Response) => { + setCookies(res.headers.raw()['set-cookie'], url); + return res; + }); +} + +function setCookies(header: string[], url: string): void { + header.forEach((cookie: string) => { + cookieJar.setCookieSync(cookie, url); + }); +} + +export default fetch; diff --git a/src/renderer.ts b/src/renderer.ts index e448f26..b414bdd 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -4,7 +4,7 @@ import App from './renderer/App.svelte'; -(() => +((): void => new App({ target: document.querySelector('#app'), props: { diff --git a/src/renderer/App.svelte b/src/renderer/App.svelte index bf374d2..8825d63 100644 --- a/src/renderer/App.svelte +++ b/src/renderer/App.svelte @@ -1,62 +1,74 @@
-

Hello World

-

{ text }

- +

Login

+
+ + + submit +
diff --git a/src/renderer/components/1-atoms/Bttn.svelte b/src/renderer/components/1-atoms/Bttn.svelte new file mode 100644 index 0000000..b2e89ab --- /dev/null +++ b/src/renderer/components/1-atoms/Bttn.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/renderer/components/1-atoms/Button.svelte b/src/renderer/components/1-atoms/Button.svelte deleted file mode 100644 index 94badb0..0000000 --- a/src/renderer/components/1-atoms/Button.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/src/renderer/components/2-molecules/Divide.svelte b/src/renderer/components/2-molecules/Divide.svelte index 5108e35..cd65de2 100644 --- a/src/renderer/components/2-molecules/Divide.svelte +++ b/src/renderer/components/2-molecules/Divide.svelte @@ -1,117 +1,117 @@
{ - console.log(args); -}); - export default { sendCredentials, }; diff --git a/src/types/error.ts b/src/types/error.ts new file mode 100644 index 0000000..3510485 --- /dev/null +++ b/src/types/error.ts @@ -0,0 +1,17 @@ +export const enum Errors { + ERROR = 'ERROR', + ENOLOGIN = 'ENOLOGIN', + ELOGINFAIL = 'ELOGINFAIL', +} + +const messages = { + [Errors.ERROR]: 'error', + [Errors.ENOLOGIN]: 'no login form found', + [Errors.ELOGINFAIL]: 'login failed', +}; + +export default class RenaiError extends Error { + constructor(eno: Errors = Errors.ERROR, msg: string = '') { + super(`Error ${eno}: ${messages[eno]}.${msg ? ` ${msg}` : ''}`); + } +} diff --git a/src/types/ipc.ts b/src/types/ipc.ts index a8f5522..8920e22 100644 --- a/src/types/ipc.ts +++ b/src/types/ipc.ts @@ -1,6 +1,5 @@ const enum IpcChannels { Credentials = 'CREDENTIALS', - Pong = 'PONG', } interface ICredentials { diff --git a/tslint.json b/tslint.json index b474f86..06304da 100644 --- a/tslint.json +++ b/tslint.json @@ -20,11 +20,12 @@ "no-floating-promises": true, "no-unused-expression": true, "await-promise": true, - "no-inferrable-types": true, + "no-inferrable-types": [true, "ignore-params", "ignore-properties"], "prefer-for-of": true, "no-empty": [true, "allow-empty-functions"], "no-magic-numbers": true, - "no-parameter-reassignment": true + "no-parameter-reassignment": true, + "arrow-return-shorthand": true }, "jsRules": true } diff --git a/webpack.config.js b/webpack.config.js index 5441138..3aaeabe 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ const path = require('path'); module.exports = { + mode: 'production', entry: { bundle: path.resolve(__dirname, 'src/renderer.ts'), }, @@ -33,6 +34,10 @@ module.exports = { alias: { atoms: path.resolve(__dirname, 'src/renderer/components/1-atoms'), molecules: path.resolve(__dirname, 'src/renderer/components/2-molecules'), + polymers: path.resolve(__dirname, 'src/renderer/components/3-polymers'), + cells: path.resolve(__dirname, 'src/renderer/components/4-cells'), + organisms: path.resolve(__dirname, 'src/renderer/components/5-organisms'), + templates: path.resolve(__dirname, 'src/renderer/components/6-templates'), services: path.resolve(__dirname, 'src/renderer/services'), }, },