Skip to content

Commit

Permalink
0.8 branch + nhentai 3.0.0
Browse files Browse the repository at this point in the history
hahaha I subbed to Paperback Patreon just to update this 💀
This should be a working start for nhentai on 0.8 - but settings seem unstable in the app for now.
  • Loading branch information
ItemCookie committed Jun 6, 2023
1 parent 355108b commit 9a696b0
Show file tree
Hide file tree
Showing 8 changed files with 566 additions and 36 deletions.
25 changes: 18 additions & 7 deletions src/NHentai/NHentai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ import {
import { Tags } from './Data';
import { checkCloudflare } from './Utils';
import { Data } from './Data';
import {
Resettings,
SettingKeys,
resetSettings,
} from './Resettings';
import { settingsNavButton } from './ui/SettingsUI';

export const NHentaiInfo: SourceInfo = {
version: Data.info.version,
Expand Down Expand Up @@ -90,12 +96,16 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP
readonly stateManager = App.createSourceStateManager();

async getSourceMenu(): Promise<DUISection> {
return Promise.resolve(App.createDUISection({
return App.createDUISection({
id: 'main',
header: 'Source Settings',
rows: () => Promise.resolve([]),
footer: 'You might need to restart the app for some changes to apply visually.',
isHidden: false,
}));
rows: async () => [
settingsNavButton(this.stateManager, this.requestManager),
resetSettings(this.stateManager),
],
});
}

async getSearchResults(query: SearchRequest, metadata: SearchMetadata): Promise<PagedResults> {
Expand All @@ -108,16 +118,16 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP
suffix: this.resolvesTag(query.includedTags, Tags.withoutSuffix) ? '' : undefined,
});

// TODO: Setting for 1 or 2 pages
const results = await Search.searchMany(2, ctx, this.getSearchObjects(), metadata);
const double = await Resettings.get(this.stateManager, SettingKeys.DoubleSearch);
const results = await Search.searchMany(double ? 2 : 1, ctx, this.getSearchObjects(), metadata);

return App.createPagedResults({
results: results.partials,
metadata: results.metadata,
});
}

