From 0a2a26617610875969e06ab960a77e4155fa0f09 Mon Sep 17 00:00:00 2001 From: Xymorot Date: Thu, 7 Jan 2021 04:51:07 +0100 Subject: [PATCH] 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. --- package-lock.json | 31 ++++++ package.json | 2 + src/main/core/container.ts | 4 +- src/main/modules/app-window/app-window.ts | 22 ++++ .../modules/app-window/file-app-window.ts | 3 +- .../modules/app-window/main-app-window.ts | 7 +- .../modules/app-window/site-app-window.ts | 3 +- src/main/modules/app-window/url-app-window.ts | 3 +- .../nhentai/nhentai-api-interface.d.ts | 2 + src/main/modules/nhentai/nhentai-api.ts | 4 + .../nhentai/nhentai-app-window-interface.d.ts | 2 + .../nhentai/nhentai-app-window.spec.ts | 65 ++++++++++++ .../modules/nhentai/nhentai-app-window.ts | 100 +++++++++++++++++- .../modules/nhentai/nhentai-ipc-controller.ts | 24 ++++- .../modules/nhentai/nhentai-source-getter.ts | 23 ++++ src/main/modules/nhentai/nhentai-util.ts | 16 +++ src/main/modules/nhentai/nhentai.d.ts | 13 +++ .../source/source-getter-interface.d.ts | 5 + src/renderer/App.svelte | 2 + .../3-polymers/NhentaiGetWork.svelte | 18 ++++ src/renderer/services/api.ts | 4 + types/entities/work.d.ts | 3 + types/ipc.d.ts | 1 + 23 files changed, 347 insertions(+), 10 deletions(-) create mode 100644 src/main/modules/nhentai/nhentai-app-window.spec.ts create mode 100644 src/main/modules/nhentai/nhentai-source-getter.ts create mode 100644 src/main/modules/source/source-getter-interface.d.ts create mode 100644 src/renderer/components/3-polymers/NhentaiGetWork.svelte create mode 100644 types/entities/work.d.ts diff --git a/package-lock.json b/package-lock.json index ebb9e07..96ebbb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index f28766b..d187a7e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/main/core/container.ts b/src/main/core/container.ts index 2e62300..1ff8412 100644 --- a/src/main/core/container.ts +++ b/src/main/core/container.ts @@ -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); diff --git a/src/main/modules/app-window/app-window.ts b/src/main/modules/app-window/app-window.ts index 91cd97b..4f67ba8 100644 --- a/src/main/modules/app-window/app-window.ts +++ b/src/main/modules/app-window/app-window.ts @@ -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 { + return new Promise((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; } diff --git a/src/main/modules/app-window/file-app-window.ts b/src/main/modules/app-window/file-app-window.ts index 28b3e93..09eb27a 100644 --- a/src/main/modules/app-window/file-app-window.ts +++ b/src/main/modules/app-window/file-app-window.ts @@ -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; } diff --git a/src/main/modules/app-window/main-app-window.ts b/src/main/modules/app-window/main-app-window.ts index d83a41b..4c1c573 100644 --- a/src/main/modules/app-window/main-app-window.ts +++ b/src/main/modules/app-window/main-app-window.ts @@ -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, }, diff --git a/src/main/modules/app-window/site-app-window.ts b/src/main/modules/app-window/site-app-window.ts index cc44f2b..321b88d 100644 --- a/src/main/modules/app-window/site-app-window.ts +++ b/src/main/modules/app-window/site-app-window.ts @@ -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(); } diff --git a/src/main/modules/app-window/url-app-window.ts b/src/main/modules/app-window/url-app-window.ts index f0fdc97..5e031e8 100644 --- a/src/main/modules/app-window/url-app-window.ts +++ b/src/main/modules/app-window/url-app-window.ts @@ -32,12 +32,13 @@ export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInte private loadWait: Promise = Promise.resolve(); protected constructor( + logger: LoggerInterface, sessionHelper: SessionHelperInterface, uri: string, options: BrowserWindowConstructorOptions = {}, loadOptions: LoadURLOptions = {} ) { - super(sessionHelper, uri, { + super(logger, sessionHelper, uri, { ...options, ...{ webPreferences: { diff --git a/src/main/modules/nhentai/nhentai-api-interface.d.ts b/src/main/modules/nhentai/nhentai-api-interface.d.ts index ade8ffe..8fc2d24 100644 --- a/src/main/modules/nhentai/nhentai-api-interface.d.ts +++ b/src/main/modules/nhentai/nhentai-api-interface.d.ts @@ -1,3 +1,5 @@ interface NhentaiApiInterface { getFavorites(): Promise; + + getGallery(identifier: string): Promise; } diff --git a/src/main/modules/nhentai/nhentai-api.ts b/src/main/modules/nhentai/nhentai-api.ts index 82cfe5f..939e223 100644 --- a/src/main/modules/nhentai/nhentai-api.ts +++ b/src/main/modules/nhentai/nhentai-api.ts @@ -13,4 +13,8 @@ export class NhentaiApi implements NhentaiApiInterface { public getFavorites(): Promise { return this.appWindow.getFavorites(); } + + public getGallery(identifier: string): Promise { + return this.appWindow.getGallery(identifier); + } } diff --git a/src/main/modules/nhentai/nhentai-app-window-interface.d.ts b/src/main/modules/nhentai/nhentai-app-window-interface.d.ts index 8e70f91..bfaec59 100644 --- a/src/main/modules/nhentai/nhentai-app-window-interface.d.ts +++ b/src/main/modules/nhentai/nhentai-app-window-interface.d.ts @@ -2,4 +2,6 @@ import type { SiteAppWindowInterface } from '../app-window/site-app-window-inter interface NhentaiAppWindowInterface extends SiteAppWindowInterface { getFavorites(): Promise; + + getGallery(identifier: string): Promise; } diff --git a/src/main/modules/nhentai/nhentai-app-window.spec.ts b/src/main/modules/nhentai/nhentai-app-window.spec.ts new file mode 100644 index 0000000..98edf38 --- /dev/null +++ b/src/main/modules/nhentai/nhentai-app-window.spec.ts @@ -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); +}); diff --git a/src/main/modules/nhentai/nhentai-app-window.ts b/src/main/modules/nhentai/nhentai-app-window.ts index d0627f1..955fbf8 100644 --- a/src/main/modules/nhentai/nhentai-app-window.ts +++ b/src/main/modules/nhentai/nhentai-app-window.ts @@ -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 { @@ -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 { + 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 { + 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; + } } diff --git a/src/main/modules/nhentai/nhentai-ipc-controller.ts b/src/main/modules/nhentai/nhentai-ipc-controller.ts index 5deb2dc..03c08e9 100644 --- a/src/main/modules/nhentai/nhentai-ipc-controller.ts +++ b/src/main/modules/nhentai/nhentai-ipc-controller.ts @@ -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 { + 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); } } diff --git a/src/main/modules/nhentai/nhentai-source-getter.ts b/src/main/modules/nhentai/nhentai-source-getter.ts new file mode 100644 index 0000000..c945471 --- /dev/null +++ b/src/main/modules/nhentai/nhentai-source-getter.ts @@ -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 { + const gallery = await this.nhentaiApi.getGallery(identifier); + + const work = new Work(); + + work.nameCanonical = gallery.title.main; + + return work; + } +} diff --git a/src/main/modules/nhentai/nhentai-util.ts b/src/main/modules/nhentai/nhentai-util.ts index 9eccd36..77bc5da 100644 --- a/src/main/modules/nhentai/nhentai-util.ts +++ b/src/main/modules/nhentai/nhentai-util.ts @@ -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]) { diff --git a/src/main/modules/nhentai/nhentai.d.ts b/src/main/modules/nhentai/nhentai.d.ts index 60be1ac..5829958 100644 --- a/src/main/modules/nhentai/nhentai.d.ts +++ b/src/main/modules/nhentai/nhentai.d.ts @@ -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[]; + }; } diff --git a/src/main/modules/source/source-getter-interface.d.ts b/src/main/modules/source/source-getter-interface.d.ts new file mode 100644 index 0000000..23ca512 --- /dev/null +++ b/src/main/modules/source/source-getter-interface.d.ts @@ -0,0 +1,5 @@ +import { Work } from '../../entities/library/work'; + +interface SourceGetterInterface { + find(identifier: string): Promise; +} diff --git a/src/renderer/App.svelte b/src/renderer/App.svelte index c6f06eb..2bbeed7 100644 --- a/src/renderer/App.svelte +++ b/src/renderer/App.svelte @@ -1,4 +1,5 @@ @@ -45,4 +46,5 @@
+
diff --git a/src/renderer/components/3-polymers/NhentaiGetWork.svelte b/src/renderer/components/3-polymers/NhentaiGetWork.svelte new file mode 100644 index 0000000..37cf2b1 --- /dev/null +++ b/src/renderer/components/3-polymers/NhentaiGetWork.svelte @@ -0,0 +1,18 @@ + + +
+ {t('Get')} +
{JSON.stringify(work)}
+
diff --git a/src/renderer/services/api.ts b/src/renderer/services/api.ts index 4ef95ad..60006d6 100644 --- a/src/renderer/services/api.ts +++ b/src/renderer/services/api.ts @@ -30,3 +30,7 @@ const ipcClient: IpcClient = { export function nhentaiSaveFavorites(): Promise { return ipcClient.ask(IpcChannel.NHENTAI_SAVE_FAVORITES) as Promise; } + +export function nhentaiGetWork(galleryId: string): Promise { + return ipcClient.ask(IpcChannel.NHENTAI_GET_WORK, { galleryId }) as Promise; +} diff --git a/types/entities/work.d.ts b/types/entities/work.d.ts new file mode 100644 index 0000000..4abbbce --- /dev/null +++ b/types/entities/work.d.ts @@ -0,0 +1,3 @@ +type Work = { + nameCanonical: string; +}; diff --git a/types/ipc.d.ts b/types/ipc.d.ts index 5964495..e91812d 100644 --- a/types/ipc.d.ts +++ b/types/ipc.d.ts @@ -1,5 +1,6 @@ declare const enum IpcChannel { NHENTAI_SAVE_FAVORITES = 'NHENTAI_SAVE_FAVORITES', + NHENTAI_GET_WORK = 'NHENTAI_GET_WORK', } type IpcPayload = {