Add TopLiba (#1003)

* test

* cleanup

deleted unused `fetchFile` import
replaced fetch with `fetchApi`

* eslint

* fix

* fix multi

* Hmm, what to do next.

* test URLSearchParams

* fix
This commit is contained in:
Rider21
2024-06-30 07:28:11 +03:00
committed by GitHub
parent 938e3b16a3
commit fae73534dd
121 changed files with 10534 additions and 8079 deletions
+12 -10
View File
@@ -1,11 +1,10 @@
import { fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs'; import { Filters } from '@libs/filterInputs';
import { load as loadCheerio } from 'cheerio'; import { load as loadCheerio } from 'cheerio';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
// import { isUrlAbsolute } from "@libs/isAbsoluteUrl"; // import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
// import { parseMadaraDate } from "@libs/parseMadaraDate";
class TemplatePlugin implements Plugin.PluginBase { class TemplatePlugin implements Plugin.PluginBase {
id = ''; id = '';
@@ -42,15 +41,15 @@ class TemplatePlugin implements Plugin.PluginBase {
// TODO: get here data from the site and // TODO: get here data from the site and
// un-comment and fill-in the relevant fields // un-comment and fill-in the relevant fields
// novel.name = ""; // novel.name = '';
// novel.artist = ""; // novel.artist = '';
// novel.author = ""; // novel.author = '';
novel.cover = defaultCover; novel.cover = defaultCover;
// novel.genres = ""; // novel.genres = '';
// novel.status = NovelStatus.Completed; // novel.status = NovelStatus.Completed;
// novel.summary = ""; // novel.summary = '';
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
// TODO: here parse the chapter list // TODO: here parse the chapter list
@@ -75,12 +74,15 @@ class TemplatePlugin implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
pageNo: number, pageNo: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
// get novels using the search term // get novels using the search term
return novels; return novels;
} }
resolveUrl = (path: string, isNovel?: boolean) =>
this.site + (isNovel ? '/book/' : '/chapter/') + path;
} }
export default new TemplatePlugin(); export default new TemplatePlugin();
+168
View File
@@ -0,0 +1,168 @@
// @ts-check
import eslint from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';
import tseslint from 'typescript-eslint';
import globals from 'globals';
const globalsHermes = [
//hermes@0.72
'AggregateError',
'Array',
'ArrayBuffer',
'BigInt',
'BigInt64Array',
'BigUint64Array',
'Boolean',
'DataView',
'Date',
'DebuggerInternal',
'Error',
'EvalError',
'Float32Array',
'Float64Array',
'Function',
'HermesInternal',
'Infinity',
'Int16Array',
'Int32Array',
'Int8Array',
'JSON',
'Map',
'Math',
'NaN',
'Number',
'Object',
'Promise',
'Proxy',
'QuitError',
'RangeError',
'ReferenceError',
'Reflect',
'RegExp',
'Set',
'String',
'Symbol',
'SyntaxError',
'TimeoutError',
'TypeError',
'URIError',
'Uint16Array',
'Uint32Array',
'Uint8Array',
'Uint8ClampedArray',
'WeakMap',
'WeakSet',
'__defineGetter__',
'__defineSetter__',
'__lookupGetter__',
'__lookupSetter__',
'__proto__',
'clearTimeout',
'constructor',
'createHeapSnapshot',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'escape',
'gc',
'globalThis',
'hasOwnProperty',
'isFinite',
'isNaN',
'isPrototypeOf',
'loadSegment',
'parseFloat',
'parseInt',
'print',
'propertyIsEnumerable',
'setImmediate',
'setTimeout',
'toLocaleString',
'toString',
'undefined',
'unescape',
'valueOf',
//react-native
'AbortController',
'Blob',
'ErrorUtils',
'Event',
'EventTarget',
'File',
'FileReader',
'FormData',
'Headers',
'Intl',
'URL',
'URLSearchParams',
'WebSocket',
'XMLHttpRequest',
'__DEV__',
'__dirname',
'__fbBatchedBridgeConfig',
'alert',
'cancelAnimationFrame',
'cancelIdleCallback',
'clearImmediate',
'clearInterval',
'console',
'document',
'exports',
'fetch',
'global',
'module',
'navigator',
'process',
'queueMicrotask',
'requestAnimationFrame',
'requestIdleCallback',
'require',
'setInterval',
'window',
].map(key => ({ [key]: 'readonly' }));
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
prettierConfig,
{
ignores: ['.js', 'docs', 'proxy_server.js'],
},
{
files: ['./src/plugins/*/*.ts', './scripts/multisrc/*/template.ts'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/ban-ts-comment': 'off',
'no-case-declarations': 'warn',
'no-undef': 'error',
},
languageOptions: {
ecmaVersion: 5,
sourceType: 'module',
globals: Object.assign(...globalsHermes),
},
},
{
files: ['**/*.{ts,tsx,mts,cts,js}'],
ignores: ['./src/plugins/*/*.ts', './scripts/multisrc/*/template.ts'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/ban-ts-comment': 'off',
'no-undef': 'error',
},
languageOptions: {
globals: {
...globals.serviceworker,
...globals.browser,
},
},
},
);
-1
View File
@@ -1,4 +1,3 @@
import path from 'path';
import fs from 'fs'; import fs from 'fs';
let content = `import { Plugin } from '@typings/plugin';\n`; let content = `import { Plugin } from '@typings/plugin';\n`;
+9352 -6980
View File
File diff suppressed because it is too large Load Diff
+69 -61
View File
@@ -1,62 +1,70 @@
{ {
"name": "lnreader-plugins", "name": "lnreader-plugins",
"version": "3.0.0", "version": "3.0.0",
"description": "Plugins repo for LNReader", "description": "Plugins repo for LNReader",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node importPlugins.js && concurrently \"npm run dev\" \"npm run proxy\"", "clearMultisrc": "find . -type f -wholename \"*plugins*\\[*\\]*.[t,j]s\" -delete",
"dev": "vite", "dev": "vite",
"proxy": "node proxy_server.js", "generate": "node ./scripts/multisrc/generate.js",
"test": "exit 0", "host-linux": "chmod +x ./host.sh && ./host.sh",
"json": "node ./scripts/json_plugins.js", "host-windows": "powershell ./host.ps1",
"generate": "node ./scripts/multisrc/generate.js", "icons": "npm run clearMultisrc && npm run generate && npx tsc -m node16 && npm run json && node ./scripts/icon_reloading.js",
"clearMultisrc": "find . -type f -wholename \"*plugins*\\[*\\]*.[t,j]s\" -delete", "json": " node ./scripts/json_plugins.js",
"host-linux": "chmod +x ./host.sh && ./host.sh", "lint": "npx eslint .",
"host-windows": "powershell ./host.ps1", "prepare": "husky install",
"prettier": "prettier --write \"./**/*.{js,ts}\"", "prettier": "prettier --write \"./**/*.{js,ts}\"",
"prepare": "husky install", "proxy": "node proxy_server.js",
"icons": "npm run clearMultisrc && npm run generate && npx tsc -m node16 && npm run json && node ./scripts/icon_reloading.js" "start": "node importPlugins.js && concurrently \"npm run dev\" \"npm run proxy\"",
}, "test": "exit 0"
"author": "LNReader", },
"license": "MIT", "author": "LNReader",
"dependencies": { "license": "MIT",
"@emotion/react": "^11.11.4", "dependencies": {
"@emotion/styled": "^11.11.5", "@emotion/react": "^11.11.4",
"@fontsource/roboto": "^5.0.13", "@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20", "@fontsource/roboto": "^5.0.13",
"@mui/material": "^5.15.20", "@mui/icons-material": "^5.15.20",
"@reduxjs/toolkit": "^2.2.5", "@mui/material": "^5.15.20",
"cheerio": "^1.0.0-rc.12", "@reduxjs/toolkit": "^2.2.5",
"dayjs": "^1.11.10", "cheerio": "^1.0.0-rc.12",
"htmlparser2": "^9.1.0", "dayjs": "^1.11.10",
"protobufjs": "^7.2.6", "htmlparser2": "^9.1.0",
"qs": "^6.11.2", "protobufjs": "^7.2.6",
"react": "^18.2.0", "qs": "^6.11.2",
"react-dom": "^18.2.0", "react": "^18.2.0",
"react-redux": "^9.1.2", "react-dom": "^18.2.0",
"react-virtuoso": "^4.7.11", "react-redux": "^9.1.2",
"react-window": "^1.8.10", "react-virtuoso": "^4.7.11",
"sanitize-html": "^2.12.1", "react-window": "^1.8.10",
"urlencode": "^2.0.0" "sanitize-html": "^2.12.1",
}, "urlencode": "^2.0.0"
"devDependencies": { },
"@types/qs": "^6.9.15", "devDependencies": {
"@types/react": "^18.2.66", "@eslint/js": "^9.5.0",
"@types/react-dom": "^18.2.22", "@types/eslint__js": "^8.42.3",
"@types/react-window": "^1.8.8", "@types/qs": "^6.9.15",
"@vitejs/plugin-react-swc": "^3.5.0", "@types/react": "^18.2.66",
"concurrently": "^8.2.2", "@types/react-dom": "^18.2.22",
"http-proxy": "^1.18.1", "@types/react-window": "^1.8.8",
"husky": "^9.0.11", "@vitejs/plugin-react-swc": "^3.5.0",
"image-size": "^1.1.1", "concurrently": "^8.2.2",
"lint-staged": "^15.2.2", "eslint": "^8.57.0",
"prettier": "3.2.5", "eslint-config-prettier": "^9.1.0",
"terser": "^5.31.1", "globals": "^15.6.0",
"ts-node": "^10.9.2", "http-proxy": "^1.18.1",
"vite": "^5.2.0" "husky": "^9.0.11",
}, "image-size": "^1.1.1",
"lint-staged": { "lint-staged": "^15.2.2",
"**/*.{js,ts,jsx,tsx}": "prettier --write" "prettier": "^3.2.5",
} "terser": "^5.31.1",
} "ts-node": "^10.9.2",
"typescript": "^5.5.2",
"typescript-eslint": "^7.14.1",
"vite": "^5.2.0"
},
"lint-staged": {
"**/*.{js,ts,jsx,tsx}": "prettier --write"
}
}
-2
View File
@@ -1,7 +1,5 @@
import http from 'http'; import http from 'http';
import httpProxy from 'http-proxy'; import httpProxy from 'http-proxy';
import url from 'url';
import fs from 'fs';
const CLIENT_HOST = 'http://localhost:3000'; const CLIENT_HOST = 'http://localhost:3000';
const proxy = httpProxy.createProxyServer({}); const proxy = httpProxy.createProxyServer({});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