async getSearchTags?(): Promise<TagSection[]> {
async getSearchTags(): Promise<TagSection[]> {
const sections: Record<string, TagSection> = {};

sections.sorting = App.createTagSection({
Expand Down Expand Up @@ -185,7 +195,7 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP
App.createHomeSection({
id: source,
title: SortDefs.getName(source),
type: HomeSectionType.singleRowNormal,
type: HomeSectionType.singleRowNormal, // TODO: Maybe change? not sure what this does yet.
containsMoreItems: true,
}),
);
Expand All @@ -197,6 +207,7 @@ export class NHentai implements SearchResultsProviding, MangaProviding, ChapterP
Search.createWithSettings(this.stateManager, undefined, { sort: section.id }).then(async (ctx) => {
const results = await Search.search(ctx, this.getSearchObjects(), {});
section.items = results.partials ?? [];
section.containsMoreItems = results.metadata.shouldStop === false && section.items.length > 0;
sectionCallback(section);
}),
);
Expand Down
218 changes: 218 additions & 0 deletions src/NHentai/Resettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { SourceStateManager } from '@paperback/types';
import {
LangDefs,
SearchContext,
SearchResults,
SortDefs,
} from './models';
import { dumbify } from './Utils';

export type SearchHistoryEntry = {
/**
* The full search query used.
*/
text: string;
/**
* The sort used.
*/
sort?: string;
/**
* The status code returned by the search request.
*/
status?: number;
/**
* Whether the `cf-mitigated: challenge` header was present.
*/
challenged?: boolean;
/**
* Whether the page was to be skipped.
*/
skipped?: boolean;
/**
* The reason given to the search result.
*/
reason?: string;
/**
* Whether the search was by fallback.
*/
fallback?: boolean;
/**
* The next page to search.
*/
nextPage?: number;
/**
* The last page to search.
*/
maxPage?: number;
shouldStop?: boolean;
/**
* Whether searches stopped.
*/
stopped?: boolean;
};

export const SettingKeys = Object.freeze({
// History
CollectSearches: 'collect_searches',
CollectSearchesLimit: 'collect_searches_limit',
SearchHistory: 'search_history',
// Searching
AlwaysFallback: 'always_fallback',
DoubleSearch: 'double_search',
SearchSuffix: 'search_suffix',
Language: 'language',
Sorting: 'sorting',
});

export interface SettingsObject {
// History
[SettingKeys.CollectSearches]: boolean;
[SettingKeys.CollectSearchesLimit]: number;
[SettingKeys.SearchHistory]: SearchHistoryEntry[] | Readonly<SearchHistoryEntry[]>;
// Searching
[SettingKeys.AlwaysFallback]: boolean;
[SettingKeys.DoubleSearch]: boolean;
[SettingKeys.SearchSuffix]: string;
[SettingKeys.Language]: string;
[SettingKeys.Sorting]: string;
}

export const DefaultSettings: Readonly<SettingsObject> = Object.freeze({
// History
[SettingKeys.CollectSearches]: false,
[SettingKeys.CollectSearchesLimit]: 20,
[SettingKeys.SearchHistory]: Object.freeze([]),
// Searching
[SettingKeys.AlwaysFallback]: false,
[SettingKeys.DoubleSearch]: false,
[SettingKeys.SearchSuffix]: '',
[SettingKeys.Language]: LangDefs.getDefault() ?? 'english',
[SettingKeys.Sorting]: SortDefs.getDefault() ?? 'date',
});

export type SettingKey = (typeof SettingKeys)[(keyof typeof SettingKeys)];
export type SettingType<T extends SettingKey> = SettingsObject[T];

export const Resettings = {

_isValid(key: SettingKey, val: unknown): boolean {
if (val == undefined) {
return false;
}
const def = DefaultSettings[key];
if (typeof def !== typeof val) {
return false;
}
if (Array.isArray(def) !== Array.isArray(val)) {
return false;
}
return true;
},

async set<T extends SettingKey, P extends SettingType<T>>(states: SourceStateManager | undefined, key: T, value?: P): Promise<P> {
if (states == undefined) {
return DefaultSettings[key] as P;
}
const old = this.get<T, P>(states, key);
if (value == undefined || this._isValid(key, value)) {
await states.store(key, value ?? null);
}
return old;
},

async get<T extends SettingKey, P extends SettingType<T>>(states: SourceStateManager | undefined, key: T): Promise<P> {
if (states == undefined) {
return DefaultSettings[key] as P;
}
const stored = await states.retrieve(key) as P;
if (!this._isValid(key, stored)) {
if (stored !== null) {
await states.store(key, null);
}
return DefaultSettings[key] as P;
}
return stored;
},

async has<T extends SettingKey>(states: SourceStateManager | undefined, key: T): Promise<boolean> {
if (states == undefined) {
return false;
}
const stored = await states.retrieve(key);
return this._isValid(key, stored);
},

async clear(states: SourceStateManager | undefined): Promise<void> {
if (states == undefined) {
return;
}
const tasks: Promise<unknown>[] = [];
for (const key of Object.values(SettingKeys)) {
tasks.push(this.set(states, key));
}
await Promise.allSettled(tasks);
},

// Search

async setLanguage(states: SourceStateManager | undefined, source?: string | null): Promise<void> {
await this.set(states, SettingKeys.Language, LangDefs.findSource(source ?? undefined));
},

async setSorting(states: SourceStateManager | undefined, source?: string | null): Promise<void> {
await this.set(states, SettingKeys.Sorting, SortDefs.findSource(source ?? undefined));
},

async setSearchSuffix(states: SourceStateManager | undefined, suffix?: string | null): Promise<void> {
await this.set(states, SettingKeys.SearchSuffix, suffix != null ? dumbify(suffix) : undefined);
},

// History

_createHistoryEntry(ctx: SearchContext | string, results?: SearchResults): SearchHistoryEntry {
return {
text: typeof ctx === 'string' ? ctx : ctx.text,
sort: typeof ctx !== 'string' ? ctx.sort : undefined,
stopped: results?.partials == undefined || results.partials.length <= 0,
status: results?.status,
challenged: results?.challenged,
skipped: results?.skip,
reason: results?.reason,
fallback: results?.fallback,
nextPage: results?.metadata.nextPage,
maxPage: results?.metadata.maxPage,
shouldStop: results?.metadata.shouldStop,
};
},

async addHistoryEntry(states: SourceStateManager | undefined, ctx: SearchContext, results?: SearchResults): Promise<void> {
if (!await this.get(states, SettingKeys.CollectSearches)) {
return;
}
const entry = this._createHistoryEntry(ctx, results);
const history = [...await this.get(states, SettingKeys.SearchHistory)];
if (history.length >= await this.get(states, SettingKeys.CollectSearchesLimit)) {
history.pop();
}
history.splice(0, 0, entry);
await this.set(states, SettingKeys.SearchHistory, history);
},

_clamp(val: number, boundA: number, boundB: number): number {
const min = Math.min(boundA,boundB);
const max = Math.max(boundA,boundB);
return Math.min(Math.max(val, min), max);
},

async setLimit(states: SourceStateManager | undefined, limit?: number | null): Promise<void> {
await this.set(states, SettingKeys.CollectSearchesLimit, limit != null ? Math.floor(this._clamp(limit, 10, 250)) : undefined);
},

};

export const resetSettings = (states: SourceStateManager) =>
App.createDUIButton({
id: 'reset',
label: 'Reset to Default',
onTap: () => Resettings.clear(states),
});
4 changes: 2 additions & 2 deletions src/NHentai/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NHentaiInfo } from './NHentai';
import { Data } from './Data';

/**
* A simple string formatter, which replaces named {curly_braces}
Expand Down Expand Up @@ -79,6 +79,6 @@ export const dumbify = (smart: string): string => {
*/
export const checkCloudflare = (challenged: boolean) => {
if (challenged) {
throw new Error(`Cloudflare Challenge:\nPlease go to the homepage of ${NHentaiInfo.name} and press the cloud icon.`);
throw new Error(`Cloudflare Challenge:\nPlease go to the homepage of ${Data.info.name} and press the cloud icon.`);
}
};
6 changes: 3 additions & 3 deletions src/NHentai/models/BookParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const BookParser = {
mangaId: book.bookId.toString(),
image: Paths.galleryCover(book.mediaId, book.images.cover.type as string),
title: book.titles.pretty,
subtitle: LangDefs.getSubtitle(getLanguages(book)),
subtitle: LangDefs.getSubtitle(getLanguages(book), true),
}),

