feat: add functionality to get a work entity from a nhentai gallery id
This is more of a vertical slice of the intended functionality and needs to be extended.
This commit is contained in:
parent
799b7271e3
commit
0a2a266176
|
@ -1535,6 +1535,12 @@
|
|||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/deep-equal-in-any-order": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-equal-in-any-order/-/deep-equal-in-any-order-1.0.1.tgz",
|
||||
"integrity": "sha512-hUWUUE53WjKfcCncSmWmNXVNNT+0Iz7gYFnov3zdCXrX3Thxp1Cnmfd5LwWOeCVUV5LhpiFgS05vaAG72doo9w==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
|
||||
|
@ -3447,6 +3453,16 @@
|
|||
"type-detect": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"deep-equal-in-any-order": {
|
||||
"version": "1.0.28",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal-in-any-order/-/deep-equal-in-any-order-1.0.28.tgz",
|
||||
"integrity": "sha512-qq3jffpGmAG9kGpZGKusjRwoGxmFgIqNW076HQmV9rNdrFsgTcpuCyp6dBhzdVCWgQDkgRmvZLYAilV4u2BsfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.mapvalues": "^4.6.0",
|
||||
"sort-any": "^1.1.21"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
|
@ -6547,6 +6563,12 @@
|
|||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.mapvalues": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz",
|
||||
"integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.set": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
|
||||
|
@ -9109,6 +9131,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"sort-any": {
|
||||
"version": "1.1.23",
|
||||
"resolved": "https://registry.npmjs.org/sort-any/-/sort-any-1.1.23.tgz",
|
||||
"integrity": "sha512-aY92w1RkjIyJd1l+O4btCwfAIfZm2r+zA6+cfKbKUO5D5MEZlqY27B7QyHHIsEShBsvx+Ur1Oq3v/gfR6wxD/w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"source-list-map": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
"@types/better-sqlite3": "^5.4.1",
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/chai-fs": "^2.0.2",
|
||||
"@types/deep-equal-in-any-order": "^1.0.1",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/minimist": "^1.2.1",
|
||||
|
@ -76,6 +77,7 @@
|
|||
"chai-fs": "^2.0.0",
|
||||
"chokidar": "^3.4.3",
|
||||
"concurrently": "^5.3.0",
|
||||
"deep-equal-in-any-order": "^1.0.28",
|
||||
"electron": "^10.2.0",
|
||||
"electron-mocha": "^10.0.0",
|
||||
"electron-rebuild": "^2.3.4",
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Logger } from '../modules/logger/logger';
|
|||
import { NhentaiApi } from '../modules/nhentai/nhentai-api';
|
||||
import '../modules/nhentai/nhentai-ipc-controller';
|
||||
import { NhentaiAppWindow } from '../modules/nhentai/nhentai-app-window';
|
||||
import { NhentaiSourceGetter } from '../modules/nhentai/nhentai-source-getter';
|
||||
import { SessionHelper } from '../modules/session/session-helper';
|
||||
import { Store } from '../modules/store/store';
|
||||
import BindingToSyntax = interfaces.BindingToSyntax;
|
||||
|
@ -34,7 +35,8 @@ container.bind('store').to(Store);
|
|||
|
||||
container.bind('session-helper').to(SessionHelper);
|
||||
|
||||
container.bind('nhentai-api').to(NhentaiApi);
|
||||
container.bind('nhentai-app-window').to(NhentaiAppWindow);
|
||||
container.bind('nhentai-api').to(NhentaiApi);
|
||||
container.bind('nhentai-source-getter').to(NhentaiSourceGetter);
|
||||
|
||||
container.bind('app-window-main').to(MainAppWindow);
|
||||
|
|
|
@ -4,6 +4,7 @@ import path from 'path';
|
|||
import { isDev } from '../../core/env';
|
||||
import type { SessionHelperInterface } from '../session/session-helper-interface';
|
||||
import type { AppWindowInterface } from './app-window-interface';
|
||||
import { WindowClosedError } from './window-closed-error';
|
||||
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
|
||||
|
||||
let defaultOptions: BrowserWindowConstructorOptions = {
|
||||
|
@ -35,6 +36,8 @@ export abstract class AppWindow implements AppWindowInterface {
|
|||
|
||||
protected _window: BrowserWindow | null = null;
|
||||
|
||||
protected readonly logger: LoggerInterface;
|
||||
|
||||
protected readonly sessionHelper: SessionHelperInterface;
|
||||
|
||||
protected options: BrowserWindowConstructorOptions;
|
||||
|
@ -44,10 +47,12 @@ export abstract class AppWindow implements AppWindowInterface {
|
|||
protected abstract loadOptions: LoadFileOptions | LoadURLOptions;
|
||||
|
||||
protected constructor(
|
||||
logger: LoggerInterface,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {}
|
||||
) {
|
||||
this.logger = logger;
|
||||
this.sessionHelper = sessionHelper;
|
||||
this.options = { ...defaultOptions, ...options };
|
||||
this.uri = uri;
|
||||
|
@ -104,5 +109,22 @@ export abstract class AppWindow implements AppWindowInterface {
|
|||
|
||||
protected onClosed(): void {}
|
||||
|
||||
protected getInnerHtml(selector: string): Promise<string> {
|
||||
return new Promise<string>((resolve) => {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
this._window.webContents
|
||||
.executeJavaScript(`document.querySelector('${selector}').innerHTML`)
|
||||
.then((innerHtml) => {
|
||||
resolve(innerHtml);
|
||||
})
|
||||
.catch(() => {
|
||||
void this.logger.warning(`Could not get the inner HTML of an element with the selector '${selector}'.`);
|
||||
resolve('');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract load(window: BrowserWindow): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -6,12 +6,13 @@ export abstract class FileAppWindow extends AppWindow {
|
|||
protected loadOptions: LoadFileOptions;
|
||||
|
||||
protected constructor(
|
||||
logger: LoggerInterface,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadFileOptions = {}
|
||||
) {
|
||||
super(sessionHelper, uri, options);
|
||||
super(logger, sessionHelper, uri, options);
|
||||
this.loadOptions = loadOptions;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,11 @@ import { FileAppWindow } from './file-app-window';
|
|||
|
||||
@injectable()
|
||||
export class MainAppWindow extends FileAppWindow {
|
||||
public constructor(@inject('session-helper') sessionHelper: SessionHelperInterface) {
|
||||
super(sessionHelper, 'frontend/index.html', {
|
||||
public constructor(
|
||||
@inject('logger') logger: LoggerInterface,
|
||||
@inject('session-helper') sessionHelper: SessionHelperInterface
|
||||
) {
|
||||
super(logger, sessionHelper, 'frontend/index.html', {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
},
|
||||
|
|
|
@ -12,12 +12,13 @@ export abstract class SiteAppWindow extends UrlAppWindow implements SiteAppWindo
|
|||
private windowLock: MutexInterface;
|
||||
|
||||
protected constructor(
|
||||
logger: LoggerInterface,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadURLOptions = {}
|
||||
) {
|
||||
super(sessionHelper, uri, options, loadOptions);
|
||||
super(logger, sessionHelper, uri, options, loadOptions);
|
||||
this.windowLock = new SimpleMutex();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,12 +32,13 @@ export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInte
|
|||
private loadWait: Promise<void> = Promise.resolve();
|
||||
|
||||
protected constructor(
|
||||
logger: LoggerInterface,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadURLOptions = {}
|
||||
) {
|
||||
super(sessionHelper, uri, {
|
||||
super(logger, sessionHelper, uri, {
|
||||
...options,
|
||||
...{
|
||||
webPreferences: {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
interface NhentaiApiInterface {
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
|
||||
getGallery(identifier: string): Promise<Nhentai.Gallery>;
|
||||
}
|
||||
|
|
|
@ -13,4 +13,8 @@ export class NhentaiApi implements NhentaiApiInterface {
|
|||
public getFavorites(): Promise<NodeJS.ReadableStream> {
|
||||
return this.appWindow.getFavorites();
|
||||
}
|
||||
|
||||
public getGallery(identifier: string): Promise<Nhentai.Gallery> {
|
||||
return this.appWindow.getGallery(identifier);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,6 @@ import type { SiteAppWindowInterface } from '../app-window/site-app-window-inter
|
|||
|
||||
interface NhentaiAppWindowInterface extends SiteAppWindowInterface {
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
|
||||
getGallery(identifier: string): Promise<Nhentai.Gallery>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import chai, { expect } from 'chai';
|
||||
import deepEqualInAnyOrder from 'deep-equal-in-any-order';
|
||||
import { describe, it, before } from 'mocha';
|
||||
import { container } from '../../core/container';
|
||||
import { LoggerMock } from '../logger/logger.mock';
|
||||
import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
|
||||
|
||||
chai.use(deepEqualInAnyOrder);
|
||||
|
||||
describe('Nhentai App Window', () => {
|
||||
before(() => {
|
||||
container.unbind('logger');
|
||||
container.bind('logger').to(LoggerMock);
|
||||
});
|
||||
|
||||
it('gets the gallery information from an identifier @slow', async () => {
|
||||
const nhentaiAppWindow: NhentaiAppWindowInterface = container.get('nhentai-app-window');
|
||||
|
||||
let expectedGallery: Nhentai.Gallery = {
|
||||
title: {
|
||||
pre: '[Homunculus]',
|
||||
main: 'Renai Sample',
|
||||
post: '[English] [Decensored]',
|
||||
},
|
||||
artists: ['homunculus'],
|
||||
groups: [],
|
||||
parodies: [],
|
||||
characters: [],
|
||||
tags: [
|
||||
'group',
|
||||
'stockings',
|
||||
'schoolgirl uniform',
|
||||
'glasses',
|
||||
'nakadashi',
|
||||
'incest',
|
||||
'tankoubon',
|
||||
'defloration',
|
||||
'swimsuit',
|
||||
'ffm threesome',
|
||||
'sister',
|
||||
'schoolboy uniform',
|
||||
'bikini',
|
||||
'uncensored',
|
||||
'small breasts',
|
||||
],
|
||||
};
|
||||
let gallery = await nhentaiAppWindow.getGallery('117300');
|
||||
expect(gallery).deep.equalInAnyOrder(expectedGallery, 'Renai Sample is not got correctly');
|
||||
|
||||
expectedGallery = {
|
||||
title: {
|
||||
pre: '(COMIC1☆12) [MOSQUITONE. (Great Mosu)]',
|
||||
main: 'Koisuru Dai Akuma | The Archdemon In Love',
|
||||
post: '(Gabriel DropOut) [English] {Tanjoubi + Hennojin} [Decensored]',
|
||||
},
|
||||
artists: ['great mosu'],
|
||||
groups: ['mosquitone.'],
|
||||
parodies: ['gabriel dropout'],
|
||||
characters: ['satanichia kurumizawa mcdowell'],
|
||||
tags: ['sole female', 'sole male', 'defloration', 'uncensored', 'kissing'],
|
||||
};
|
||||
gallery = await nhentaiAppWindow.getGallery('273405');
|
||||
expect(gallery).deep.equalInAnyOrder(expectedGallery, 'The Archdemon in Love is not got correctly!');
|
||||
}).timeout(5000);
|
||||
});
|
|
@ -19,14 +19,29 @@ import {
|
|||
coverLinkSelector,
|
||||
downloadLinkId,
|
||||
getGalleryId,
|
||||
getBookUrl,
|
||||
preTitleSelector,
|
||||
tagLabelArtists,
|
||||
labeledTagContainerSelector,
|
||||
tagNameSelector,
|
||||
tagSelector,
|
||||
tagLabelGroups,
|
||||
tagLabelParodies,
|
||||
tagLabelCharacters,
|
||||
tagLabelTags,
|
||||
mainTitleSelector,
|
||||
postTitleSelector,
|
||||
} from './nhentai-util';
|
||||
|
||||
const waitInterval = 2000;
|
||||
|
||||
@injectable()
|
||||
export class NhentaiAppWindow extends SiteAppWindow implements NhentaiAppWindowInterface {
|
||||
public constructor(@inject('session-helper') sessionHelper: SessionHelperInterface) {
|
||||
super(sessionHelper, nhentaiUrl);
|
||||
public constructor(
|
||||
@inject('logger') logger: LoggerInterface,
|
||||
@inject('session-helper') sessionHelper: SessionHelperInterface
|
||||
) {
|
||||
super(logger, sessionHelper, nhentaiUrl);
|
||||
}
|
||||
|
||||
public async getFavorites(): Promise<NodeJS.ReadableStream> {
|
||||
|
@ -70,11 +85,75 @@ export class NhentaiAppWindow extends SiteAppWindow implements NhentaiAppWindowI
|
|||
|
||||
return readable;
|
||||
} catch (e) {
|
||||
this.close();
|
||||
release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async getGallery(identifier: string): Promise<Nhentai.Gallery> {
|
||||
if (this.isClosed()) {
|
||||
await this.open();
|
||||
}
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
|
||||
const gallery: Nhentai.Gallery = {
|
||||
title: {
|
||||
pre: '',
|
||||
main: '',
|
||||
post: '',
|
||||
},
|
||||
artists: [],
|
||||
groups: [],
|
||||
parodies: [],
|
||||
characters: [],
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const release = await this.acquireLock();
|
||||
const bookUrl = getBookUrl(identifier);
|
||||
|
||||
try {
|
||||
await this.loadUrlSafe(bookUrl);
|
||||
await Promise.all([
|
||||
this.getInnerHtml(preTitleSelector).then((preTitle) => {
|
||||
gallery.title.pre = preTitle.trim();
|
||||
}),
|
||||
this.getInnerHtml(mainTitleSelector).then((mainTitle) => {
|
||||
gallery.title.main = mainTitle;
|
||||
}),
|
||||
this.getInnerHtml(postTitleSelector).then((postTitle) => {
|
||||
gallery.title.post = postTitle.trim();
|
||||
}),
|
||||
this.getTags(tagLabelArtists).then((artists: string[]) => {
|
||||
gallery.artists = artists;
|
||||
}),
|
||||
this.getTags(tagLabelGroups).then((groups: string[]) => {
|
||||
gallery.groups = groups;
|
||||
}),
|
||||
this.getTags(tagLabelParodies).then((parodies: string[]) => {
|
||||
gallery.parodies = parodies;
|
||||
}),
|
||||
this.getTags(tagLabelCharacters).then((characters: string[]) => {
|
||||
gallery.characters = characters;
|
||||
}),
|
||||
this.getTags(tagLabelTags).then((tags: string[]) => {
|
||||
gallery.tags = tags;
|
||||
}),
|
||||
]);
|
||||
this.close();
|
||||
release();
|
||||
} catch (e) {
|
||||
this.close();
|
||||
release();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return gallery;
|
||||
}
|
||||
|
||||
protected getCsp(): Session.ContentSecurityPolicy {
|
||||
return {
|
||||
'default-src': ['nhentai.net'],
|
||||
|
@ -186,4 +265,21 @@ export class NhentaiAppWindow extends SiteAppWindow implements NhentaiAppWindowI
|
|||
torrentFile: readable,
|
||||
};
|
||||
}
|
||||
|
||||
private getTags(tagLabel: string): Promise<string[]> {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
return this._window.webContents.executeJavaScript(
|
||||
`Array.from(
|
||||
document.querySelectorAll('${labeledTagContainerSelector}')
|
||||
).filter(
|
||||
(tagContainer) => tagContainer.textContent.includes('${tagLabel}:')
|
||||
).map(
|
||||
(tagContainer) => Array.from(tagContainer.querySelectorAll('${tagSelector}'))
|
||||
).flat().map(
|
||||
(tagElement) => tagElement.querySelector('${tagNameSelector}').innerHTML
|
||||
)`
|
||||
) as Promise<string[]>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
import path from 'path';
|
||||
import { createWriteStream } from 'fs-extra';
|
||||
import { container } from '../../core/container';
|
||||
import { Database, getConnection } from '../../core/database';
|
||||
import type { Work } from '../../entities/library/work';
|
||||
import type { DialogInterface } from '../dialog/dialog-interface';
|
||||
import { answer } from '../ipc/annotations/answer';
|
||||
import type { SourceGetterInterface } from '../source/source-getter-interface';
|
||||
|
||||
export class NhentaiIpcController implements IpcController {
|
||||
private readonly nhentaiApi: NhentaiApiInterface;
|
||||
|
||||
private readonly nhentaiSourceGetter: SourceGetterInterface;
|
||||
|
||||
private readonly translator: I18nTranslatorInterface;
|
||||
|
||||
private readonly dialog: DialogInterface;
|
||||
|
||||
private constructor(nhentaiApi: NhentaiApiInterface, translator: I18nTranslatorInterface, dialog: DialogInterface) {
|
||||
private constructor(
|
||||
nhentaiApi: NhentaiApiInterface,
|
||||
nhentaiSourceGetter: SourceGetterInterface,
|
||||
translator: I18nTranslatorInterface,
|
||||
dialog: DialogInterface
|
||||
) {
|
||||
this.nhentaiApi = nhentaiApi;
|
||||
this.nhentaiSourceGetter = nhentaiSourceGetter;
|
||||
this.translator = translator;
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
@ -39,10 +50,19 @@ export class NhentaiIpcController implements IpcController {
|
|||
});
|
||||
}
|
||||
|
||||
@answer(IpcChannel.NHENTAI_GET_WORK)
|
||||
public async nhentaiGetWork({ galleryId }: { galleryId: string }): Promise<Work> {
|
||||
const work = await this.nhentaiSourceGetter.find(galleryId);
|
||||
const { manager } = await getConnection(Database.LIBRARY);
|
||||
|
||||
return manager.save(work);
|
||||
}
|
||||
|
||||
public get(): NhentaiIpcController {
|
||||
const nhentaiApi: NhentaiApiInterface = container.get('nhentai-api');
|
||||
const nhentaiSourceGetter: SourceGetterInterface = container.get('nhentai-source-getter');
|
||||
const translator: I18nTranslatorInterface = container.get('i18n-translator');
|
||||
const dialog: DialogInterface = container.get('dialog');
|
||||
return new NhentaiIpcController(nhentaiApi, translator, dialog);
|
||||
return new NhentaiIpcController(nhentaiApi, nhentaiSourceGetter, translator, dialog);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { inject } from '../../core/inject';
|
||||
import { Work } from '../../entities/library/work';
|
||||
import type { SourceGetterInterface } from '../source/source-getter-interface';
|
||||
|
||||
@injectable()
|
||||
export class NhentaiSourceGetter implements SourceGetterInterface {
|
||||
private nhentaiApi: NhentaiApiInterface;
|
||||
|
||||
public constructor(@inject('nhentai-api') nhentaiApi: NhentaiApiInterface) {
|
||||
this.nhentaiApi = nhentaiApi;
|
||||
}
|
||||
|
||||
public async find(identifier: string): Promise<Work> {
|
||||
const gallery = await this.nhentaiApi.getGallery(identifier);
|
||||
|
||||
const work = new Work();
|
||||
|
||||
work.nameCanonical = gallery.title.main;
|
||||
|
||||
return work;
|
||||
}
|
||||
}
|
|
@ -11,11 +11,27 @@ export const downloadLinkId = 'download';
|
|||
|
||||
export const nextFavoritePageSelector = 'a.next';
|
||||
export const coverLinkSelector = 'a.cover';
|
||||
export const preTitleSelector = 'h1.title .before';
|
||||
export const mainTitleSelector = 'h1.title .pretty';
|
||||
export const postTitleSelector = 'h1.title .after';
|
||||
export const labeledTagContainerSelector = '.tag-container.field-name';
|
||||
export const tagSelector = '.tag';
|
||||
export const tagNameSelector = 'span.name';
|
||||
|
||||
export const tagLabelParodies = 'Parodies';
|
||||
export const tagLabelCharacters = 'Characters';
|
||||
export const tagLabelTags = 'Tags';
|
||||
export const tagLabelArtists = 'Artists';
|
||||
export const tagLabelGroups = 'Groups';
|
||||
|
||||
export function getFavoritePageUrl(page?: number): string {
|
||||
return `${url + paths.favorites}${page ? `?page=${page}` : ''}`;
|
||||
}
|
||||
|
||||
export function getBookUrl(galleryId: string): string {
|
||||
return `${url}g/${galleryId}/`;
|
||||
}
|
||||
|
||||
export function getGalleryId(bookUrl: string): string {
|
||||
const regExpExecArray = /https:\/\/nhentai\.net\/g\/(\d+)/.exec(bookUrl);
|
||||
if (regExpExecArray && regExpExecArray[1]) {
|
||||
|
|
|
@ -3,4 +3,17 @@ declare namespace Nhentai {
|
|||
name: string;
|
||||
torrentFile: NodeJS.ReadableStream;
|
||||
};
|
||||
|
||||
type Gallery = {
|
||||
title: {
|
||||
pre: string;
|
||||
main: string;
|
||||
post: string;
|
||||
};
|
||||
artists: string[];
|
||||
groups: string[];
|
||||
parodies: string[];
|
||||
characters: string[];
|
||||
tags: string[];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { Work } from '../../entities/library/work';
|
||||
|
||||
interface SourceGetterInterface {
|
||||
find(identifier: string): Promise<Work>;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import NhentaiGetWork from './components/3-polymers/NhentaiGetWork.svelte';
|
||||
import NhentaiLogin from './components/3-polymers/NhentaiLogin.svelte';
|
||||
</script>
|
||||
|
||||
|
@ -45,4 +46,5 @@
|
|||
|
||||
<main>
|
||||
<NhentaiLogin></NhentaiLogin>
|
||||
<NhentaiGetWork />
|
||||
</main>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
import { nhentaiGetWork } from '../../services/api';
|
||||
import SvelteButton from '../1-atoms/SvelteButton.svelte';
|
||||
import { t } from '../../services/utils';
|
||||
|
||||
let galleryId;
|
||||
let work;
|
||||
|
||||
async function handleClick() {
|
||||
work = await nhentaiGetWork(galleryId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="nhentai-get-work">
|
||||
<label><input type="text" placeholder="177013" bind:value="{galleryId}" /></label
|
||||
><SvelteButton on:click="{handleClick}">{t('Get')}</SvelteButton>
|
||||
<div>{JSON.stringify(work)}</div>
|
||||
</div>
|
|
@ -30,3 +30,7 @@ const ipcClient: IpcClient = {
|
|||
export function nhentaiSaveFavorites(): Promise<void> {
|
||||
return ipcClient.ask(IpcChannel.NHENTAI_SAVE_FAVORITES) as Promise<void>;
|
||||
}
|
||||
|
||||
export function nhentaiGetWork(galleryId: string): Promise<Work> {
|
||||
return ipcClient.ask(IpcChannel.NHENTAI_GET_WORK, { galleryId }) as Promise<Work>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
type Work = {
|
||||
nameCanonical: string;
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
declare const enum IpcChannel {
|
||||
NHENTAI_SAVE_FAVORITES = 'NHENTAI_SAVE_FAVORITES',
|
||||
NHENTAI_GET_WORK = 'NHENTAI_GET_WORK',
|
||||
}
|
||||
|
||||
type IpcPayload = {
|
||||
|
|
Loading…
Reference in New Issue