fix: linter issues
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
if (scriptContent) {
|
||||
const gibMatch = scriptContent.match(/var gib = (\[.*?\])/);
|
||||
if (gibMatch) {
|
||||
const gibArray = eval(gibMatch[1]) as string[];
|
||||
const gibArray = eval(gibMatch[1]);
|
||||
gibArray.forEach(cssClass => {
|
||||
loadedCheerio(`.${cssClass}`).remove();
|
||||
});
|
||||
@@ -33,4 +33,4 @@
|
||||
.replace(/\u2060/g, '')
|
||||
.replace(//g, '') // ­
|
||||
.replace(/[\u202F\u2007\u200B]/g, '') || ''
|
||||
);
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CheerioAPI, load as loadCheerio, load } from 'cheerio';
|
||||
import { CheerioAPI, load as loadCheerio } from 'cheerio';
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
@@ -17,7 +17,7 @@ export type FictioneerMetadata = {
|
||||
options: FictioneerOptions;
|
||||
};
|
||||
|
||||
class FictioneerPlugin implements Plugin.PluginBase {
|
||||
export class FictioneerPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -32,16 +32,38 @@ class FictioneerPlugin implements Plugin.PluginBase {
|
||||
this.icon = `multisrc/fictioneer/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
const versionIncrements = metadata.options?.versionIncrements || 0;
|
||||
this.version = `1.0.${0 + versionIncrements}`;
|
||||
this.version = `1.1.${0 + versionIncrements}`;
|
||||
this.options = metadata.options;
|
||||
}
|
||||
|
||||
private parseNovels(
|
||||
loadedCheerio: CheerioAPI,
|
||||
selector: string,
|
||||
): Plugin.NovelItem[] {
|
||||
return loadedCheerio(selector)
|
||||
.map((i, el) => {
|
||||
const element = loadedCheerio(el);
|
||||
const novelName = element.find('h3 > a').text();
|
||||
const novelCover = element.find('a.cell-img:has(img)').attr('href');
|
||||
const novelUrl = element.find('h3 > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
return {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: new URL(novelUrl, this.site).pathname.substring(1),
|
||||
};
|
||||
})
|
||||
.toArray();
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
// {
|
||||
// showLatestNovels,
|
||||
// filters,
|
||||
// }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const req = await fetchApi(
|
||||
this.site +
|
||||
@@ -53,23 +75,10 @@ class FictioneerPlugin implements Plugin.PluginBase {
|
||||
const body = await req.text();
|
||||
const loadedCheerio = loadCheerio(body);
|
||||
|
||||
return loadedCheerio(
|
||||
return this.parseNovels(
|
||||
loadedCheerio,
|
||||
'#featured-list > li > div > div, #list-of-stories > li > div > div',
|
||||
)
|
||||
.map((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h3 > a').text();
|
||||
const novelCover = loadedCheerio(el)
|
||||
.find('a.cell-img:has(img)')
|
||||
.attr('href');
|
||||
const novelUrl = loadedCheerio(el).find('h3 > a').attr('href');
|
||||
|
||||
return {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl!.replace(this.site + '/', '').replace(/\/$/, ''),
|
||||
};
|
||||
})
|
||||
.toArray();
|
||||
);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
@@ -94,6 +103,8 @@ class FictioneerPlugin implements Plugin.PluginBase {
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(',');
|
||||
|
||||
loadedCheerio('section.story__summary .related-stories-block').remove();
|
||||
novel.summary = loadedCheerio('section.story__summary').text();
|
||||
|
||||
novel.chapters = loadedCheerio('li.chapter-group__list-item._publish')
|
||||
@@ -108,15 +119,12 @@ class FictioneerPlugin implements Plugin.PluginBase {
|
||||
)
|
||||
.map((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('a').text();
|
||||
const chapterUrl = loadedCheerio(el)
|
||||
.find('a')
|
||||
.attr('href')
|
||||
?.replace(this.site + '/', '')
|
||||
.replace(/\/$/, '');
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
return {
|
||||
name: chapterName,
|
||||
path: chapterUrl!,
|
||||
path: new URL(chapterUrl, this.site).pathname.substring(1),
|
||||
};
|
||||
})
|
||||
.toArray();
|
||||
@@ -152,23 +160,12 @@ class FictioneerPlugin implements Plugin.PluginBase {
|
||||
const body = await req.text();
|
||||
const loadedCheerio = loadCheerio(body);
|
||||
|
||||
return loadedCheerio('#search-result-list > li > div > div')
|
||||
.map((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h3 > a').text();
|
||||
const novelCover = loadedCheerio(el)
|
||||
.find('a.cell-img:has(img)')
|
||||
.attr('href');
|
||||
const novelUrl = loadedCheerio(el).find('h3 > a').attr('href');
|
||||
|
||||
return {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl!.replace(this.site + '/', '').replace(/\/$/, ''),
|
||||
};
|
||||
})
|
||||
.toArray();
|
||||
return this.parseNovels(
|
||||
loadedCheerio,
|
||||
'#search-result-list > li > div > div',
|
||||
);
|
||||
}
|
||||
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + '/' + path + '/';
|
||||
// resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
// this.site + '/' + path + '/';
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ import * as cheerio from 'cheerio';
|
||||
import * as path from 'path';
|
||||
import list from './sources.json' with { type: 'json' };
|
||||
import { HotNovelPubMetadata } from './template';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
async function getFilters(sources: HotNovelPubMetadata) {
|
||||
const filters: any = {
|
||||
const filters: Filters = {
|
||||
sort: {
|
||||
type: 'Picker',
|
||||
label: 'Order',
|
||||
@@ -70,7 +71,7 @@ async function start() {
|
||||
const result = [];
|
||||
for (const sources of list) {
|
||||
console.log('updating the filters in', sources.sourceName);
|
||||
const NewFilters = await getFilters(sources as any);
|
||||
const NewFilters = await getFilters(sources as HotNovelPubMetadata);
|
||||
sources.filters = NewFilters;
|
||||
result.push(sources);
|
||||
}
|
||||
|
||||
@@ -48,10 +48,7 @@ const generator = function generator(metadata) {
|
||||
|
||||
const pluginScript = `
|
||||
${HotNovelPubTemplate}
|
||||
const plugin = new HotNovelPubPlugin(${JSON.stringify(metadata).replace(
|
||||
/"type":"([^"]+)"/g,
|
||||
'"type":FilterTypes.$1',
|
||||
)});
|
||||
const plugin = new HotNovelPubPlugin(${JSON.stringify(metadata)});
|
||||
export default plugin;
|
||||
`.trim();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
@@ -15,7 +15,7 @@ type HotNovelPubOptions = {
|
||||
lang?: string;
|
||||
};
|
||||
|
||||
class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
export class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -38,7 +38,10 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.apiSite + '/books/';
|
||||
url += showLatestNovels ? 'new' : filters?.sort?.value || 'hot';
|
||||
@@ -52,7 +55,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
headers: {
|
||||
lang: this.lang,
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}).then((res: Response) => res.json());
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (result.status && result.data.books.data?.length) {
|
||||
@@ -75,7 +78,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
lang: this.lang,
|
||||
},
|
||||
},
|
||||
).then(res => res.json());
|
||||
).then((res: Response) => res.json());
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
name: json.data.book.name,
|
||||
@@ -110,8 +113,8 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||
res.text(),
|
||||
const body = await fetchApi(this.resolveUrl(chapterPath)).then(
|
||||
(res: Response) => res.text(),
|
||||
);
|
||||
|
||||
let chapterText =
|
||||
@@ -144,7 +147,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ key_search: searchTerm }),
|
||||
}).then(res => res.json());
|
||||
}).then((res: Response) => res.json());
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (result.status && result.data.books?.length) {
|
||||
|
||||
@@ -21,10 +21,7 @@ const generator = function generator(metadata) {
|
||||
|
||||
const pluginScript = `
|
||||
${IfreedomTemplate}
|
||||
const plugin = new IfreedomPlugin(${JSON.stringify(metadata).replace(
|
||||
/"type":"([^"]+)"/g,
|
||||
'"type":FilterTypes.$1',
|
||||
)});
|
||||
const plugin = new IfreedomPlugin(${JSON.stringify(metadata)});
|
||||
export default plugin;
|
||||
`.trim();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Filters, FilterToValues } from '@libs/filterInputs';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { Parser } from 'htmlparser2';
|
||||
@@ -12,7 +12,7 @@ export type IfreedomMetadata = {
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
class IfreedomPlugin implements Plugin.PluginBase {
|
||||
export class IfreedomPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -25,14 +25,14 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/ifreedom/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = '1.1.0';
|
||||
this.version = '1.1.1';
|
||||
this.filters = metadata.filters;
|
||||
}
|
||||
|
||||
parseNovels(url: string) {
|
||||
return fetchApi(url)
|
||||
.then(res => res.text())
|
||||
.then(html => {
|
||||
.then((res: Response) => res.text())
|
||||
.then((html: string) => {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
let tempNovel = {} as Plugin.NovelItem;
|
||||
let isInsideNovelCard = false;
|
||||
@@ -83,7 +83,8 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = `${this.site}/vse-knigi/?sort=${showLatestNovels ? 'По дате обновления' : filters?.sort?.value || 'По рейтингу'}`;
|
||||
|
||||
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||
Object.entries(filters || {}).forEach(([type, filter]) => {
|
||||
const { value } = filter as FilterToValues<Filters>[string];
|
||||
if (Array.isArray(value) && value.length) {
|
||||
url += `&${type}[]=${value.join(`&${type}[]=`)}`;
|
||||
}
|
||||
@@ -94,7 +95,9 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const html = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const html = await fetchApi(this.site + novelPath).then((res: Response) =>
|
||||
res.text(),
|
||||
);
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: '',
|
||||
@@ -288,7 +291,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
const body = await fetchApi(this.site + chapterPath).then((res: Response) =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
@@ -328,7 +331,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number = 1,
|
||||
page = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/vse-knigi/?searchname=${encodeURIComponent(searchTerm)}&bpage=${page}`;
|
||||
return this.parseNovels(url);
|
||||
@@ -357,7 +360,7 @@ function parseStatus(statusString: string): string {
|
||||
return NovelStatus.Unknown;
|
||||
}
|
||||
|
||||
function parseDate(dateString: string = ''): string | null {
|
||||
function parseDate(dateString = ''): string | null {
|
||||
const months: Record<string, number> = {
|
||||
января: 1,
|
||||
февраля: 2,
|
||||
|
||||
@@ -14,10 +14,10 @@ export type LightNovelWorldMetadata = {
|
||||
sourceSite: string;
|
||||
sourceName: string;
|
||||
options?: LightNovelWorldOptions;
|
||||
filters?: any;
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
class LightNovelWorld implements Plugin.PagePlugin {
|
||||
export class LightNovelWorld implements Plugin.PagePlugin {
|
||||
id: string;
|
||||
name: string;
|
||||
site: string;
|
||||
@@ -52,7 +52,7 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
link += `${filters.status.value}/`;
|
||||
link += page;
|
||||
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
const body = await fetchApi(link).then((r: Response) => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
@@ -84,7 +84,9 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const body = await fetchApi(this.site + novelPath).then(r => r.text());
|
||||
const body = await fetchApi(this.site + novelPath).then((r: Response) =>
|
||||
r.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const totalChapters = parseInt(
|
||||
@@ -113,7 +115,7 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const url = this.site + novelPath + '/chapters/page-' + page;
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const body = await fetchApi(url).then((res: Response) => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
@@ -144,7 +146,9 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then((r: Response) =>
|
||||
r.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
@@ -156,7 +160,7 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}lnsearchlive`;
|
||||
const link = `${this.site}search`;
|
||||
const response = await fetchApi(link).then(r => r.text());
|
||||
const response = await fetchApi(link).then((r: Response) => r.text());
|
||||
const token = parseHTML(response);
|
||||
const verifytoken = token('#novelSearchForm > input').attr('value');
|
||||
|
||||
@@ -167,7 +171,7 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
method: 'POST',
|
||||
headers: { LNRequestVerifyToken: verifytoken! },
|
||||
body: formData,
|
||||
}).then(r => r.json());
|
||||
}).then((r: Response) => r.json());
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ export type LightNovelWPMetadata = {
|
||||
sourceSite: string;
|
||||
sourceName: string;
|
||||
options?: LightNovelWPOptions;
|
||||
filters?: any;
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
export class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -34,7 +34,7 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
filters?: Filters;
|
||||
|
||||
hideLocked = storage.get('hideLocked');
|
||||
pluginSettings?: Record<string, any>;
|
||||
pluginSettings?: Filters;
|
||||
|
||||
constructor(metadata: LightNovelWPMetadata) {
|
||||
this.id = metadata.id;
|
||||
@@ -42,7 +42,7 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
this.icon = `multisrc/lightnovelwp/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
const versionIncrements = metadata.options?.versionIncrements || 0;
|
||||
this.version = `1.1.${9 + versionIncrements}`;
|
||||
this.version = `1.1.${10 + versionIncrements}`;
|
||||
this.options = metadata.options ?? ({} as LightNovelWPOptions);
|
||||
this.filters = metadata.filters satisfies Filters;
|
||||
|
||||
@@ -99,12 +99,12 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
const articles = html.match(/<article([^]*?)<\/article>/g) || [];
|
||||
articles.forEach(article => {
|
||||
const [, novelUrl, novelName] =
|
||||
article.match(/<a href="([^\"]*)".*? title="([^\"]*)"/) || [];
|
||||
article.match(/<a href="([^"]*)".*? title="([^"]*)"/) || [];
|
||||
|
||||
if (novelName && novelUrl) {
|
||||
const novelCover =
|
||||
article.match(
|
||||
/<img [^>]*?src="([^\"]*)"[^>]*?(?: data-src="([^\"]*)")?[^>]*>/,
|
||||
/<img [^>]*?src="([^"]*)"[^>]*?(?: data-src="([^"]*)")?[^>]*>/,
|
||||
) || [];
|
||||
|
||||
let novelPath;
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
"sourceName": "DaoNovel",
|
||||
"options": {
|
||||
"useNewChapterEndpoint": true,
|
||||
"versionIncrements": 1
|
||||
"versionIncrements": 2,
|
||||
"down": true,
|
||||
"downSince": 1777153113000
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -150,7 +152,10 @@
|
||||
"sourceSite": "https://novel4up.com/",
|
||||
"sourceName": "Novel4Up",
|
||||
"options": {
|
||||
"lang": "Arabic"
|
||||
"lang": "Arabic",
|
||||
"versionIncrements": 1,
|
||||
"down": true,
|
||||
"downSince": 1777153113000
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -420,7 +425,10 @@
|
||||
"sourceName": "Belle Reservoir",
|
||||
"options": {
|
||||
"useNewChapterEndpoint": true,
|
||||
"lang": "English"
|
||||
"versionIncrements": 1,
|
||||
"lang": "English",
|
||||
"down": true,
|
||||
"downSince": 1777153113000
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -439,7 +447,10 @@
|
||||
"sourceName": "Coral Boutique",
|
||||
"options": {
|
||||
"useNewChapterEndpoint": true,
|
||||
"lang": "English"
|
||||
"lang": "English",
|
||||
"versionIncrements": 1,
|
||||
"down": true,
|
||||
"downSince": 1777153113000
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { Cheerio, AnyNode, CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { Cheerio, CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { AnyNode } from 'domhandler';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -24,10 +25,10 @@ export type MadaraMetadata = {
|
||||
sourceSite: string;
|
||||
sourceName: string;
|
||||
options?: MadaraOptions;
|
||||
filters?: any;
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
class MadaraPlugin implements Plugin.PluginBase {
|
||||
export class MadaraPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -37,7 +38,7 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
filters?: Filters | undefined;
|
||||
|
||||
hideLocked = storage.get('hideLocked');
|
||||
pluginSettings?: Record<string, any>;
|
||||
pluginSettings?: Filters;
|
||||
|
||||
constructor(metadata: MadaraMetadata) {
|
||||
this.id = metadata.id;
|
||||
@@ -45,7 +46,7 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
this.icon = `multisrc/madara/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
const versionIncrements = metadata.options?.versionIncrements || 0;
|
||||
this.version = `1.0.${8 + versionIncrements}`;
|
||||
this.version = `1.0.${9 + versionIncrements}`;
|
||||
this.options = metadata.options;
|
||||
this.filters = metadata.filters;
|
||||
|
||||
@@ -302,7 +303,7 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
html = await fetchApi(this.site + novelPath + 'ajax/chapters/', {
|
||||
method: 'POST',
|
||||
referrer: this.site + novelPath,
|
||||
}).then(res => res.text());
|
||||
}).then((res: Response) => res.text());
|
||||
} else {
|
||||
const novelId =
|
||||
loadedCheerio('.rating-post-id').attr('value') ||
|
||||
@@ -316,7 +317,7 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
html = await fetchApi(this.site + 'wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}).then(res => res.text());
|
||||
}).then((res: Response) => res.text());
|
||||
}
|
||||
|
||||
if (html !== '0') {
|
||||
|
||||
@@ -13,7 +13,9 @@ export const generateAll = function () {
|
||||
readFileSync(`${__dirname}/filters/mtlnovel.json`, 'utf-8'),
|
||||
);
|
||||
source.filters = filters;
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// for the linter
|
||||
}
|
||||
console.log(
|
||||
`[mtlnovel] Generating: ${source.id}`.padEnd(35),
|
||||
source.filters ? '🔎with filters🔍' : '🚫 no filters 🚫',
|
||||
|
||||
@@ -14,10 +14,10 @@ export type MTLNovelMetadata = {
|
||||
sourceSite: string;
|
||||
sourceName: string;
|
||||
options?: MTLNovelOptions;
|
||||
filters?: any;
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
class MTLNovelPlugin implements Plugin.PluginBase {
|
||||
export class MTLNovelPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
|
||||
@@ -17,7 +17,7 @@ export type NovelCoolMetadata = {
|
||||
options: NovelCoolOptions;
|
||||
};
|
||||
|
||||
class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
export class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -78,7 +78,7 @@ class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
page_size: '20',
|
||||
}).toString(),
|
||||
},
|
||||
).then(res => res.json());
|
||||
).then((res: Response) => res.json());
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
list.forEach(novel =>
|
||||
@@ -110,7 +110,7 @@ class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
lang: this.options.langCode,
|
||||
}).toString(),
|
||||
},
|
||||
).then(res => res.json());
|
||||
).then((res: Response) => res.json());
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
@@ -142,7 +142,7 @@ class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
lang: this.options.langCode,
|
||||
}).toString(),
|
||||
},
|
||||
).then(res => res.json());
|
||||
).then((res: Response) => res.json());
|
||||
|
||||
list.forEach(chapter => {
|
||||
if (!chapter.is_locked) {
|
||||
@@ -179,7 +179,7 @@ class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
lang: this.options.langCode,
|
||||
}).toString(),
|
||||
},
|
||||
).then(res => res.json());
|
||||
).then((res: Response) => res.json());
|
||||
|
||||
return info.content;
|
||||
}
|
||||
@@ -207,7 +207,7 @@ class NovelCoolPlugin implements Plugin.PluginBase {
|
||||
page_size: '20',
|
||||
}).toString(),
|
||||
},
|
||||
).then(res => res.json());
|
||||
).then((res: Response) => res.json());
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
list.forEach(novel =>
|
||||
@@ -266,7 +266,7 @@ type Novel = {
|
||||
last_url?: string;
|
||||
category_str: string;
|
||||
category_list: string[];
|
||||
star_list: any[];
|
||||
// star_list: any[];
|
||||
int_mark: string;
|
||||
time: string;
|
||||
show_ads: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Parser } from 'htmlparser2';
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { fetchApi, FetchInit } from '@libs/fetch';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
@@ -15,7 +15,7 @@ export type RanobesMetadata = {
|
||||
options?: RanobesOptions;
|
||||
};
|
||||
|
||||
class RanobesPlugin implements Plugin.PluginBase {
|
||||
export class RanobesPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -32,7 +32,7 @@ class RanobesPlugin implements Plugin.PluginBase {
|
||||
this.options = metadata.options as RanobesOptions;
|
||||
}
|
||||
|
||||
async safeFecth(url: string, init: any = {}): Promise<string> {
|
||||
async safeFecth(url: string, init?: FetchInit): Promise<string> {
|
||||
const r = await fetchApi(url, init);
|
||||
if (!r.ok)
|
||||
throw new Error(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Parser } from 'htmlparser2';
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { fetchApi, FetchInit } from '@libs/fetch';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
@@ -35,7 +35,7 @@ export type ReadNovelFullMetadata = {
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
export class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -50,7 +50,7 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
this.icon = `multisrc/readnovelfull/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
const versionIncrements = metadata.options?.versionIncrements || 0;
|
||||
this.version = `2.2.${0 + versionIncrements}`;
|
||||
this.version = `2.2.${1 + versionIncrements}`;
|
||||
this.options = metadata.options;
|
||||
this.filters = metadata.filters;
|
||||
}
|
||||
@@ -145,7 +145,10 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const {
|
||||
pageParam = 'page',
|
||||
@@ -582,9 +585,10 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
' ': ' ',
|
||||
'\u200C': '', // this is probably a breaking change, report if paragraphs look weird
|
||||
};
|
||||
const escapeHtml = (text: string): string =>
|
||||
text.replace(/[&<>"' ]/g, char => escapeMap[char]);
|
||||
const escapeHtml = (text: string) =>
|
||||
text.replace(/[&<>"'\xA0\u200C]/g, char => escapeMap[char]);
|
||||
|
||||
const parser = new Parser({
|
||||
onopentag(name, attribs) {
|
||||
@@ -602,7 +606,7 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
break;
|
||||
case ParsingState.Chapter:
|
||||
if (name === 'sub') {
|
||||
if (name === 'sub' || name === 'iframe') {
|
||||
pushState(ParsingState.Hidden);
|
||||
} else if (name === 'div') {
|
||||
depth++;
|
||||
@@ -655,7 +659,8 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
ontext(text) {
|
||||
if (currentState() === ParsingState.Chapter) {
|
||||
chapterHtml.push(escapeHtml(text));
|
||||
const data = escapeHtml(text);
|
||||
chapterHtml.push(data.trim().replace(/\s\s+/, ' '));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -663,7 +668,7 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
const state = currentState();
|
||||
|
||||
if (state === ParsingState.Hidden) {
|
||||
if (name === 'sub') {
|
||||
if (name === 'sub' || name === 'iframe') {
|
||||
popState();
|
||||
} else if (name === 'div') {
|
||||
depthHide--;
|
||||
@@ -728,7 +733,7 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
const url = `${this.site}${searchPage}${!postSearch ? `?${params.toString()}` : ''}`;
|
||||
|
||||
const fetchOptions: RequestInit | undefined = postSearch
|
||||
const fetchOptions: FetchInit | undefined = postSearch
|
||||
? {
|
||||
method: 'POST',
|
||||
body: params.toString(),
|
||||
|
||||
@@ -2,7 +2,7 @@ require('module-alias/register');
|
||||
import * as fs from 'fs';
|
||||
import * as cheerio from 'cheerio';
|
||||
import * as path from 'path';
|
||||
import { FilterTypes, FilterOption } from '@libs/filterInputs';
|
||||
import { Filters, FilterTypes, FilterOption } from '@libs/filterInputs';
|
||||
const type: string[] = ['genres', 'status', 'sort'];
|
||||
|
||||
async function getFilters(name: string, url: string) {
|
||||
@@ -10,7 +10,7 @@ async function getFilters(name: string, url: string) {
|
||||
res.text(),
|
||||
);
|
||||
const $: cheerio.CheerioAPI = cheerio.load(html);
|
||||
const filters: any = {
|
||||
const filters: Filters = {
|
||||
'sort': {
|
||||
type: FilterTypes.Picker,
|
||||
label: 'Sort By',
|
||||
@@ -76,9 +76,9 @@ async function getFilters(name: string, url: string) {
|
||||
.map((index, element) => loadedCheerio(element).attr('href'))
|
||||
.get();
|
||||
// ===================== tags ======================
|
||||
for (let i = 0; i < allPage.length; i++) {
|
||||
console.log('fetch', url + allPage[i]);
|
||||
const resTags = await fetch(url + allPage[i]).then(res => res.text());
|
||||
for (const page of allPage) {
|
||||
console.log('fetch', url + page);
|
||||
const resTags = await fetch(url + page).then(res => res.text());
|
||||
const $: cheerio.CheerioAPI = cheerio.load(resTags);
|
||||
$('.tag-items > li > a').each((index, element) =>
|
||||
filters['tags'].options.push({
|
||||
@@ -97,10 +97,10 @@ async function getFilters(name: string, url: string) {
|
||||
await sleep(3000);
|
||||
console.log(
|
||||
'fetch',
|
||||
url + allPage[i].replace('-0.html', `-${pageNo + 1}.html`),
|
||||
url + page.replace('-0.html', `-${pageNo + 1}.html`),
|
||||
);
|
||||
const resTags = await fetch(
|
||||
url + allPage[i].replace('-0.html', `-${pageNo + 1}.html`),
|
||||
url + page.replace('-0.html', `-${pageNo + 1}.html`),
|
||||
).then(res => res.text());
|
||||
const $: cheerio.CheerioAPI = cheerio.load(resTags);
|
||||
|
||||
@@ -141,9 +141,9 @@ async function askGetFilter() {
|
||||
async (url: string) => {
|
||||
try {
|
||||
await getFilters(name, url);
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error('Error while getting filters from', url);
|
||||
console.log(e.message || e);
|
||||
console.log(e instanceof Error ? e.message : e);
|
||||
}
|
||||
readline.close();
|
||||
},
|
||||
|
||||
@@ -13,11 +13,11 @@ export type ReadwnMetadata = {
|
||||
id: string;
|
||||
sourceSite: string;
|
||||
sourceName: string;
|
||||
filters?: any;
|
||||
filters?: Filters;
|
||||
options?: ReadwnOptions;
|
||||
};
|
||||
|
||||
class ReadwnPlugin implements Plugin.PluginBase {
|
||||
export class ReadwnPlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -31,13 +31,16 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
this.icon = `multisrc/readwn/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
const versionIncrements = metadata.options?.versionIncrements || 0;
|
||||
this.version = `1.0.${2 + versionIncrements}`;
|
||||
this.version = `1.0.${3 + versionIncrements}`;
|
||||
this.filters = metadata.filters;
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + '/list/';
|
||||
url += (filters?.genres?.value || 'all') + '/';
|
||||
@@ -50,7 +53,7 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
url = this.site + '/tags/' + filters.tags.value + '-0.html';
|
||||
}
|
||||
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const body = await fetchApi(url).then((res: Response) => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('li.novel-item')
|
||||
@@ -68,7 +71,9 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then((res: Response) =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
@@ -186,7 +191,7 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
const body = await fetchApi(this.site + chapterPath).then((res: Response) =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
@@ -205,11 +210,11 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tempid: '1',
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}).toString(),
|
||||
}).then(res => res.text());
|
||||
}).then((res: Response) => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('li.novel-item')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { FilterToValues, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -18,7 +18,7 @@ const headers = {
|
||||
'accept-encoding': 'gzip',
|
||||
};
|
||||
|
||||
class RulatePlugin implements Plugin.PluginBase {
|
||||
export class RulatePlugin implements Plugin.PluginBase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
@@ -32,14 +32,14 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
this.name = metadata.sourceName + ' (API)';
|
||||
this.icon = `multisrc/rulate/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = '1.0.' + (0 + metadata.versionIncrements);
|
||||
this.version = '1.0.' + (1 + metadata.versionIncrements);
|
||||
this.filters = metadata.filters;
|
||||
this.key = metadata.key;
|
||||
}
|
||||
|
||||
parseNovels(url: string) {
|
||||
return fetchApi(url, { headers })
|
||||
.then(res => res.json() as Promise<SearchResponse>)
|
||||
.then((res: Response) => res.json() as Promise<SearchResponse>)
|
||||
.then((data: SearchResponse) => {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
@@ -59,12 +59,16 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + '/api3/searchBooks?limit=40&page=' + page;
|
||||
url += '&sort=' + (showLatestNovels ? '4' : filters?.sort?.value || '6');
|
||||
|
||||
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||
Object.entries(filters || {}).forEach(([type, filter]) => {
|
||||
const { value } = filter as FilterToValues<Filters>[string];
|
||||
if (value instanceof Array && value.length) {
|
||||
url += '&' + value.map(val => type + '[]=' + val).join('&');
|
||||
}
|
||||
@@ -76,7 +80,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number = 1,
|
||||
page = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/api3/searchBooks?t=${encodeURIComponent(
|
||||
searchTerm,
|
||||
@@ -88,14 +92,16 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
const book = await fetchApi(
|
||||
this.site + '/api3/book?book_id=' + novelPath + '&key=' + this.key,
|
||||
{ headers },
|
||||
).then(res => res.json() as Promise<BookResponse>);
|
||||
).then((res: Response) => res.json() as Promise<BookResponse>);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
name: book.response.t_title || book.response.s_title,
|
||||
path: novelPath,
|
||||
cover: book.response.img,
|
||||
genres: [book.response.genres, book.response.tags]
|
||||
.flatMap(c => c?.map?.((g: any) => g.title || g.name))
|
||||
.flatMap(c =>
|
||||
c?.map?.((g: { title?: string; name?: string }) => g.title || g.name),
|
||||
)
|
||||
.join(','),
|
||||
summary: book.response.description,
|
||||
author: book.response.author,
|
||||
@@ -118,12 +124,12 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
'&key=' +
|
||||
this.key,
|
||||
{ headers },
|
||||
).then(res => res.json() as Promise<ChaptersResponse>);
|
||||
).then((res: Response) => res.json() as Promise<ChaptersResponse>);
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
if (chaptersData.response && Array.isArray(chaptersData.response)) {
|
||||
chaptersData.response.forEach(chapter => {
|
||||
chaptersData.response.forEach((chapter: ChapterResponse) => {
|
||||
if (chapter.can_read && chapter.subscription === 0) {
|
||||
chapters.push({
|
||||
name: chapter.title + (chapter.illustrated ? ' 🖼️' : ''),
|
||||
@@ -150,7 +156,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
'&key=' +
|
||||
this.key,
|
||||
{ headers },
|
||||
).then(res => res.json() as Promise<ChapterTextResponse>);
|
||||
).then((res: Response) => res.json() as Promise<ChapterTextResponse>);
|
||||
|
||||
return body.response.text;
|
||||
}
|
||||
@@ -158,7 +164,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
this.site + '/book/' + path + (isNovel ? '/' : '/ready_new');
|
||||
}
|
||||
|
||||
interface SearchResponse {
|
||||
type SearchResponse = {
|
||||
status: string;
|
||||
response: {
|
||||
t_title?: string;
|
||||
@@ -166,9 +172,9 @@ interface SearchResponse {
|
||||
id: number;
|
||||
img: string;
|
||||
}[];
|
||||
}
|
||||
};
|
||||
|
||||
interface BookResponse {
|
||||
type BookResponse = {
|
||||
response: {
|
||||
t_title?: string;
|
||||
s_title: string;
|
||||
@@ -181,22 +187,24 @@ interface BookResponse {
|
||||
status: string;
|
||||
rate?: { sum: number; count: number };
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
interface ChaptersResponse {
|
||||
response: {
|
||||
title: string;
|
||||
id: number;
|
||||
ord: number;
|
||||
cdate: number;
|
||||
subscription: number;
|
||||
can_read: boolean;
|
||||
illustrated?: boolean;
|
||||
}[];
|
||||
}
|
||||
type ChapterResponse = {
|
||||
title: string;
|
||||
id: number;
|
||||
ord: number;
|
||||
cdate: number;
|
||||
subscription: number;
|
||||
can_read: boolean;
|
||||
illustrated?: boolean;
|
||||
};
|
||||
|
||||
interface ChapterTextResponse {
|
||||
type ChaptersResponse = {
|
||||
response: ChapterResponse[];
|
||||
};
|
||||
|
||||
type ChapterTextResponse = {
|
||||
response: {
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user