/**
Expand All @@ -82,7 +82,7 @@ export const BookParser = {
mangaId: booklet.bookId,
image: booklet.thumbnail,
title: booklet.title,
subtitle: booklet.languages.length > 0 ? `${LangDefs.getSubtitle(booklet.languages)}?` : 'Fallback',
subtitle: booklet.languages.length > 0 ? `${LangDefs.getSubtitle(booklet.languages, true)}?` : 'Fallback',
}),

/**
Expand All @@ -96,7 +96,7 @@ export const BookParser = {
id: mangaId ?? book.bookId.toString(),
chapNum: 1,
name: book.titles.english,
langCode: LangDefs.getPriorityShortName(getLanguages(book)),
langCode: LangDefs.getSubtitle(getLanguages(book), true, true),
time: new Date(book.uploaded),
}),

Expand Down
6 changes: 3 additions & 3 deletions src/NHentai/models/Languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,12 @@ export class LanguageDefinitions {
* @param sort Wether to sort the sources first.
* @returns The source(s) combined into a subtitle.
*/
getSubtitle(sources: string[], sort = true): string {
getSubtitle(sources: string[], sort = true, alwaysShort = false): string {
const filtered = this.getFilteredSources(sources, sort);
if (filtered.length <= 0) {
return 'Unknown';
return alwaysShort ? '??' : 'Unknown';
}
if (filtered.length === 1) {
if (filtered.length === 1 && !alwaysShort) {
return this.getName(filtered[0] ?? 'Unknown');
}
return filtered.map((lang) => this.getShortName(lang)).join('|');
Expand Down
Loading

0 comments on commit 9a696b0

Please sign in to comment.