+2 -1
View File
@@ -1,3 +1,4 @@
/* global Buffer */
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import sizeOf from 'image-size'; import sizeOf from 'image-size';
@@ -83,7 +84,7 @@ const notAvailableImage = fs.readFileSync(
const exist = fs.existsSync(icon); const exist = fs.existsSync(icon);
if (!exist) { if (!exist) {
const dir = icon.match(/^.*[\\\/]/)[0]; const dir = path.dirname(icon);
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
-1
View File
@@ -1,6 +1,5 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { exit } from 'process';
import languages from './languages.js'; import languages from './languages.js';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { minify } from './terser.js'; import { minify } from './terser.js';
+1 -1
View File
@@ -28,7 +28,7 @@ const generate = async name => {
const filePath = path.join( const filePath = path.join(
pluginsDir, pluginsDir,
lang.toLowerCase(), lang.toLowerCase(),
filename.replace(/[\s-\.]+/g, '') + `[${name}].ts`, filename.replace(/[\s-.]+/g, '') + `[${name}].ts`,
); );
fs.writeFileSync(filePath, pluginScript, { encoding: 'utf-8' }); fs.writeFileSync(filePath, pluginScript, { encoding: 'utf-8' });
} }
+6 -7
View File
@@ -1,7 +1,9 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import path from 'path'; import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
const FilterTypes = { const FilterTypes = {
TextInput: 'Text', TextInput: 'Text',
Picker: 'Picker', Picker: 'Picker',
@@ -40,12 +42,9 @@ export const generateAll = function () {
}; };
const generator = function generator(metadata) { const generator = function generator(metadata) {
const HotNovelPubTemplate = readFileSync( const HotNovelPubTemplate = readFileSync(join(folder, 'template.ts'), {
'./scripts/multisrc/hotnovelpub/template.ts', encoding: 'utf-8',
{ });
encoding: 'utf-8',
},
);
const pluginScript = ` const pluginScript = `
${HotNovelPubTemplate} ${HotNovelPubTemplate}
+2 -6
View File
@@ -2,7 +2,6 @@ import { fetchApi } from '@libs/fetch';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs';
export interface HotNovelPubMetadata { export interface HotNovelPubMetadata {
id: string; id: string;
@@ -135,10 +134,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
return chapterText.replace(/\.copy right hot novel pub/g, ''); return chapterText.replace(/\.copy right hot novel pub/g, '');
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const result: responseSearch = await fetchApi(this.apiSite + '/search', { const result: responseSearch = await fetchApi(this.apiSite + '/search', {
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8', 'Content-Type': 'application/json;charset=utf-8',
@@ -162,7 +158,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
return novels; return novels;
} }
resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path; resolveUrl = (path: string) => this.site + '/' + path;
} }
interface responseNovels { interface responseNovels {
+7 -7
View File
@@ -1,7 +1,10 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import defaultSettings from './settings.json' with { type: 'json' }; import defaultSettings from './settings.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import path from 'path'; import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
export const generateAll = function () { export const generateAll = function () {
return list.map(metadata => { return list.map(metadata => {
@@ -12,12 +15,9 @@ export const generateAll = function () {
}; };
const generator = function generator(metadata) { const generator = function generator(metadata) {
const IfreedomTemplate = readFileSync( const IfreedomTemplate = readFileSync(join(folder, 'template.ts'), {
'./scripts/multisrc/ifreedom/template.ts', encoding: 'utf-8',
{ });
encoding: 'utf-8',
},
);
const pluginScript = ` const pluginScript = `
${IfreedomTemplate} ${IfreedomTemplate}
+1 -1
View File
@@ -187,7 +187,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
} }
parseDate = (dateString: string | undefined = '') => { parseDate = (dateString: string | undefined = '') => {
const months: { [key: string]: number } = { const months: Record<string, number> = {
января: 1, января: 1,
февраля: 2, февраля: 2,
марта: 3, марта: 3,
+15 -12
View File
@@ -1,13 +1,19 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
export const generateAll = function () { export const generateAll = function () {
return list.map(source => { return list.map(source => {
let filters = {}; const exist = existsSync(join(folder, 'filters', source.id + '.json'));
try { if (exist) {
filters = require(`./filters/${source.id}`); const filters = readFileSync(
source.filters = filters.filters; join(folder, 'filters', source.id + '.json'),
} catch (e) {} );
source.filters = JSON.parse(filters);
}
console.log( console.log(
`[lightnovelworld] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`, `[lightnovelworld] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
); );
@@ -16,12 +22,9 @@ export const generateAll = function () {
}; };
const generator = function generator(source) { const generator = function generator(source) {
const LightNovelWPTemplate = readFileSync( const LightNovelWPTemplate = readFileSync(join(folder, 'template.ts'), {
'./scripts/multisrc/lightnovelworld/template.ts', encoding: 'utf-8',
{ });
encoding: 'utf-8',
},
);
const pluginScript = ` const pluginScript = `
${LightNovelWPTemplate} ${LightNovelWPTemplate}
+5 -8
View File
@@ -148,22 +148,19 @@ class LightNovelWorld implements Plugin.PagePlugin {
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
let chapterText = loadedCheerio('#chapter-container').html() || ''; const chapterText = loadedCheerio('#chapter-container').html() || '';
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}lnsearchlive`; const url = `${this.site}lnsearchlive`;
const link = `${this.site}search`; const link = `${this.site}search`;
const response = await fetchApi(link).then(r => r.text()); const response = await fetchApi(link).then(r => r.text());
const token = parseHTML(response); const token = parseHTML(response);
let verifytoken = token('#novelSearchForm > input').attr('value'); const verifytoken = token('#novelSearchForm > input').attr('value');
let formData = new FormData(); const formData = new FormData();
formData.append('inputContent', searchTerm); formData.append('inputContent', searchTerm);
const results = await fetchApi(url, { const results = await fetchApi(url, {
@@ -172,7 +169,7 @@ class LightNovelWorld implements Plugin.PagePlugin {
body: formData, body: formData,
}).then(r => r.json()); }).then(r => r.json());
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
const loadedCheerio = parseHTML(results.resultview); const loadedCheerio = parseHTML(results.resultview);
+15 -12
View File
@@ -1,13 +1,19 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
export const generateAll = function () { export const generateAll = function () {
return list.map(source => { return list.map(source => {
let filters = {}; const exist = existsSync(join(folder, 'filters', source.id + '.json'));
try { if (exist) {
filters = require(`./filters/${source.id}`); const filters = readFileSync(
source.filters = filters.filters; join(folder, 'filters', source.id + '.json'),
} catch (e) {} );
source.filters = JSON.parse(filters);
}
console.log( console.log(
`[lightnovelwp] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`, `[lightnovelwp] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
); );
@@ -16,12 +22,9 @@ export const generateAll = function () {
}; };
const generator = function generator(source) { const generator = function generator(source) {
const LightNovelWPTemplate = readFileSync( const LightNovelWPTemplate = readFileSync(join(folder, 'template.ts'), {
'./scripts/multisrc/lightnovelwp/template.ts', encoding: 'utf-8',
{ });
encoding: 'utf-8',
},
);
const pluginScript = ` const pluginScript = `
${LightNovelWPTemplate.replace( ${LightNovelWPTemplate.replace(
+1 -1
View File
@@ -44,7 +44,7 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
getHostname(url: string): string { getHostname(url: string): string {
url = url.split('/')[2]; url = url.split('/')[2];
let url_parts = url.split('.'); const url_parts = url.split('.');
url_parts.pop(); // remove TLD url_parts.pop(); // remove TLD
return url_parts.join('.'); return url_parts.join('.');
} }
+20 -14
View File
@@ -1,33 +1,39 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
export const generateAll = function () { export const generateAll = function () {
return list.map(metadata => { return list.map(source => {
let filters = {}; const exist = existsSync(join(folder, 'filters', source.id + '.json'));
try { if (exist) {
filters = require(`./filters/${metadata.id}`); const filters = readFileSync(
metadata.filters = filters.filters; join(folder, 'filters', source.id + '.json'),
} catch (e) {} );
source.filters = JSON.parse(filters);
}
console.log( console.log(
`[madara] Generating: ${metadata.id}${' '.repeat(20 - metadata.id.length)} ${metadata.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`, `[madara] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
); );
return generator(metadata); return generator(source);
}); });
}; };
const generator = function generator(metadata) { const generator = function generator(source) {
const madaraTemplate = readFileSync('./scripts/multisrc/madara/template.ts', { const madaraTemplate = readFileSync(join(folder, 'template.ts'), {
encoding: 'utf-8', encoding: 'utf-8',
}); });
const pluginScript = ` const pluginScript = `
${madaraTemplate} ${madaraTemplate}
const plugin = new MadaraPlugin(${JSON.stringify(metadata)}); const plugin = new MadaraPlugin(${JSON.stringify(source)});
export default plugin; export default plugin;
`.trim(); `.trim();
return { return {
lang: metadata.options?.lang || 'English', lang: source.options?.lang || 'English',
filename: metadata.sourceName, filename: source.sourceName,
pluginScript, pluginScript,
}; };
}; };
+4 -4
View File
@@ -1,5 +1,5 @@
import { fetchApi } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Cheerio, AnyNode, CheerioAPI, load as parseHTML } from 'cheerio'; import { Cheerio, AnyNode, CheerioAPI, load as parseHTML } from 'cheerio';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
@@ -82,7 +82,7 @@ class MadaraPlugin implements Plugin.PluginBase {
getHostname(url: string): string { getHostname(url: string): string {
url = url.split('/')[2]; url = url.split('/')[2];
let url_parts = url.split('.'); const url_parts = url.split('.');
url_parts.pop(); // remove TLD url_parts.pop(); // remove TLD
return url_parts.join('.'); return url_parts.join('.');
} }
@@ -203,7 +203,7 @@ class MadaraPlugin implements Plugin.PluginBase {
}); });
loadedCheerio('div.summary__content .code-block,script').remove(); loadedCheerio('div.summary__content .code-block,script').remove();
let summary = const summary =
loadedCheerio('div.summary__content') || loadedCheerio('div.summary__content') ||
loadedCheerio('#tab-manga-about') || loadedCheerio('#tab-manga-about') ||
loadedCheerio('.post-content_item h5:contains("Summary")').next(); loadedCheerio('.post-content_item h5:contains("Summary")').next();
@@ -250,7 +250,7 @@ class MadaraPlugin implements Plugin.PluginBase {
releaseDate = dayjs().format('LL'); releaseDate = dayjs().format('LL');
} }
let chapterUrl = loadedCheerio(element).find('a').attr('href') || ''; const chapterUrl = loadedCheerio(element).find('a').attr('href') || '';
chapters.push({ chapters.push({
name: chapterName, name: chapterName,
+14 -12
View File
@@ -1,31 +1,33 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import path from 'path'; import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
export const generateAll = function () { export const generateAll = function () {
return list.map(metadata => { return list.map(source => {
console.log( console.log(
`[readnovelfull] Generating: ${metadata.id}${' '.repeat(20 - metadata.id.length)}`, `[readnovelfull] Generating: ${source.id}${' '.repeat(20 - source.id.length)}`,
); );
return generator(metadata); return generator(source);
}); });
}; };
const generator = function generator(metadata) { const generator = function generator(source) {
const readNovelFullTemplate = readFileSync( const readNovelFullTemplate = readFileSync(join(folder, 'template.ts'), {
'./scripts/multisrc/readnovelfull/template.ts', encoding: 'utf-8',
{ encoding: 'utf-8' }, });
);
const pluginScript = ` const pluginScript = `
${readNovelFullTemplate} ${readNovelFullTemplate}
const plugin = new ReadNovelFullPlugin(${JSON.stringify(metadata)}); const plugin = new ReadNovelFullPlugin(${JSON.stringify(source)});
export default plugin; export default plugin;
`.trim(); `.trim();
return { return {
lang: metadata.options?.lang || 'English', lang: source.options?.lang || 'English',
filename: metadata.sourceName, filename: source.sourceName,
pluginScript, pluginScript,
}; };
}; };
+19 -13
View File
@@ -1,34 +1,40 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import path from 'path'; import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
export const generateAll = function () { export const generateAll = function () {
return list.map(metadata => { return list.map(source => {
try { const exist = existsSync(join(folder, 'filters', source.id + '.json'));
const { filters } = require(`./filters/${metadata.id}`); if (exist) {
metadata.filters = filters; const filters = readFileSync(
} catch (e) {} join(folder, 'filters', source.id + '.json'),
);
source.filters = JSON.parse(filters);
}
console.log( console.log(
`[readwn] Generating: ${metadata.id}${' '.repeat(20 - metadata.id.length)} ${metadata.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`, `[readwn] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
); );
return generator(metadata); return generator(source);
}); });
}; };
const generator = function generator(metadata) { const generator = function generator(source) {
const readwnTemplate = readFileSync('./scripts/multisrc/readwn/template.ts', { const readwnTemplate = readFileSync(join(folder, 'template.ts'), {
encoding: 'utf-8', encoding: 'utf-8',
}); });
const pluginScript = ` const pluginScript = `
${readwnTemplate} ${readwnTemplate}
const plugin = new ReadwnPlugin(${JSON.stringify(metadata)}); const plugin = new ReadwnPlugin(${JSON.stringify(source)});
export default plugin; export default plugin;
`.trim(); `.trim();
return { return {
lang: 'english', lang: 'english',
filename: metadata.sourceName, filename: source.sourceName,
pluginScript, pluginScript,
}; };
}; };
+1 -1
View File
@@ -90,7 +90,7 @@ async function getFilters(name: string, url: string) {
?.replace('-0.html', ''), ?.replace('-0.html', ''),
}), }),
); );
let nextPage = $('.pagination > li:last-child > a').attr('href'); const nextPage = $('.pagination > li:last-child > a').attr('href');
if (nextPage) { if (nextPage) {
const allpage = parseInt(nextPage.replace(/[^0-9]/g, '') || '0', 10); const allpage = parseInt(nextPage.replace(/[^0-9]/g, '') || '0', 10);
for (let pageNo = 0; pageNo < allpage; pageNo++) { for (let pageNo = 0; pageNo < allpage; pageNo++) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { fetchApi } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
+10 -7
View File
@@ -1,7 +1,10 @@
import list from './sources.json' with { type: 'json' }; import list from './sources.json' with { type: 'json' };
import defaultSettings from './settings.json' with { type: 'json' }; import defaultSettings from './settings.json' with { type: 'json' };
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const folder = dirname(fileURLToPath(import.meta.url));
const FilterTypes = { const FilterTypes = {
TextInput: 'Text', TextInput: 'Text',
Picker: 'Picker', Picker: 'Picker',
@@ -31,20 +34,20 @@ export const generateAll = function () {
} }
return { ...p, filters: d ? undefined : filters }; return { ...p, filters: d ? undefined : filters };
}) })
.map(metadata => { .map(source => {
console.log(`[rulate]: Generating`, metadata.id); console.log(`[rulate]: Generating`, source.id);
return generator(metadata); return generator(source);
}); });
}; };
const generator = function generator(metadata) { const generator = function generator(source) {
const rulateTemplate = readFileSync('./scripts/multisrc/rulate/template.ts', { const rulateTemplate = readFileSync(join(folder, 'template.ts'), {
encoding: 'utf-8', encoding: 'utf-8',
}); });
const pluginScript = ` const pluginScript = `
${rulateTemplate} ${rulateTemplate}
const plugin = new RulatePlugin(${JSON.stringify(metadata) const plugin = new RulatePlugin(${JSON.stringify(source)
.replace(/"type":"([^"]+)"/g, '"type":FilterTypes.$1') .replace(/"type":"([^"]+)"/g, '"type":FilterTypes.$1')
.replace(/\.XCheckbox/g, '.ExcludableCheckboxGroup') //remember to redo .replace(/\.XCheckbox/g, '.ExcludableCheckboxGroup') //remember to redo
.replace(/\.Checkbox/g, '.CheckboxGroup')}); .replace(/\.Checkbox/g, '.CheckboxGroup')});
@@ -53,7 +56,7 @@ export default plugin;
return { return {
lang: 'russian', lang: 'russian',
filename: metadata.sourceName, filename: source.sourceName,
pluginScript, pluginScript,
}; };
}; };
+1 -1
View File
@@ -224,7 +224,7 @@ class RulatePlugin implements Plugin.PluginBase {
} }
parseDate = (dateString: string | undefined = '') => { parseDate = (dateString: string | undefined = '') => {
const months: { [key: string]: number } = { const months: Record<string, number> = {
'янв.': 1, 'янв.': 1,
'февр.': 2, 'февр.': 2,
'мар.': 3, 'мар.': 3,
-1
View File
@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import './App.css'; import './App.css';
import { import {
Box,
Container, Container,
createTheme, createTheme,
CssBaseline, CssBaseline,
+2 -3
View File
@@ -1,4 +1,4 @@
import React, { forwardRef, useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
@@ -18,8 +18,7 @@ import { Search, Translate } from '@mui/icons-material';
import { searchPlugins } from '@provider/plugins'; import { searchPlugins } from '@provider/plugins';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FixedSizeList, ListChildComponentProps } from 'react-window'; import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch } from 'react-redux';
import { AppState } from '@redux/store';
import { selectPlugin } from '@redux/pluginSlice'; import { selectPlugin } from '@redux/pluginSlice';
import usePlugin from '@hooks/usePlugin'; import usePlugin from '@hooks/usePlugin';
+1 -1
View File
@@ -1,4 +1,4 @@
import { Box, Button, Tooltip, withStyles } from '@mui/material'; import { Box, Button, Tooltip } from '@mui/material';
import { createTheme, ThemeProvider } from '@mui/material/styles'; import { createTheme, ThemeProvider } from '@mui/material/styles';
import React from 'react'; import React from 'react';
import CopiableItem from './CopiableItem'; import CopiableItem from './CopiableItem';
-1
View File
@@ -1,4 +1,3 @@
import React from 'react';
import { styled, TextareaAutosize } from '@mui/material'; import { styled, TextareaAutosize } from '@mui/material';
const blue = { const blue = {
@@ -1,6 +1,5 @@
import PathItem from '@components/PathItem'; import PathItem from '@components/PathItem';
import { import {
Box,
Paper, Paper,
Table, Table,
TableBody, TableBody,
@@ -8,7 +7,6 @@ import {
TableContainer, TableContainer,
TableHead, TableHead,
TableRow, TableRow,
Typography,
} from '@mui/material'; } from '@mui/material';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -1,7 +1,6 @@
import { import {
Box, Box,
Button, Button,
Divider,
Pagination, Pagination,
Stack, Stack,
TextField, TextField,
@@ -64,7 +64,7 @@ export default function PopularNovels() {
setNovels([]); setNovels([]);
if (plugin?.filters) { if (plugin?.filters) {
let filters = {}; const filters = {};
for (const fKey in plugin.filters) { for (const fKey in plugin.filters) {
filters[fKey] = { filters[fKey] = {
type: plugin.filters[fKey].type, type: plugin.filters[fKey].type,
@@ -6,14 +6,13 @@ import NovelItemCard from '../components/NovelItemCard';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from '@redux/store'; import { AppState } from '@redux/store';
import { getPlugin } from '@provider/plugins'; import { getPlugin } from '@provider/plugins';
import { Filters } from '@libs/filterInputs';
export default function SearchNovels() { export default function SearchNovels() {
const selectedPluginItem = useSelector( const selectedPluginItem = useSelector(
(state: AppState) => state.plugin.selected, (state: AppState) => state.plugin.selected,
); );
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [page, setPage] = useState(1); const [page,] = useState(1);
const [plugin, setPlugin] = useState<Plugin.PluginBase | undefined>(); const [plugin, setPlugin] = useState<Plugin.PluginBase | undefined>();
const [novels, setNovels] = useState<Plugin.NovelItem[]>([]); const [novels, setNovels] = useState<Plugin.NovelItem[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
+5 -3
View File
@@ -1,6 +1,8 @@
/* global Buffer, RequestInit */
import { parse as parseProto } from 'protobufjs'; import { parse as parseProto } from 'protobufjs';
type FetchInit = { interface FetchInit {
headers?: Record<string, string | undefined> | Headers; headers?: Record<string, string | undefined> | Headers;
method?: string; method?: string;
body?: FormData | string; body?: FormData | string;
@@ -10,10 +12,10 @@ type FetchInit = {
| undefined | undefined
| FormData | FormData
| Headers; | Headers;
}; }
const makeInit = async (init?: FetchInit) => { const makeInit = async (init?: FetchInit) => {
let defaultHeaders: Record<string, string> = { const defaultHeaders: Record<string, string> = {
'Connection': 'keep-alive', 'Connection': 'keep-alive',
'Accept': '*/*', 'Accept': '*/*',
'Accept-Language': '*', 'Accept-Language': '*',
+10 -10
View File
@@ -11,36 +11,36 @@ export enum FilterTypes {
ExcludableCheckboxGroup = 'XCheckbox', ExcludableCheckboxGroup = 'XCheckbox',
} }
type SwitchFilter = { interface SwitchFilter {
type: FilterTypes.Switch; type: FilterTypes.Switch;
/** Default value */ /** Default value */
value: boolean; value: boolean;
}; }
type TextFilter = { interface TextFilter {
type: FilterTypes.TextInput; type: FilterTypes.TextInput;
/** Default value */ /** Default value */
value: string; value: string;
}; }
type CheckboxFilter = { interface CheckboxFilter {
type: FilterTypes.CheckboxGroup; type: FilterTypes.CheckboxGroup;
options: readonly FilterOption[]; options: readonly FilterOption[];
/** Default value */ /** Default value */
value: string[]; value: string[];
}; }
type PickerFilter = { interface PickerFilter {
type: FilterTypes.Picker; type: FilterTypes.Picker;
options: readonly FilterOption[]; options: readonly FilterOption[];
/** Default value */ /** Default value */
value: string; value: string;
}; }
type ExcludableCheckboxFilter = { interface ExcludableCheckboxFilter {
type: FilterTypes.ExcludableCheckboxGroup; type: FilterTypes.ExcludableCheckboxGroup;
options: readonly FilterOption[]; options: readonly FilterOption[];
value: { include?: string[]; exclude?: string[] }; value: { include?: string[]; exclude?: string[] };
}; }
export type Filters = Record< export type Filters = Record<
string, string,
+2 -8
View File
@@ -1,6 +1,3 @@
import fs from 'fs';
import path from 'path';
class Storage { class Storage {
private db: Record<string, { created: Date; value: any; expires?: number }>; private db: Record<string, { created: Date; value: any; expires?: number }>;
@@ -54,7 +51,6 @@ class Storage {
/** /**
* Deletes a key from the storage. * Deletes a key from the storage.
* *
* @param pluginID - The ID of the plugin.
* @param key - The key to delete. * @param key - The key to delete.
*/ */
delete(key: string): void { delete(key: string): void {
@@ -64,7 +60,7 @@ class Storage {
/** /**
* Clears all stored items from storage. * Clears all stored items from storage.
*/ */
clearAll(pluginID: string): void { clearAll(): void {
this.db = {}; this.db = {};
} }
} }
@@ -84,9 +80,7 @@ https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
/** /**
* Represents the structure of a storage object with string keys and values. * Represents the structure of a storage object with string keys and values.
*/ */
interface StorageObject { type StorageObject = Record<string, any>;
[key: string]: any;
}
/** /**
* Represents a simplified version of the browser's localStorage. * Represents a simplified version of the browser's localStorage.
+1 -1
View File
@@ -14,7 +14,7 @@ import 'protobufjs';
const { fetch: originalFetch } = window; const { fetch: originalFetch } = window;
window.fetch = async (...args) => { window.fetch = async (...args) => {
let [resource, config] = args; const [resource, config] = args;
if (resource.toString().includes('localhost')) { if (resource.toString().includes('localhost')) {
const response = await originalFetch(resource, config); const response = await originalFetch(resource, config);
return response; return response;
+6 -6
View File
@@ -1,5 +1,5 @@
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { fetchText, fetchFile } from '@libs/fetch'; import { fetchText } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -35,7 +35,7 @@ class Shu69 implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
loadedCheerio('div.book-coverlist').each((i, el) => { loadedCheerio('div.book-coverlist').each((i, el) => {
let url = loadedCheerio(el).find('a.cover').attr('href'); const url = loadedCheerio(el).find('a.cover').attr('href');
const novelName = loadedCheerio(el).find('h4.name').text().trim(); const novelName = loadedCheerio(el).find('h4.name').text().trim();
const novelCover = loadedCheerio(el).find('a.cover > img').attr('src'); const novelCover = loadedCheerio(el).find('a.cover > img').attr('src');
@@ -59,7 +59,7 @@ class Shu69 implements Plugin.PluginBase {
const body = await fetchText(url); const body = await fetchText(url);
if (body === '') throw Error('无法获取小说内容,请检查网络'); if (body === '') throw Error('无法获取小说内容,请检查网络');
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
@@ -84,7 +84,7 @@ class Shu69 implements Plugin.PluginBase {
novel.genres = ''; novel.genres = '';
// Table of Content is on a different page than the summary page // Table of Content is on a different page than the summary page
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
const allUrl = loadedCheerio('dd.all > a').attr('href'); const allUrl = loadedCheerio('dd.all > a').attr('href');
if (allUrl) { if (allUrl) {
@@ -156,12 +156,12 @@ class Shu69 implements Plugin.PluginBase {
}); });
if (body === '') throw Error('无法获取搜索结果,请检查网络'); if (body === '') throw Error('无法获取搜索结果,请检查网络');
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
loadedCheerio('div.book-coverlist').each((i, el) => { loadedCheerio('div.book-coverlist').each((i, el) => {
let url = loadedCheerio(el).find('a.cover').attr('href'); const url = loadedCheerio(el).find('a.cover').attr('href');
const novelName = loadedCheerio(el).find('h4.name').text().trim(); const novelName = loadedCheerio(el).find('h4.name').text().trim();
const novelCover = loadedCheerio(el).find('a.cover > img').attr('src'); const novelCover = loadedCheerio(el).find('a.cover > img').attr('src');
+4 -4
View File
@@ -1,5 +1,5 @@
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { fetchText, fetchFile } from '@libs/fetch'; import { fetchText } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
// import { encode } from 'urlencode'; // import { encode } from 'urlencode';
@@ -66,7 +66,7 @@ class XinShu69 implements Plugin.PluginBase {
const body = await fetchText(url, {}, 'gbk'); const body = await fetchText(url, {}, 'gbk');
if (body === '') throw Error('无法获取小说内容,请检查网络'); if (body === '') throw Error('无法获取小说内容,请检查网络');
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
@@ -97,7 +97,7 @@ class XinShu69 implements Plugin.PluginBase {
} }
// Table of Content is on a different page than the summary page // Table of Content is on a different page than the summary page
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
const chaptersUrl = loadedCheerio('a.more-btn').attr('href'); const chaptersUrl = loadedCheerio('a.more-btn').attr('href');
if (chaptersUrl) { if (chaptersUrl) {
@@ -160,7 +160,7 @@ class XinShu69 implements Plugin.PluginBase {
); );
if (body === '') throw Error('无法获取搜索结果,请检查网络'); if (body === '') throw Error('无法获取搜索结果,请检查网络');
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
+6 -6
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchText, fetchFile } from '@libs/fetch'; import { fetchText } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -29,7 +29,7 @@ class Linovelib implements Plugin.PluginBase {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
loadedCheerio('.module-rank-booklist .book-layout').each((i, el) => { loadedCheerio('.module-rank-booklist .book-layout').each((i, el) => {
let url = loadedCheerio(el).attr('href'); const url = loadedCheerio(el).attr('href');
const novelName = loadedCheerio(el).find('.book-title').text(); const novelName = loadedCheerio(el).find('.book-title').text();
const novelCover = loadedCheerio(el) const novelCover = loadedCheerio(el)
@@ -55,7 +55,7 @@ class Linovelib implements Plugin.PluginBase {
const body = await fetchText(url); const body = await fetchText(url);
if (body === '') throw Error('无法获取小说内容,请检查网络'); if (body === '') throw Error('无法获取小说内容,请检查网络');
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
@@ -83,7 +83,7 @@ class Linovelib implements Plugin.PluginBase {
.join(','); .join(',');
// Table of Content is on a different page than the summary page // Table of Content is on a different page than the summary page
let chapter: Plugin.ChapterItem[] = []; const chapter: Plugin.ChapterItem[] = [];
const idPattern = /\/(\d+)\.html/; const idPattern = /\/(\d+)\.html/;
const novelId = url.match(idPattern)?.[1]; const novelId = url.match(idPattern)?.[1];
@@ -421,7 +421,7 @@ class Linovelib implements Plugin.PluginBase {
}; };
let url = this.site + chapterPath; let url = this.site + chapterPath;
let baseUrl = url; const baseUrl = url;
do { do {
const page = await loadPage(url); const page = await loadPage(url);
hasNextPage = page.pageHasNextPage; hasNextPage = page.pageHasNextPage;
@@ -502,7 +502,7 @@ class Linovelib implements Plugin.PluginBase {
// await addPage(pageCheerio, redirect); // await addPage(pageCheerio, redirect);
pageCheerio('.book-ol .book-layout').each((i, el) => { pageCheerio('.book-ol .book-layout').each((i, el) => {
let nUrl = pageCheerio(el).attr('href'); const nUrl = pageCheerio(el).attr('href');
const novelName = pageCheerio(el).find('.book-title').text(); const novelName = pageCheerio(el).find('.book-title').text();
const novelCover = pageCheerio(el) const novelCover = pageCheerio(el)
+1 -1
View File
@@ -1,5 +1,5 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import qs from 'qs'; import qs from 'qs';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
+6 -9
View File
@@ -1,5 +1,5 @@
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import qs from 'qs'; import qs from 'qs';
@@ -48,7 +48,7 @@ class NovelsOnline implements Plugin.PluginBase {
const result = await fetchApi(this.site + novelPath).then(res => const result = await fetchApi(this.site + novelPath).then(res =>
res.text(), res.text(),
); );
let $ = parseHTML(result); const $ = parseHTML(result);
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
@@ -58,8 +58,8 @@ class NovelsOnline implements Plugin.PluginBase {
}; };
$('.novel-detail-item').each((i, el) => { $('.novel-detail-item').each((i, el) => {
let detailName = $(el).find('h6').text(); const detailName = $(el).find('h6').text();
let detail = $(el).find('.novel-detail-body'); const detail = $(el).find('.novel-detail-body');
switch (detailName) { switch (detailName) {
case 'Description': case 'Description':
@@ -111,11 +111,8 @@ class NovelsOnline implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string, const result = await fetchApi(this.site + '/sResults.php', {
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const result = await fetchApi('https://novelsonline.net/sResults.php', {
headers: { headers: {
Accept: '*/*', Accept: '*/*',
'Accept-Language': 'pl,en-US;q=0.7,en;q=0.3', 'Accept-Language': 'pl,en-US;q=0.7,en;q=0.3',
+40 -47
View File
@@ -1,5 +1,5 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
@@ -8,54 +8,49 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
name = 'StorySeedling'; name = 'StorySeedling';
icon = 'src/en/storyseedling/icon.png'; icon = 'src/en/storyseedling/icon.png';
site = 'https://storyseedling.com/'; site = 'https://storyseedling.com/';
version = '1.0.1'; version = '1.0.2';
async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> { async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let url = 'https://storyseedling.com/ajax'; const body = await fetchApi(this.site + 'browse').then(r => r.text());
const postUrl = `https://storyseedling.com/browse/`;
const body = await fetchApi(postUrl).then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
var postValue = loadedCheerio('div[ax-load][x-data]') const postValue = loadedCheerio('div[ax-load][x-data]')
.attr('x-data') .attr('x-data')
?.replace("browse('", '') ?.replace("browse('", '')
.replace("')", '') as string; .replace("')", '') as string;
var data = new FormData(); const data = new FormData();
data.append('search', ''); data.append('search', '');
data.append('orderBy', 'recent'); data.append('orderBy', 'recent');
data.append('curpage', pageNo.toString()); data.append('curpage', pageNo.toString());
data.append('post', postValue); data.append('post', postValue);
data.append('action', 'fetch_browse'); data.append('action', 'fetch_browse');
var response = await ( const response: any = await fetchApi(this.site + 'ajax', {
await fetchApi(url, { body: data, method: 'POST' }) body: data,
).json(); method: 'POST',
}).then(res => res.json());
response.data.posts.forEach((element: any) => { response.data.posts.forEach((element: any) =>
const novel = { novels.push({
name: element.title, name: element.title,
cover: element.thumbnail, cover: element.thumbnail,
path: element.permalink.replace(this.site, ''), path: element.permalink.replace(this.site, ''),
}; }),
novels.push(novel); );
});
return novels; return novels;
} }
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
var site = this.site; const site = this.site;
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
name: '', name: '',
}; };
const body = await fetchApi(this.site + novelPath).then(r => r.text()); const body = await fetchApi(this.site + novelPath).then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
novel.name = loadedCheerio('h1').text().trim(); novel.name = loadedCheerio('h1').text().trim();
@@ -68,7 +63,8 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
if (!novel.cover) { if (!novel.cover) {
novel.cover = defaultCover; novel.cover = defaultCover;
} }
let genres: string[] = [];
const genres: string[] = [];
loadedCheerio( loadedCheerio(
'section[x-data="{ tab: location.hash.substr(1) || \'chapters\' }"].relative > div > div > div.flex.flex-wrap > a', 'section[x-data="{ tab: location.hash.substr(1) || \'chapters\' }"].relative > div > div > div.flex.flex-wrap > a',
).each(function () { ).each(function () {
@@ -81,7 +77,7 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
.trim() .trim()
.replace(/(\r\n|\n|\r)/gm, ''); .replace(/(\r\n|\n|\r)/gm, '');
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
loadedCheerio( loadedCheerio(
'div.grid.w-full.grid-cols-1.gap-4.md\\:grid-cols-2 > a', 'div.grid.w-full.grid-cols-1.gap-4.md\\:grid-cols-2 > a',
@@ -89,20 +85,20 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
if (loadedCheerio(this).find('> div').length == 2) { if (loadedCheerio(this).find('> div').length == 2) {
return; return;
} }
let name = loadedCheerio(this).find('.truncate').text().trim(); const name = loadedCheerio(this).find('.truncate').text().trim();
let url = loadedCheerio(this).attr('href') as string; const url = loadedCheerio(this).attr('href') as string;
let releaseTime = loadedCheerio(this) const releaseTime = loadedCheerio(this)
.find('div > div > small') .find('div > div > small')
.text() .text()
.trim(); .trim();
let chapterNumber = name.split('-')[0].trim().split(' ')[1]; const chapterNumber = name.split('-')[0].trim().split(' ')[1];
const chapter: Plugin.ChapterItem = {
chapters.push({
name: name, name: name,
path: url.replace(site, ''), path: url.replace(site, ''),
releaseTime: releaseTime, releaseTime,
chapterNumber: parseInt(chapterNumber), chapterNumber: parseInt(chapterNumber),
}; });
chapters.push(chapter);
}); });
novel.chapters = chapters; novel.chapters = chapters;
return novel; return novel;
@@ -111,8 +107,8 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
const body = await fetchApi(this.site + chapterPath).then(r => r.text()); const body = await fetchApi(this.site + chapterPath).then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
let t = loadedCheerio('div.justify-center > div.mb-4'); const t = loadedCheerio('div.justify-center > div.mb-4');
let chapterText = t.html() || ''; const chapterText = t.html() || '';
return chapterText; return chapterText;
} }
@@ -121,38 +117,35 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
searchTerm: string, searchTerm: string,
pageNo: number, pageNo: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let url = 'https://storyseedling.com/ajax';
const postUrl = `https://storyseedling.com/browse/`;
const body = await fetchApi(postUrl).then(r => r.text());
const body = await fetchApi(this.site + 'browse').then(r => r.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
var postValue = loadedCheerio('div[ax-load][x-data]') const postValue = loadedCheerio('div[ax-load][x-data]')
.attr('x-data') .attr('x-data')
?.replace("browse('", '') ?.replace("browse('", '')
.replace("')", '') as string; .replace("')", '') as string;
var data = new FormData(); const data = new FormData();
data.append('search', searchTerm); data.append('search', searchTerm);
data.append('orderBy', 'recent'); data.append('orderBy', 'recent');
data.append('curpage', pageNo.toString()); data.append('curpage', pageNo.toString());
data.append('post', postValue); data.append('post', postValue);
data.append('action', 'fetch_browse'); data.append('action', 'fetch_browse');
var response = await ( const response: any = await fetchApi(this.site + 'ajax', {
await fetchApi(url, { body: data, method: 'POST' }) body: data,
).json(); method: 'POST',
}).then(res => res.json());
response.data.posts.forEach((element: any) => { response.data.posts.forEach((element: any) =>
const novel = { novels.push({
name: element.title, name: element.title,
cover: element.thumbnail, cover: element.thumbnail,
path: element.permalink.replace(this.site, ''), path: element.permalink.replace(this.site, ''),
}; }),
novels.push(novel); );
});
return novels; return novels;
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
+5 -5
View File
@@ -1,7 +1,7 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
@@ -48,7 +48,7 @@ class BLN implements Plugin.PluginBase {
link += '&state=' + filters.status.value; link += '&state=' + filters.status.value;
link += '&page=' + page; link += '&page=' + page;
const result = await fetch(link); const result = await fetchApi(link);
if (!result.ok) { if (!result.ok) {
console.error(await result.text()); console.error(await result.text());
// TODO: Cloudflare protection or other error // TODO: Cloudflare protection or other error
@@ -61,7 +61,7 @@ class BLN implements Plugin.PluginBase {
} }
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const result = await fetch(this.site + novelPath); const result = await fetchApi(this.site + novelPath);
const body = await result.text(); const body = await result.text();
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
@@ -146,7 +146,7 @@ class BLN implements Plugin.PluginBase {
} }
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const result = await fetch(this.site + chapterPath); const result = await fetchApi(this.site + chapterPath);
const body = await result.text(); const body = await result.text();
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
@@ -162,7 +162,7 @@ class BLN implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search_novels/${searchTerm}?page=${page}`; const url = `${this.site}search_novels/${searchTerm}?page=${page}`;
const result = await fetch(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
+2 -2
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -52,7 +52,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> { async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(novelUrl).then(result => result.text()); const body = await fetchApi(novelUrl).then(result => result.text());
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
url: novelUrl, url: novelUrl,
+6 -13
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
@@ -9,7 +9,6 @@ class DDLPlugin implements Plugin.PluginBase {
site = 'https://www.divinedaolibrary.com/'; site = 'https://www.divinedaolibrary.com/';
version = '1.0.1'; version = '1.0.1';
icon = 'src/en/divinedaolibrary/icon.png'; icon = 'src/en/divinedaolibrary/icon.png';
filters?: undefined;
parseNovels(loadedCheerio: CheerioAPI, searchTerm?: string) { parseNovels(loadedCheerio: CheerioAPI, searchTerm?: string) {
let novels: Plugin.NovelItem[] = []; let novels: Plugin.NovelItem[] = [];
@@ -40,11 +39,8 @@ class DDLPlugin implements Plugin.PluginBase {
return novels; return novels;
} }
async popularNovels( async popularNovels(): Promise<Plugin.NovelItem[]> {
pageNo: number, const link = this.site + 'novels';
options: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
let link = this.site + 'novels';
const body = await fetchApi(link).then(res => res.text()); const body = await fetchApi(link).then(res => res.text());
@@ -56,7 +52,7 @@ class DDLPlugin implements Plugin.PluginBase {
const result = await fetchApi(this.site + novelPath); const result = await fetchApi(this.site + novelPath);
const body = await result.text(); const body = await result.text();
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
@@ -116,11 +112,8 @@ class DDLPlugin implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string, const url = this.site + 'novels';
pageNo: number,
): Promise<Plugin.NovelItem[]> {
let url = this.site + 'novels';
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+5 -8
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
@@ -68,7 +68,7 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
const result = await fetchApi(this.site + novelPath); const result = await fetchApi(this.site + novelPath);
const body = await result.text(); const body = await result.text();
let loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
loadedCheerio('.glyphicon-menu-right').closest('li').remove(); loadedCheerio('.glyphicon-menu-right').closest('li').remove();
const pagenav = loadedCheerio('.page-nav').prev().find('a'); const pagenav = loadedCheerio('.page-nav').prev().find('a');
const lastPageStr = pagenav.attr('title')?.match(/(\d+)/); const lastPageStr = pagenav.attr('title')?.match(/(\d+)/);
@@ -84,8 +84,8 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
}; };
loadedCheerio('.info > div > h3').each(function () { loadedCheerio('.info > div > h3').each(function () {
let detailName = loadedCheerio(this).text(); const detailName = loadedCheerio(this).text();
let detail = loadedCheerio(this) const detail = loadedCheerio(this)
.siblings() .siblings()
.map((i, el) => loadedCheerio(el).text()) .map((i, el) => loadedCheerio(el).text())
.toArray() .toArray()
@@ -127,10 +127,7 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search?keyword=${searchTerm}`; const url = `${this.site}search?keyword=${searchTerm}`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+3 -6
View File
@@ -1,5 +1,5 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -43,10 +43,7 @@ class FaqWikiUs implements Plugin.PluginBase {
return novels; return novels;
} }
async popularNovels( async popularNovels(): Promise<Plugin.NovelItem[]> {
page: number,
{ showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
const body = await fetchApi(this.site).then(res => res.text()); const body = await fetchApi(this.site).then(res => res.text());
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
@@ -79,7 +76,7 @@ class FaqWikiUs implements Plugin.PluginBase {
const div = loadedCheerio('.book-review-block__meta-item-value'); const div = loadedCheerio('.book-review-block__meta-item-value');
div.html(div.html()?.replace(/(?<=>)([^<]+)(?=<br\s*\/?>)/g, '<p>$1</p>')!); div.html(div.html()?.replace(/(?<=>)([^<]+)(?=<br\s*\/?>)/g, '<p>$1</p>'));
loadedCheerio('.book-review-block__meta-item-value strong').each( loadedCheerio('.book-review-block__meta-item-value strong').each(
(i, el) => { (i, el) => {
+1 -2
View File
@@ -264,8 +264,7 @@ class Foxteller implements Plugin.PluginBase {
return novels; return novels;
} }
resolveUrl = (path: string, isNovel?: boolean) => resolveUrl = (path: string) => this.site + '/novel/' + path;
this.site + '/novel/' + path;
filters = { filters = {
order: { order: {
+1 -2
View File
@@ -1,6 +1,5 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus';
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import qs from 'qs'; import qs from 'qs';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
+6 -9
View File
@@ -1,5 +1,5 @@
import { Parser } from 'htmlparser2'; import { Parser } from 'htmlparser2';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -85,7 +85,7 @@ class LightNovelPub implements Plugin.PagePlugin {
let isSummary = false; let isSummary = false;
let isGenres = false; let isGenres = false;
let isTags = false; let isTags = false;
let genreArray: string[] = []; const genreArray: string[] = [];
let isCover = false; let isCover = false;
const parser = new Parser({ const parser = new Parser({
onopentag(name, attribs) { onopentag(name, attribs) {
@@ -245,7 +245,7 @@ class LightNovelPub implements Plugin.PagePlugin {
const parser = new Parser({ const parser = new Parser({
onopentag(name, attribs) { onopentag(name, attribs) {
if (isChapter && name === 'div') { if (isChapter && name === 'div') {
let stylediv = attribs['style']; const stylediv = attribs['style'];
if (stylediv) { if (stylediv) {
chapterText += `<div style="${stylediv}">`; chapterText += `<div style="${stylediv}">`;
isStyleText = true; isStyleText = true;
@@ -254,7 +254,7 @@ class LightNovelPub implements Plugin.PagePlugin {
} }
} }
if (isChapter && name === 'table') { if (isChapter && name === 'table') {
let w = attribs['width']; const w = attribs['width'];
if (w) { if (w) {
chapterText += `<table width="${w}">`; chapterText += `<table width="${w}">`;
} else { } else {
@@ -268,7 +268,7 @@ class LightNovelPub implements Plugin.PagePlugin {
chapterText += `<tr>`; chapterText += `<tr>`;
} }
if (isChapter && name === 'td') { if (isChapter && name === 'td') {
let w1 = attribs['width']; const w1 = attribs['width'];
if (w1) { if (w1) {
chapterText += `<td width="${w1}">`; chapterText += `<td width="${w1}">`;
} else { } else {
@@ -334,10 +334,7 @@ class LightNovelPub implements Plugin.PagePlugin {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}lnsearchlive`; const url = `${this.site}lnsearchlive`;
const link = `${this.site}search`; const link = `${this.site}search`;
const response = await fetchApi(link).then(r => r.text()); const response = await fetchApi(link).then(r => r.text());
+4 -7
View File
@@ -1,5 +1,5 @@
import { Parser } from 'htmlparser2'; import { Parser } from 'htmlparser2';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
@@ -87,7 +87,7 @@ class LnMTLPlugin implements Plugin.PagePlugin {
let isStatusKey = false; let isStatusKey = false;
let isList = 0; let isList = 0;
let isGenres = false; let isGenres = false;
let genreArray: string[] = []; const genreArray: string[] = [];
const parser = new Parser({ const parser = new Parser({
onopentag(name, attribs) { onopentag(name, attribs) {
if (name === 'img' && attribs['class']?.includes('img-rounded')) { if (name === 'img' && attribs['class']?.includes('img-rounded')) {
@@ -272,10 +272,7 @@ class LnMTLPlugin implements Plugin.PagePlugin {
return chapterText.replace(/„/g, '“'); return chapterText.replace(/„/g, '“');
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const html = await fetchApi(this.site) const html = await fetchApi(this.site)
.then(b => b.text()) .then(b => b.text())
.then(r => r.replace(/>\s+</g, '><')); .then(r => r.replace(/>\s+</g, '><'));
@@ -312,7 +309,7 @@ class LnMTLPlugin implements Plugin.PagePlugin {
parser.write(html); parser.write(html);
parser.end(); parser.end();
const search = await fetch(`${this.site}${list}`); const search = await fetchApi(`${this.site}${list}`);
const data = await search.json(); const data = await search.json();
const nov = data.filter((res: { name: string }) => const nov = data.filter((res: { name: string }) =>
+10 -14
View File
@@ -1,5 +1,5 @@
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
@@ -21,7 +21,9 @@ class MTLNovel implements Plugin.PluginBase {
const headers = new Headers(); const headers = new Headers();
headers.append('Alt-Used', 'www.mtlnovel.com'); headers.append('Alt-Used', 'www.mtlnovel.com');
const body = await fetch(link, { headers }).then(result => result.text()); const body = await fetchApi(link, { headers }).then(result =>
result.text(),
);
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
@@ -50,7 +52,7 @@ class MTLNovel implements Plugin.PluginBase {
headers.append('Referer', `${this.site}novel-list/`); headers.append('Referer', `${this.site}novel-list/`);
headers.append('Alt-Used', 'www.mtlnovel.com'); headers.append('Alt-Used', 'www.mtlnovel.com');
const body = await fetch(this.site + novelPath, { const body = await fetchApi(this.site + novelPath, {
headers, headers,
}).then(result => result.text()); }).then(result => result.text());
@@ -70,7 +72,7 @@ class MTLNovel implements Plugin.PluginBase {
const chapterListUrl = this.site + novelPath + 'chapter-list/'; const chapterListUrl = this.site + novelPath + 'chapter-list/';
const getChapters = async () => { const getChapters = async () => {
const listResult = await fetch(chapterListUrl, { headers }); const listResult = await fetchApi(chapterListUrl, { headers });
const listBody = await listResult.text(); const listBody = await listResult.text();
loadedCheerio = parseHTML(listBody); loadedCheerio = parseHTML(listBody);
@@ -101,7 +103,7 @@ class MTLNovel implements Plugin.PluginBase {
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const headers = new Headers(); const headers = new Headers();
headers.append('Alt-Used', 'www.mtlnovel.com'); headers.append('Alt-Used', 'www.mtlnovel.com');
const body = await fetch(this.site + chapterPath, { headers }).then(r => const body = await fetchApi(this.site + chapterPath, { headers }).then(r =>
r.text(), r.text(),
); );
@@ -112,19 +114,13 @@ class MTLNovel implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const headers = new Headers(); const headers = new Headers();
headers.append('Alt-Used', 'www.mtlnovel.com'); headers.append('Alt-Used', 'www.mtlnovel.com');
const searchUrl = const searchUrl =
this.site + this.site + 'wp-admin/admin-ajax.php?action=autosuggest&q=' + searchTerm;
'wp-admin/admin-ajax.php?action=autosuggest&q=' +
searchTerm +
'&__amp_source_origin=https%3A%2F%2Fwww.mtlnovel.com';
const res = await fetch(searchUrl, { headers }); const res = await fetchApi(searchUrl, { headers });
const result = await res.json(); const result = await res.json();
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
+4 -10
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs';
class MTLReader implements Plugin.PluginBase { class MTLReader implements Plugin.PluginBase {
id = 'mtlreader'; id = 'mtlreader';
@@ -9,6 +8,7 @@ class MTLReader implements Plugin.PluginBase {
version = '1.0.0'; version = '1.0.0';
icon = 'src/en/mtlreader/icon.png'; icon = 'src/en/mtlreader/icon.png';
site = 'https://mtlreader.com/'; site = 'https://mtlreader.com/';
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -31,10 +31,7 @@ class MTLReader implements Plugin.PluginBase {
return novels; return novels;
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
options: Plugin.PopularNovelsOptions<Filters>,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}novels?page=${pageNo}`; const url = `${this.site}novels?page=${pageNo}`;
const body = await fetchApi(url).then(r => r.text()); const body = await fetchApi(url).then(r => r.text());
@@ -90,10 +87,7 @@ class MTLReader implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const body = await fetchApi(this.site).then(r => r.text()); const body = await fetchApi(this.site).then(r => r.text());
const tokenCheerio = parseHTML(body); const tokenCheerio = parseHTML(body);
const token = tokenCheerio('input[name="_token"]').attr('value'); const token = tokenCheerio('input[name="_token"]').attr('value');
+4 -7
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
@@ -73,8 +73,8 @@ class NovelBin implements Plugin.PluginBase {
}; };
loadedCheerio('ul.info > li > h3').each(function () { loadedCheerio('ul.info > li > h3').each(function () {
let detailName = loadedCheerio(this).text(); const detailName = loadedCheerio(this).text();
let detail = loadedCheerio(this) const detail = loadedCheerio(this)
.siblings() .siblings()
.map((i, el) => loadedCheerio(el).text()) .map((i, el) => loadedCheerio(el).text())
.toArray() .toArray()
@@ -137,10 +137,7 @@ class NovelBin implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}search/?keyword=${searchTerm}`; const url = `${this.site}search/?keyword=${searchTerm}`;
const body = await fetchApi(url).then(r => r.text()); const body = await fetchApi(url).then(r => r.text());
+2 -7
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs';
class NovelBuddy implements Plugin.PluginBase { class NovelBuddy implements Plugin.PluginBase {
id = 'novelbuddy'; id = 'novelbuddy';
@@ -9,7 +8,6 @@ class NovelBuddy implements Plugin.PluginBase {
site = 'https://novelbuddy.io/'; site = 'https://novelbuddy.io/';
version = '1.0.0'; version = '1.0.0';
icon = 'src/en/novelbuddy/icon.png'; icon = 'src/en/novelbuddy/icon.png';
filters?: undefined; //TODO: Filters, Filters in Filters required
parseNovels(loadedCheerio: CheerioAPI) { parseNovels(loadedCheerio: CheerioAPI) {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -33,10 +31,7 @@ class NovelBuddy implements Plugin.PluginBase {
return novels; return novels;
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
options: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}popular?page=${pageNo}`; const url = `${this.site}popular?page=${pageNo}`;
const result = await fetchApi(url); const result = await fetchApi(url);
+1 -1
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
+4 -12
View File
@@ -1,7 +1,6 @@
import { load, load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
class NovelHall implements Plugin.PluginBase { class NovelHall implements Plugin.PluginBase {
@@ -9,13 +8,9 @@ class NovelHall implements Plugin.PluginBase {
name = 'Novel Hall'; name = 'Novel Hall';
version = '1.0.1'; version = '1.0.1';
icon = 'src/en/novelhall/icon.png'; icon = 'src/en/novelhall/icon.png';
filters?: Filters | undefined; //TODO: Filters Requires hideOnSelect
site = 'https://novelhall.com/'; site = 'https://novelhall.com/';
async popularNovels( async popularNovels(page: number): Promise<Plugin.NovelItem[]> {
page: number,
options: Plugin.PopularNovelsOptions<Filters>,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}all2022-${page}.html`; const url = `${this.site}all2022-${page}.html`;
const body = await fetchApi(url).then(r => r.text()); const body = await fetchApi(url).then(r => r.text());
@@ -94,10 +89,7 @@ class NovelHall implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): 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=${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);
+16 -15
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-case-declarations */
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
@@ -142,7 +143,7 @@ class NovelUpdates implements Plugin.PluginBase {
loadedCheerio = parseHTML(text); loadedCheerio = parseHTML(text);
const nameReplacements: { [key: string]: string } = { const nameReplacements: Record<string, string> = {
'v': 'volume ', 'v': 'volume ',
'c': ' chapter ', 'c': ' chapter ',
'part': 'part ', 'part': 'part ',
@@ -151,7 +152,7 @@ class NovelUpdates implements Plugin.PluginBase {
loadedCheerio('li.sp_li_chp').each((i, el) => { loadedCheerio('li.sp_li_chp').each((i, el) => {
let chapterName = loadedCheerio(el).text(); let chapterName = loadedCheerio(el).text();
for (let name in nameReplacements) { for (const name in nameReplacements) {
chapterName = chapterName.replace(name, nameReplacements[name]); chapterName = chapterName.replace(name, nameReplacements[name]);
} }
chapterName = chapterName.replace(/\b\w/g, l => l.toUpperCase()).trim(); chapterName = chapterName.replace(/\b\w/g, l => l.toUpperCase()).trim();
@@ -170,7 +171,7 @@ class NovelUpdates implements Plugin.PluginBase {
} }
getLocation(href: string) { getLocation(href: string) {
var match = href.match( const match = href.match(
/^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/, /^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/,
); );
return match && `${match[1]}//${match[3]}`; return match && `${match[1]}//${match[3]}`;
@@ -198,7 +199,7 @@ class NovelUpdates implements Plugin.PluginBase {
} }
break; break;
case 'asuratls': case 'asuratls':
let titleElementAsura = loadedCheerio('.post-body div b').first(); const titleElementAsura = loadedCheerio('.post-body div b').first();
chapterTitle = titleElementAsura.text()!; chapterTitle = titleElementAsura.text()!;
titleElementAsura.remove(); titleElementAsura.remove();
chapterContent = loadedCheerio('.post-body').html()!; chapterContent = loadedCheerio('.post-body').html()!;
@@ -235,7 +236,7 @@ class NovelUpdates implements Plugin.PluginBase {
loadedCheerio('#readerarea.rdminimal') loadedCheerio('#readerarea.rdminimal')
.children() .children()
.each((idx, ele) => { .each((idx, ele) => {
let elementText = loadedCheerio(ele).text(); const elementText = loadedCheerio(ele).text();
if (elementText.includes(chapterStringHelScans)) { if (elementText.includes(chapterStringHelScans)) {
chapterTitle = elementText; chapterTitle = elementText;
loadedCheerio(ele).remove(); loadedCheerio(ele).remove();
@@ -295,12 +296,12 @@ class NovelUpdates implements Plugin.PluginBase {
case 'ko-fi': case 'ko-fi':
chapterText = loadedCheerio('script:contains("shadowDom.innerHTML")') chapterText = loadedCheerio('script:contains("shadowDom.innerHTML")')
.html() .html()
?.match(/shadowDom\.innerHTML \+= '(<div.*?)';/)![1]!; ?.match(/shadowDom\.innerHTML \+= '(<div.*?)';/)![1];
break; break;
case 'mirilu': case 'mirilu':
bloatClasses = ['#jp-post-flair']; bloatClasses = ['#jp-post-flair'];
bloatClasses.map(tag => loadedCheerio(tag).remove()); bloatClasses.map(tag => loadedCheerio(tag).remove());
let titleElementMirilu = loadedCheerio( const titleElementMirilu = loadedCheerio(
'.entry-content p strong', '.entry-content p strong',
).first(); ).first();
chapterTitle = titleElementMirilu.text()!; chapterTitle = titleElementMirilu.text()!;
@@ -360,10 +361,10 @@ class NovelUpdates implements Plugin.PluginBase {
} }
break; break;
case 'rainofsnow': case 'rainofsnow':
let displayedDivSnow = loadedCheerio('.bb-item').filter(function () { const displayedDivSnow = loadedCheerio('.bb-item').filter(function () {
return loadedCheerio(this).css('display') === 'block'; return loadedCheerio(this).css('display') === 'block';
}); });
let loadedCheerioSnow = parseHTML(displayedDivSnow.html()!); const loadedCheerioSnow = parseHTML(displayedDivSnow.html()!);
bloatClasses = [ bloatClasses = [
'.responsivevoice-button', '.responsivevoice-button',
'.zoomdesc-cont p img', '.zoomdesc-cont p img',
@@ -371,7 +372,7 @@ class NovelUpdates implements Plugin.PluginBase {
]; ];
bloatClasses.map(tag => loadedCheerioSnow(tag).remove()); bloatClasses.map(tag => loadedCheerioSnow(tag).remove());
chapterContent = loadedCheerioSnow('.zoomdesc-cont').html()!; chapterContent = loadedCheerioSnow('.zoomdesc-cont').html()!;
let titleElementSnow = loadedCheerioSnow('.scroller h2').first(); const titleElementSnow = loadedCheerioSnow('.scroller h2').first();
if (titleElementSnow.length) { if (titleElementSnow.length) {
chapterTitle = titleElementSnow.text()!; chapterTitle = titleElementSnow.text()!;
titleElementSnow.remove(); titleElementSnow.remove();
@@ -473,7 +474,7 @@ class NovelUpdates implements Plugin.PluginBase {
]; ];
bloatClasses.map(tag => loadedCheerioSyringe(tag).remove()); bloatClasses.map(tag => loadedCheerioSyringe(tag).remove());
chapterContent = loadedCheerioSyringe('.entry-content').html()!; chapterContent = loadedCheerioSyringe('.entry-content').html()!;
let titleElementSyringe = const titleElementSyringe =
loadedCheerioSyringe('.entry-content h3').first(); loadedCheerioSyringe('.entry-content h3').first();
if (titleElementSyringe.length) { if (titleElementSyringe.length) {
chapterTitle = titleElementSyringe.text(); chapterTitle = titleElementSyringe.text();
@@ -535,7 +536,7 @@ class NovelUpdates implements Plugin.PluginBase {
bloatClasses = ['hr', 'p:contains("\u00a0")']; bloatClasses = ['hr', 'p:contains("\u00a0")'];
bloatClasses.map(tag => loadedCheerio(tag).remove()); bloatClasses.map(tag => loadedCheerio(tag).remove());
chapterContent = loadedCheerio('.text-left').html()!; chapterContent = loadedCheerio('.text-left').html()!;
let titleElementZetro = loadedCheerio('.text-left h2').first(); const titleElementZetro = loadedCheerio('.text-left h2').first();
if (titleElementZetro.length) { if (titleElementZetro.length) {
chapterTitle = titleElementZetro.text()!; chapterTitle = titleElementZetro.text()!;
titleElementZetro.remove(); titleElementZetro.remove();
@@ -590,7 +591,7 @@ class NovelUpdates implements Plugin.PluginBase {
/** /**
* Detect if the site is a Blogspot site * Detect if the site is a Blogspot site
*/ */
let isBlogspotStr = const isBlogspotStr =
loadedCheerio('meta[name="google-adsense-platform-domain"]').attr( loadedCheerio('meta[name="google-adsense-platform-domain"]').attr(
'content', 'content',
) || loadedCheerio('meta[name="generator"]').attr('content'); ) || loadedCheerio('meta[name="generator"]').attr('content');
@@ -604,7 +605,7 @@ class NovelUpdates implements Plugin.PluginBase {
/** /**
* Detect if the site is a WordPress site * Detect if the site is a WordPress site
*/ */
let isWordPressStr = const isWordPressStr =
loadedCheerio('#dcl_comments-js-extra').html() || loadedCheerio('#dcl_comments-js-extra').html() ||
loadedCheerio('meta[name="generator"]').attr('content') || loadedCheerio('meta[name="generator"]').attr('content') ||
loadedCheerio('footer').text()!; loadedCheerio('footer').text()!;
@@ -726,7 +727,7 @@ class NovelUpdates implements Plugin.PluginBase {
loadedCheerio('.title_story').first().text() || loadedCheerio('.title_story').first().text() ||
loadedCheerio('.active').first().text() || loadedCheerio('.active').first().text() ||
loadedCheerio('head title').first().text()!; loadedCheerio('head title').first().text()!;
let chapterSubtitle = loadedCheerio('.cat-series').first().text() || ''; const chapterSubtitle = loadedCheerio('.cat-series').first().text() || '';
if (chapterSubtitle) { if (chapterSubtitle) {
chapterTitle = chapterSubtitle; chapterTitle = chapterSubtitle;
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
+9 -17
View File
@@ -1,6 +1,5 @@
import { Parser } from 'htmlparser2'; import { Parser } from 'htmlparser2';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
class RanobesPlugin implements Plugin.PagePlugin { class RanobesPlugin implements Plugin.PagePlugin {
@@ -8,7 +7,6 @@ class RanobesPlugin implements Plugin.PagePlugin {
name = 'Ranobes'; name = 'Ranobes';
icon = 'src/en/ranobes/icon.png'; icon = 'src/en/ranobes/icon.png';
site = 'https://ranobes.top'; site = 'https://ranobes.top';
filters?: Filters | undefined;
version = '2.0.0'; version = '2.0.0';
async sleep(ms: number) { async sleep(ms: number) {
@@ -81,14 +79,8 @@ class RanobesPlugin implements Plugin.PagePlugin {
return chapter.reverse(); return chapter.reverse();
} }
async popularNovels( async popularNovels(page: number): Promise<Plugin.NovelItem[]> {
page: number, const link = `${this.site}/novels/page/${page}/`;
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
let link = `${this.site}/novels/page/${page}/`;
const body = await fetchApi(link).then(r => r.text()); const body = await fetchApi(link).then(r => r.text());
return this.parseNovels(body); return this.parseNovels(body);
@@ -107,7 +99,7 @@ class RanobesPlugin implements Plugin.PagePlugin {
chapters: [], chapters: [],
totalPages: 1, totalPages: 1,
}; };
let baseUrl = this.site; const baseUrl = this.site;
let isCover = false; let isCover = false;
let isAuthor = false; let isAuthor = false;
let isSummary = false; let isSummary = false;
@@ -115,7 +107,7 @@ class RanobesPlugin implements Plugin.PagePlugin {
let isStatusText = false; let isStatusText = false;
let isGenres = false; let isGenres = false;
let isGenresText = false; let isGenresText = false;
let genreArray: string[] = []; const genreArray: string[] = [];
let novelId = ''; let novelId = '';
const parser = new Parser({ const parser = new Parser({
onopentag(name, attribs) { onopentag(name, attribs) {
@@ -276,7 +268,7 @@ class RanobesPlugin implements Plugin.PagePlugin {
const parser = new Parser({ const parser = new Parser({
onopentag(name, attribs) { onopentag(name, attribs) {
if (isChapter && name === 'div') { if (isChapter && name === 'div') {
let stylediv = attribs['style']; const stylediv = attribs['style'];
if (stylediv) { if (stylediv) {
chapterText += `<div style="${stylediv}">`; chapterText += `<div style="${stylediv}">`;
isStyleText = true; isStyleText = true;
@@ -285,7 +277,7 @@ class RanobesPlugin implements Plugin.PagePlugin {
} }
} }
if (isChapter && name === 'table') { if (isChapter && name === 'table') {
let w = attribs['width']; const w = attribs['width'];
if (w) { if (w) {
chapterText += `<table width="${w}">`; chapterText += `<table width="${w}">`;
} else { } else {
@@ -299,7 +291,7 @@ class RanobesPlugin implements Plugin.PagePlugin {
chapterText += `<tr>`; chapterText += `<tr>`;
} }
if (isChapter && name === 'td') { if (isChapter && name === 'td') {
let w1 = attribs['width']; const w1 = attribs['width'];
if (w1) { if (w1) {
chapterText += `<td width="${w1}">`; chapterText += `<td width="${w1}">`;
} else { } else {
@@ -369,7 +361,7 @@ class RanobesPlugin implements Plugin.PagePlugin {
searchTerm: string, searchTerm: string,
page: number, page: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let link = `${this.site}/search/${searchTerm}/page/${page}`; const link = `${this.site}/search/${searchTerm}/page/${page}`;
const body = await fetchApi(link).then(r => r.text()); const body = await fetchApi(link).then(r => r.text());
return this.parseNovels(body); return this.parseNovels(body);
+2 -5
View File
@@ -1,6 +1,6 @@
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { isUrlAbsolute } from '@libs/isAbsoluteUrl'; import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -152,10 +152,7 @@ class ReadLiteNovel implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const url = const url =
this.site + `/search/autocomplete?dataType=json&query=${searchTerm}`; this.site + `/search/autocomplete?dataType=json&query=${searchTerm}`;
const result = await fetchApi(url); const result = await fetchApi(url);
+27 -33
View File
@@ -1,12 +1,10 @@
import { fetchFile, fetchText } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs'; import { Filters } from '@libs/filterInputs';
import { load as loadCheerio, CheerioAPI } from 'cheerio'; import { load as loadCheerio } from 'cheerio';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus';
import { NovelItem } from '../../test_web/static/js'; import { NovelItem } from '../../test_web/static/js';
// import { isUrlAbsolute } from "@libs/isAbsoluteUrl"; // import { isUrlAbsolute } from "@libs/isAbsoluteUrl";
// import { parseMadaraDate } from "@libs/parseMadaraDate";
interface FuzzySearchOptions { interface FuzzySearchOptions {
caseSensitive: boolean; caseSensitive: boolean;
@@ -69,8 +67,8 @@ class FuzzySearch<Item> {
} }
const results: { item: Item; score: number }[] = []; const results: { item: Item; score: number }[] = [];
for (let item of this.haystack) { for (const item of this.haystack) {
for (let value of this.getItems(item)) { for (const value of this.getItems(item)) {
const score = this.isMatch(value, query); const score = this.isMatch(value, query);
if (score !== undefined) { if (score !== undefined) {
results.push({ item, score }); results.push({ item, score });
@@ -112,11 +110,11 @@ class FuzzySearch<Item> {
nearestIndexesFor(item: string, query: string): number[] | undefined { nearestIndexesFor(item: string, query: string): number[] | undefined {
const letters = query.split(''); const letters = query.split('');
let indexes: number[][] = []; const indexes: number[][] = [];
const idxFL = this.idxFirstLetter(item, query); const idxFL = this.idxFirstLetter(item, query);
for (let idx of idxFL) { for (const idx of idxFL) {
indexes.push([idx]); indexes.push([idx]);
for (let i = 1; i < letters.length; i++) { for (let i = 1; i < letters.length; i++) {
const letter = letters[i]; const letter = letters[i];
@@ -139,8 +137,8 @@ class FuzzySearch<Item> {
return a[0] - b[0]; return a[0] - b[0];
} }
let res_a = a[a.length - 1] - a[0]; const res_a = a[a.length - 1] - a[0];
let res_b = b[b.length - 1] - b[0]; const res_b = b[b.length - 1] - b[0];
return res_a - res_b; return res_a - res_b;
})[0]; })[0];
@@ -167,21 +165,20 @@ class ReLibraryPlugin implements Plugin.PluginBase {
icon = 'src/en/relibrary/icon.png'; icon = 'src/en/relibrary/icon.png';
site = 'https://re-library.com'; site = 'https://re-library.com';
version = '1.0.0'; version = '1.0.0';
filters: Filters | undefined = undefined;
private searchFunc: FuzzySearch<Plugin.NovelItem> = new FuzzySearch( private searchFunc = new FuzzySearch<Plugin.NovelItem>(item => [item.name], {
item => [item.name], sort: true,
{ sort: true, caseSensitive: false }, caseSensitive: false,
); });
private async popularNovelsInner(url: string): Promise<Plugin.NovelItem[]> { private async popularNovelsInner(url: string): Promise<Plugin.NovelItem[]> {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
const result = await fetch(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
const loadedCheerio = loadCheerio(body); const loadedCheerio = loadCheerio(body);
loadedCheerio('.entry-content > ol > li').each((_i, el) => { loadedCheerio('.entry-content > ol > li').each((_i, el) => {
let novel: Partial<NovelItem> = {}; const novel: Partial<NovelItem> = {};
novel.name = loadedCheerio(el).find('h3 > a').text(); novel.name = loadedCheerio(el).find('h3 > a').text();
novel.path = loadedCheerio(el) novel.path = loadedCheerio(el)
.find('table > tbody > tr > td > a') .find('table > tbody > tr > td > a')
@@ -201,12 +198,12 @@ class ReLibraryPlugin implements Plugin.PluginBase {
private async lastestNovelsInner(url: string): Promise<Plugin.NovelItem[]> { private async lastestNovelsInner(url: string): Promise<Plugin.NovelItem[]> {
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
const result = await fetch(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
const loadedCheerio = loadCheerio(body); const loadedCheerio = loadCheerio(body);
loadedCheerio('article.type-page.page').each((_i, el) => { loadedCheerio('article.type-page.page').each((_i, el) => {
let novel: Partial<Plugin.NovelItem> = {}; const novel: Partial<Plugin.NovelItem> = {};
novel.name = loadedCheerio(el).find('.entry-title').text(); novel.name = loadedCheerio(el).find('.entry-title').text();
novel.path = loadedCheerio(el).find('.entry-title a').attr('href'); novel.path = loadedCheerio(el).find('.entry-title a').attr('href');
if (novel.path === undefined || novel.name === undefined) return; if (novel.path === undefined || novel.name === undefined) return;
@@ -224,10 +221,7 @@ class ReLibraryPlugin implements Plugin.PluginBase {
async popularNovels( async popularNovels(
pageNo: number, pageNo: number,
{ { showLatestNovels }: Plugin.PopularNovelsOptions<Filters>,
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
// The most-popular page only has a single page, so we return an empty array in case you ask for the impossible // The most-popular page only has a single page, so we return an empty array in case you ask for the impossible
// the lastest page do have paginated result, so we support that. // the lastest page do have paginated result, so we support that.
@@ -241,7 +235,7 @@ class ReLibraryPlugin implements Plugin.PluginBase {
} }
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
let novel: Partial<Plugin.SourceNovel> = { const novel: Partial<Plugin.SourceNovel> = {
path: novelPath, path: novelPath,
}; };
// title: .entry-content > header.entry-header > .entry-title // title: .entry-content > header.entry-header > .entry-title
@@ -250,7 +244,7 @@ class ReLibraryPlugin implements Plugin.PluginBase {
// synopis: .entry-content > div.su-box > div.su-box-content // synopis: .entry-content > div.su-box > div.su-box-content
// chapters: .entry-content > div.su-accordion <then> li.page_item[] // chapters: .entry-content > div.su-accordion <then> li.page_item[]
const result = await fetch(`${this.site}/${novelPath}`); const result = await fetchApi(`${this.site}/${novelPath}`);
const body = await result.text(); const body = await result.text();
const loadedCheerio = loadCheerio(body); const loadedCheerio = loadCheerio(body);
@@ -271,7 +265,7 @@ class ReLibraryPlugin implements Plugin.PluginBase {
// Genres in comma separated "list" // Genres in comma separated "list"
novel.genres = (() => { novel.genres = (() => {
let genres: string[] = []; const genres: string[] = [];
loadedCheerio( loadedCheerio(
'.entry-content > table > tbody > tr > td > p > span > a', '.entry-content > table > tbody > tr > td > p > span > a',
).each((_i, el) => { ).each((_i, el) => {
@@ -303,7 +297,7 @@ class ReLibraryPlugin implements Plugin.PluginBase {
'.entry-content > div.su-box > div.su-box-content', '.entry-content > div.su-box > div.su-box-content',
).text(); ).text();
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
let chapter_idx = 0; let chapter_idx = 0;
loadedCheerio('.entry-content > div.su-accordion').each((_i1, el) => { loadedCheerio('.entry-content > div.su-accordion').each((_i1, el) => {
@@ -336,16 +330,16 @@ class ReLibraryPlugin implements Plugin.PluginBase {
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
// parse chapter text here // parse chapter text here
const result = await fetch(`${this.site}/${chapterPath}`); const result = await fetchApi(`${this.site}/${chapterPath}`);
const body = await result.text(); const body = await result.text();
const loadedCheerio = loadCheerio(body); const loadedCheerio = loadCheerio(body);
let text: string[] = []; const text: string[] = [];
loadedCheerio('.entry-content > p') loadedCheerio('.entry-content > p')
.slice(1) .slice(1)
.each((_i, el) => { .each((_i, el) => {
loadedCheerio(el).find('span').remove(); loadedCheerio(el).find('span').remove();
let t = loadedCheerio(el).html(); const t = loadedCheerio(el).html();
if (t === undefined) return; if (t === undefined) return;
text.push(`<p>${t}</p>`); text.push(`<p>${t}</p>`);
}); });
@@ -363,14 +357,14 @@ class ReLibraryPlugin implements Plugin.PluginBase {
// We only want to serve a single "page" since we do the search client side. // We only want to serve a single "page" since we do the search client side.
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
const req = await fetch(`${this.site}/translations/`); const req = await fetchApi(`${this.site}/translations/`);
const body = await req.text(); const body = await req.text();
const loadedCheerio = loadCheerio(body); const loadedCheerio = loadCheerio(body);
loadedCheerio('.entry-content table a').each((_i, el) => { loadedCheerio('.entry-content table a').each((_i, el) => {
let e = loadedCheerio(el); const e = loadedCheerio(el);
if (e && e.attr('href') && e.text()) { if (e && e.attr('href') && e.text()) {
let path = e.attr('href')!; let path = e.attr('href')!;
if (path.startsWith(this.site)) { if (path.startsWith(this.site)) {
+5 -5
View File
@@ -1,5 +1,5 @@
import { Parser } from 'htmlparser2'; import { Parser } from 'htmlparser2';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -86,7 +86,7 @@ class RoyalRoad implements Plugin.PluginBase {
let isSpan = 0; let isSpan = 0;
let isTags = false; let isTags = false;
let isGenres = false; let isGenres = false;
let genreArray: string[] = []; const genreArray: string[] = [];
let isFooter = false; let isFooter = false;
let isScript = false; let isScript = false;
let chapterJson: ChapterEntry[] = []; let chapterJson: ChapterEntry[] = [];
@@ -226,7 +226,7 @@ class RoyalRoad implements Plugin.PluginBase {
const parser = new Parser({ const parser = new Parser({
onopentag(name, attribs) { onopentag(name, attribs) {
if (isChapter && name === 'div') { if (isChapter && name === 'div') {
let stylediv = attribs['style']; const stylediv = attribs['style'];
if (stylediv) { if (stylediv) {
chapterText += `<div style="${stylediv}">`; chapterText += `<div style="${stylediv}">`;
isStyleText = true; isStyleText = true;
@@ -235,7 +235,7 @@ class RoyalRoad implements Plugin.PluginBase {
} }
} }
if (isChapter && name === 'table') { if (isChapter && name === 'table') {
let w = attribs['width']; const w = attribs['width'];
if (w) { if (w) {
chapterText += `<table width="${w}">`; chapterText += `<table width="${w}">`;
} else { } else {
@@ -249,7 +249,7 @@ class RoyalRoad implements Plugin.PluginBase {
chapterText += `<tr>`; chapterText += `<tr>`;
} }
if (isChapter && name === 'td') { if (isChapter && name === 'td') {
let w1 = attribs['width']; const w1 = attribs['width'];
if (w1) { if (w1) {
chapterText += `<td width="${w1}">`; chapterText += `<td width="${w1}">`;
} else { } else {
+2 -5
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -173,10 +173,7 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string,
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const url = `${this.site}?s=${searchTerm}&post_type=fictionposts`; const url = `${this.site}?s=${searchTerm}&post_type=fictionposts`;
const result = await fetchApi(url); const result = await fetchApi(url);
const body = await result.text(); const body = await result.text();
+3 -3
View File
@@ -1,5 +1,5 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
@@ -25,7 +25,7 @@ class WTRLAB implements Plugin.PluginBase {
link += `&page=${page}`; //TODO Genre & Advance Searching Filter. Ez to implement, too much manual work, too lazy. link += `&page=${page}`; //TODO Genre & Advance Searching Filter. Ez to implement, too much manual work, too lazy.
if (showLatestNovels) { if (showLatestNovels) {
const response = await fetch('https://wtr-lab.com/api/home/recent', { const response = await fetchApi(this.site + 'api/home/recent', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -148,7 +148,7 @@ class WTRLAB implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const response = await fetch(this.site + 'api/search', { const response = await fetchApi(this.site + 'api/search', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Referer: this.site + this.sourceLang, Referer: this.site + this.sourceLang,
+8 -15
View File
@@ -1,5 +1,5 @@
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile, fetchProto } from '@libs/fetch'; import { fetchApi, fetchProto } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs'; import { Filters } from '@libs/filterInputs';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -105,6 +105,7 @@ class WuxiaWorld implements Plugin.PluginBase {
name = 'Wuxia World'; name = 'Wuxia World';
icon = 'src/en/wuxiaworld/icon.png'; icon = 'src/en/wuxiaworld/icon.png';
site = 'https://www.wuxiaworld.com/'; site = 'https://www.wuxiaworld.com/';
apiSite = 'https://api2.wuxiaworld.com/wuxiaworld.api.v2.';
filters?: Filters | undefined; filters?: Filters | undefined;
version = '0.5.0'; version = '0.5.0';
@@ -126,10 +127,7 @@ class WuxiaWorld implements Plugin.PluginBase {
return novels; return novels;
} }
async popularNovels( async popularNovels(): Promise<Plugin.NovelItem[]> {
pageNo: number,
options: Plugin.PopularNovelsOptions<Filters>,
): Promise<Plugin.NovelItem[]> {
const link = `${this.site}api/novels`; const link = `${this.site}api/novels`;
const result = await fetchApi(link); const result = await fetchApi(link);
@@ -146,7 +144,7 @@ class WuxiaWorld implements Plugin.PluginBase {
responseType: 'GetNovelResponse', responseType: 'GetNovelResponse',
requestData: { slug: novelPath.split('/')[1] }, requestData: { slug: novelPath.split('/')[1] },
}, },
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Novels/GetNovel', this.apiSite + 'Novels/GetNovel',
{ {
headers: { headers: {
'Content-Type': 'application/grpc-web+proto', 'Content-Type': 'application/grpc-web+proto',
@@ -201,7 +199,7 @@ class WuxiaWorld implements Plugin.PluginBase {
responseType: 'GetChapterListResponse', responseType: 'GetChapterListResponse',
requestData: { novelId: data.item?.id }, requestData: { novelId: data.item?.id },
}, },
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapterList', this.apiSite + 'Chapters/GetChapterList',
{ {
headers: { headers: {
'Content-Type': 'application/grpc-web+proto', 'Content-Type': 'application/grpc-web+proto',
@@ -256,7 +254,7 @@ class WuxiaWorld implements Plugin.PluginBase {
}, },
}, },
}, },
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapter', this.apiSite + 'Chapters/GetChapter',
{ {
headers: { headers: {
'Content-Type': 'application/grpc-web+proto', 'Content-Type': 'application/grpc-web+proto',
@@ -271,13 +269,8 @@ class WuxiaWorld implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
async searchNovels( async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
searchTerm: string, const url = this.site + 'api/novels/search?query=' + searchTerm;
pageNo: number,
): Promise<Plugin.NovelItem[]> {
const searchUrl = 'https://www.wuxiaworld.com/api/novels/search?query=';
const url = searchUrl + searchTerm;
const result = await fetchApi(url); const result = await fetchApi(url);
const data = await result.json(); const data = await result.json();
+6 -6
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -27,7 +27,7 @@ class ChireadsPlugin implements Plugin.PluginBase {
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions, { filters, showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let url = this.site; let url = this.site;
let tag: string = 'all'; let tag = 'all';
if (showLatestNovels) url += '/category/translatedtales/page/' + pageNo; if (showLatestNovels) url += '/category/translatedtales/page/' + pageNo;
else { else {
if ( if (
@@ -41,7 +41,7 @@ class ChireadsPlugin implements Plugin.PluginBase {
} }
let $ = await this.getCheerio(url); let $ = await this.getCheerio(url);
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let novel: Plugin.NovelItem; let novel: Plugin.NovelItem;
if (showLatestNovels || tag !== 'all') { if (showLatestNovels || tag !== 'all') {
@@ -133,7 +133,7 @@ class ChireadsPlugin implements Plugin.PluginBase {
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const novel: Plugin.SourceNovel = { path: novelPath, name: 'Sans titre' }; const novel: Plugin.SourceNovel = { path: novelPath, name: 'Sans titre' };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = novel.name =
$('.inform-product-txt').first().text().trim() || $('.inform-product-txt').first().text().trim() ||
@@ -146,7 +146,7 @@ class ChireadsPlugin implements Plugin.PluginBase {
$('.inform-inform-txt').text().trim() || $('.inform-inform-txt').text().trim() ||
$('.inform-intr-txt').text().trim(); $('.inform-intr-txt').text().trim();
let infos = const infos =
$('div.inform-product-txt > div.inform-intr-col').text().trim() || $('div.inform-product-txt > div.inform-intr-col').text().trim() ||
$('div.inform-inform-data > h6').text().trim(); $('div.inform-inform-data > h6').text().trim();
if (infos.includes('Auteur : ')) if (infos.includes('Auteur : '))
@@ -178,7 +178,7 @@ class ChireadsPlugin implements Plugin.PluginBase {
break; break;
} }
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
let chapterList = $('.chapitre-table a'); let chapterList = $('.chapitre-table a');
if (!chapterList.length) { if (!chapterList.length) {
+15 -27
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -12,7 +11,6 @@ class HarkenEliwoodPlugin implements Plugin.PluginBase {
icon = 'src/fr/harkeneliwood/icon.png'; icon = 'src/fr/harkeneliwood/icon.png';
site = 'https://harkeneliwood.wordpress.com'; site = 'https://harkeneliwood.wordpress.com';
version = '1.0.0'; version = '1.0.0';
filters: Filters | undefined = undefined;
async getCheerio(url: string): Promise<CheerioAPI> { async getCheerio(url: string): Promise<CheerioAPI> {
const r = await fetchApi(url, { const r = await fetchApi(url, {
@@ -23,19 +21,13 @@ class HarkenEliwoodPlugin implements Plugin.PluginBase {
return $; return $;
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
if (pageNo > 1) return []; if (pageNo > 1) return [];
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let novel: Plugin.NovelItem; let novel: Plugin.NovelItem;
let url = this.site; const url = this.site;
let $ = await this.getCheerio(url + '/projets/'); const $ = await this.getCheerio(url + '/projets/');
$('#content .entry-content [href]') $('#content .entry-content [href]')
// We don't collect items for Facebook and Twitter. // We don't collect items for Facebook and Twitter.
.not('[rel="nofollow noopener noreferrer"]') .not('[rel="nofollow noopener noreferrer"]')
@@ -60,14 +52,14 @@ class HarkenEliwoodPlugin implements Plugin.PluginBase {
name: 'Sans titre', name: 'Sans titre',
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = $('#content h1.entry-title').text().trim(); novel.name = $('#content h1.entry-title').text().trim();
novel.cover = novel.cover =
$('#content .entry-content p img').first().attr('src') || defaultCover; $('#content .entry-content p img').first().attr('src') || defaultCover;
novel.summary = this.getSummary($('#content .entry-content').text()); novel.summary = this.getSummary($('#content .entry-content').text());
novel.author = this.getAuthor($('#content .entry-content').text()); novel.author = this.getAuthor($('#content .entry-content').text());
novel.status = NovelStatus.Ongoing; novel.status = NovelStatus.Ongoing;
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
$('#content .entry-content p a').each((i, elem) => { $('#content .entry-content p a').each((i, elem) => {
const chapterName = $(elem).text().trim(); const chapterName = $(elem).text().trim();
const chapterUrl = $(elem).attr('href'); const chapterUrl = $(elem).attr('href');
@@ -88,14 +80,13 @@ class HarkenEliwoodPlugin implements Plugin.PluginBase {
} }
getSummary(text: string) { getSummary(text: string) {
let resume: string = ''; let resume = '';
const regexResume1: RegExp = /Synopsis :([\s\S]*)Traduction anglaise/i; const regexResume1 = /Synopsis :([\s\S]*)Traduction anglaise/i;
const regexResume2: RegExp = /Synopsis :([\s\S]*)Raw :/i; const regexResume2 = /Synopsis :([\s\S]*)Raw :/i;
const regexResume3: RegExp = const regexResume3 = /Synopsis 1 :([\s\S]*)Synopsis 2 :([\s\S]*)Raw :/i;
/Synopsis 1 :([\s\S]*)Synopsis 2 :([\s\S]*)Raw :/i; const regexResume4 = /Synopsis :([\s\S]*)Prélude/i;
const regexResume4: RegExp = /Synopsis :([\s\S]*)Prélude/i; const regexResume5 = /Synospis :([\s\S]*)Original /i;
const regexResume5: RegExp = /Synospis :([\s\S]*)Original /i; const regexResume6 = /([\s\S]*)Raw :/i;
const regexResume6: RegExp = /([\s\S]*)Raw :/i;
const match1: RegExpExecArray | null = regexResume1.exec(text); const match1: RegExpExecArray | null = regexResume1.exec(text);
const match2: RegExpExecArray | null = regexResume2.exec(text); const match2: RegExpExecArray | null = regexResume2.exec(text);
@@ -151,12 +142,9 @@ class HarkenEliwoodPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let popularNovels = this.popularNovels(1, { const popularNovels = this.popularNovels(1);
showLatestNovels: true,
filters: undefined,
});
let novels = (await popularNovels).filter(novel => const novels = (await popularNovels).filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+8 -19
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -11,7 +10,6 @@ class KissWoodPlugin implements Plugin.PluginBase {
icon = 'src/fr/kisswood/icon.png'; icon = 'src/fr/kisswood/icon.png';
site = 'https://kisswood.eu'; site = 'https://kisswood.eu';
version = '1.0.0'; version = '1.0.0';
filters: Filters | undefined = undefined;
async getCheerio(url: string): Promise<CheerioAPI> { async getCheerio(url: string): Promise<CheerioAPI> {
const r = await fetchApi(url); const r = await fetchApi(url);
@@ -41,7 +39,7 @@ class KissWoodPlugin implements Plugin.PluginBase {
novel: Plugin.SourceNovel, novel: Plugin.SourceNovel,
url: string, url: string,
): Promise<Plugin.SourceNovel> { ): Promise<Plugin.SourceNovel> {
let $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
const textArray: string[] = $('.entry-content p') const textArray: string[] = $('.entry-content p')
.map((_, element) => $(element).text().trim()) .map((_, element) => $(element).text().trim())
@@ -88,18 +86,12 @@ class KissWoodPlugin implements Plugin.PluginBase {
return ''; return '';
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
if (pageNo > 1) return []; if (pageNo > 1) return [];
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
const $ = await this.getCheerio(this.site); const $ = await this.getCheerio(this.site);
var listUrlCover: string[] = []; const listUrlCover: string[] = [];
$('nav div div ul li ul li').each((i, elem) => { $('nav div div ul li ul li').each((i, elem) => {
if ($(elem).text().trim() === 'Sommaire') { if ($(elem).text().trim() === 'Sommaire') {
const novelName = $(elem) const novelName = $(elem)
@@ -137,7 +129,7 @@ class KissWoodPlugin implements Plugin.PluginBase {
status: NovelStatus.Ongoing, status: NovelStatus.Ongoing,
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
let novelUrl = null; let novelUrl = null;
$('nav div div ul li ul li').each((i, elem) => { $('nav div div ul li ul li').each((i, elem) => {
@@ -160,7 +152,7 @@ class KissWoodPlugin implements Plugin.PluginBase {
'.entry-content blockquote a', '.entry-content blockquote a',
].join(', '); ].join(', ');
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
$(chapterSelectors).each((i, elem) => { $(chapterSelectors).each((i, elem) => {
const chapterName = $(elem).text().trim(); const chapterName = $(elem).text().trim();
const chapterUrl = $(elem).attr('href')?.replace('http://', 'https://'); const chapterUrl = $(elem).attr('href')?.replace('http://', 'https://');
@@ -224,12 +216,9 @@ class KissWoodPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let popularNovels = this.popularNovels(1, { const popularNovels = this.popularNovels(1);
showLatestNovels: true,
filters: undefined,
});
let novels = (await popularNovels).filter(novel => const novels = (await popularNovels).filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+16 -24
View File
@@ -1,5 +1,5 @@
import { load, CheerioAPI } from 'cheerio'; import { load, CheerioAPI } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
@@ -24,7 +24,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
} }
parseDate(date: string): string { parseDate(date: string): string {
const monthMapping: { [key: string]: string } = { const monthMapping: Record<string, string> = {
janvier: 'January', janvier: 'January',
fevrier: 'February', fevrier: 'February',
mars: 'March', mars: 'March',
@@ -49,7 +49,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
$: CheerioAPI, $: CheerioAPI,
showLatestNovels: boolean | undefined, showLatestNovels: boolean | undefined,
): Plugin.NovelItem[] { ): Plugin.NovelItem[] {
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
$('article').each((i, el) => { $('article').each((i, el) => {
const cheerio = $(el); const cheerio = $(el);
@@ -83,7 +83,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
let url = this.site; let url = this.site;
if (showLatestNovels) url += 'chapitre'; if (showLatestNovels) url += 'chapitre';
else { else {
let cat_gen: string = 'all'; let cat_gen = 'all';
if (filters && typeof filters.categorie_genre.value == 'string') if (filters && typeof filters.categorie_genre.value == 'string')
cat_gen = filters.categorie_genre.value; cat_gen = filters.categorie_genre.value;
if ( if (
@@ -98,7 +98,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
else url += 'roman'; else url += 'roman';
} }
url += '/page/' + pageNo; url += '/page/' + pageNo;
let $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
if (!$) return []; if (!$) return [];
return this.parseNovels($, showLatestNovels); return this.parseNovels($, showLatestNovels);
} }
@@ -107,16 +107,16 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
const $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
if (!$) throw new Error('Failed to load page (open in web view)'); if (!$) throw new Error('Failed to load page (open in web view)');
let novel: Plugin.SourceNovel = { path: novelPath, name: 'Untitled' }; const novel: Plugin.SourceNovel = { path: novelPath, name: 'Untitled' };
novel.name = novel.name =
( (
$('div.entry-content > div > strong')[0].nextSibling as Text | null $('div.entry-content > div > strong')[0].nextSibling as string | null
)?.nodeValue?.trim() || 'Untitled'; )?.nodeValue?.trim() || 'Untitled';
novel.cover = $('.su-row > div > div > img').attr('src') || defaultCover; novel.cover = $('.su-row > div > div > img').attr('src') || defaultCover;
let novelInfos = $('div[data-title=Tomes] >').toArray(); const novelInfos = $('div[data-title=Tomes] >').toArray();
novelInfos.pop(); novelInfos.pop();
novelInfos.shift(); novelInfos.shift();
novel.summary = novel.summary =
@@ -149,7 +149,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
if (genres) if (genres)
novel.genres = novel.genres ? novel.genres + ',' + genres : genres; novel.genres = novel.genres ? novel.genres + ',' + genres : genres;
let status = $("strong:contains('Statut :')").parent().attr('class'); const status = $("strong:contains('Statut :')").parent().attr('class');
switch (status) { switch (status) {
case 'type etat0': case 'type etat0':
case 'type etat1': case 'type etat1':
@@ -169,13 +169,13 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
break; break;
} }
let novelChapters: Plugin.ChapterItem[] = []; const novelChapters: Plugin.ChapterItem[] = [];
const volumes = $('div[data-title=Tomes] > div').last().contents(); const volumes = $('div[data-title=Tomes] > div').last().contents();
const hasMultipleVolumes = volumes.length > 1; const hasMultipleVolumes = volumes.length > 1;
let chapterName = ''; let chapterName = '';
let site = this.site; const site = this.site;
volumes.each((volumeIndex: number, el) => { volumes.each((volumeIndex: number, el) => {
if (hasMultipleVolumes) chapterName = 'T.' + (volumeIndex + 1) + ' '; if (hasMultipleVolumes) chapterName = 'T.' + (volumeIndex + 1) + ' ';
$(el) $(el)
@@ -189,20 +189,16 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
const dateHtml = const dateHtml =
cheerio.html()?.substring(cheerio.html()?.indexOf('</a>') || 0) || cheerio.html()?.substring(cheerio.html()?.indexOf('</a>') || 0) ||
''; '';
let releaseDate = const releaseDate =
dateHtml?.substring( dateHtml?.substring(
dateHtml.indexOf('(') + 1, dateHtml.indexOf('(') + 1,
dateHtml.indexOf(')'), dateHtml.indexOf(')'),
) || undefined; ) || undefined;
if (releaseDate)
try {
releaseDate = this.parseDate(releaseDate);
} catch (e) {}
const chapterUrl = cheerio.find('a').attr('href'); const chapterUrl = cheerio.find('a').attr('href');
if (chapterUrl) { if (chapterUrl) {
const chapter: Plugin.ChapterItem = { const chapter: Plugin.ChapterItem = {
name: newChapterName, name: newChapterName,
releaseTime: releaseDate, releaseTime: this.parseDate(releaseDate),
path: chapterUrl.replace(site, ''), path: chapterUrl.replace(site, ''),
chapterNumber: chapterIndex, chapterNumber: chapterIndex,
}; };
@@ -232,13 +228,9 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
}); });
if (dates.length == hrefs.length) if (dates.length == hrefs.length)
dates.forEach((date, index) => { dates.forEach((date, index) => {
let releaseDate = date;
try {
releaseDate = this.parseDate(date);
} catch (e) {}
const chapter: Plugin.ChapterItem = { const chapter: Plugin.ChapterItem = {
name: newChapterName + ' (' + (index + 1) + ')', name: newChapterName + ' (' + (index + 1) + ')',
releaseTime: releaseDate, releaseTime: this.parseDate(date),
path: hrefs[index].replace(site, ''), path: hrefs[index].replace(site, ''),
chapterNumber: chapterIndex + (index + 1) / 1000, chapterNumber: chapterIndex + (index + 1) / 1000,
}; };
@@ -258,7 +250,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
if (!$) throw new Error('Failed to load page (open in web view)'); if (!$) throw new Error('Failed to load page (open in web view)');
$('.mistape_caption').remove(); $('.mistape_caption').remove();
let chapterText = const chapterText =
$('.chapter-content').html() || $('.entry-content').html() || ''; $('.chapter-content').html() || $('.entry-content').html() || '';
return chapterText; return chapterText;
} }
@@ -268,7 +260,7 @@ class NovelDeGlacePlugin implements Plugin.PluginBase {
num: number, num: number,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (num !== 1) return []; // only 1 page of results if (num !== 1) return []; // only 1 page of results
let url = this.site + 'roman'; const url = this.site + 'roman';
const $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
if (!$) throw new Error('Failed to load page (open in web view)'); if (!$) throw new Error('Failed to load page (open in web view)');
+8 -19
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -11,7 +10,6 @@ class NovhellPlugin implements Plugin.PluginBase {
icon = 'src/fr/novhell/icon.png'; icon = 'src/fr/novhell/icon.png';
site = 'https://novhell.org'; site = 'https://novhell.org';
version = '1.0.1'; version = '1.0.1';
filters: Filters | undefined = undefined;
async getCheerio(url: string): Promise<CheerioAPI> { async getCheerio(url: string): Promise<CheerioAPI> {
const r = await fetchApi(url, { const r = await fetchApi(url, {
@@ -22,19 +20,13 @@ class NovhellPlugin implements Plugin.PluginBase {
return $; return $;
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
if (pageNo > 1) return []; if (pageNo > 1) return [];
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let novel: Plugin.NovelItem; let novel: Plugin.NovelItem;
let url = this.site; const url = this.site;
let $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
$('article div div div figure').each((i, elem) => { $('article div div div figure').each((i, elem) => {
let novelName = $(elem) let novelName = $(elem)
.find('figcaption span strong') .find('figcaption span strong')
@@ -65,7 +57,7 @@ class NovhellPlugin implements Plugin.PluginBase {
name: 'Sans titre', name: 'Sans titre',
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = novel.name =
$('meta[property="og:title"]') $('meta[property="og:title"]')
@@ -114,7 +106,7 @@ class NovhellPlugin implements Plugin.PluginBase {
.replace('Synopsis', '') .replace('Synopsis', '')
.replace(':', '') .replace(':', '')
.trim(); .trim();
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
$('main div article div div section div div div div div p a').each( $('main div article div div section div div div div div p a').each(
(i, elem) => { (i, elem) => {
@@ -190,12 +182,9 @@ class NovhellPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let popularNovels = this.popularNovels(1, { const popularNovels = this.popularNovels(1);
showLatestNovels: true,
filters: undefined,
});
let novels = (await popularNovels).filter(novel => const novels = (await popularNovels).filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+7 -7
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
@@ -39,7 +39,7 @@ class PhenixScansTradPlugin implements Plugin.PluginBase {
const order = showLatestNovels ? 'update' : 'popular'; const order = showLatestNovels ? 'update' : 'popular';
const url = `${this.site}/manga/?page=${pageNo}${filter}&status=&type=novel&order=${order}`; const url = `${this.site}/manga/?page=${pageNo}${filter}&status=&type=novel&order=${order}`;
let $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
$('div div div div a').each((i, elem) => { $('div div div div a').each((i, elem) => {
const novelName = $(elem).attr('title')?.trim(); const novelName = $(elem).attr('title')?.trim();
const novelUrl = $(elem).attr('href'); const novelUrl = $(elem).attr('href');
@@ -63,7 +63,7 @@ class PhenixScansTradPlugin implements Plugin.PluginBase {
name: 'Sans titre', name: 'Sans titre',
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = $('h1[itemprop=name]').text().replace('– Novel', '').trim(); novel.name = $('h1[itemprop=name]').text().replace('– Novel', '').trim();
novel.cover = novel.cover =
@@ -80,7 +80,7 @@ class PhenixScansTradPlugin implements Plugin.PluginBase {
$('.tsinfo .imptdt:contains(Statut)').text().replace('Statut', '').trim(), $('.tsinfo .imptdt:contains(Statut)').text().replace('Statut', '').trim(),
); );
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
$('ul li:has(div.chbox):has(div.eph-num)').each((i, elem) => { $('ul li:has(div.chbox):has(div.eph-num)').each((i, elem) => {
const chapterName = $(elem).find('a .chapternum').text().trim(); const chapterName = $(elem).find('a .chapternum').text().trim();
const chapterUrl = $(elem).find('a').attr('href'); const chapterUrl = $(elem).find('a').attr('href');
@@ -98,7 +98,7 @@ class PhenixScansTradPlugin implements Plugin.PluginBase {
} }
parseDate(date: string): string { parseDate(date: string): string {
const monthMapping: { [key: string]: string } = { const monthMapping: Record<string, string> = {
janvier: 'January', janvier: 'January',
fevrier: 'February', fevrier: 'February',
mars: 'March', mars: 'March',
@@ -149,12 +149,12 @@ class PhenixScansTradPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let popularNovels = this.popularNovels(1, { const popularNovels = this.popularNovels(1, {
showLatestNovels: true, showLatestNovels: true,
filters: undefined, filters: undefined,
}); });
let novels = (await popularNovels).filter(novel => const novels = (await popularNovels).filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+8 -19
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -12,7 +11,6 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
icon = 'src/fr/warriorlegendtrad/icon.png'; icon = 'src/fr/warriorlegendtrad/icon.png';
site = 'https://warriorlegendtrad.fr'; site = 'https://warriorlegendtrad.fr';
version = '1.0.0'; version = '1.0.0';
filters: Filters | undefined = undefined;
regexAuthors = [/Auteur\u00A0:([^\n]*)/]; regexAuthors = [/Auteur\u00A0:([^\n]*)/];
@@ -27,13 +25,7 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
return $; return $;
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
if (pageNo > 2) return []; if (pageNo > 2) return [];
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -48,7 +40,7 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
url = this.site + '/crea'; url = this.site + '/crea';
} }
let $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
$('div div div article').each((i, elem) => { $('div div div article').each((i, elem) => {
const novelName = $(elem).find('.entry-wrapper h2').first().text().trim(); const novelName = $(elem).find('.entry-wrapper h2').first().text().trim();
const novelUrl = $(elem).find('.entry-wrapper h2 a').attr('href'); const novelUrl = $(elem).find('.entry-wrapper h2 a').attr('href');
@@ -73,7 +65,7 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
name: 'Sans titre', name: 'Sans titre',
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = $('.site-main article header h1').text().trim(); novel.name = $('.site-main article header h1').text().trim();
novel.cover = novel.cover =
@@ -84,7 +76,7 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
novel.summary = this.extractInfo(entryContentText, this.regexSummary); novel.summary = this.extractInfo(entryContentText, this.regexSummary);
novel.status = this.getStatus(entryContentText); novel.status = this.getStatus(entryContentText);
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
$('div div article div') $('div div article div')
.find('h2 a, h3 a') .find('h2 a, h3 a')
.each((i, elem) => { .each((i, elem) => {
@@ -150,7 +142,7 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const $ = await this.getCheerio(this.site + chapterPath); const $ = await this.getCheerio(this.site + chapterPath);
var contenuHtml = ''; let contenuHtml = '';
$('.entry-content') $('.entry-content')
.contents() .contents()
.each(function () { .each(function () {
@@ -171,12 +163,9 @@ class WarriorLegendTradPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let popularNovels = this.popularNovels(1, { const popularNovels = this.popularNovels(1);
showLatestNovels: true,
filters: undefined,
});
let novels = (await popularNovels).filter(novel => const novels = (await popularNovels).filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+10 -21
View File
@@ -1,7 +1,6 @@
import { CheerioAPI, load } from 'cheerio'; import { CheerioAPI, load } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -12,7 +11,6 @@ class WuxialnscantradPlugin implements Plugin.PluginBase {
icon = 'src/fr/wuxialnscantrad/icon.png'; icon = 'src/fr/wuxialnscantrad/icon.png';
site = 'https://wuxialnscantrad.wordpress.com'; site = 'https://wuxialnscantrad.wordpress.com';
version = '1.0.0'; version = '1.0.0';
filters: Filters | undefined = undefined;
async getCheerio(url: string): Promise<CheerioAPI> { async getCheerio(url: string): Promise<CheerioAPI> {
const r = await fetchApi(url, { const r = await fetchApi(url, {
@@ -23,19 +21,13 @@ class WuxialnscantradPlugin implements Plugin.PluginBase {
return $; return $;
} }
async popularNovels( async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
pageNo: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
if (pageNo > 1) return []; if (pageNo > 1) return [];
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let novel: Plugin.NovelItem; let novel: Plugin.NovelItem;
let url = this.site; const url = this.site;
let $ = await this.getCheerio(url); const $ = await this.getCheerio(url);
$('#menu-item-2210 ul li').each((i, elem) => { $('#menu-item-2210 ul li').each((i, elem) => {
const novelName = $(elem).first().text().trim(); const novelName = $(elem).first().text().trim();
const novelUrl = $(elem).find('a').attr('href'); const novelUrl = $(elem).find('a').attr('href');
@@ -58,7 +50,7 @@ class WuxialnscantradPlugin implements Plugin.PluginBase {
name: 'Sans titre', name: 'Sans titre',
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = $('.entry-title').text().trim(); novel.name = $('.entry-title').text().trim();
novel.cover = novel.cover =
@@ -72,8 +64,8 @@ class WuxialnscantradPlugin implements Plugin.PluginBase {
novel.summary = this.getSummary(entryContentText); novel.summary = this.getSummary(entryContentText);
novel.status = this.getStatus(entryContentText); novel.status = this.getStatus(entryContentText);
var pathChapter = $('.entry-content ul').first().children('li'); const pathChapter = $('.entry-content ul').first().children('li');
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
pathChapter.each((i, elem) => { pathChapter.each((i, elem) => {
const chapterName = $(elem).text().trim(); const chapterName = $(elem).text().trim();
const chapterUrl = $(elem).find('a').attr('href'); const chapterUrl = $(elem).find('a').attr('href');
@@ -164,7 +156,7 @@ class WuxialnscantradPlugin implements Plugin.PluginBase {
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const $ = await this.getCheerio(this.site + chapterPath); const $ = await this.getCheerio(this.site + chapterPath);
var contenuHtml = ''; let contenuHtml = '';
$('.entry-content') $('.entry-content')
.contents() .contents()
.each(function () { .each(function () {
@@ -189,12 +181,9 @@ class WuxialnscantradPlugin implements Plugin.PluginBase {
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
let popularNovels = this.popularNovels(1, { const popularNovels = this.popularNovels(1);
showLatestNovels: true,
filters: undefined,
});
let novels = (await popularNovels).filter(novel => const novels = (await popularNovels).filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+9 -11
View File
@@ -1,7 +1,6 @@
import { Cheerio, CheerioAPI, load, Element } from 'cheerio'; import { Cheerio, CheerioAPI, load, Element } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
@@ -11,7 +10,6 @@ class XiaowazPlugin implements Plugin.PluginBase {
icon = 'src/fr/xiaowaz/icon.png'; icon = 'src/fr/xiaowaz/icon.png';
site = 'https://xiaowaz.fr'; site = 'https://xiaowaz.fr';
version = '1.0.1'; version = '1.0.1';
filters: Filters | undefined = undefined;
static novels: Plugin.NovelItem[] | undefined; static novels: Plugin.NovelItem[] | undefined;
async getCheerio(url: string): Promise<CheerioAPI> { async getCheerio(url: string): Promise<CheerioAPI> {
@@ -35,9 +33,9 @@ class XiaowazPlugin implements Plugin.PluginBase {
} }
async getAllNovels(): Promise<Plugin.NovelItem[]> { async getAllNovels(): Promise<Plugin.NovelItem[]> {
let novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
let novel: Plugin.NovelItem; let novel: Plugin.NovelItem;
let $ = await this.getCheerio(this.site); const $ = await this.getCheerio(this.site);
const categories: Cheerio<Element>[] = [ const categories: Cheerio<Element>[] = [
$('li.page_item').find('a:contains("Séries")').parent(), $('li.page_item').find('a:contains("Séries")').parent(),
@@ -78,7 +76,7 @@ class XiaowazPlugin implements Plugin.PluginBase {
return novels; return novels;
} }
async popularNovels(pageNo: number, {}: any): Promise<Plugin.NovelItem[]> { async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
const PAGE_SIZE = 5; const PAGE_SIZE = 5;
if (!XiaowazPlugin.novels) XiaowazPlugin.novels = await this.getAllNovels(); if (!XiaowazPlugin.novels) XiaowazPlugin.novels = await this.getAllNovels();
@@ -99,7 +97,7 @@ class XiaowazPlugin implements Plugin.PluginBase {
name: 'Sans titre', name: 'Sans titre',
}; };
let $ = await this.getCheerio(this.site + novelPath); const $ = await this.getCheerio(this.site + novelPath);
novel.name = $('.card_title').text().trim(); novel.name = $('.card_title').text().trim();
novel.cover = novel.cover =
@@ -164,12 +162,12 @@ class XiaowazPlugin implements Plugin.PluginBase {
//Search for chapter links within ul/li tags; otherwise, within p/a tags, //Search for chapter links within ul/li tags; otherwise, within p/a tags,
//but not both at the same time because otherwise, on the TDG page, it redirects to a PDF download link. //but not both at the same time because otherwise, on the TDG page, it redirects to a PDF download link.
var pathChapter = $('.entry-content ul li a'); let pathChapter = $('.entry-content ul li a');
if (pathChapter.length === 0) { if (pathChapter.length === 0) {
pathChapter = $('.entry-content p a'); pathChapter = $('.entry-content p a');
} }
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
pathChapter.each((i, elem) => { pathChapter.each((i, elem) => {
const chapterName = $(elem).text().trim(); const chapterName = $(elem).text().trim();
const chapterUrl = $(elem).attr('href'); const chapterUrl = $(elem).attr('href');
@@ -272,11 +270,11 @@ class XiaowazPlugin implements Plugin.PluginBase {
if (pageNo !== 1) return []; if (pageNo !== 1) return [];
if (!XiaowazPlugin.novels) XiaowazPlugin.novels = await this.getAllNovels(); if (!XiaowazPlugin.novels) XiaowazPlugin.novels = await this.getAllNovels();
let popularNovels = XiaowazPlugin.novels; const popularNovels = XiaowazPlugin.novels;
// Normalize the text to remove accents and other special characters // Normalize the text to remove accents and other special characters
// This ensures that the search term and novel names are compared accurately // This ensures that the search term and novel names are compared accurately
let novels = popularNovels.filter(novel => const novels = popularNovels.filter(novel =>
novel.name novel.name
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
+5 -5
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
@@ -36,7 +36,7 @@ class IndoWebNovel implements Plugin.PluginBase {
} }
async popularNovels( async popularNovels(
page: number = 1, page = 1,
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>, { filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
/* /*
@@ -50,7 +50,7 @@ class IndoWebNovel implements Plugin.PluginBase {
if (filters.genre.value.length) if (filters.genre.value.length)
link += filters.genre.value.map(i => `&genre[]=${i}`).join(''); link += filters.genre.value.map(i => `&genre[]=${i}`).join('');
*/ */
let link = this.site + `page/${page}/?s`; const link = this.site + `page/${page}/?s`;
const result = await fetchApi(link); const result = await fetchApi(link);
const body = await result.text(); const body = await result.text();
@@ -114,7 +114,7 @@ class IndoWebNovel implements Plugin.PluginBase {
async searchNovels( async searchNovels(
searchTerm: string, searchTerm: string,
page: number = 1, page = 1,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
/* /*
let link = `${this.site}advanced-search/page/${page}/?title=${searchTerm}&author=&yearx=`; let link = `${this.site}advanced-search/page/${page}/?title=${searchTerm}&author=&yearx=`;
@@ -123,7 +123,7 @@ class IndoWebNovel implements Plugin.PluginBase {
link += `&order=${this.filters.sort.value}`; link += `&order=${this.filters.sort.value}`;
link += this.filters.lang.value.map(i => `&country[]=${i}`).join(''); link += this.filters.lang.value.map(i => `&country[]=${i}`).join('');
*/ */
let link = this.site + `page/${page}/?s=${searchTerm}`; const link = this.site + `page/${page}/?s=${searchTerm}`;
const result = await fetchApi(link); const result = await fetchApi(link);
const body = await result.text(); const body = await result.text();
+3 -3
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
@@ -80,8 +80,8 @@ class NovelRingan implements Plugin.PluginBase {
loadedCheerio('img.ts-post-image').attr('data-sxrx'); loadedCheerio('img.ts-post-image').attr('data-sxrx');
loadedCheerio('.maininfo li').each(function () { loadedCheerio('.maininfo li').each(function () {
let detailName = loadedCheerio(this).find('b').text().trim(); const detailName = loadedCheerio(this).find('b').text().trim();
let detail = loadedCheerio(this).find('b').remove().end().text().trim(); const detail = loadedCheerio(this).find('b').remove().end().text().trim();
switch (detailName) { switch (detailName) {
case 'Status:': case 'Status:':
+1 -1
View File
@@ -1,5 +1,5 @@
import { CheerioAPI, load as parseHTML } from 'cheerio'; import { CheerioAPI, load as parseHTML } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs'; import { Filters, FilterTypes } from '@libs/filterInputs';
+18 -15
View File
@@ -1,5 +1,5 @@
import { load, load as loadCheerio } from 'cheerio'; import { load as loadCheerio } from 'cheerio';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
@@ -20,7 +20,7 @@ class Syosetu implements Plugin.PluginBase {
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}; };
searchUrl = (pagenum?: number, order?: string) => { searchUrl = (pagenum?: number, order?: string) => {
return `https://yomou.syosetu.com/search.php?order=${order || 'hyoka'}${ return `${this.site}search.php?order=${order || 'hyoka'}${
pagenum !== undefined pagenum !== undefined
? `&p=${pagenum <= 1 || pagenum > 100 ? '1' : pagenum}` // check if pagenum is between 1 and 100 ? `&p=${pagenum <= 1 || pagenum > 100 ? '1' : pagenum}` // check if pagenum is between 1 and 100
: '' // if isn't don't set ?p : '' // if isn't don't set ?p
@@ -71,15 +71,15 @@ class Syosetu implements Plugin.PluginBase {
return novels; return novels;
} }
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
const result = await fetch(this.novelPrefix + novelPath, { const result = await fetchApi(this.novelPrefix + novelPath, {
headers: this.headers, headers: this.headers,
}); });
const body = await result.text(); const body = await result.text();
const loadedCheerio = loadCheerio(body, { decodeEntities: false }); const loadedCheerio = loadCheerio(body, { decodeEntities: false });
// create novel object // create novel object
let novel: Plugin.SourceNovel = { const novel: Plugin.SourceNovel = {
path: novelPath, path: novelPath,
name: loadedCheerio('.novel_title').text(), name: loadedCheerio('.novel_title').text(),
author: loadedCheerio('.novel_writername').text().replace('作者:', ''), author: loadedCheerio('.novel_writername').text().replace('作者:', ''),
@@ -93,7 +93,7 @@ class Syosetu implements Plugin.PluginBase {
novel.summary = loadedCheerio('#novel_ex') novel.summary = loadedCheerio('#novel_ex')
.text() .text()
.replace(/<\s*br.*?>/g, '\n'); .replace(/<\s*br.*?>/g, '\n');
cqGetChapters.each(function (i, e) { cqGetChapters.each(() => {
const chapterA = loadedCheerio(this).find('a'); const chapterA = loadedCheerio(this).find('a');
const [chapterName, releaseDate, chapterUrl] = [ const [chapterName, releaseDate, chapterUrl] = [
// set the variables // set the variables
@@ -121,9 +121,12 @@ class Syosetu implements Plugin.PluginBase {
*/ */
// get summary for oneshot chapter // get summary for oneshot chapter
const nameResult = await fetch(this.searchUrl() + `&word=${novel.name}`, { const nameResult = await fetchApi(
headers: this.headers, this.searchUrl() + `&word=${novel.name}`,
}); {
headers: this.headers,
},
);
const nameBody = await nameResult.text(); const nameBody = await nameResult.text();
const summaryQuery = loadCheerio(nameBody, { const summaryQuery = loadCheerio(nameBody, {
decodeEntities: false, decodeEntities: false,
@@ -148,7 +151,7 @@ class Syosetu implements Plugin.PluginBase {
return novel; return novel;
} }
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const result = await fetch(this.novelPrefix + chapterPath, { const result = await fetchApi(this.novelPrefix + chapterPath, {
headers: this.headers, headers: this.headers,
}); });
const body = await result.text(); const body = await result.text();
@@ -158,7 +161,7 @@ class Syosetu implements Plugin.PluginBase {
decodeEntities: false, decodeEntities: false,
}); });
let chapterText = const chapterText =
cheerioQuery('#novel_honbun') // get chapter text cheerioQuery('#novel_honbun') // get chapter text
.html() || ''; .html() || '';
return chapterText; return chapterText;
@@ -173,12 +176,12 @@ class Syosetu implements Plugin.PluginBase {
const getNovelsFromPage = async (pagenumber: number) => { const getNovelsFromPage = async (pagenumber: number) => {
// load page // load page
const url = this.searchUrl(pagenumber) + `&word=${searchTerm}`; const url = this.searchUrl(pagenumber) + `&word=${searchTerm}`;
const result = await fetch(url, { headers: this.headers }); const result = await fetchApi(url, { headers: this.headers });
const body = await result.text(); const body = await result.text();
// Cheerio it! // Cheerio it!
const cheerioQuery = loadCheerio(body, { decodeEntities: false }); const cheerioQuery = loadCheerio(body, { decodeEntities: false });
let pageNovels: Plugin.NovelItem[] = []; const pageNovels: Plugin.NovelItem[] = [];
// find class=searchkekka_box // find class=searchkekka_box
cheerioQuery('.searchkekka_box').each((i, e) => { cheerioQuery('.searchkekka_box').each((i, e) => {
// get div with link and name // get div with link and name
@@ -218,7 +221,7 @@ class Syosetu implements Plugin.PluginBase {
return novels; return novels;
} }
resolveUrl(path: string, isNovel?: boolean | undefined): string { resolveUrl(path: string): string {
return this.novelPrefix + path; return this.novelPrefix + path;
} }
filters = { filters = {
+4 -4
View File
@@ -1,4 +1,4 @@
import { fetchFile, fetchApi } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import qs from 'qs'; import qs from 'qs';
@@ -7,12 +7,12 @@ class Agitoon implements Plugin.PluginBase {
id = 'agit.xyz'; id = 'agit.xyz';
name = 'Agitoon'; name = 'Agitoon';
icon = 'src/kr/agitoon/icon.png'; icon = 'src/kr/agitoon/icon.png';
site = 'https://agit659.xyz'; site = 'https://agit660.xyz';
version = '3.0.2'; version = '3.0.3';
async popularNovels( async popularNovels(
pageNo: number, pageNo: number,
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions, { showLatestNovels }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const res = await fetchApi(this.site + '/novel/index.update.php', { const res = await fetchApi(this.site + '/novel/index.update.php', {
headers: { headers: {
+7 -7
View File
@@ -1,4 +1,4 @@
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
@@ -100,11 +100,11 @@ class NovelkiPL implements Plugin.PluginBase {
//TODO: Dodać tłumacza danej serii :D //TODO: Dodać tłumacza danej serii :D
let chapters: Plugin.ChapterItem[] = []; const chapters: Plugin.ChapterItem[] = [];
let chaptersList = loadedCheerio('.chapters > .col-md-3 > div').get(); const chaptersList = loadedCheerio('.chapters > .col-md-3 > div').get();
chaptersList.forEach((e, i) => { chaptersList.forEach((e, i) => {
let urlChapters = loadedCheerio(e).find('a').attr('href') || ''; const urlChapters = loadedCheerio(e).find('a').attr('href') || '';
const chapter: Plugin.ChapterItem = { const chapter: Plugin.ChapterItem = {
name: loadedCheerio(e).find('a')?.text().trim(), name: loadedCheerio(e).find('a')?.text().trim(),
@@ -125,8 +125,8 @@ class NovelkiPL implements Plugin.PluginBase {
return novel; return novel;
} }
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
var pattern = /\/projekty\/([^\/]+)\/([^\/]+)/; const pattern = /\/projekty\/([^/]+)\/([^/]+)/;
let codeChapter = pattern.exec(chapterPath) || ''; const codeChapter = pattern.exec(chapterPath) || '';
const body = await fetchApi( const body = await fetchApi(
`${this.site}/api/reader/chapters/${codeChapter[2]}`, `${this.site}/api/reader/chapters/${codeChapter[2]}`,
).then(res => { ).then(res => {
@@ -151,7 +151,7 @@ class NovelkiPL implements Plugin.PluginBase {
}); });
const loadedCheerio = parseHTML(body); const loadedCheerio = parseHTML(body);
let novels: Plugin.NovelItem[] = loadedCheerio('#projects > div') const novels: Plugin.NovelItem[] = loadedCheerio('#projects > div')
.map((index, element) => ({ .map((index, element) => ({
name: loadedCheerio(element) name: loadedCheerio(element)
.find('.card-title') .find('.card-title')
+2 -3
View File
@@ -1,7 +1,6 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -129,7 +128,7 @@ class freedlit implements Plugin.PluginBase {
} }
parseDate = (dateString: string | undefined = '') => { parseDate = (dateString: string | undefined = '') => {
const months: { [key: string]: number } = { const months: Record<string, number> = {
января: 1, января: 1,
февраля: 2, февраля: 2,
марта: 3, марта: 3,
+9 -10
View File
@@ -1,7 +1,7 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { storage } from '@libs/storage'; import { storage } from '@libs/storage';
@@ -12,14 +12,15 @@ class AuthorToday implements Plugin.PluginBase {
name = 'Автор Тудей'; name = 'Автор Тудей';
icon = 'src/ru/authortoday/icon.png'; icon = 'src/ru/authortoday/icon.png';
site = 'https://author.today'; site = 'https://author.today';
apiUrl = 'https://api.author.today/v1/'; apiSite = 'https://api.author.today/v1/';
imageSite = 'https://cm.author.today/content/';
version = '1.1.0'; version = '1.1.0';
async popularNovels( async popularNovels(
pageNo: number, pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions, { showLatestNovels, filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let url = this.apiUrl + 'catalog/search?page=' + pageNo; let url = this.apiSite + 'catalog/search?page=' + pageNo;
if (filters?.genre?.value) { if (filters?.genre?.value) {
url += '&genre=' + filters.genre.value; url += '&genre=' + filters.genre.value;
} }
@@ -48,9 +49,7 @@ class AuthorToday implements Plugin.PluginBase {
result?.searchResults?.forEach(novel => result?.searchResults?.forEach(novel =>
novels.push({ novels.push({
name: novel.title, name: novel.title,
cover: novel.coverUrl cover: novel.coverUrl ? this.imageSite + novel.coverUrl : defaultCover,
? 'https://cm.author.today/content/' + novel.coverUrl
: defaultCover,
path: novel.id.toString(), path: novel.id.toString(),
}), }),
); );
@@ -61,7 +60,7 @@ class AuthorToday implements Plugin.PluginBase {
async parseNovel(workID: string): Promise<Plugin.SourceNovel> { async parseNovel(workID: string): Promise<Plugin.SourceNovel> {
if (!this.user) this.user = await this.getUser(); if (!this.user) this.user = await this.getUser();
const book: responseBook = await fetchApi( const book: responseBook = await fetchApi(
this.apiUrl + 'work/' + workID + '/details', this.apiSite + 'work/' + workID + '/details',
{ {
headers: { headers: {
Authorization: 'Bearer ' + this.user?.token || 'guest', Authorization: 'Bearer ' + this.user?.token || 'guest',
@@ -93,7 +92,7 @@ class AuthorToday implements Plugin.PluginBase {
} }
const chaptersJSON: ChaptersEntity[] = await fetchApi( const chaptersJSON: ChaptersEntity[] = await fetchApi(
this.apiUrl + 'work/' + workID + '/content', this.apiSite + 'work/' + workID + '/content',
{ {
headers: { headers: {
Authorization: 'Bearer ' + this.user?.token || 'guest', Authorization: 'Bearer ' + this.user?.token || 'guest',
@@ -123,7 +122,7 @@ class AuthorToday implements Plugin.PluginBase {
const [workID, chapterID] = chapterPath.split('/'); const [workID, chapterID] = chapterPath.split('/');
if (!this.user) this.user = await this.getUser(); if (!this.user) this.user = await this.getUser();
const result: encryptedСhapter = await fetchApi( const result: encryptedСhapter = await fetchApi(
this.apiUrl + `work/${workID}/chapter/${chapterID}/text`, this.apiSite + `work/${workID}/chapter/${chapterID}/text`,
{ {
headers: { headers: {
Authorization: 'Bearer ' + this.user?.token || 'guest', Authorization: 'Bearer ' + this.user?.token || 'guest',
@@ -183,7 +182,7 @@ class AuthorToday implements Plugin.PluginBase {
let user = storage.get('user') || { userId: '', token: 'guest' }; let user = storage.get('user') || { userId: '', token: 'guest' };
if (user && user.userId && user.token) { if (user && user.userId && user.token) {
const currentUser: currentUser = await fetchApi( const currentUser: currentUser = await fetchApi(
this.apiUrl + 'account/current-user', this.apiSite + 'account/current-user',
{ {
headers: { headers: {
Authorization: 'Bearer ' + user.token || 'guest', Authorization: 'Bearer ' + user.token || 'guest',
+5 -3
View File
@@ -1,6 +1,6 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -11,6 +11,7 @@ class Bookriver implements Plugin.PluginBase {
id = 'bookriver'; id = 'bookriver';
name = 'Bookriver'; name = 'Bookriver';
site = 'https://bookriver.ru'; site = 'https://bookriver.ru';
apiSite = 'https://api.bookriver.ru/api/v1/';
version = '1.0.1'; version = '1.0.1';
icon = 'src/ru/bookriver/icon.png'; icon = 'src/ru/bookriver/icon.png';
@@ -99,7 +100,7 @@ class Bookriver implements Plugin.PluginBase {
} }
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const url = 'https://api.bookriver.ru/api/v1/books/chapter/text/'; const url = this.apiSite + 'books/chapter/text/';
const { data }: responseChapter = await fetchApi( const { data }: responseChapter = await fetchApi(
url + chapterPath.split('/').pop(), url + chapterPath.split('/').pop(),
).then(res => res.json()); ).then(res => res.json());
@@ -117,7 +118,8 @@ class Bookriver implements Plugin.PluginBase {
pageNo: number | undefined = 1, pageNo: number | undefined = 1,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
const url = const url =
'https://api.bookriver.ru/api/v1/search/autocomplete?keyword=' + this.apiSite +
'search/autocomplete?keyword=' +
searchTerm + searchTerm +
'&page=' + '&page=' +
pageNo + pageNo +
+5 -6
View File
@@ -1,6 +1,6 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
@@ -10,12 +10,13 @@ class ficbook implements Plugin.PluginBase {
id = 'ficbook'; id = 'ficbook';
name = 'ficbook'; name = 'ficbook';
site = 'https://ficbook.net'; site = 'https://ficbook.net';
imageSite = 'https://images.ficbook.net/fanfic-covers/';
version = '1.0.1'; version = '1.0.1';
icon = 'src/ru/ficbook/icon.png'; icon = 'src/ru/ficbook/icon.png';
async popularNovels( async popularNovels(
pageNo: number, pageNo: number,
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions, { filters }: Plugin.PopularNovelsOptions,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let url = this.site; let url = this.site;
@@ -162,9 +163,7 @@ class ficbook implements Plugin.PluginBase {
data?.data?.forEach(novel => { data?.data?.forEach(novel => {
const name = novel.title.trim(); const name = novel.title.trim();
const path = '/readfic/' + novel.slug; const path = '/readfic/' + novel.slug;
const cover = novel.cover const cover = novel.cover ? this.imageSite + novel.cover : defaultCover;
? 'https://images.ficbook.net/fanfic-covers/' + novel.cover
: defaultCover;
novels.push({ name, cover, path }); novels.push({ name, cover, path });
}); });
@@ -173,7 +172,7 @@ class ficbook implements Plugin.PluginBase {
} }
parseDate = (dateString: string | undefined = '') => { parseDate = (dateString: string | undefined = '') => {
const months: { [key: string]: number } = { const months: Record<string, number> = {
января: 1, января: 1,
февраля: 2, февраля: 2,
марта: 3, марта: 3,
+3 -3
View File
@@ -1,6 +1,6 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -84,7 +84,7 @@ class Jaomix implements Plugin.PluginBase {
}; };
loadedCheerio('#info-book > p').each(function () { loadedCheerio('#info-book > p').each(function () {
let text = loadedCheerio(this).text().replace(/,/g, '').split(' '); const text = loadedCheerio(this).text().replace(/,/g, '').split(' ');
if (text[0] === 'Автор:') { if (text[0] === 'Автор:') {
novel.author = text.splice(1).join(' '); novel.author = text.splice(1).join(' ');
} else if (text[0] === 'Жанры:') { } else if (text[0] === 'Жанры:') {
@@ -168,7 +168,7 @@ class Jaomix implements Plugin.PluginBase {
} }
parseDate = (dateString: string | undefined = '') => { parseDate = (dateString: string | undefined = '') => {
const months: { [key: string]: number } = { const months: Record<string, number> = {
Янв: 1, Янв: 1,
Фев: 2, Фев: 2,
Мар: 3, Мар: 3,
+4 -3
View File
@@ -1,10 +1,10 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
const statusKey: { [key: string]: string } = { const statusKey: Record<string, string> = {
'0': NovelStatus.Unknown, '0': NovelStatus.Unknown,
'1': NovelStatus.Ongoing, '1': NovelStatus.Ongoing,
'2': NovelStatus.Completed, '2': NovelStatus.Completed,
@@ -16,6 +16,7 @@ class Neobook implements Plugin.PluginBase {
id = 'neobook'; id = 'neobook';
name = 'Neobook'; name = 'Neobook';
site = 'https://neobook.org'; site = 'https://neobook.org';
apiSite = 'https://api.neobook.org/';
version = '1.0.1'; version = '1.0.1';
icon = 'src/ru/neobook/icon.png'; icon = 'src/ru/neobook/icon.png';
@@ -47,7 +48,7 @@ class Neobook implements Plugin.PluginBase {
formData.append('filter_timeread', filters?.timeread?.value || '0-999999'); formData.append('filter_timeread', filters?.timeread?.value || '0-999999');
const { bundle_books }: { bundle_books: BundleBooks } = await fetchApi( const { bundle_books }: { bundle_books: BundleBooks } = await fetchApi(
'https://api.neobook.org/', this.apiSite,
{ {
method: 'post', method: 'post',
body: formData, body: formData,
+9 -7
View File
@@ -1,6 +1,6 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -8,6 +8,7 @@ class novelOvh implements Plugin.PluginBase {
id = 'novelovh'; id = 'novelovh';
name = 'НовелОВХ'; name = 'НовелОВХ';
site = 'https://novel.ovh'; site = 'https://novel.ovh';
apiSite = 'https://api.novel.ovh/v2/';
version = '1.0.2'; version = '1.0.2';
icon = 'src/ru/novelovh/icon.png'; icon = 'src/ru/novelovh/icon.png';
@@ -61,8 +62,10 @@ class novelOvh implements Plugin.PluginBase {
switch (person.type) { switch (person.type) {
case 'AUTHOR': case 'AUTHOR':
novel.author = person.publisher.name; novel.author = person.publisher.name;
break;
case 'ARTIST': case 'ARTIST':
novel.artist = person.publisher.name; novel.artist = person.publisher.name;
break;
} }
}); });
@@ -89,7 +92,7 @@ class novelOvh implements Plugin.PluginBase {
async parseChapter(chapterPath: string): Promise<string> { async parseChapter(chapterPath: string): Promise<string> {
const book: responseChapter = await fetchApi( const book: responseChapter = await fetchApi(
'https://api.novel.ovh/v2/chapters/' + chapterPath.split('/')[1], this.apiSite + 'chapters/' + chapterPath.split('/')[1],
).then(res => res.json()); ).then(res => res.json());
const image = Object.fromEntries( const image = Object.fromEntries(
@@ -101,7 +104,7 @@ class novelOvh implements Plugin.PluginBase {
} }
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> { async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
const url = `https://api.novel.ovh/v2/books?type=NOVEL&search=${searchTerm}`; const url = this.apiSite + 'books?type=NOVEL&search=' + searchTerm;
const books: BooksEntity[] = await fetchApi(url).then(res => res.json()); const books: BooksEntity[] = await fetchApi(url).then(res => res.json());
const novels: Plugin.NovelItem[] = []; const novels: Plugin.NovelItem[] = [];
@@ -118,8 +121,8 @@ class novelOvh implements Plugin.PluginBase {
jsonToHtml = ( jsonToHtml = (
json: ContentEntity[], json: ContentEntity[],
image: { [key: string]: string }, image: Record<string, string>,
html: string = '', html = '',
) => { ) => {
json.forEach(element => { json.forEach(element => {
switch (element.type) { switch (element.type) {
@@ -158,8 +161,7 @@ class novelOvh implements Plugin.PluginBase {
return html; return html;
}; };
resolveUrl = (path: string, isNovel?: boolean) => resolveUrl = (path: string) => this.site + '/novel/' + path;
this.site + '/novel/' + path;
filters = { filters = {
sort: { sort: {
+3 -3
View File
@@ -1,11 +1,11 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const statusKey: { [key: string]: string } = { const statusKey: Record<string, string> = {
'active': NovelStatus.Ongoing, 'active': NovelStatus.Ongoing,
'completed': NovelStatus.Completed, 'completed': NovelStatus.Completed,
'freezed': NovelStatus.OnHiatus, 'freezed': NovelStatus.OnHiatus,
@@ -195,7 +195,7 @@ class TL implements Plugin.PluginBase {
return chapterText; return chapterText;
} }
resolveUrl = (path: string, isNovel?: boolean) => 'https://' + path; resolveUrl = (path: string) => 'https://' + path;
filters = { filters = {
tags: { tags: {
+4 -9
View File
@@ -1,10 +1,10 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const statusKey: { [key: number]: string } = { const statusKey: Record<number, string> = {
1: NovelStatus.Ongoing, 1: NovelStatus.Ongoing,
2: NovelStatus.Completed, 2: NovelStatus.Completed,
3: NovelStatus.OnHiatus, 3: NovelStatus.OnHiatus,
@@ -133,7 +133,7 @@ class RNBH implements Plugin.PluginBase {
.substring(indexA, indexB) .substring(indexA, indexB)
.replace( .replace(
/<img data-media-id="(.*?)".*?>/g, /<img data-media-id="(.*?)".*?>/g,
`<img src="${this.site}/api/media/$1"/>`, `<img src="${this.site}/api/media/$1">`,
); );
return chapterText; return chapterText;
@@ -164,8 +164,7 @@ class RNBH implements Plugin.PluginBase {
return novels; return novels;
} }
resolveUrl = (path: string, isNovel?: boolean) => resolveUrl = (path: string) => this.site + '/ranobe/' + path;
this.site + '/ranobe/' + path;
filters = { filters = {
sort: { sort: {
@@ -1236,10 +1235,6 @@ interface GenresOrEntity {
description?: string | null; description?: string | null;
} }
interface responseChapters {
marked_chapters_ids?: null[] | null;
volumes?: VolumesEntity[] | null;
}
interface VolumesEntity { interface VolumesEntity {
id: number; id: number;
num: number; num: number;
+4 -17
View File
@@ -1,12 +1,12 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { storage, localStorage } from '@libs/storage'; import { storage, localStorage } from '@libs/storage';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const statusKey: { [key: number]: string } = { const statusKey: Record<number, string> = {
1: NovelStatus.Ongoing, 1: NovelStatus.Ongoing,
2: NovelStatus.Completed, 2: NovelStatus.Completed,
3: NovelStatus.OnHiatus, 3: NovelStatus.OnHiatus,
@@ -126,7 +126,7 @@ class RLIB implements Plugin.PluginBase {
novel.genres = genres.join(', '); novel.genres = genres.join(', ');
} }
const branch_id: { [key: number]: string } = {}; const branch_id: Record<number, string> = {};
if (data.teams.length) { if (data.teams.length) {
data.teams.forEach( data.teams.forEach(
({ name, details }) => (branch_id[details?.branch_id || '0'] = name), ({ name, details }) => (branch_id[details?.branch_id || '0'] = name),
@@ -477,7 +477,7 @@ class RLIB implements Plugin.PluginBase {
export default new RLIB(); export default new RLIB();
function jsonToHtml(json: HTML[], html: string = '') { function jsonToHtml(json: HTML[], html = '') {
json.forEach(element => { json.forEach(element => {
switch (element.type) { switch (element.type) {
case 'hardBreak': case 'hardBreak':
@@ -592,12 +592,6 @@ interface User {
id: number; id: number;
} }
interface Rating {
average: string;
votes: number;
votesFormated: string;
}
interface DataClass { interface DataClass {
id: number; id: number;
name: string; name: string;
@@ -717,10 +711,3 @@ interface DataChapter {
branches_count: number; branches_count: number;
branches: Branch[]; branches: Branch[];
} }
interface BranchesEntity {
id: number;
branch_id?: number;
created_at: string;
teams?: BranchTeam[];
user: User;
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -97,7 +97,7 @@ class RNRF implements Plugin.PluginBase {
const jsonRaw = body.match(regex); const jsonRaw = body.match(regex);
if (jsonRaw instanceof Array && jsonRaw[1]) { if (jsonRaw instanceof Array && jsonRaw[1]) {
let chapter: string = const chapter: string =
JSON.parse(jsonRaw[1])?.props?.pageProps?.chapter?.content?.text || ''; JSON.parse(jsonRaw[1])?.props?.pageProps?.chapter?.content?.text || '';
if (chapter.includes('<img')) { if (chapter.includes('<img')) {
+2 -3
View File
@@ -1,6 +1,6 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -147,8 +147,7 @@ class ReN implements Plugin.PluginBase {
return novels; return novels;
} }
resolveUrl = (path: string, isNovel?: boolean) => resolveUrl = (path: string) => this.site + '/novel/' + path;
this.site + '/novel/' + path;
filters = { filters = {
sort: { sort: {
+6 -6
View File
@@ -1,10 +1,10 @@
import { Plugin } from '@typings/plugin'; import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs'; import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover'; import { defaultCover } from '@libs/defaultCover';
import { fetchApi, fetchFile } from '@libs/fetch'; import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus'; import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio'; import { load as parseHTML } from 'cheerio';
//import dayjs from 'dayjs'; import dayjs from 'dayjs';
class RV implements Plugin.PluginBase { class RV implements Plugin.PluginBase {
id = 'RV'; id = 'RV';
@@ -85,7 +85,7 @@ class RV implements Plugin.PluginBase {
chapters.push({ chapters.push({
name: 'Глава ' + chapter.number + ' ' + (chapter.name || ''), name: 'Глава ' + chapter.number + ' ' + (chapter.name || ''),
path: novelPath + '/' + chapter.id, path: novelPath + '/' + chapter.id,
//releaseTime: dayjs(chapter.created_at).format('LLL'), releaseTime: dayjs(chapter.created_at).format('LLL'),
chapterNumber: chapterIndex + 1, chapterNumber: chapterIndex + 1,
}); });
} }
@@ -176,7 +176,7 @@ interface chapters {
} }
function unicodeToUtf8(unicode: string) { function unicodeToUtf8(unicode: string) {
return unicode.replace(/\\u([\d\w]{4})/gi, (match, hex) => { return unicode.replace(/\\u([\d\w]{4})/gi, (match, hex) =>
return String.fromCharCode(parseInt(hex, 16)); String.fromCharCode(parseInt(hex, 16)),
}); );
} }
+167
View File
@@ -0,0 +1,167 @@
import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs';
import { fetchApi } from '@libs/fetch';
import { load as parseHTML } from 'cheerio';
class TopLiba implements Plugin.PluginBase {
id = 'TopLiba';
name = 'ТопЛиба';
site = 'https://topliba.com';
version = '1.0.0';
icon = 'src/ru/topliba/icon.png';
_token = ''; //I think it's permanent?
//PgRebp4cULzQKfJBjsEPeECcDQyrVfP5DvcxIxRo
async fetchNovels(
page: number,
{
showLatestNovels,
filters,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
searchTerm?: string,
): Promise<Plugin.NovelItem[]> {
const data = new URLSearchParams({
order_field: showLatestNovels ? 'date' : filters?.sort?.value || 'rating',
p: page,
});
if (searchTerm) data.append('q', searchTerm);
const body = await fetchApi(this.site + '/?' + data.toString()).then(res =>
res.text(),
);
const novels: Plugin.NovelItem[] = [];
this._token = body.match(/<meta name="_token" content="(.*?)"/)?.[1];
const elements = body.match(/<img class="cover" data-original=".*>/g) || [];
elements.forEach(element => {
const [, path, name] =
element.match(/data-original=".*covers\/(.*?)_.*title="(.*?)"/) || [];
if (path && name) {
novels.push({
name,
cover: this.site + '/covers/' + path + '.jpg',
path,
});
}
});
return novels;
}
popularNovels = this.fetchNovels;
async searchNovels(
searchTerm: string,
page: number,
): Promise<Plugin.NovelItem[]> {
const defaultOptions: any = {
showLatestNovels: false,
filters: {},
};
return this.fetchNovels(page, defaultOptions, searchTerm);
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
res.text(),
);
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
path: novelPath,
name: loadedCheerio('div > h1').text().trim(),
cover: this.site + '/covers/' + novelPath + '.jpg',
summary: loadedCheerio('.description').text().trim(),
author: loadedCheerio('.book-author > a').text().trim(),
genres: loadedCheerio('.book-genres > div > a')
.map((index, element) => loadedCheerio(element).text())
.get()
.join(','),
};
const chaptersHTML = await fetchApi(this.resolveUrl(novelPath)).then(res =>
res.text(),
);
this._token =
chaptersHTML.match(/<meta name="_token" content="(.*?)"/)?.[1] ||
body.match(/<meta name="_token" content="(.*?)"/)?.[1] ||
this._token;
const chapters: Plugin.ChapterItem[] = [];
const elements =
chaptersHTML.match(
/<li class="padding-\d+" data-capter="\d+">([\s\S]*?)</g,
) || [];
elements.forEach((chapter, chapterIndex) => {
const [, padding, capter, name] =
chapter.match(
/class="padding-(\d+)" data-capter="(\d+)">([\s\S]*?)</,
) || [];
if (padding && capter && name) {
const id =
padding === '0' ? capter : padding + '-' + (parseInt(capter, 10) - 1);
chapters.push({
name: name.trim(),
path: novelPath + '?' + id,
releaseTime: null,
chapterNumber: chapterIndex + 1,
});
}
});
novel.chapters = chapters;
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const [bookID, chapterID] = chapterPath.split('?');
if (!this._token) {
const chaptersHTML = await fetchApi(this.resolveUrl(bookID)).then(res =>
res.text(),
);
this._token = chaptersHTML.match(
/<meta name="_token" content="(.*?)"/,
)?.[1];
}
const chapterText = await fetchApi(this.resolveUrl(bookID) + '/chapter', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Referer: this.resolveUrl(bookID),
Origin: this.site,
},
method: 'POST',
body: new URLSearchParams({
chapter: chapterID,
_token: this._token,
}).toString(),
}).then(res => res.text());
return chapterText;
}
resolveUrl = (path: string, isNovel?: boolean) =>
this.site + (isNovel ? '/books/' + path : '/reader/' + path.split('?')[0]);
filters = {
sort: {
label: 'Сортировка:',
value: 'rating',
options: [
{ label: 'По рейтингу', value: 'rating' },
{ label: 'По популярности', value: 'num_downloads' },
{ label: 'По году выхода', value: 'year' },
{ label: 'По дате добавления', value: 'date' },
{ label: 'По названию', value: 'title' },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new TopLiba();

Some files were not shown because too many files have changed in this diff Show More