fix: linter issues

This commit is contained in:
K1ngfish3r
2026-04-23 01:04:42 +05:00
parent 5e195b7f53
commit 2396df38bb
111 changed files with 2738 additions and 2160 deletions
@@ -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, '') || ''
);
);
+43 -46
View File
@@ -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);
}
+1 -4
View File
@@ -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();
+11 -8
View File
@@ -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) {
+1 -4
View File
@@ -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();
+13 -10
View File
@@ -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,
+12 -8
View File
@@ -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[] = [];
+6 -6
View File
@@ -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;
+15 -4
View File
@@ -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
}
},
{
+8 -7
View File
@@ -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') {
+3 -1
View File
@@ -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 🚫',
+2 -2
View File
@@ -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;
+7 -7
View File
@@ -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;
+3 -3
View File
@@ -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(
+15 -10
View File
@@ -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 {
'"': '&quot;',
"'": '&#39;',
' ': '&nbsp;',
'\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(),
+9 -9
View File
@@ -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();
},
+14 -9
View File
@@ -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')
+37 -29
View File
@@ -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;
};
}
};