Fix special characters breaking search in most sources (#1557)

This commit is contained in:
Adam Kemish
2025-03-15 15:35:31 +08:00
committed by GitHub
parent 3a10e24545
commit ea7965bdda
28 changed files with 103 additions and 73 deletions
+6 -2
View File
@@ -25,7 +25,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
this.name = metadata.sourceName; this.name = metadata.sourceName;
this.icon = `multisrc/ifreedom/${metadata.id.toLowerCase()}/icon.png`; this.icon = `multisrc/ifreedom/${metadata.id.toLowerCase()}/icon.png`;
this.site = metadata.sourceSite; this.site = metadata.sourceSite;
this.version = '1.0.1'; this.version = '1.0.2';
this.filters = metadata.filters; this.filters = metadata.filters;
} }
@@ -166,7 +166,11 @@ class IfreedomPlugin implements Plugin.PluginBase {
page: number | undefined = 1, page: number | undefined = 1,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = const url =
this.site + '/vse-knigi/?searchname=' + searchTerm + '&bpage=' + page; this.site +
'/vse-knigi/?searchname=' +
encodeURIComponent(searchTerm) +
'&bpage=' +
page;
const result = await fetchApi(url).then(res => res.text()); const result = await fetchApi(url).then(res => res.text());
const loadedCheerio = parseHTML(result); const loadedCheerio = parseHTML(result);
+6 -3
View File
@@ -42,7 +42,7 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
this.icon = `multisrc/lightnovelwp/${metadata.id.toLowerCase()}/icon.png`; this.icon = `multisrc/lightnovelwp/${metadata.id.toLowerCase()}/icon.png`;
this.site = metadata.sourceSite; this.site = metadata.sourceSite;
const versionIncrements = metadata.options?.versionIncrements || 0; const versionIncrements = metadata.options?.versionIncrements || 0;
this.version = `1.1.${6 + versionIncrements}`; this.version = `1.1.${7 + versionIncrements}`;
this.options = metadata.options ?? ({} as LightNovelWPOptions); this.options = metadata.options ?? ({} as LightNovelWPOptions);
this.filters = metadata.filters satisfies Filters; this.filters = metadata.filters satisfies Filters;
@@ -103,7 +103,9 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
if (novelName && novelUrl) { if (novelName && novelUrl) {
const novelCover = const novelCover =
article.match(/<img [^>]*?src="([^\"]*)"[^>]*?(?: data-src="([^\"]*)")?[^>]*>/) || []; article.match(
/<img [^>]*?src="([^\"]*)"[^>]*?(?: data-src="([^\"]*)")?[^>]*>/,
) || [];
let novelPath; let novelPath;
if (novelUrl.includes(this.site)) { if (novelUrl.includes(this.site)) {
@@ -444,7 +446,8 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = this.site + 'page/' + page + '/?s=' + searchTerm; const url =
this.site + 'page/' + page + '/?s=' + encodeURIComponent(searchTerm);
const html = await this.safeFecth(url, true); const html = await this.safeFecth(url, true);
return this.parseNovels(html); return this.parseNovels(html);
} }
+22 -7
View File
@@ -45,7 +45,7 @@ class MadaraPlugin implements Plugin.PluginBase {
this.icon = `multisrc/madara/${metadata.id.toLowerCase()}/icon.png`; this.icon = `multisrc/madara/${metadata.id.toLowerCase()}/icon.png`;
this.site = metadata.sourceSite; this.site = metadata.sourceSite;
const versionIncrements = metadata.options?.versionIncrements || 0; const versionIncrements = metadata.options?.versionIncrements || 0;
this.version = `1.0.${6 + versionIncrements}`; this.version = `1.0.${7 + versionIncrements}`;
this.options = metadata.options; this.options = metadata.options;
this.filters = metadata.filters; this.filters = metadata.filters;
@@ -63,18 +63,33 @@ class MadaraPlugin implements Plugin.PluginBase {
translateDragontea(text: Cheerio<AnyNode>): Cheerio<AnyNode> { translateDragontea(text: Cheerio<AnyNode>): Cheerio<AnyNode> {
if (this.id !== 'dragontea') return text; if (this.id !== 'dragontea') return text;
const $ = parseHTML(text.html()?.replace('\n', '').replace(/<br\s*\/?>/g, '\n') || ''); const $ = parseHTML(
text
.html()
?.replace('\n', '')
.replace(/<br\s*\/?>/g, '\n') || '',
);
const reverseAlpha = 'zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA'; const reverseAlpha = 'zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA';
const forwardAlpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const forwardAlpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
text.html($.html()); text.html($.html());
text.find('*').addBack().contents().filter((_, el) => el.nodeType === 3).each((_, el) => { text
.find('*')
.addBack()
.contents()
.filter((_, el) => el.nodeType === 3)
.each((_, el) => {
const $el = $(el); const $el = $(el);
const translated = $el.text().normalize('NFD').split('') const translated = $el
.text()
.normalize('NFD')
.split('')
.map(char => { .map(char => {
const base = char.normalize('NFC'); const base = char.normalize('NFC');
const idx = forwardAlpha.indexOf(base); const idx = forwardAlpha.indexOf(base);
return idx >= 0 ? reverseAlpha[idx] + char.slice(base.length) : char; return idx >= 0
? reverseAlpha[idx] + char.slice(base.length)
: char;
}) })
.join(''); .join('');
$el.replaceWith(translated.replace('\n', '<br>')); $el.replaceWith(translated.replace('\n', '<br>'));
@@ -261,7 +276,7 @@ class MadaraPlugin implements Plugin.PluginBase {
if (this.options?.useNewChapterEndpoint) { if (this.options?.useNewChapterEndpoint) {
html = await fetchApi(this.site + novelPath + 'ajax/chapters/', { html = await fetchApi(this.site + novelPath + 'ajax/chapters/', {
method: 'POST', method: 'POST',
referrer: this.site + novelPath referrer: this.site + novelPath,
}).then(res => res.text()); }).then(res => res.text());
} else { } else {
const novelId = const novelId =
@@ -347,7 +362,7 @@ class MadaraPlugin implements Plugin.PluginBase {
'/page/' + '/page/' +
pageNo + pageNo +
'/?s=' + '/?s=' +
searchTerm + encodeURIComponent(searchTerm) +
'&post_type=wp-manga'; '&post_type=wp-manga';
const loadedCheerio = await this.getCheerio(url, true); const loadedCheerio = await this.getCheerio(url, true);
return this.parseNovels(loadedCheerio); return this.parseNovels(loadedCheerio);
+4 -2
View File
@@ -33,7 +33,7 @@ class MTLNovelPlugin implements Plugin.PluginBase {
this.icon = 'multisrc/mtlnovel/mtlnovel/icon.png'; this.icon = 'multisrc/mtlnovel/mtlnovel/icon.png';
this.site = metadata.sourceSite; this.site = metadata.sourceSite;
this.mainUrl = 'https://www.mtlnovels.com/'; this.mainUrl = 'https://www.mtlnovels.com/';
this.version = '1.1.2'; this.version = '1.1.3';
this.options = metadata.options ?? ({} as MTLNovelOptions); this.options = metadata.options ?? ({} as MTLNovelOptions);
this.filters = metadata.filters satisfies Filters; this.filters = metadata.filters satisfies Filters;
} }
@@ -203,7 +203,9 @@ class MTLNovelPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
const searchUrl = const searchUrl =
this.site + 'wp-admin/admin-ajax.php?action=autosuggest&q=' + searchTerm; this.site +
'wp-admin/admin-ajax.php?action=autosuggest&q=' +
encodeURIComponent(searchTerm);
const res = await this.safeFecth(searchUrl); const res = await this.safeFecth(searchUrl);
const result = await res.json(); const result = await res.json();
+2 -2
View File
@@ -34,7 +34,7 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
this.icon = `multisrc/readnovelfull/${metadata.id.toLowerCase()}/icon.png`; this.icon = `multisrc/readnovelfull/${metadata.id.toLowerCase()}/icon.png`;
this.site = metadata.sourceSite; this.site = metadata.sourceSite;
const versionIncrements = metadata.options?.versionIncrements || 0; const versionIncrements = metadata.options?.versionIncrements || 0;
this.version = `1.0.${0 + versionIncrements}`; this.version = `1.0.${1 + versionIncrements}`;
this.options = metadata.options; this.options = metadata.options;
} }
@@ -229,7 +229,7 @@ class ReadNovelFullPlugin implements Plugin.PluginBase {
this.site + this.site +
'/' + '/' +
this.options?.searchPage this.options?.searchPage
.replace('%%SEARCH%%', searchTerm) .replace('%%SEARCH%%', encodeURIComponent(searchTerm))
.replace('%%PAGE%%', page.toString()); .replace('%%PAGE%%', page.toString());
const $ = await this.getCheerio(url, true); const $ = await this.getCheerio(url, true);
+4 -2
View File
@@ -26,7 +26,7 @@ class RulatePlugin implements Plugin.PluginBase {
this.name = metadata.sourceName; this.name = metadata.sourceName;
this.icon = `multisrc/rulate/${metadata.id.toLowerCase()}/icon.png`; this.icon = `multisrc/rulate/${metadata.id.toLowerCase()}/icon.png`;
this.site = metadata.sourceSite; this.site = metadata.sourceSite;
this.version = '1.0.' + (1 + metadata.versionIncrements); this.version = '1.0.' + (2 + metadata.versionIncrements);
this.filters = metadata.filters; this.filters = metadata.filters;
} }
@@ -217,7 +217,9 @@ class RulatePlugin implements Plugin.PluginBase {
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
const result: response[] = await fetchApi( const result: response[] = await fetchApi(
this.site + '/search/autocomplete?query=' + searchTerm, this.site +
'/search/autocomplete?query=' +
encodeURIComponent(searchTerm),
).then(res => res.json()); ).then(res => res.json());
result.forEach(novel => { result.forEach(novel => {
+2 -2
View File
@@ -9,7 +9,7 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
name = 'AllNovelFull'; name = 'AllNovelFull';
icon = 'src/en/allnovelfull/icon.png'; icon = 'src/en/allnovelfull/icon.png';
site = 'https://allnovelfull.net'; site = 'https://allnovelfull.net';
version = '1.0.1'; version = '1.0.2';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -140,7 +140,7 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}/search?keyword=${searchTerm}&page=${page}`; const url = `${this.site}/search?keyword=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -7,7 +7,7 @@ import { defaultCover } from '@libs/defaultCover';
class ArchiveOfOurOwn implements Plugin.PluginBase { class ArchiveOfOurOwn implements Plugin.PluginBase {
id = 'archiveofourown'; id = 'archiveofourown';
name = 'Archive Of Our Own'; name = 'Archive Of Our Own';
version = '1.0.2'; version = '1.0.3';
icon = 'src/en/ao3/icon.png'; icon = 'src/en/ao3/icon.png';
site = 'https://archiveofourown.org/'; site = 'https://archiveofourown.org/';
@@ -282,7 +282,7 @@ class ArchiveOfOurOwn implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const searchUrl = `${this.site}works/search?page=${page}&work_search%5Blanguage_id%5D=en&work_search%5Bquery%5D=${searchTerm}`; const searchUrl = `${this.site}works/search?page=${page}&work_search%5Blanguage_id%5D=en&work_search%5Bquery%5D=${encodeURIComponent(searchTerm)}`;
const result = await fetchApi(searchUrl); const result = await fetchApi(searchUrl);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -10,7 +10,7 @@ class BLN implements Plugin.PluginBase {
name = 'BestLightNovel'; name = 'BestLightNovel';
icon = 'src/en/bestlightnovel/icon.png'; icon = 'src/en/bestlightnovel/icon.png';
site = 'https://bestlightnovel.com/'; site = 'https://bestlightnovel.com/';
version = '1.0.0'; version = '1.0.1';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -160,7 +160,7 @@ class BLN implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search_novels/${searchTerm}?page=${page}`; const url = `${this.site}search_novels/${encodeURIComponent(searchTerm)}?page=${page}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -8,7 +8,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
id = 'comrademao'; id = 'comrademao';
name = 'Comrade Mao'; name = 'Comrade Mao';
site = 'https://comrademao.com/'; site = 'https://comrademao.com/';
version = '1.0.0'; version = '1.0.1';
icon = 'src/en/comrademao/icon.png'; icon = 'src/en/comrademao/icon.png';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
@@ -149,7 +149,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
pageNo: number, pageNo: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}page/${pageNo}/?s=${searchTerm}&post_type=novel`; const url = `${this.site}page/${pageNo}/?s=${encodeURIComponent(searchTerm)}&post_type=novel`;
const body = await fetchApi(url).then(result => result.text()); const body = await fetchApi(url).then(result => result.text());
+3 -1
View File
@@ -4,11 +4,13 @@ import { Filters, FilterTypes, FilterValueWithType } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
//TODO: this looks similar to fictioneer source? maybe use multisrc someday
class DDLPlugin implements Plugin.PluginBase { class DDLPlugin implements Plugin.PluginBase {
id = 'DDL.com'; id = 'DDL.com';
name = 'Divine Dao Library'; name = 'Divine Dao Library';
site = 'https://www.divinedaolibrary.com/'; site = 'https://www.divinedaolibrary.com/';
version = '1.1.0'; version = '1.1.1';
icon = 'src/en/divinedaolibrary/icon.png'; icon = 'src/en/divinedaolibrary/icon.png';
filters = { filters = {
+2 -2
View File
@@ -6,7 +6,7 @@ import { Plugin } from '@typings/plugin';
class EarlyNovelPlugin implements Plugin.PagePlugin { class EarlyNovelPlugin implements Plugin.PagePlugin {
id = 'earlynovel'; id = 'earlynovel';
name = 'Early Novel'; name = 'Early Novel';
version = '1.0.0'; version = '1.0.1';
icon = 'src/en/earlynovel/icon.png'; icon = 'src/en/earlynovel/icon.png';
site = 'https://earlynovel.net/'; site = 'https://earlynovel.net/';
@@ -128,7 +128,7 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search?keyword=${searchTerm}`; const url = `${this.site}search?keyword=${encodeURIComponent(searchTerm)}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -18,7 +18,7 @@ class Genesis implements Plugin.PluginBase {
icon = 'src/en/genesis/icon.png'; icon = 'src/en/genesis/icon.png';
customCSS = 'src/en/genesis/customCSS.css'; customCSS = 'src/en/genesis/customCSS.css';
site = 'https://genesistudio.com'; site = 'https://genesistudio.com';
version = '1.1.0'; version = '1.1.1';
imageRequestInit?: Plugin.ImageRequestInit | undefined = { imageRequestInit?: Plugin.ImageRequestInit | undefined = {
headers: { headers: {
@@ -465,7 +465,7 @@ class Genesis implements Plugin.PluginBase {
pageNo: number, pageNo: number,
): Promise<Plugin.SourceNovel[]> { ): Promise<Plugin.SourceNovel[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
const url = `${this.site}/api/novels/search?serialization=All&sort=Popular&title=${searchTerm}`; const url = `${this.site}/api/novels/search?serialization=All&sort=Popular&title=${encodeURIComponent(searchTerm)}`;
const json = await fetchApi(url).then(r => r.json()); const json = await fetchApi(url).then(r => r.json());
return this.parseNovels(json); return this.parseNovels(json);
} }
+2 -2
View File
@@ -5,7 +5,7 @@ import { Plugin } from '@typings/plugin';
class MTLReader implements Plugin.PluginBase { class MTLReader implements Plugin.PluginBase {
id = 'mtlreader'; id = 'mtlreader';
name = 'MTL Reader'; name = 'MTL Reader';
version = '1.0.0'; version = '1.0.1';
icon = 'src/en/mtlreader/icon.png'; icon = 'src/en/mtlreader/icon.png';
site = 'https://mtlreader.com/'; site = 'https://mtlreader.com/';
@@ -92,7 +92,7 @@ class MTLReader implements Plugin.PluginBase {
const tokenCheerio = parseHTML(body); const tokenCheerio = parseHTML(body);
const token = tokenCheerio('input[name="_token"]').attr('value'); const token = tokenCheerio('input[name="_token"]').attr('value');
const searchUrl = `${this.site}search?_token=${token}&input=${searchTerm}`; const searchUrl = `${this.site}search?_token=${token}&input=${encodeURIComponent(searchTerm)}`;
const seacrhBody = await fetchApi(searchUrl).then(r => r.text()); const seacrhBody = await fetchApi(searchUrl).then(r => r.text());
const loadedCheerio = parseHTML(seacrhBody); const loadedCheerio = parseHTML(seacrhBody);
return this.parseNovels(loadedCheerio); return this.parseNovels(loadedCheerio);
+2 -2
View File
@@ -9,7 +9,7 @@ class NovelBin implements Plugin.PluginBase {
icon = 'src/en/novelbin/icon.png'; icon = 'src/en/novelbin/icon.png';
site = 'https://novelbin.com/'; site = 'https://novelbin.com/';
subsite = 'https://novelbin.lanovels.net/'; subsite = 'https://novelbin.lanovels.net/';
version = '1.2.0'; version = '1.2.1';
imageRequestInit?: Plugin.ImageRequestInit | undefined = { imageRequestInit?: Plugin.ImageRequestInit | undefined = {
headers: { headers: {
'referrer': this.site, 'referrer': this.site,
@@ -144,7 +144,7 @@ class NovelBin implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search/?keyword=${searchTerm}`; const url = `${this.site}search/?keyword=${encodeURIComponent(searchTerm)}`;
const body = await fetchApi(url).then(r => r.text()); const body = await fetchApi(url).then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
+2 -2
View File
@@ -7,7 +7,7 @@ class NovelBuddy implements Plugin.PluginBase {
id = 'novelbuddy'; id = 'novelbuddy';
name = 'NovelBuddy.io'; name = 'NovelBuddy.io';
site = 'https://novelbuddy.io/'; site = 'https://novelbuddy.io/';
version = '1.0.2'; version = '1.0.3';
icon = 'src/en/novelbuddy/icon.png'; icon = 'src/en/novelbuddy/icon.png';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
@@ -177,7 +177,7 @@ class NovelBuddy implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search?q=${searchTerm}&page=${page}`; const url = `${this.site}search?q=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -7,7 +7,7 @@ import { Plugin } from '@typings/plugin';
class NovelFire implements Plugin.PluginBase { class NovelFire implements Plugin.PluginBase {
id = 'novelfire'; id = 'novelfire';
name = 'Novel Fire'; name = 'Novel Fire';
version = '1.0.1'; version = '1.0.2';
icon = 'src/en/novelfire/icon.png'; icon = 'src/en/novelfire/icon.png';
site = 'https://novelfire.net/'; site = 'https://novelfire.net/';
@@ -230,7 +230,7 @@ class NovelFire implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search?keyword=${searchTerm}&page=${page}`; const url = `${this.site}search?keyword=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -6,7 +6,7 @@ import { Filters, FilterTypes } from '@libs/filterInputs';
class NovelFull implements Plugin.PluginBase { class NovelFull implements Plugin.PluginBase {
id = 'novelfull'; id = 'novelfull';
name = 'NovelFull'; name = 'NovelFull';
version = '1.0.2'; version = '1.0.3';
icon = 'src/en/novelfull/icon.png'; icon = 'src/en/novelfull/icon.png';
site = 'https://novelfull.com/'; site = 'https://novelfull.com/';
@@ -142,7 +142,7 @@ class NovelFull implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const searchUrl = `${this.site}search?keyword=${searchTerm}&page=${page}`; const searchUrl = `${this.site}search?keyword=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(searchUrl); const result = await fetchApi(searchUrl);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -6,7 +6,7 @@ import { defaultCover } from '@libs/defaultCover';
class NovelHall implements Plugin.PluginBase { class NovelHall implements Plugin.PluginBase {
id = 'novelhall'; id = 'novelhall';
name = 'Novel Hall'; name = 'Novel Hall';
version = '1.0.1'; version = '1.0.2';
icon = 'src/en/novelhall/icon.png'; icon = 'src/en/novelhall/icon.png';
site = 'https://novelhall.com/'; site = 'https://novelhall.com/';
@@ -90,7 +90,7 @@ class NovelHall implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `${this.site}index.php?s=so&module=book&keyword=${searchTerm}`; const url = `${this.site}index.php?s=so&module=book&keyword=${encodeURIComponent(searchTerm)}`;
const body = await fetchApi(url).then(r => r.text()); const body = await fetchApi(url).then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
+2 -2
View File
@@ -9,7 +9,7 @@ import { NovelStatus } from '@libs/novelStatus';
class Novelight implements Plugin.PagePlugin { class Novelight implements Plugin.PagePlugin {
id = 'novelight'; id = 'novelight';
name = 'Novelight'; name = 'Novelight';
version = '1.1.0'; version = '1.1.1';
icon = 'src/en/novelight/icon.png'; icon = 'src/en/novelight/icon.png';
site = 'https://novelight.net/'; site = 'https://novelight.net/';
@@ -188,7 +188,7 @@ class Novelight implements Plugin.PagePlugin {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `${this.site}catalog/?search=${searchTerm}`; const url = `${this.site}catalog/?search=${encodeURIComponent(searchTerm)}`;
const body = await fetchApi(url).then(r => r.text()); const body = await fetchApi(url).then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
+2 -2
View File
@@ -6,7 +6,7 @@ import { Plugin } from '@typings/plugin';
class NovelUpdates implements Plugin.PluginBase { class NovelUpdates implements Plugin.PluginBase {
id = 'novelupdates'; id = 'novelupdates';
name = 'Novel Updates'; name = 'Novel Updates';
version = '0.7.15'; version = '0.7.16';
icon = 'src/en/novelupdates/icon.png'; icon = 'src/en/novelupdates/icon.png';
customCSS = 'src/en/novelupdates/customCSS.css'; customCSS = 'src/en/novelupdates/customCSS.css';
site = 'https://www.novelupdates.com/'; site = 'https://www.novelupdates.com/';
@@ -1004,7 +1004,7 @@ class NovelUpdates implements Plugin.PluginBase {
); );
searchTerm = longestSearchTerm.replace(/[‘’]/g, "'").replace(/\s+/g, '+'); searchTerm = longestSearchTerm.replace(/[‘’]/g, "'").replace(/\s+/g, '+');
const url = `${this.site}series-finder/?sf=1&sh=${searchTerm}&sort=srank&order=asc&pg=${page}`; const url = `${this.site}series-finder/?sf=1&sh=${encodeURIComponent(searchTerm)}&sort=srank&order=asc&pg=${page}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -6,7 +6,7 @@ import { Filters, FilterTypes } from '@libs/filterInputs';
class PawRead implements Plugin.PluginBase { class PawRead implements Plugin.PluginBase {
id = 'pawread'; id = 'pawread';
name = 'PawRead'; name = 'PawRead';
version = '2.0.0'; version = '2.0.1';
icon = 'src/en/pawread/icon.png'; icon = 'src/en/pawread/icon.png';
site = 'https://m.pawread.com/'; site = 'https://m.pawread.com/';
@@ -128,7 +128,7 @@ class PawRead implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const searchUrl = `${this.site}search/?keywords=${searchTerm}&page=${page}`; const searchUrl = `${this.site}search/?keywords=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(searchUrl); const result = await fetchApi(searchUrl);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -9,7 +9,7 @@ class Rainofsnow implements Plugin.PagePlugin {
name = 'Rainofsnow'; name = 'Rainofsnow';
icon = 'src/en/rainofsnow/icon.png'; icon = 'src/en/rainofsnow/icon.png';
site = 'https://rainofsnow.com/'; site = 'https://rainofsnow.com/';
version = '1.1.1'; version = '1.1.2';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -154,7 +154,7 @@ class Rainofsnow implements Plugin.PagePlugin {
// no page number, infinite scroll // no page number, infinite scroll
const newSearch = searchTerm.replace(/\s+/g, '+'); const newSearch = searchTerm.replace(/\s+/g, '+');
const url = this.site + '?s=' + newSearch; const url = this.site + '?s=' + encodeURIComponent(newSearch);
const result = await fetch(url); const result = await fetch(url);
const body = await result.text(); const body = await result.text();
+3 -2
View File
@@ -8,7 +8,7 @@ import dayjs from 'dayjs';
class ReadLiteNovel implements Plugin.PluginBase { class ReadLiteNovel implements Plugin.PluginBase {
id = 'rln.app'; id = 'rln.app';
name = 'ReadLiteNovel'; name = 'ReadLiteNovel';
version = '1.0.0'; version = '1.0.1';
icon = 'src/en/readlitenovel/icon.png'; icon = 'src/en/readlitenovel/icon.png';
site = 'https://rln.app'; site = 'https://rln.app';
@@ -154,7 +154,8 @@ class ReadLiteNovel implements Plugin.PluginBase {
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = const url =
this.site + `/search/autocomplete?dataType=json&query=${searchTerm}`; this.site +
`/search/autocomplete?dataType=json&query=${encodeURIComponent(searchTerm)}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.json(); const body = await result.json();
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
+2 -2
View File
@@ -7,7 +7,7 @@ import { NovelStatus } from '@libs/novelStatus';
class RoyalRoad implements Plugin.PluginBase { class RoyalRoad implements Plugin.PluginBase {
id = 'royalroad'; id = 'royalroad';
name = 'Royal Road'; name = 'Royal Road';
version = '2.1.2'; version = '2.1.3';
icon = 'src/en/royalroad/icon.png'; icon = 'src/en/royalroad/icon.png';
site = 'https://www.royalroad.com/'; site = 'https://www.royalroad.com/';
@@ -258,7 +258,7 @@ class RoyalRoad implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const searchUrl = `${this.site}fictions/search?page=${page}&title=${searchTerm}`; const searchUrl = `${this.site}fictions/search?page=${page}&title=${encodeURIComponent(searchTerm)}`;
const body = await fetchApi(searchUrl).then(r => r.text()); const body = await fetchApi(searchUrl).then(r => r.text());
return this.parseNovels(body); return this.parseNovels(body);
+2 -2
View File
@@ -9,7 +9,7 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
name = 'Scribble Hub'; name = 'Scribble Hub';
icon = 'src/en/scribblehub/icon.png'; icon = 'src/en/scribblehub/icon.png';
site = 'https://www.scribblehub.com/'; site = 'https://www.scribblehub.com/';
version = '1.0.1'; version = '1.0.2';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -177,7 +177,7 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `${this.site}?s=${searchTerm}&post_type=fictionposts`; const url = `${this.site}?s=${encodeURIComponent(searchTerm)}&post_type=fictionposts`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+2 -2
View File
@@ -7,7 +7,7 @@ import { storage } from '@libs/storage';
class Webnovel implements Plugin.PluginBase { class Webnovel implements Plugin.PluginBase {
id = 'webnovel'; id = 'webnovel';
name = 'Webnovel'; name = 'Webnovel';
version = '1.0.2'; version = '1.0.3';
icon = 'src/en/webnovel/icon.png'; icon = 'src/en/webnovel/icon.png';
site = 'https://www.webnovel.com'; site = 'https://www.webnovel.com';
headers = { headers = {
@@ -240,7 +240,7 @@ class Webnovel implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
searchTerm = searchTerm.replace(/\s+/g, '+'); searchTerm = searchTerm.replace(/\s+/g, '+');
const url = `${this.site}/search?keywords=${searchTerm}&pageIndex=${pageNo}${type ? `&type=${type}` : ''}`; const url = `${this.site}/search?keywords=${encodeURIComponent(searchTerm)}&pageIndex=${pageNo}${type ? `&type=${type}` : ''}`;
const result = await fetchApi(url, { const result = await fetchApi(url, {
headers: this.headers, headers: this.headers,
}); });
+3 -2
View File
@@ -107,7 +107,7 @@ class WuxiaWorld implements Plugin.PluginBase {
site = 'https://www.wuxiaworld.com/'; site = 'https://www.wuxiaworld.com/';
apiSite = 'https://api2.wuxiaworld.com/wuxiaworld.api.v2.'; apiSite = 'https://api2.wuxiaworld.com/wuxiaworld.api.v2.';
filters?: Filters | undefined; filters?: Filters | undefined;
version = '0.5.0'; version = '0.5.1';
parseNovels(data: { items: NovelEntry[] }) { parseNovels(data: { items: NovelEntry[] }) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -270,7 +270,8 @@ class WuxiaWorld implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = this.site + 'api/novels/search?query=' + searchTerm; const url =
this.site + 'api/novels/search?query=' + encodeURIComponent(searchTerm);
const result = await fetchApi(url); const result = await fetchApi(url);
const data = await result.json(); const data = await result.json();