diff --git a/src/NHentai/NHentai.ts b/src/NHentai/NHentai.ts index cba262a..2cbe68e 100644 --- a/src/NHentai/NHentai.ts +++ b/src/NHentai/NHentai.ts @@ -43,7 +43,7 @@ import { popularTags } from './tags.json' const NHENTAI_URL = 'https://nhentai.net' export const NHentaiInfo: SourceInfo = { - version: '4.0.8', + version: '4.0.9', name: 'nhentai', icon: 'icon.png', author: 'NotMarek & Netsky', @@ -84,7 +84,7 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP stateManager = App.createSourceStateManager() - // Sourrce Settings + // Source Settings async getSourceMenu(): Promise { return Promise.resolve(App.createDUISection({ id: 'main', @@ -132,9 +132,20 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP this.CloudFlareError(response.status) const jsonData = this.parseJson(response) + + // Add the manga ID to the read list + await this.addToReadMangaIds(mangaId) + return parseChapterDetails(jsonData, mangaId) } + // Method to store read manga IDs + async addToReadMangaIds(mangaId: string): Promise { + const readMangaIds = await this.stateManager.retrieve('read_manga_ids') ?? {} + readMangaIds[`read_manga_${mangaId}`] = true + await this.stateManager.store('read_manga_ids', readMangaIds) + } + async getSearchTags(): Promise { const arrayTags: Tag[] = [] @@ -150,6 +161,8 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP async getSearchResults(query: SearchRequest, metadata: any): Promise { const page: number = metadata?.page ?? 1 const title: string = query.title ?? '' + const skipReadManga = await this.stateManager.retrieve('skip_read_manga') ?? false + const readMangaIds = skipReadManga ? await this.getReadMangaIds() : [] if (metadata?.stopSearch ?? false) { return App.createPagedResults({ @@ -178,7 +191,7 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP } }) - // Normal search query + // Normal search query } else { const q: string = encodeURIComponent(`${title} ${query?.includedTags?.map((x: Tag) => ` +${x.id}`)} `) + await this.generateQuery() @@ -190,8 +203,9 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP this.CloudFlareError(response.status) const jsonData = this.parseJson(response) + const results = parseSearch(jsonData).filter(manga => !readMangaIds.includes(manga.mangaId)) return App.createPagedResults({ - results: parseSearch(jsonData), + results, metadata: { page: page + 1 } @@ -200,6 +214,8 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP } async getHomePageSections(sectionCallback: (section: HomeSection) => void): Promise { + const skipReadManga = await this.stateManager.retrieve('skip_read_manga') ?? false + const readMangaIds = skipReadManga ? await this.getReadMangaIds() : [] const sections = [ { request: App.createRequest({ @@ -236,6 +252,30 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP containsMoreItems: true, type: HomeSectionType.singleRowNormal }) + }, + { + request: App.createRequest({ + url: `${NHENTAI_URL}/api/galleries/search?query=${await this.generateQuery()}&sort=popular-month`, + method: 'GET' + }), + sectionID: App.createHomeSection({ + id: 'popular-month', + title: 'Popular Monthly', + containsMoreItems: true, + type: HomeSectionType.singleRowNormal + }) + }, + { + request: App.createRequest({ + url: `${NHENTAI_URL}/api/galleries/search?query=${await this.generateQuery()}&sort=popular`, + method: 'GET' + }), + sectionID: App.createHomeSection({ + id: 'popular', + title: 'Popular All-Time', + containsMoreItems: true, + type: HomeSectionType.singleRowNormal + }) } ] @@ -251,7 +291,7 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP if (hasNoResults(jsonData)) { return } - section.sectionID.items = parseSearch(jsonData) + section.sectionID.items = parseSearch(jsonData).filter(manga => !readMangaIds.includes(manga.mangaId)) sectionCallback(section.sectionID) }) @@ -263,6 +303,8 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP async getViewMoreItems(homepageSectionId: string, metadata: any): Promise { let page: number = metadata?.page ?? 1 + const skipReadManga = await this.stateManager.retrieve('skip_read_manga') ?? false + const readMangaIds = skipReadManga ? await this.getReadMangaIds() : [] const request = App.createRequest({ url: `${NHENTAI_URL}/api/galleries/search?query=${await this.generateQuery()}&sort=${homepageSectionId}&page=${page}`, method: 'GET' @@ -273,14 +315,23 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP const jsonData = this.parseJson(response) page++ + const results = parseSearch(jsonData).filter(manga => !readMangaIds.includes(manga.mangaId)) return App.createPagedResults({ - results: parseSearch(jsonData), + results, metadata: { page: page } }) } + async getReadMangaIds(): Promise { + const allData = await this.stateManager.retrieve('read_manga_ids') + if (!allData) { + return [] + } + return Object.keys(allData).filter(key => key.startsWith('read_manga_')).map(key => key.replace('read_manga_', '')) + } + CloudFlareError(status: number): void { if (status == 503 || status == 403) { throw new Error(`CLOUDFLARE BYPASS ERROR:\nPlease go to the homepage of <${NHentai.name}> and press the cloud icon.`) diff --git a/src/NHentai/NHentaiHelper.ts b/src/NHentai/NHentaiHelper.ts index 4d3d1d5..aa52681 100644 --- a/src/NHentai/NHentaiHelper.ts +++ b/src/NHentai/NHentaiHelper.ts @@ -87,6 +87,12 @@ class NHSortOrderClass { name: 'Most Recent', NHCode: 'date', shortcuts: ['s:r', 's:recent', 'sort:r', 'sort:recent'] + }, + { + // Sort by popular this month + name: 'Popular This Month', + NHCode: 'popular-month', + shortcuts: ['s:pm', 's:m', 's:popular-month', 'sort:pm', 'sort:m', 'sort:popular-month'] } ] diff --git a/src/NHentai/NHentaiInterfaces.ts b/src/NHentai/NHentaiInterfaces.ts index 5a53201..ce921b1 100644 --- a/src/NHentai/NHentaiInterfaces.ts +++ b/src/NHentai/NHentaiInterfaces.ts @@ -1,6 +1,6 @@ export interface ImagePageObject { - t: 'j' | 'p' | 'g';// JPG (≧◡≦) + t: 'j' | 'p' | 'g' | 'w'; // Added 'w' for webp w: number; h: number; } @@ -107,5 +107,5 @@ export interface QueryResponse { export interface RequestMetadata { nextPage?: number; maxPages?: number; - sort: 'popular-today' | 'popular-week' | 'popular' | ''; + sort: 'popular-today' | 'popular-week' | 'popular-month' | 'popular' | ''; } \ No newline at end of file diff --git a/src/NHentai/NHentaiParser.ts b/src/NHentai/NHentaiParser.ts index 84ef906..4dd725f 100644 --- a/src/NHentai/NHentaiParser.ts +++ b/src/NHentai/NHentaiParser.ts @@ -34,7 +34,7 @@ export const parseMangaDetails = (data: Gallery): SourceManga => { image: `https://t.nhentai.net/galleries/${data.media_id}/cover.${typeOfImage(data.images.cover)}`, status: 'Completed', tags: [App.createTagSection({ id: 'tags', label: 'Tags', tags: tags })], - desc: `Pages: ${data.num_pages}` + desc: `Pages: ${data.num_pages} - Favorites: ${data.num_favorites}` }) }) } @@ -76,7 +76,7 @@ export const parseSearch = (data: QueryResponse): PartialSourceManga[] => { image: `https://t.nhentai.net/galleries/${gallery.media_id}/cover.${typeOfImage(gallery.images.cover)}`, title: gallery.title.pretty, mangaId: gallery.id.toString(), - subtitle: NHLanguages.getName(getLanguage(gallery)) + subtitle: `${NHLanguages.getName(getLanguage(gallery)).substring(0, 3)} - Pgs. ${gallery.num_pages}` })) collectedIds.push(gallery.id.toString()) } diff --git a/src/NHentai/NHentaiSettings.ts b/src/NHentai/NHentaiSettings.ts index 23e21d6..3f27580 100644 --- a/src/NHentai/NHentaiSettings.ts +++ b/src/NHentai/NHentaiSettings.ts @@ -18,7 +18,8 @@ export const getSortOrders = async (stateManager: SourceStateManager): Promise => { - return (await stateManager.retrieve('extra_args') as string) ?? '-lolicon -shotacon -yaoi' + const value = await stateManager.retrieve('extra_args'); + return typeof value === 'string' ? value : ''; } @@ -75,6 +76,14 @@ export const settings = (stateManager: SourceStateManager): DUINavigationButton ) } }) + }), + App.createDUISwitch({ + id: 'skip_read_manga', + label: 'Skip Read Manga', + value: App.createDUIBinding({ + get: async () => await stateManager.retrieve('skip_read_manga') ?? false, + set: async (newValue) => await stateManager.store('skip_read_manga', newValue) + }) }) ] },