Prettier (#852)
* add prettier * lint-staged * prettier * upgrade husky
This commit is contained in:
@@ -0,0 +1 @@
|
||||
_
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.js
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true,
|
||||
useTabs: false,
|
||||
bracketSameLine: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
arrowParens: 'avoid',
|
||||
quoteProps: 'preserve',
|
||||
};
|
||||
+20
-20
@@ -1,18 +1,18 @@
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { load as loadCheerio } from "cheerio";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { load as loadCheerio } from 'cheerio';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
// import { isUrlAbsolute } from "@libs/isAbsoluteUrl";
|
||||
// import { parseMadaraDate } from "@libs/parseMadaraDate";
|
||||
|
||||
class TemplatePlugin implements Plugin.PluginBase {
|
||||
id = "";
|
||||
name = "";
|
||||
icon = "";
|
||||
site = "https://example.com";
|
||||
version = "1.0.0";
|
||||
id = '';
|
||||
name = '';
|
||||
icon = '';
|
||||
site = 'https://example.com';
|
||||
version = '1.0.0';
|
||||
filters: Filters | undefined = undefined;
|
||||
|
||||
async popularNovels(
|
||||
@@ -20,14 +20,14 @@ class TemplatePlugin implements Plugin.PluginBase {
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
/** Add your fetching code here */
|
||||
novels.push({
|
||||
name: "Novel1",
|
||||
path: "/novels/1",
|
||||
name: 'Novel1',
|
||||
path: '/novels/1',
|
||||
cover: defaultCover,
|
||||
});
|
||||
return novels;
|
||||
@@ -35,7 +35,7 @@ class TemplatePlugin implements Plugin.PluginBase {
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: 'Untitled'
|
||||
name: 'Untitled',
|
||||
};
|
||||
|
||||
// TODO: get here data from the site and
|
||||
@@ -55,9 +55,9 @@ class TemplatePlugin implements Plugin.PluginBase {
|
||||
|
||||
// TODO: add each chapter to the list using
|
||||
const chapter: Plugin.ChapterItem = {
|
||||
name: "",
|
||||
path: "",
|
||||
releaseTime: "",
|
||||
name: '',
|
||||
path: '',
|
||||
releaseTime: '',
|
||||
chapterNumber: 0,
|
||||
};
|
||||
chapters.push(chapter);
|
||||
@@ -67,12 +67,12 @@ class TemplatePlugin implements Plugin.PluginBase {
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
// parse chapter text here
|
||||
const chapterText = "";
|
||||
const chapterText = '';
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
@@ -1,70 +1,64 @@
|
||||
require("module-alias/register");
|
||||
require('module-alias/register');
|
||||
|
||||
import path from "path";
|
||||
import dayjs from "dayjs";
|
||||
import express from "express";
|
||||
import bodyParser from "body-parser";
|
||||
import * as pluginApi from "./test_web/api/plugins";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import path from 'path';
|
||||
import dayjs from 'dayjs';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import * as pluginApi from './test_web/api/plugins';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
const app = express();
|
||||
const port = Number(process.argv[2]);
|
||||
if (!port) {
|
||||
process.exit(0);
|
||||
}
|
||||
const host = "localhost";
|
||||
const host = 'localhost';
|
||||
|
||||
const dirname = path.join(__dirname, "..");
|
||||
const dirname = path.join(__dirname, '..');
|
||||
|
||||
export const getHeaders = () => {
|
||||
return pluginApi.fetchHeaders;
|
||||
};
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const xHeaders = req.headers["x-custom-headers"];
|
||||
if (xHeaders && typeof xHeaders === "string") {
|
||||
const xHeaders = req.headers['x-custom-headers'];
|
||||
if (xHeaders && typeof xHeaders === 'string') {
|
||||
for (const [headerName, headerValue] of Object.entries(
|
||||
pluginApi.fetchHeaders
|
||||
pluginApi.fetchHeaders,
|
||||
)) {
|
||||
if (
|
||||
typeof headerName === "string" &&
|
||||
typeof headerValue === "string"
|
||||
)
|
||||
if (typeof headerName === 'string' && typeof headerValue === 'string')
|
||||
delete pluginApi.fetchHeaders[headerName];
|
||||
}
|
||||
const headers = JSON.parse(xHeaders);
|
||||
for (const [headerName, headerValue] of Object.entries(headers)) {
|
||||
if (
|
||||
typeof headerName === "string" &&
|
||||
typeof headerValue === "string"
|
||||
)
|
||||
if (typeof headerName === 'string' && typeof headerValue === 'string')
|
||||
pluginApi.fetchHeaders[headerName] = headerValue;
|
||||
}
|
||||
}
|
||||
if (pluginApi.fetchHeaders.userAgent)
|
||||
console.log("Custom headers: ", pluginApi.fetchHeaders);
|
||||
console.log('Custom headers: ', pluginApi.fetchHeaders);
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use("/static", express.static(path.join(dirname, "test_web", "static")));
|
||||
app.use("/icons", express.static(path.join(dirname, "icons")));
|
||||
app.get("/all_plugins", (req, res) => {
|
||||
app.use('/static', express.static(path.join(dirname, 'test_web', 'static')));
|
||||
app.use('/icons', express.static(path.join(dirname, 'icons')));
|
||||
app.get('/all_plugins', (req, res) => {
|
||||
const allPlugins = pluginApi.all_plugins();
|
||||
res.json(allPlugins);
|
||||
});
|
||||
app.post("/filters", async (req, res) => {
|
||||
const filters = await pluginApi.getFilter(req.body["pluginRequirePath"]);
|
||||
app.post('/filters', async (req, res) => {
|
||||
const filters = await pluginApi.getFilter(req.body['pluginRequirePath']);
|
||||
res.json(filters || []);
|
||||
});
|
||||
app.post("/popularNovels/", async (req, res) => {
|
||||
const requirePath = req.body["pluginRequirePath"];
|
||||
const showLatestNovels = req.body["showLatestNovels"] || false;
|
||||
app.post('/popularNovels/', async (req, res) => {
|
||||
const requirePath = req.body['pluginRequirePath'];
|
||||
const showLatestNovels = req.body['showLatestNovels'] || false;
|
||||
const defaultFilters = await pluginApi.getFilter(requirePath);
|
||||
const filters = showLatestNovels
|
||||
? defaultFilters
|
||||
: req.body["filters"] || defaultFilters;
|
||||
const page = parseInt(req.body["page"]);
|
||||
: req.body['filters'] || defaultFilters;
|
||||
const page = parseInt(req.body['page']);
|
||||
try {
|
||||
const novels = await pluginApi.popularNovels(requirePath, page || 1, {
|
||||
showLatestNovels,
|
||||
@@ -75,80 +69,80 @@ app.post("/popularNovels/", async (req, res) => {
|
||||
res.json({ error: String(err) });
|
||||
}
|
||||
});
|
||||
app.post("/searchNovels/", async (req, res) => {
|
||||
const page = parseInt(req.body["page"]) || 1;
|
||||
app.post('/searchNovels/', async (req, res) => {
|
||||
const page = parseInt(req.body['page']) || 1;
|
||||
try {
|
||||
const novels = await pluginApi.searchNovels(
|
||||
req.body["pluginRequirePath"],
|
||||
req.body['pluginRequirePath'],
|
||||
page,
|
||||
req.body["searchTerm"]
|
||||
req.body['searchTerm'],
|
||||
);
|
||||
res.json(novels);
|
||||
} catch (err: unknown) {
|
||||
res.json({ error: String(err) });
|
||||
}
|
||||
});
|
||||
app.post("/parseNovel/", async (req, res) => {
|
||||
app.post('/parseNovel/', async (req, res) => {
|
||||
try {
|
||||
const sourceNovel = await pluginApi.parseNovel(
|
||||
req.body["pluginRequirePath"],
|
||||
req.body["novelPath"]
|
||||
req.body['pluginRequirePath'],
|
||||
req.body['novelPath'],
|
||||
);
|
||||
res.json(sourceNovel);
|
||||
} catch (err: unknown) {
|
||||
res.json({ error: String(err) });
|
||||
}
|
||||
});
|
||||
app.post("/hasParsePage", async (req, res) => {
|
||||
app.post('/hasParsePage', async (req, res) => {
|
||||
try {
|
||||
const parsePage = await pluginApi.hasParsePage(
|
||||
req.body["pluginRequirePath"]
|
||||
)
|
||||
req.body['pluginRequirePath'],
|
||||
);
|
||||
res.json({ hasParsePage: parsePage !== undefined });
|
||||
} catch (err: unknown) {
|
||||
res.json({ hasParsePage: false });
|
||||
}
|
||||
});
|
||||
app.post("/parsePage/", async (req, res) => {
|
||||
app.post('/parsePage/', async (req, res) => {
|
||||
try {
|
||||
const sourcePage = await pluginApi.parsePage(
|
||||
req.body["pluginRequirePath"],
|
||||
req.body["novelPath"],
|
||||
req.body["page"]
|
||||
req.body['pluginRequirePath'],
|
||||
req.body['novelPath'],
|
||||
req.body['page'],
|
||||
);
|
||||
res.json(sourcePage);
|
||||
} catch (err: unknown) {
|
||||
res.json({ error: String(err) });
|
||||
}
|
||||
});
|
||||
app.post("/parseChapter/", async (req, res) => {
|
||||
app.post('/parseChapter/', async (req, res) => {
|
||||
try {
|
||||
const chapterText = await pluginApi.parseChapter(
|
||||
req.body["pluginRequirePath"],
|
||||
req.body["chapterPath"]
|
||||
req.body['pluginRequirePath'],
|
||||
req.body['chapterPath'],
|
||||
);
|
||||
res.send(chapterText);
|
||||
} catch (err: unknown) {
|
||||
res.json({ error: String(err) });
|
||||
}
|
||||
});
|
||||
app.post("/fetchImage/", async (req, res) => {
|
||||
app.post('/fetchImage/', async (req, res) => {
|
||||
try {
|
||||
const base64 = await pluginApi.fetchImage(
|
||||
req.body["pluginRequirePath"],
|
||||
req.body["url"]
|
||||
req.body['pluginRequirePath'],
|
||||
req.body['url'],
|
||||
);
|
||||
res.send(base64);
|
||||
} catch (err: unknown) {
|
||||
res.json({ error: String(err) });
|
||||
}
|
||||
});
|
||||
app.post("/resolveUrl/", async (req, res) => {
|
||||
app.post('/resolveUrl/', async (req, res) => {
|
||||
try {
|
||||
const fullURL = await pluginApi.resolveUrl(
|
||||
req.body["pluginRequirePath"],
|
||||
req.body["path"],
|
||||
req.body["isNovel"],
|
||||
req.body['pluginRequirePath'],
|
||||
req.body['path'],
|
||||
req.body['isNovel'],
|
||||
);
|
||||
res.send(fullURL);
|
||||
} catch (err: unknown) {
|
||||
@@ -156,8 +150,8 @@ app.post("/resolveUrl/", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(path.join(dirname, "test_web", "index.html"));
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(dirname, 'test_web', 'index.html'));
|
||||
});
|
||||
|
||||
app.listen(port, host, () => {
|
||||
@@ -165,17 +159,17 @@ app.listen(port, host, () => {
|
||||
});
|
||||
|
||||
//Dayjs localization
|
||||
const language = Intl?.DateTimeFormat()?.resolvedOptions()?.locale || "en";
|
||||
const language = Intl?.DateTimeFormat()?.resolvedOptions()?.locale || 'en';
|
||||
|
||||
require("dayjs/locale/ar");
|
||||
require("dayjs/locale/de");
|
||||
require("dayjs/locale/es");
|
||||
require("dayjs/locale/it");
|
||||
require("dayjs/locale/pt");
|
||||
require("dayjs/locale/ru");
|
||||
require("dayjs/locale/tr");
|
||||
require("dayjs/locale/uk");
|
||||
require("dayjs/locale/zh");
|
||||
require('dayjs/locale/ar');
|
||||
require('dayjs/locale/de');
|
||||
require('dayjs/locale/es');
|
||||
require('dayjs/locale/it');
|
||||
require('dayjs/locale/pt');
|
||||
require('dayjs/locale/ru');
|
||||
require('dayjs/locale/tr');
|
||||
require('dayjs/locale/uk');
|
||||
require('dayjs/locale/zh');
|
||||
|
||||
dayjs.locale(language);
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const defaultCover =
|
||||
"https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true";
|
||||
'https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true';
|
||||
|
||||
+29
-22
@@ -22,10 +22,10 @@ const makeInit = async (init?: FetchInit) => {
|
||||
'Cache-Control': 'max-age=0',
|
||||
};
|
||||
try {
|
||||
const { getHeaders } = await import("../index.js");
|
||||
const { getHeaders } = await import('../index.js');
|
||||
defaultHeaders = {
|
||||
...defaultHeaders,
|
||||
...getHeaders()
|
||||
...getHeaders(),
|
||||
};
|
||||
} catch {
|
||||
// nothing to do
|
||||
@@ -75,11 +75,11 @@ export const fetchFile = async function (url: string, init?: FetchInit) {
|
||||
console.log(url, init);
|
||||
try {
|
||||
const res = await fetch(url, init as RequestInit);
|
||||
if (!res.ok) return "";
|
||||
if (!res.ok) return '';
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
return Buffer.from(arrayBuffer).toString("base64");
|
||||
return Buffer.from(arrayBuffer).toString('base64');
|
||||
} catch (e) {
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,18 +94,18 @@ export const fetchFile = async function (url: string, init?: FetchInit) {
|
||||
export const fetchText = async function (
|
||||
url: string,
|
||||
init?: FetchInit,
|
||||
encoding?: string
|
||||
encoding?: string,
|
||||
): Promise<string> {
|
||||
init = await makeInit(init);
|
||||
console.log(url, init);
|
||||
try {
|
||||
const res = await fetch(url, init as RequestInit);
|
||||
if (!res.ok) return "";
|
||||
if (!res.ok) return '';
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
const decoder = new TextDecoder(encoding);
|
||||
return decoder.decode(arrayBuffer);
|
||||
} catch (e) {
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,40 +119,47 @@ interface ProtoRequestInit {
|
||||
|
||||
const BYTE_MARK = BigInt((1 << 8) - 1);
|
||||
|
||||
export const fetchProto = async function<ReturnType> (
|
||||
export const fetchProto = async function <ReturnType>(
|
||||
protoInit: ProtoRequestInit,
|
||||
url: string,
|
||||
init?: FetchInit,
|
||||
) {
|
||||
const protoRoot = parseProto(protoInit.proto).root;
|
||||
const RequestMessge = protoRoot.lookupType(protoInit.requestType);
|
||||
if (RequestMessge.verify(protoInit.requestData)){
|
||||
if (RequestMessge.verify(protoInit.requestData)) {
|
||||
throw new Error('Invalid Proto');
|
||||
}
|
||||
// encode request data
|
||||
const encodedrequest = RequestMessge.encode(protoInit.requestData).finish();
|
||||
const requestLength = BigInt(encodedrequest.length);
|
||||
const headers = new Uint8Array(
|
||||
Array(5).fill(0).map((v, idx) => {
|
||||
if(idx === 0) return 0;
|
||||
Array(5)
|
||||
.fill(0)
|
||||
.map((v, idx) => {
|
||||
if (idx === 0) return 0;
|
||||
return Number((requestLength >> BigInt(8 * (5 - idx - 1))) & BYTE_MARK);
|
||||
})
|
||||
}),
|
||||
);
|
||||
init = await makeInit(init);
|
||||
const bodyArray = new Uint8Array(headers.length + encodedrequest.length)
|
||||
bodyArray.set(headers, 0)
|
||||
bodyArray.set(encodedrequest, headers.length)
|
||||
const bodyArray = new Uint8Array(headers.length + encodedrequest.length);
|
||||
bodyArray.set(headers, 0);
|
||||
bodyArray.set(encodedrequest, headers.length);
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
...init,
|
||||
body: bodyArray
|
||||
} as RequestInit
|
||||
).then(r => r.arrayBuffer())
|
||||
body: bodyArray,
|
||||
} as RequestInit)
|
||||
.then(r => r.arrayBuffer())
|
||||
.then(arr => {
|
||||
// decode response data
|
||||
const payload = new Uint8Array(arr);
|
||||
const length = Number(BigInt(payload[1] << 24) | BigInt(payload[2] << 16) | BigInt(payload[3] << 8) | BigInt(payload[4]));
|
||||
const length = Number(
|
||||
BigInt(payload[1] << 24) |
|
||||
BigInt(payload[2] << 16) |
|
||||
BigInt(payload[3] << 8) |
|
||||
BigInt(payload[4]),
|
||||
);
|
||||
const ResponseMessage = protoRoot.lookupType(protoInit.responseType);
|
||||
return ResponseMessage.decode(payload.slice(5, 5 + length));
|
||||
}) as ReturnType
|
||||
}
|
||||
}) as ReturnType;
|
||||
};
|
||||
|
||||
+14
-14
@@ -4,11 +4,11 @@ export interface FilterOption {
|
||||
}
|
||||
|
||||
export enum FilterTypes {
|
||||
TextInput = "Text",
|
||||
Picker = "Picker",
|
||||
CheckboxGroup = "Checkbox",
|
||||
Switch = "Switch",
|
||||
ExcludableCheckboxGroup = "XCheckbox",
|
||||
TextInput = 'Text',
|
||||
Picker = 'Picker',
|
||||
CheckboxGroup = 'Checkbox',
|
||||
Switch = 'Switch',
|
||||
ExcludableCheckboxGroup = 'XCheckbox',
|
||||
}
|
||||
|
||||
type SwitchFilter = {
|
||||
@@ -44,7 +44,7 @@ type ExcludableCheckboxFilter = {
|
||||
|
||||
export type Filters = Record<
|
||||
string,
|
||||
| { label: string } & (
|
||||
{ label: string } & (
|
||||
| PickerFilter
|
||||
| CheckboxFilter
|
||||
| TextFilter
|
||||
@@ -54,25 +54,25 @@ export type Filters = Record<
|
||||
>;
|
||||
|
||||
export type FilterToValues<
|
||||
T extends Record<string, { type: FilterTypes }> | undefined
|
||||
T extends Record<string, { type: FilterTypes }> | undefined,
|
||||
> = T extends undefined
|
||||
? undefined
|
||||
: {
|
||||
[K in keyof T]: Omit<
|
||||
{ type: NonNullable<T>[K]["type"] } & Filters[string],
|
||||
"label" | "options"
|
||||
{ type: NonNullable<T>[K]['type'] } & Filters[string],
|
||||
'label' | 'options'
|
||||
>;
|
||||
};
|
||||
|
||||
export type ValueOfFilter<T extends FilterTypes> =
|
||||
T extends FilterTypes.CheckboxGroup
|
||||
? CheckboxFilter["value"]
|
||||
? CheckboxFilter['value']
|
||||
: T extends FilterTypes.Picker
|
||||
? PickerFilter["value"]
|
||||
? PickerFilter['value']
|
||||
: T extends FilterTypes.Switch
|
||||
? SwitchFilter["value"]
|
||||
? SwitchFilter['value']
|
||||
: T extends FilterTypes.TextInput
|
||||
? TextFilter["value"]
|
||||
? TextFilter['value']
|
||||
: T extends FilterTypes.ExcludableCheckboxGroup
|
||||
? ExcludableCheckboxFilter["value"]
|
||||
? ExcludableCheckboxFilter['value']
|
||||
: never;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export const isUrlAbsolute = (url: string) => {
|
||||
if (url) {
|
||||
if (url.indexOf("//") === 0) {
|
||||
if (url.indexOf('//') === 0) {
|
||||
return true;
|
||||
} // URL is protocol-relative (= absolute)
|
||||
if (url.indexOf("://") === -1) {
|
||||
if (url.indexOf('://') === -1) {
|
||||
return false;
|
||||
} // URL has no protocol (= relative)
|
||||
if (url.indexOf(".") === -1) {
|
||||
if (url.indexOf('.') === -1) {
|
||||
return false;
|
||||
} // URL does not contain a dot, i.e. no TLD (= relative, possibly REST)
|
||||
if (url.indexOf("/") === -1) {
|
||||
if (url.indexOf('/') === -1) {
|
||||
return false;
|
||||
} // URL does not contain a single slash (= relative)
|
||||
if (url.indexOf(":") > url.indexOf("/")) {
|
||||
if (url.indexOf(':') > url.indexOf('/')) {
|
||||
return false;
|
||||
} // The first colon comes after the first slash (= relative)
|
||||
if (url.indexOf("://") < url.indexOf(".")) {
|
||||
if (url.indexOf('://') < url.indexOf('.')) {
|
||||
return true;
|
||||
} // Protocol is defined before first dot (= absolute)
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,17 +1,17 @@
|
||||
export const languages = {
|
||||
Arabic: "العربية",
|
||||
Chinese: "中文, 汉语, 漢語",
|
||||
English: "English",
|
||||
French: "Français",
|
||||
Indonesian: "Bahasa Indonesia",
|
||||
Japanese: "日本語",
|
||||
Korean: "조선말, 한국어",
|
||||
Polish: "Polski",
|
||||
Portuguese: "Português",
|
||||
Russian: "Русский",
|
||||
Spanish: "Español",
|
||||
Thai: "ไทย",
|
||||
Turkish: "Türkçe",
|
||||
Ukrainian: "Українська",
|
||||
Vietnamese: "Tiếng Việt",
|
||||
Arabic: 'العربية',
|
||||
Chinese: '中文, 汉语, 漢語',
|
||||
English: 'English',
|
||||
French: 'Français',
|
||||
Indonesian: 'Bahasa Indonesia',
|
||||
Japanese: '日本語',
|
||||
Korean: '조선말, 한국어',
|
||||
Polish: 'Polski',
|
||||
Portuguese: 'Português',
|
||||
Russian: 'Русский',
|
||||
Spanish: 'Español',
|
||||
Thai: 'ไทย',
|
||||
Turkish: 'Türkçe',
|
||||
Ukrainian: 'Українська',
|
||||
Vietnamese: 'Tiếng Việt',
|
||||
} as const;
|
||||
|
||||
+7
-7
@@ -1,9 +1,9 @@
|
||||
export const NovelStatus = {
|
||||
Unknown: "Unknown",
|
||||
Ongoing: "Ongoing",
|
||||
Completed: "Completed",
|
||||
Licensed: "Licensed",
|
||||
PublishingFinished: "Publishing Finished",
|
||||
Cancelled: "Cancelled",
|
||||
OnHiatus: "On Hiatus",
|
||||
Unknown: 'Unknown',
|
||||
Ongoing: 'Ongoing',
|
||||
Completed: 'Completed',
|
||||
Licensed: 'Licensed',
|
||||
PublishingFinished: 'Publishing Finished',
|
||||
Cancelled: 'Cancelled',
|
||||
OnHiatus: 'On Hiatus',
|
||||
} as const;
|
||||
Generated
+1028
-176
File diff suppressed because it is too large
Load Diff
+9
-2
@@ -11,7 +11,9 @@
|
||||
"clearMultisrc": "find . -type f -wholename \"*plugins*\\[*\\]*.[t,j]s\" -delete",
|
||||
"less": "npx less ./test_web/static/css/index.less ./test_web/static/css/index.css",
|
||||
"host-linux": "chmod +x ./host.sh && ./host.sh",
|
||||
"host-windows": "powershell ./host.ps1"
|
||||
"host-windows": "powershell ./host.ps1",
|
||||
"prettier": "prettier --write \"./**/*.{js,ts}\"",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"author": "LNReader",
|
||||
"license": "MIT",
|
||||
@@ -44,7 +46,12 @@
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jquery": "^3.5.29",
|
||||
"@types/node": "^20.11.19",
|
||||
"pre-commit": "^1.2.2",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "3.2.5",
|
||||
"tsc-watch": "^6.0.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,ts}": "prettier --write"
|
||||
}
|
||||
}
|
||||
|
||||
+50
-46
@@ -1,22 +1,22 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchText, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchText, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class Shu69 implements Plugin.PluginBase {
|
||||
id = "69shu";
|
||||
name = "69书吧";
|
||||
icon = "src/cn/69shu/icon.png";
|
||||
site = "https://www.69shu.xyz";
|
||||
version = "0.2.0";
|
||||
id = '69shu';
|
||||
name = '69书吧';
|
||||
icon = 'src/cn/69shu/icon.png';
|
||||
site = 'https://www.69shu.xyz';
|
||||
version = '0.2.0';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url: string;
|
||||
if (showLatestNovels) {
|
||||
@@ -64,27 +64,29 @@ class Shu69 implements Plugin.PluginBase {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
chapters: [],
|
||||
name: loadedCheerio('h1').text().trim()
|
||||
name: loadedCheerio('h1').text().trim(),
|
||||
};
|
||||
|
||||
novel.cover = loadedCheerio('div.cover > img').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio('#bookIntro').text().trim();
|
||||
|
||||
const bookInfo = loadedCheerio('div.caption-bookinfo > p')
|
||||
const bookInfo = loadedCheerio('div.caption-bookinfo > p');
|
||||
|
||||
novel.author = bookInfo.find("a").attr('title');
|
||||
novel.author = bookInfo.find('a').attr('title');
|
||||
|
||||
novel.artist = undefined;
|
||||
|
||||
novel.status = bookInfo.text().includes('连载') ? NovelStatus.Ongoing : NovelStatus.Completed;
|
||||
novel.status = bookInfo.text().includes('连载')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
|
||||
novel.genres = '';
|
||||
|
||||
// Table of Content is on a different page than the summary page
|
||||
let chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
const allUrl = loadedCheerio('dd.all > a').attr('href')
|
||||
const allUrl = loadedCheerio('dd.all > a').attr('href');
|
||||
if (allUrl) {
|
||||
const chaptersUrl = this.site + allUrl;
|
||||
const chaptersBody = await fetchText(chaptersUrl);
|
||||
@@ -94,7 +96,7 @@ class Shu69 implements Plugin.PluginBase {
|
||||
chaptersLoadedCheerio('dd').each((i, el) => {
|
||||
const chapterUrl = chaptersLoadedCheerio(el).find('a').attr('href');
|
||||
const chapterName = chaptersLoadedCheerio(el).find('a').text().trim();
|
||||
if(chapterUrl){
|
||||
if (chapterUrl) {
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl,
|
||||
@@ -102,10 +104,12 @@ class Shu69 implements Plugin.PluginBase {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loadedCheerio('div.panel.hidden-xs > dl.panel-chapterlist:nth-child(2) > dd').each((i, el) => {
|
||||
loadedCheerio(
|
||||
'div.panel.hidden-xs > dl.panel-chapterlist:nth-child(2) > dd',
|
||||
).each((i, el) => {
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
const chapterName = loadedCheerio(el).find('a').text().trim();
|
||||
if(chapterUrl){
|
||||
if (chapterUrl) {
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl,
|
||||
@@ -134,11 +138,11 @@ class Shu69 implements Plugin.PluginBase {
|
||||
.join('\n');
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
if (pageNo > 1) return [];
|
||||
|
||||
@@ -180,39 +184,39 @@ class Shu69 implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
rank: {
|
||||
label: "排行榜",
|
||||
value: "allvisit",
|
||||
label: '排行榜',
|
||||
value: 'allvisit',
|
||||
options: [
|
||||
{ label: "总排行榜", value: "allvisit" },
|
||||
{ label: "月排行榜", value: "monthvisit" },
|
||||
{ label: "周排行榜", value: "weekvisit" },
|
||||
{ label: "日排行榜", value: "dayvisit" },
|
||||
{ label: "收藏榜", value: "goodnum" },
|
||||
{ label: "字数榜", value: "words" },
|
||||
{ label: "推荐榜", value: "allvote" },
|
||||
{ label: "新书榜", value: "postdate" },
|
||||
{ label: "更新榜", value: "lastupdate" },
|
||||
{ label: '总排行榜', value: 'allvisit' },
|
||||
{ label: '月排行榜', value: 'monthvisit' },
|
||||
{ label: '周排行榜', value: 'weekvisit' },
|
||||
{ label: '日排行榜', value: 'dayvisit' },
|
||||
{ label: '收藏榜', value: 'goodnum' },
|
||||
{ label: '字数榜', value: 'words' },
|
||||
{ label: '推荐榜', value: 'allvote' },
|
||||
{ label: '新书榜', value: 'postdate' },
|
||||
{ label: '更新榜', value: 'lastupdate' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
label: "分类",
|
||||
value: "none",
|
||||
label: '分类',
|
||||
value: 'none',
|
||||
options: [
|
||||
{ label: "无", value: "none" },
|
||||
{ label: "全部", value: "all" },
|
||||
{ label: "玄幻", value: "xuanhuan" },
|
||||
{ label: "仙侠", value: "xianxia" },
|
||||
{ label: "都市", value: "dushi" },
|
||||
{ label: "历史", value: "lishi" },
|
||||
{ label: "游戏", value: "youxi" },
|
||||
{ label: "科幻", value: "kehuan" },
|
||||
{ label: "灵异", value: "kongbu" },
|
||||
{ label: "言情", value: "nvsheng" },
|
||||
{ label: "其它", value: "qita" },
|
||||
{ label: '无', value: 'none' },
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '玄幻', value: 'xuanhuan' },
|
||||
{ label: '仙侠', value: 'xianxia' },
|
||||
{ label: '都市', value: 'dushi' },
|
||||
{ label: '历史', value: 'lishi' },
|
||||
{ label: '游戏', value: 'youxi' },
|
||||
{ label: '科幻', value: 'kehuan' },
|
||||
{ label: '灵异', value: 'kongbu' },
|
||||
{ label: '言情', value: 'nvsheng' },
|
||||
{ label: '其它', value: 'qita' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
}
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
|
||||
+51
-40
@@ -1,20 +1,20 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchText, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { encode } from "urlencode";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchText, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { encode } from 'urlencode';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class XinShu69 implements Plugin.PluginBase {
|
||||
id = "69xinshu";
|
||||
name = "69书吧";
|
||||
icon = "src/cn/69xinshu/icon.png";
|
||||
site = "https://www.69shu.pro";
|
||||
version = "0.1.1";
|
||||
id = '69xinshu';
|
||||
name = '69书吧';
|
||||
icon = 'src/cn/69xinshu/icon.png';
|
||||
site = 'https://www.69shu.pro';
|
||||
version = '0.1.1';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url: string;
|
||||
if (pageNo === 1) {
|
||||
@@ -30,12 +30,19 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
const novelsList = pageNo === 1 ? loadedCheerio('div.newbox > ul > li') : loadedCheerio('li');
|
||||
const novelsList =
|
||||
pageNo === 1
|
||||
? loadedCheerio('div.newbox > ul > li')
|
||||
: loadedCheerio('li');
|
||||
novelsList.each((i, e) => {
|
||||
const novelUrl = loadedCheerio(e).find('li > div > h3 > a:nth-child(2)').attr('href');
|
||||
const novelUrl = loadedCheerio(e)
|
||||
.find('li > div > h3 > a:nth-child(2)')
|
||||
.attr('href');
|
||||
|
||||
if (novelUrl) {
|
||||
const novelName = loadedCheerio(e).find('li > div > h3 > a:nth-child(2)').text();
|
||||
const novelName = loadedCheerio(e)
|
||||
.find('li > div > h3 > a:nth-child(2)')
|
||||
.text();
|
||||
const novelCover = loadedCheerio(e)
|
||||
.find('li > a > img')
|
||||
.attr('data-src');
|
||||
@@ -64,16 +71,14 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
chapters: [],
|
||||
name: loadedCheerio('h1 > a').text()
|
||||
name: loadedCheerio('h1 > a').text(),
|
||||
};
|
||||
|
||||
novel.cover = loadedCheerio('div.bookimg2 > img').attr(
|
||||
'src',
|
||||
);
|
||||
novel.cover = loadedCheerio('div.bookimg2 > img').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio('div.navtxt').text().trim();
|
||||
|
||||
const bookInfo = loadedCheerio('div.booknav2').text()
|
||||
const bookInfo = loadedCheerio('div.booknav2').text();
|
||||
|
||||
const authorMatch = bookInfo.match(/作者:(.*)/);
|
||||
if (authorMatch) {
|
||||
@@ -82,7 +87,9 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
|
||||
novel.artist = undefined;
|
||||
|
||||
novel.status = bookInfo.includes('连载') ? NovelStatus.Ongoing : NovelStatus.Completed;
|
||||
novel.status = bookInfo.includes('连载')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
|
||||
const genresMatch = bookInfo.match(/分类:(.*)/);
|
||||
if (genresMatch) {
|
||||
@@ -101,7 +108,7 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
chaptersLoadedCheerio('li').each((i, e) => {
|
||||
const chapterUrl = chaptersLoadedCheerio(e).find('a').attr('href');
|
||||
|
||||
if (chapterUrl?.startsWith("https://")) {
|
||||
if (chapterUrl?.startsWith('https://')) {
|
||||
const chapterName = chaptersLoadedCheerio(e).find('a').text().trim();
|
||||
|
||||
chapters.push({
|
||||
@@ -133,11 +140,11 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
.join('\n');
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
if (pageNo > 1) return [];
|
||||
|
||||
@@ -146,7 +153,11 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
formData.append('searchkey', encode(searchTerm, 'gbk'));
|
||||
formData.append('searchtype', 'all');
|
||||
|
||||
const body = await fetchText(searchUrl, { method: 'post', body: formData, }, 'gbk');
|
||||
const body = await fetchText(
|
||||
searchUrl,
|
||||
{ method: 'post', body: formData },
|
||||
'gbk',
|
||||
);
|
||||
if (body === '') throw Error('无法获取搜索结果,请检查网络');
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
@@ -174,22 +185,22 @@ class XinShu69 implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
class: {
|
||||
label: "分类",
|
||||
value: "0",
|
||||
label: '分类',
|
||||
value: '0',
|
||||
options: [
|
||||
{ label: "全部分类", value: "0" },
|
||||
{ label: "言情小说", value: "3" },
|
||||
{ label: "玄幻魔法", value: "1" },
|
||||
{ label: "修真武侠", value: "2" },
|
||||
{ label: "穿越时空", value: "11" },
|
||||
{ label: "都市小说", value: "9" },
|
||||
{ label: "历史军事", value: "4" },
|
||||
{ label: "游戏竞技", value: "5" },
|
||||
{ label: "科幻空间", value: "6" },
|
||||
{ label: "悬疑惊悚", value: "7" },
|
||||
{ label: "同人小说", value: "8" },
|
||||
{ label: "官场职场", value: "10" },
|
||||
{ label: "青春校园", value: "12" },
|
||||
{ label: '全部分类', value: '0' },
|
||||
{ label: '言情小说', value: '3' },
|
||||
{ label: '玄幻魔法', value: '1' },
|
||||
{ label: '修真武侠', value: '2' },
|
||||
{ label: '穿越时空', value: '11' },
|
||||
{ label: '都市小说', value: '9' },
|
||||
{ label: '历史军事', value: '4' },
|
||||
{ label: '游戏竞技', value: '5' },
|
||||
{ label: '科幻空间', value: '6' },
|
||||
{ label: '悬疑惊悚', value: '7' },
|
||||
{ label: '同人小说', value: '8' },
|
||||
{ label: '官场职场', value: '10' },
|
||||
{ label: '青春校园', value: '12' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+189
-199
@@ -1,22 +1,22 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchText, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchText, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class Linovelib implements Plugin.PluginBase {
|
||||
id = "linovelib";
|
||||
name = "Linovelib";
|
||||
icon = "src/cn/linovelib/icon.png";
|
||||
site = "https://www.bilinovel.com";
|
||||
version = "1.1.1";
|
||||
id = 'linovelib';
|
||||
name = 'Linovelib';
|
||||
icon = 'src/cn/linovelib/icon.png';
|
||||
site = 'https://www.bilinovel.com';
|
||||
version = '1.1.1';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const rank = showLatestNovels ? 'lastupdate' : filters.rank.value;
|
||||
const url = `${this.site}/top/${rank}/${pageNo}.html`;
|
||||
@@ -28,13 +28,13 @@ class Linovelib implements Plugin.PluginBase {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".module-rank-booklist .book-layout").each((i, el) => {
|
||||
let url = loadedCheerio(el).attr("href");
|
||||
loadedCheerio('.module-rank-booklist .book-layout').each((i, el) => {
|
||||
let 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)
|
||||
.find("div.book-cover > img")
|
||||
.attr("data-src");
|
||||
.find('div.book-cover > img')
|
||||
.attr('data-src');
|
||||
if (!url) return;
|
||||
|
||||
const novel = {
|
||||
@@ -60,27 +60,27 @@ class Linovelib implements Plugin.PluginBase {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
chapters: [],
|
||||
name: loadedCheerio("#bookDetailWrapper .book-title").text()
|
||||
name: loadedCheerio('#bookDetailWrapper .book-title').text(),
|
||||
};
|
||||
|
||||
novel.cover = loadedCheerio("#bookDetailWrapper img.book-cover").attr(
|
||||
"src"
|
||||
novel.cover = loadedCheerio('#bookDetailWrapper img.book-cover').attr(
|
||||
'src',
|
||||
);
|
||||
|
||||
novel.summary = loadedCheerio("#bookSummary content").text();
|
||||
novel.summary = loadedCheerio('#bookSummary content').text();
|
||||
|
||||
novel.author = loadedCheerio(
|
||||
"#bookDetailWrapper .book-rand-a a"
|
||||
).text();
|
||||
novel.author = loadedCheerio('#bookDetailWrapper .book-rand-a a').text();
|
||||
|
||||
const meta = loadedCheerio("#bookDetailWrapper .book-meta").text()
|
||||
novel.status = meta.includes("完结") ? NovelStatus.Completed : NovelStatus.Ongoing;
|
||||
const meta = loadedCheerio('#bookDetailWrapper .book-meta').text();
|
||||
novel.status = meta.includes('完结')
|
||||
? NovelStatus.Completed
|
||||
: NovelStatus.Ongoing;
|
||||
|
||||
novel.genres = loadedCheerio(".tag-small.red")
|
||||
.children("a")
|
||||
novel.genres = loadedCheerio('.tag-small.red')
|
||||
.children('a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
// Table of Content is on a different page than the summary page
|
||||
let chapter: Plugin.ChapterItem[] = [];
|
||||
@@ -88,22 +88,22 @@ class Linovelib implements Plugin.PluginBase {
|
||||
const idPattern = /\/(\d+)\.html/;
|
||||
const novelId = url.match(idPattern)?.[1];
|
||||
|
||||
const chaptersUrl =
|
||||
this.site + loadedCheerio("#btnReadBook").attr("href");
|
||||
const chaptersUrl = this.site + loadedCheerio('#btnReadBook').attr('href');
|
||||
const chaptersBody = await fetchText(chaptersUrl);
|
||||
|
||||
const chaptersLoadedCheerio = parseHTML(chaptersBody);
|
||||
|
||||
let volumeName: string, chapterId: number;
|
||||
|
||||
chaptersLoadedCheerio("#volumes .chapter-li:not(.volume-cover)").each((i, el) => {
|
||||
if (chaptersLoadedCheerio(el).hasClass("chapter-bar")) {
|
||||
chaptersLoadedCheerio('#volumes .chapter-li:not(.volume-cover)').each(
|
||||
(i, el) => {
|
||||
if (chaptersLoadedCheerio(el).hasClass('chapter-bar')) {
|
||||
volumeName = chaptersLoadedCheerio(el).text();
|
||||
return;
|
||||
} else {
|
||||
const urlPart = chaptersLoadedCheerio(el)
|
||||
.find(".chapter-li-a")
|
||||
.attr("href");
|
||||
.find('.chapter-li-a')
|
||||
.attr('href');
|
||||
const chapterIdMatch = urlPart?.match(idPattern);
|
||||
|
||||
// Sometimes the href attribute does not contain the url, but javascript:cid(0).
|
||||
@@ -118,8 +118,8 @@ class Linovelib implements Plugin.PluginBase {
|
||||
const chapterUrl = `/novel/${novelId}/${chapterId}.html`;
|
||||
const chapterName =
|
||||
volumeName +
|
||||
" — " +
|
||||
chaptersLoadedCheerio(el).find(".chapter-index").text().trim();
|
||||
' — ' +
|
||||
chaptersLoadedCheerio(el).find('.chapter-index').text().trim();
|
||||
const releaseDate = null;
|
||||
|
||||
if (!chapterId) return;
|
||||
@@ -129,7 +129,8 @@ class Linovelib implements Plugin.PluginBase {
|
||||
releaseTime: releaseDate,
|
||||
path: chapterUrl,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
novel.chapters = chapter;
|
||||
|
||||
@@ -138,10 +139,10 @@ class Linovelib implements Plugin.PluginBase {
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
let chapterName,
|
||||
chapterText = "",
|
||||
chapterText = '',
|
||||
hasNextPage,
|
||||
pageHasNextPage,
|
||||
pageText = "";
|
||||
pageText = '';
|
||||
let pageNumber = 1;
|
||||
|
||||
/*
|
||||
@@ -256,167 +257,163 @@ class Linovelib implements Plugin.PluginBase {
|
||||
// '': '裸',
|
||||
// };
|
||||
const skillgg: Record<string, string> = {
|
||||
"\u201c": "\u300c",
|
||||
"\u201d": "\u300d",
|
||||
"\u2018": "\u300e",
|
||||
"\u2019": "\u300f",
|
||||
"\ue82c": "\u7684",
|
||||
"\ue852": "\u4e00",
|
||||
"\ue82d": "\u662f",
|
||||
"\ue819": "\u4e86",
|
||||
"\ue856": "\u6211",
|
||||
"\ue857": "\u4e0d",
|
||||
"\ue816": "\u4eba",
|
||||
"\ue83c": "\u5728",
|
||||
"\ue830": "\u4ed6",
|
||||
"\ue82e": "\u6709",
|
||||
"\ue836": "\u8fd9",
|
||||
"\ue859": "\u4e2a",
|
||||
"\ue80a": "\u4e0a",
|
||||
"\ue855": "\u4eec",
|
||||
"\ue842": "\u6765",
|
||||
"\ue858": "\u5230",
|
||||
"\ue80b": "\u65f6",
|
||||
"\ue81f": "\u5927",
|
||||
"\ue84a": "\u5730",
|
||||
"\ue853": "\u4e3a",
|
||||
"\ue81e": "\u5b50",
|
||||
"\ue822": "\u4e2d",
|
||||
"\ue813": "\u4f60",
|
||||
"\ue85b": "\u8bf4",
|
||||
"\ue807": "\u751f",
|
||||
"\ue818": "\u56fd",
|
||||
"\ue810": "\u5e74",
|
||||
"\ue812": "\u7740",
|
||||
"\ue851": "\u5c31",
|
||||
"\ue801": "\u90a3",
|
||||
"\ue80c": "\u548c",
|
||||
"\ue815": "\u8981",
|
||||
"\ue84c": "\u5979",
|
||||
"\ue840": "\u51fa",
|
||||
"\ue848": "\u4e5f",
|
||||
"\ue835": "\u5f97",
|
||||
"\ue800": "\u91cc",
|
||||
"\ue826": "\u540e",
|
||||
"\ue863": "\u81ea",
|
||||
"\ue861": "\u4ee5",
|
||||
"\ue854": "\u4f1a",
|
||||
"\ue827": "\u5bb6",
|
||||
"\ue83b": "\u53ef",
|
||||
"\ue85d": "\u4e0b",
|
||||
"\ue84d": "\u800c",
|
||||
"\ue862": "\u8fc7",
|
||||
"\ue81c": "\u5929",
|
||||
"\ue81d": "\u53bb",
|
||||
"\ue860": "\u80fd",
|
||||
"\ue843": "\u5bf9",
|
||||
"\ue82f": "\u5c0f",
|
||||
"\ue802": "\u591a",
|
||||
"\ue831": "\u7136",
|
||||
"\ue84b": "\u4e8e",
|
||||
"\ue837": "\u5fc3",
|
||||
"\ue829": "\u5b66",
|
||||
"\ue85e": "\u4e48",
|
||||
"\ue83a": "\u4e4b",
|
||||
"\ue832": "\u90fd",
|
||||
"\ue808": "\u597d",
|
||||
"\ue841": "\u770b",
|
||||
"\ue821": "\u8d77",
|
||||
"\ue845": "\u53d1",
|
||||
"\ue803": "\u5f53",
|
||||
"\ue828": "\u6ca1",
|
||||
"\ue81b": "\u6210",
|
||||
"\ue83e": "\u53ea",
|
||||
"\ue820": "\u5982",
|
||||
"\ue84e": "\u4e8b",
|
||||
"\ue85a": "\u628a",
|
||||
"\ue806": "\u8fd8",
|
||||
"\ue83f": "\u7528",
|
||||
"\ue833": "\u7b2c",
|
||||
"\ue811": "\u6837",
|
||||
"\ue804": "\u9053",
|
||||
"\ue814": "\u60f3",
|
||||
"\ue80f": "\u4f5c",
|
||||
"\ue84f": "\u79cd",
|
||||
"\ue80e": "\u5f00",
|
||||
"\ue823": "\u7f8e",
|
||||
"\ue849": "\u4e73",
|
||||
"\ue805": "\u9634",
|
||||
"\ue809": "\u6db2",
|
||||
"\ue81a": "\u830e",
|
||||
"\ue844": "\u6b32",
|
||||
"\ue847": "\u547b",
|
||||
"\ue850": "\u8089",
|
||||
"\ue824": "\u4ea4",
|
||||
"\ue85f": "\u6027",
|
||||
"\ue817": "\u80f8",
|
||||
"\ue85c": "\u79c1",
|
||||
"\ue838": "\u7a74",
|
||||
"\ue82a": "\u6deb",
|
||||
"\ue83d": "\u81c0",
|
||||
"\ue82b": "\u8214",
|
||||
"\ue80d": "\u5c04",
|
||||
"\ue839": "\u8131",
|
||||
"\ue834": "\u88f8",
|
||||
"\ue846": "\u9a9a",
|
||||
"\ue825": "\u5507",
|
||||
'\u201c': '\u300c',
|
||||
'\u201d': '\u300d',
|
||||
'\u2018': '\u300e',
|
||||
'\u2019': '\u300f',
|
||||
'\ue82c': '\u7684',
|
||||
'\ue852': '\u4e00',
|
||||
'\ue82d': '\u662f',
|
||||
'\ue819': '\u4e86',
|
||||
'\ue856': '\u6211',
|
||||
'\ue857': '\u4e0d',
|
||||
'\ue816': '\u4eba',
|
||||
'\ue83c': '\u5728',
|
||||
'\ue830': '\u4ed6',
|
||||
'\ue82e': '\u6709',
|
||||
'\ue836': '\u8fd9',
|
||||
'\ue859': '\u4e2a',
|
||||
'\ue80a': '\u4e0a',
|
||||
'\ue855': '\u4eec',
|
||||
'\ue842': '\u6765',
|
||||
'\ue858': '\u5230',
|
||||
'\ue80b': '\u65f6',
|
||||
'\ue81f': '\u5927',
|
||||
'\ue84a': '\u5730',
|
||||
'\ue853': '\u4e3a',
|
||||
'\ue81e': '\u5b50',
|
||||
'\ue822': '\u4e2d',
|
||||
'\ue813': '\u4f60',
|
||||
'\ue85b': '\u8bf4',
|
||||
'\ue807': '\u751f',
|
||||
'\ue818': '\u56fd',
|
||||
'\ue810': '\u5e74',
|
||||
'\ue812': '\u7740',
|
||||
'\ue851': '\u5c31',
|
||||
'\ue801': '\u90a3',
|
||||
'\ue80c': '\u548c',
|
||||
'\ue815': '\u8981',
|
||||
'\ue84c': '\u5979',
|
||||
'\ue840': '\u51fa',
|
||||
'\ue848': '\u4e5f',
|
||||
'\ue835': '\u5f97',
|
||||
'\ue800': '\u91cc',
|
||||
'\ue826': '\u540e',
|
||||
'\ue863': '\u81ea',
|
||||
'\ue861': '\u4ee5',
|
||||
'\ue854': '\u4f1a',
|
||||
'\ue827': '\u5bb6',
|
||||
'\ue83b': '\u53ef',
|
||||
'\ue85d': '\u4e0b',
|
||||
'\ue84d': '\u800c',
|
||||
'\ue862': '\u8fc7',
|
||||
'\ue81c': '\u5929',
|
||||
'\ue81d': '\u53bb',
|
||||
'\ue860': '\u80fd',
|
||||
'\ue843': '\u5bf9',
|
||||
'\ue82f': '\u5c0f',
|
||||
'\ue802': '\u591a',
|
||||
'\ue831': '\u7136',
|
||||
'\ue84b': '\u4e8e',
|
||||
'\ue837': '\u5fc3',
|
||||
'\ue829': '\u5b66',
|
||||
'\ue85e': '\u4e48',
|
||||
'\ue83a': '\u4e4b',
|
||||
'\ue832': '\u90fd',
|
||||
'\ue808': '\u597d',
|
||||
'\ue841': '\u770b',
|
||||
'\ue821': '\u8d77',
|
||||
'\ue845': '\u53d1',
|
||||
'\ue803': '\u5f53',
|
||||
'\ue828': '\u6ca1',
|
||||
'\ue81b': '\u6210',
|
||||
'\ue83e': '\u53ea',
|
||||
'\ue820': '\u5982',
|
||||
'\ue84e': '\u4e8b',
|
||||
'\ue85a': '\u628a',
|
||||
'\ue806': '\u8fd8',
|
||||
'\ue83f': '\u7528',
|
||||
'\ue833': '\u7b2c',
|
||||
'\ue811': '\u6837',
|
||||
'\ue804': '\u9053',
|
||||
'\ue814': '\u60f3',
|
||||
'\ue80f': '\u4f5c',
|
||||
'\ue84f': '\u79cd',
|
||||
'\ue80e': '\u5f00',
|
||||
'\ue823': '\u7f8e',
|
||||
'\ue849': '\u4e73',
|
||||
'\ue805': '\u9634',
|
||||
'\ue809': '\u6db2',
|
||||
'\ue81a': '\u830e',
|
||||
'\ue844': '\u6b32',
|
||||
'\ue847': '\u547b',
|
||||
'\ue850': '\u8089',
|
||||
'\ue824': '\u4ea4',
|
||||
'\ue85f': '\u6027',
|
||||
'\ue817': '\u80f8',
|
||||
'\ue85c': '\u79c1',
|
||||
'\ue838': '\u7a74',
|
||||
'\ue82a': '\u6deb',
|
||||
'\ue83d': '\u81c0',
|
||||
'\ue82b': '\u8214',
|
||||
'\ue80d': '\u5c04',
|
||||
'\ue839': '\u8131',
|
||||
'\ue834': '\u88f8',
|
||||
'\ue846': '\u9a9a',
|
||||
'\ue825': '\u5507',
|
||||
};
|
||||
const addPage = async (pageCheerio: CheerioAPI) => {
|
||||
const formatPage = async () => {
|
||||
// Remove JS
|
||||
pageCheerio("#acontentz .adsbygoogle").remove();
|
||||
pageCheerio("#acontentz script").remove();
|
||||
pageCheerio('#acontentz .adsbygoogle').remove();
|
||||
pageCheerio('#acontentz script').remove();
|
||||
|
||||
// Load lazyloaded images
|
||||
pageCheerio("#acontentz img.imagecontent").each((i, el) => {
|
||||
pageCheerio('#acontentz img.imagecontent').each((i, el) => {
|
||||
// Sometimes images are either in data-src or src
|
||||
const imgSrc =
|
||||
pageCheerio(el).attr("data-src") ||
|
||||
pageCheerio(el).attr("src");
|
||||
pageCheerio(el).attr('data-src') || pageCheerio(el).attr('src');
|
||||
if (imgSrc) {
|
||||
// Clean up img element
|
||||
pageCheerio(el)
|
||||
.attr("src", imgSrc)
|
||||
.removeAttr("data-src")
|
||||
.removeClass("lazyload");
|
||||
.attr('src', imgSrc)
|
||||
.removeAttr('data-src')
|
||||
.removeClass('lazyload');
|
||||
}
|
||||
});
|
||||
|
||||
// Recover the original character
|
||||
pageText = pageCheerio("#acontentz").html() || "";
|
||||
pageText = pageText.replace(
|
||||
/./g,
|
||||
(char) => skillgg[char] || char
|
||||
);
|
||||
pageText = pageCheerio('#acontentz').html() || '';
|
||||
pageText = pageText.replace(/./g, char => skillgg[char] || char);
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
await formatPage();
|
||||
chapterName =
|
||||
pageCheerio("#atitle + h3").text() +
|
||||
" — " +
|
||||
pageCheerio("#atitle").text();
|
||||
if (chapterText === "") {
|
||||
chapterText = "<h2>" + chapterName + "</h2>";
|
||||
pageCheerio('#atitle + h3').text() +
|
||||
' — ' +
|
||||
pageCheerio('#atitle').text();
|
||||
if (chapterText === '') {
|
||||
chapterText = '<h2>' + chapterName + '</h2>';
|
||||
}
|
||||
chapterText += pageText;
|
||||
};
|
||||
|
||||
const loadPage = async (url: string) => {
|
||||
const headers = {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en;q=0.6,en-GB;q=0.5,en-US;q=0.4",
|
||||
"Cache-Control": "no-cache",
|
||||
}
|
||||
'Accept':
|
||||
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language':
|
||||
'zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en;q=0.6,en-GB;q=0.5,en-US;q=0.4',
|
||||
'Cache-Control': 'no-cache',
|
||||
};
|
||||
|
||||
const body = await fetchText(url, { headers });
|
||||
const pageCheerio = parseHTML(body);
|
||||
await addPage(pageCheerio);
|
||||
pageHasNextPage =
|
||||
pageCheerio("#footlink a:last").text() === "下一页"
|
||||
? true
|
||||
: false;
|
||||
pageCheerio('#footlink a:last').text() === '下一页' ? true : false;
|
||||
return { pageCheerio, pageHasNextPage };
|
||||
};
|
||||
|
||||
@@ -426,10 +423,7 @@ class Linovelib implements Plugin.PluginBase {
|
||||
hasNextPage = page.pageHasNextPage;
|
||||
if (hasNextPage === true) {
|
||||
pageNumber++;
|
||||
url = url.replace(
|
||||
/\.html/gi,
|
||||
`_${pageNumber}` + ".html"
|
||||
);
|
||||
url = url.replace(/\.html/gi, `_${pageNumber}` + '.html');
|
||||
}
|
||||
} while (hasNextPage === true);
|
||||
|
||||
@@ -438,7 +432,7 @@ class Linovelib implements Plugin.PluginBase {
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/search/${encodeURI(searchTerm)}_${pageNo}.html`;
|
||||
|
||||
@@ -504,15 +498,13 @@ class Linovelib implements Plugin.PluginBase {
|
||||
// const redirect = pageCheerio("div.book-layout").text();
|
||||
// await addPage(pageCheerio, redirect);
|
||||
|
||||
pageCheerio(".book-ol .book-layout").each((i, el) => {
|
||||
let nUrl = pageCheerio(el).attr("href");
|
||||
pageCheerio('.book-ol .book-layout').each((i, el) => {
|
||||
let 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)
|
||||
.find("div.book-cover > img")
|
||||
.attr("data-src");
|
||||
.find('div.book-cover > img')
|
||||
.attr('data-src');
|
||||
|
||||
if (!nUrl) return;
|
||||
|
||||
@@ -527,31 +519,29 @@ class Linovelib implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
const headers = new Headers(
|
||||
{
|
||||
"Referer": `${this.site}/`,
|
||||
}
|
||||
)
|
||||
const headers = new Headers({
|
||||
'Referer': `${this.site}/`,
|
||||
});
|
||||
return await fetchFile(url, { headers });
|
||||
}
|
||||
|
||||
filters = {
|
||||
rank: {
|
||||
label: "排行榜",
|
||||
value: "monthvisit",
|
||||
label: '排行榜',
|
||||
value: 'monthvisit',
|
||||
options: [
|
||||
{ label: "月点击榜", value: "monthvisit" },
|
||||
{ label: "周点击榜", value: "weekvisit" },
|
||||
{ label: "月推荐榜", value: "monthvote" },
|
||||
{ label: "周推荐榜", value: "weekvote" },
|
||||
{ label: "月鲜花榜", value: "monthflower" },
|
||||
{ label: "周鲜花榜", value: "weekflower" },
|
||||
{ label: "月鸡蛋榜", value: "monthegg" },
|
||||
{ label: "周鸡蛋榜", value: "weekegg" },
|
||||
{ label: "最近更新", value: "lastupdate" },
|
||||
{ label: "最新入库", value: "postdate" },
|
||||
{ label: "收藏榜", value: "goodnum" },
|
||||
{ label: "新书榜", value: "newhot" },
|
||||
{ label: '月点击榜', value: 'monthvisit' },
|
||||
{ label: '周点击榜', value: 'weekvisit' },
|
||||
{ label: '月推荐榜', value: 'monthvote' },
|
||||
{ label: '周推荐榜', value: 'weekvote' },
|
||||
{ label: '月鲜花榜', value: 'monthflower' },
|
||||
{ label: '周鲜花榜', value: 'weekflower' },
|
||||
{ label: '月鸡蛋榜', value: 'monthegg' },
|
||||
{ label: '周鸡蛋榜', value: 'weekegg' },
|
||||
{ label: '最近更新', value: 'lastupdate' },
|
||||
{ label: '最新入库', value: 'postdate' },
|
||||
{ label: '收藏榜', value: 'goodnum' },
|
||||
{ label: '新书榜', value: 'newhot' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+101
-96
@@ -1,37 +1,37 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import qs from "qs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import qs from 'qs';
|
||||
|
||||
class NovelsOnline implements Plugin.PluginBase {
|
||||
id = "NO.net";
|
||||
name = "novelsOnline";
|
||||
site = "https://novelsonline.net";
|
||||
icon = "src/en/novelsonline/icon.png";
|
||||
version = "1.0.0";
|
||||
id = 'NO.net';
|
||||
name = 'novelsOnline';
|
||||
site = 'https://novelsonline.net';
|
||||
icon = 'src/en/novelsonline/icon.png';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
if (filters.genre.value !== '')
|
||||
link += `/category/${filters.genre.value}/`;
|
||||
else
|
||||
link += `/top-novel/`;
|
||||
if (filters.genre.value !== '') link += `/category/${filters.genre.value}/`;
|
||||
else link += `/top-novel/`;
|
||||
|
||||
link += page
|
||||
link += `?change_type=${filters.sort.value}`
|
||||
link += page;
|
||||
link += `?change_type=${filters.sort.value}`;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novel: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('.top-novel-block').each((i,el) => {
|
||||
loadedCheerio('.top-novel-block').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h2').text();
|
||||
const novelCover = loadedCheerio(el).find('.top-novel-cover img').attr('src');
|
||||
const novelCover = loadedCheerio(el)
|
||||
.find('.top-novel-cover img')
|
||||
.attr('src');
|
||||
const novelUrl = loadedCheerio(el).find('h2 a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -39,51 +39,55 @@ class NovelsOnline implements Plugin.PluginBase {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
return novel;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const result = await fetchApi(this.site + novelPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
let $ = parseHTML(result);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: $("h1").text() || 'Untitled',
|
||||
cover: $(".novel-cover").find("a > img").attr("src"),
|
||||
name: $('h1').text() || 'Untitled',
|
||||
cover: $('.novel-cover').find('a > img').attr('src'),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
$('.novel-detail-item').each((i,el) => {
|
||||
$('.novel-detail-item').each((i, el) => {
|
||||
let detailName = $(el).find('h6').text();
|
||||
let detail = $(el).find('.novel-detail-body');
|
||||
|
||||
switch(detailName){
|
||||
case "Description":
|
||||
switch (detailName) {
|
||||
case 'Description':
|
||||
novel.summary = detail.text();
|
||||
break;
|
||||
case "Genre":
|
||||
novel.genres = detail.find('li')
|
||||
case 'Genre':
|
||||
novel.genres = detail
|
||||
.find('li')
|
||||
.map((_, el) => $(el).text())
|
||||
.get()
|
||||
.join(', ');
|
||||
break;
|
||||
case "Author(s)":
|
||||
novel.author = detail.find('li')
|
||||
case 'Author(s)':
|
||||
novel.author = detail
|
||||
.find('li')
|
||||
.map((_, el) => $(el).text())
|
||||
.get()
|
||||
.join(', ');
|
||||
break;
|
||||
case "Status":
|
||||
case 'Status':
|
||||
novel.status = detail.text().trim();
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
novel.chapters = $("ul.chapter-chs > li > a")
|
||||
novel.chapters = $('ul.chapter-chs > li > a')
|
||||
.map((_, el) => {
|
||||
const chapterUrl = $(el).attr("href");
|
||||
const chapterUrl = $(el).attr('href');
|
||||
const chapterName = $(el).text();
|
||||
|
||||
return {
|
||||
@@ -97,39 +101,40 @@ class NovelsOnline implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const result = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const chapterText = loadedCheerio("#contentall").html() || "";
|
||||
const chapterText = loadedCheerio('#contentall').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi(
|
||||
"https://novelsonline.net/sResults.php",
|
||||
{
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi('https://novelsonline.net/sResults.php', {
|
||||
headers: {
|
||||
Accept: "*/*",
|
||||
"Accept-Language": "pl,en-US;q=0.7,en;q=0.3",
|
||||
"Content-Type":
|
||||
"application/x-www-form-urlencoded; charset=UTF-8",
|
||||
Accept: '*/*',
|
||||
'Accept-Language': 'pl,en-US;q=0.7,en;q=0.3',
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({ q: searchTerm }),
|
||||
},
|
||||
).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
|
||||
const $ = parseHTML(result);
|
||||
|
||||
const headers = $("li");
|
||||
const headers = $('li');
|
||||
return headers
|
||||
.map((i, h) => {
|
||||
const novelName = $(h).text();
|
||||
const novelUrl = $(h).find("a").attr("href");
|
||||
const novelCover = $(h).find("img").attr("src");
|
||||
const novelUrl = $(h).find('a').attr('href');
|
||||
const novelCover = $(h).find('img').attr('src');
|
||||
|
||||
if (!novelUrl) return
|
||||
if (!novelUrl) return;
|
||||
|
||||
return {
|
||||
name: novelName,
|
||||
@@ -138,7 +143,7 @@ class NovelsOnline implements Plugin.PluginBase {
|
||||
};
|
||||
})
|
||||
.get()
|
||||
.filter((sr) => sr !== null)
|
||||
.filter(sr => sr !== null);
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
@@ -147,54 +152,54 @@ class NovelsOnline implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
value: "top_rated",
|
||||
label: "Sort by",
|
||||
value: 'top_rated',
|
||||
label: 'Sort by',
|
||||
options: [
|
||||
{ label: "Top Rated", value: "top_rated" },
|
||||
{ label: "Most Viewed", value: "view" },
|
||||
{ label: 'Top Rated', value: 'top_rated' },
|
||||
{ label: 'Most Viewed', value: 'view' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
value: "",
|
||||
label: "Category",
|
||||
value: '',
|
||||
label: 'Category',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Celebrity", value: "celebrity" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "School Life", value: "school-life" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Shotacon", value: "shotacon" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Shoujo Ai", value: "shoujo-ai" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Shounen Ai", value: "shounen-ai" },
|
||||
{ label: "Slice of Life", value: "slice-of-life" },
|
||||
{ label: "Sports", value: "sports" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: "Yuri", value: "yuri" }
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Celebrity', value: 'celebrity' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Shotacon', value: 'shotacon' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Shoujo Ai', value: 'shoujo-ai' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Shounen Ai', value: 'shounen-ai' },
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
{ label: 'Yuri', value: 'yuri' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
|
||||
class StorySeedlingPlugin implements Plugin.PluginBase {
|
||||
id = "storyseedling";
|
||||
name = "StorySeedling";
|
||||
icon = "src/en/storyseedling/icon.png";
|
||||
site = "https://storyseedling.com/";
|
||||
version = "1.0.0";
|
||||
id = 'storyseedling';
|
||||
name = 'StorySeedling';
|
||||
icon = 'src/en/storyseedling/icon.png';
|
||||
site = 'https://storyseedling.com/';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
let url = "https://storyseedling.com/ajax";
|
||||
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(postUrl).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
var postValue = loadedCheerio("div[ax-load][x-data]").attr("x-data")?.replace("browse('","").replace("')", "") as string;
|
||||
var postValue = loadedCheerio('div[ax-load][x-data]')
|
||||
.attr('x-data')
|
||||
?.replace("browse('", '')
|
||||
.replace("')", '') as string;
|
||||
|
||||
var data = new FormData();
|
||||
data.set("search", "");
|
||||
data.set("orderBy", "recent");
|
||||
data.set("curpage", pageNo.toString());
|
||||
data.set("post", postValue);
|
||||
data.set("action", "fetch_browse");
|
||||
data.set('search', '');
|
||||
data.set('orderBy', 'recent');
|
||||
data.set('curpage', pageNo.toString());
|
||||
data.set('post', postValue);
|
||||
data.set('action', 'fetch_browse');
|
||||
|
||||
var response = await(await fetchApi(url, {body: data, method: "POST"})).json();
|
||||
var response = await (
|
||||
await fetchApi(url, { body: data, method: 'POST' })
|
||||
).json();
|
||||
|
||||
response.data.posts.forEach((element: any) => {
|
||||
const novel = {
|
||||
name: element.title,
|
||||
cover: element.thumbnail,
|
||||
path: element.permalink.replace(this.site, ""),
|
||||
path: element.permalink.replace(this.site, ''),
|
||||
};
|
||||
novels.push(novel);
|
||||
});
|
||||
@@ -46,43 +51,54 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
|
||||
var site = this.site;
|
||||
const novel: Plugin.SourceNovel = {
|
||||
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);
|
||||
|
||||
novel.name = loadedCheerio("h1").text().trim();
|
||||
novel.name = loadedCheerio('h1').text().trim();
|
||||
|
||||
//novel.artist = "";
|
||||
//novel.author = "";
|
||||
novel.cover = loadedCheerio('img[x-ref="art"].w-full.rounded.shadow-md').attr("src");
|
||||
novel.cover = loadedCheerio(
|
||||
'img[x-ref="art"].w-full.rounded.shadow-md',
|
||||
).attr('src');
|
||||
if (!novel.cover) {
|
||||
novel.cover = defaultCover;
|
||||
}
|
||||
let genres:string[] = [];
|
||||
loadedCheerio('section[x-data="{ tab: location.hash.substr(1) || \'chapters\' }"].relative > div > div > div.flex.flex-wrap > a').each(function(){
|
||||
let genres: string[] = [];
|
||||
loadedCheerio(
|
||||
'section[x-data="{ tab: location.hash.substr(1) || \'chapters\' }"].relative > div > div > div.flex.flex-wrap > a',
|
||||
).each(function () {
|
||||
genres.push(loadedCheerio(this).text().trim());
|
||||
});
|
||||
novel.genres = genres.join(", ");
|
||||
novel.genres = genres.join(', ');
|
||||
// novel.status = NovelStatus.Completed;
|
||||
novel.summary = loadedCheerio("div.mb-4.order-2:not(.lg\\:grid-in-buttons)").text().trim().replace(/(\r\n|\n|\r)/gm, "");
|
||||
novel.summary = loadedCheerio('div.mb-4.order-2:not(.lg\\:grid-in-buttons)')
|
||||
.text()
|
||||
.trim()
|
||||
.replace(/(\r\n|\n|\r)/gm, '');
|
||||
|
||||
let chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("div.grid.w-full.grid-cols-1.gap-4.md\\:grid-cols-2 > a").each(function() {
|
||||
if(loadedCheerio(this).find("> div").length == 2){
|
||||
loadedCheerio(
|
||||
'div.grid.w-full.grid-cols-1.gap-4.md\\:grid-cols-2 > a',
|
||||
).each(function () {
|
||||
if (loadedCheerio(this).find('> div').length == 2) {
|
||||
return;
|
||||
}
|
||||
let name = loadedCheerio(this).find(".truncate").text().trim();
|
||||
let url = loadedCheerio(this).attr("href") as string;
|
||||
let releaseTime = loadedCheerio(this).find("div > div > small").text().trim();
|
||||
let chapterNumber = name.split("-")[0].trim().split(" ")[1];
|
||||
let name = loadedCheerio(this).find('.truncate').text().trim();
|
||||
let url = loadedCheerio(this).attr('href') as string;
|
||||
let releaseTime = loadedCheerio(this)
|
||||
.find('div > div > small')
|
||||
.text()
|
||||
.trim();
|
||||
let chapterNumber = name.split('-')[0].trim().split(' ')[1];
|
||||
const chapter: Plugin.ChapterItem = {
|
||||
name: name,
|
||||
path: url.replace(site, ""),
|
||||
path: url.replace(site, ''),
|
||||
releaseTime: releaseTime,
|
||||
chapterNumber: parseInt(chapterNumber),
|
||||
};
|
||||
@@ -92,40 +108,48 @@ class StorySeedlingPlugin implements Plugin.PluginBase {
|
||||
return novel;
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site+chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let t = loadedCheerio("div.justify-center > div.mb-4");
|
||||
let t = loadedCheerio('div.justify-center > div.mb-4');
|
||||
let chapterText = t.html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
let url = "https://storyseedling.com/ajax";
|
||||
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(postUrl).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
var postValue = loadedCheerio("div[ax-load][x-data]").attr("x-data")?.replace("browse('","").replace("')", "") as string;
|
||||
var postValue = loadedCheerio('div[ax-load][x-data]')
|
||||
.attr('x-data')
|
||||
?.replace("browse('", '')
|
||||
.replace("')", '') as string;
|
||||
|
||||
var data = new FormData();
|
||||
data.set("search", searchTerm);
|
||||
data.set("orderBy", "recent");
|
||||
data.set("curpage", pageNo.toString());
|
||||
data.set("post", postValue);
|
||||
data.set("action", "fetch_browse");
|
||||
data.set('search', searchTerm);
|
||||
data.set('orderBy', 'recent');
|
||||
data.set('curpage', pageNo.toString());
|
||||
data.set('post', postValue);
|
||||
data.set('action', 'fetch_browse');
|
||||
|
||||
var response = await(await fetchApi(url, {body: data, method: "POST"})).json();
|
||||
var response = await (
|
||||
await fetchApi(url, { body: data, method: 'POST' })
|
||||
).json();
|
||||
|
||||
response.data.posts.forEach((element: any) => {
|
||||
const novel = {
|
||||
name: element.title,
|
||||
cover: element.thumbnail,
|
||||
path: element.permalink.replace(this.site, ""),
|
||||
path: element.permalink.replace(this.site, ''),
|
||||
};
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { isUrlAbsolute } from "@libs/isAbsoluteUrl";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
|
||||
class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
id = "anf.net";
|
||||
name = "AllNovelFull";
|
||||
icon = "src/en/allnovelfull/icon.png";
|
||||
site = "https://allnovelfull.net";
|
||||
version = "1.0.0";
|
||||
id = 'anf.net';
|
||||
name = 'AllNovelFull';
|
||||
icon = 'src/en/allnovelfull/icon.png';
|
||||
site = 'https://allnovelfull.net';
|
||||
version = '1.0.0';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".col-truyen-main .list-truyen .row").each((i,el) => {
|
||||
const novelUrl = loadedCheerio(el).find("h3.truyen-title > a").attr("href");
|
||||
loadedCheerio('.col-truyen-main .list-truyen .row').each((i, el) => {
|
||||
const novelUrl = loadedCheerio(el)
|
||||
.find('h3.truyen-title > a')
|
||||
.attr('href');
|
||||
if (!novelUrl) return;
|
||||
|
||||
const novelName = loadedCheerio(el)
|
||||
.find("h3.truyen-title > a")
|
||||
.text();
|
||||
const novelName = loadedCheerio(el).find('h3.truyen-title > a').text();
|
||||
|
||||
let novelCover = loadedCheerio(el).find("img.cover").attr("src");
|
||||
if (novelCover && !isUrlAbsolute(novelCover)){
|
||||
let novelCover = loadedCheerio(el).find('img.cover').attr('src');
|
||||
if (novelCover && !isUrlAbsolute(novelCover)) {
|
||||
novelCover = this.site + novelCover;
|
||||
}
|
||||
|
||||
@@ -35,18 +35,16 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
|
||||
if (filters.genres.value.length)
|
||||
link += filters.genres.value;
|
||||
else
|
||||
link += filters.order.value;
|
||||
if (filters.genres.value.length) link += filters.genres.value;
|
||||
else link += filters.order.value;
|
||||
|
||||
link += `?page=${pageNo}`;
|
||||
|
||||
@@ -66,17 +64,17 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".book > img").attr("alt") || 'Untitled',
|
||||
cover: this.site + loadedCheerio(".book > img").attr("src"),
|
||||
summary: loadedCheerio(".desc-text").text().trim(),
|
||||
name: loadedCheerio('.book > img').attr('alt') || 'Untitled',
|
||||
cover: this.site + loadedCheerio('.book > img').attr('src'),
|
||||
summary: loadedCheerio('.desc-text').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
loadedCheerio(".info > div").each((i,el) => {
|
||||
const detailName = loadedCheerio(el).find("h3").text();
|
||||
loadedCheerio('.info > div').each((i, el) => {
|
||||
const detailName = loadedCheerio(el).find('h3').text();
|
||||
const detail = loadedCheerio(el)
|
||||
.find('a')
|
||||
.map((a,ex) => loadedCheerio(ex).text())
|
||||
.map((a, ex) => loadedCheerio(ex).text())
|
||||
.toArray()
|
||||
.join(',');
|
||||
|
||||
@@ -90,12 +88,13 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
case 'Genre:':
|
||||
novel.genres = detail;
|
||||
break;
|
||||
}});
|
||||
}
|
||||
});
|
||||
|
||||
const novelId = loadedCheerio("#rating").attr("data-novel-id");
|
||||
const novelId = loadedCheerio('#rating').attr('data-novel-id');
|
||||
|
||||
const getChapters = async (id: string) => {
|
||||
const chaptersUrl = this.site + "/ajax/chapter-option?novelId=" + id;
|
||||
const chaptersUrl = this.site + '/ajax/chapter-option?novelId=' + id;
|
||||
|
||||
const data = await fetchApi(chaptersUrl);
|
||||
const chapters = await data.text();
|
||||
@@ -104,11 +103,11 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("select > option").each((i,el) => {
|
||||
loadedCheerio('select > option').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).text();
|
||||
const chapterUrl = loadedCheerio(el).attr("value");
|
||||
const chapterUrl = loadedCheerio(el).attr('value');
|
||||
|
||||
if (!chapterUrl ) return;
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
@@ -117,14 +116,14 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
});
|
||||
|
||||
return chapter;
|
||||
}
|
||||
};
|
||||
|
||||
if (novelId) {
|
||||
novel.chapters = await getChapters(novelId);
|
||||
}
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
@@ -132,14 +131,14 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#chapter-content").html() || "";
|
||||
const chapterText = loadedCheerio('#chapter-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/search?keyword=${searchTerm}&page=${page}`;
|
||||
const result = await fetchApi(url);
|
||||
@@ -147,7 +146,7 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
};
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return await fetchFile(url);
|
||||
@@ -155,57 +154,57 @@ class AllNovelFullPlugin implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "/most-popular",
|
||||
label: "Order by",
|
||||
value: '/most-popular',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "Latest Release", value: "/latest-release-novel" },
|
||||
{ label: "Hot Novel", value: "/hot-novel" },
|
||||
{ label: "Completed Novel", value: "/completed-novel" },
|
||||
{ label: "Most Popular", value: "/most-popular" },
|
||||
{ label: 'Latest Release', value: '/latest-release-novel' },
|
||||
{ label: 'Hot Novel', value: '/hot-novel' },
|
||||
{ label: 'Completed Novel', value: '/completed-novel' },
|
||||
{ label: 'Most Popular', value: '/most-popular' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
value: "",
|
||||
label: "Genre",
|
||||
value: '',
|
||||
label: 'Genre',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Shounen", value: "/genre/Shounen" },
|
||||
{ label: "Harem", value: "/genre/Harem" },
|
||||
{ label: "Comedy", value: "/genre/Comedy" },
|
||||
{ label: "Martial Arts", value: "/genre/Martial+Arts" },
|
||||
{ label: "School Life", value: "/genre/School+Life" },
|
||||
{ label: "Mystery", value: "/genre/Mystery" },
|
||||
{ label: "Shoujo", value: "/genre/Shoujo" },
|
||||
{ label: "Romance", value: "/genre/Romance" },
|
||||
{ label: "Sci-fi", value: "/genre/Sci-fi" },
|
||||
{ label: "Gender Bender", value: "/genre/Gender+Bender" },
|
||||
{ label: "Mature", value: "/genre/Mature" },
|
||||
{ label: "Fantasy", value: "/genre/Fantasy" },
|
||||
{ label: "Horror", value: "/genre/Horror" },
|
||||
{ label: "Drama", value: "/genre/Drama" },
|
||||
{ label: "Tragedy", value: "/genre/Tragedy" },
|
||||
{ label: "Supernatural", value: "/genre/Supernatural" },
|
||||
{ label: "Ecchi", value: "/genre/Ecchi" },
|
||||
{ label: "Xuanhuan", value: "/genre/Xuanhuan" },
|
||||
{ label: "Adventure", value: "/genre/Adventure" },
|
||||
{ label: "Action", value: "/genre/Action" },
|
||||
{ label: "Psychological", value: "/genre/Psychological" },
|
||||
{ label: "Xianxia", value: "/genre/Xianxia" },
|
||||
{ label: "Wuxia", value: "/genre/Wuxia" },
|
||||
{ label: "Historical", value: "/genre/Historical" },
|
||||
{ label: "Slice of Life", value: "/genre/Slice+of+Life" },
|
||||
{ label: "Seinen", value: "/genre/Seinen" },
|
||||
{ label: "Lolicon", value: "/genre/Lolicon" },
|
||||
{ label: "Adult", value: "/genre/Adult" },
|
||||
{ label: "Josei", value: "/genre/Josei" },
|
||||
{ label: "Sports", value: "/genre/Sports" },
|
||||
{ label: "Smut", value: "/genre/Smut" },
|
||||
{ label: "Mecha", value: "/genre/Mecha" },
|
||||
{ label: "Yaoi", value: "/genre/Yaoi" },
|
||||
{ label: "Shounen Ai", value: "/genre/Shounen+Ai" },
|
||||
{ label: "Magical Realism", value: "/genre/Magical+Realism" },
|
||||
{ label: "Video Games", value: "/genre/Video+Games" },
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Shounen', value: '/genre/Shounen' },
|
||||
{ label: 'Harem', value: '/genre/Harem' },
|
||||
{ label: 'Comedy', value: '/genre/Comedy' },
|
||||
{ label: 'Martial Arts', value: '/genre/Martial+Arts' },
|
||||
{ label: 'School Life', value: '/genre/School+Life' },
|
||||
{ label: 'Mystery', value: '/genre/Mystery' },
|
||||
{ label: 'Shoujo', value: '/genre/Shoujo' },
|
||||
{ label: 'Romance', value: '/genre/Romance' },
|
||||
{ label: 'Sci-fi', value: '/genre/Sci-fi' },
|
||||
{ label: 'Gender Bender', value: '/genre/Gender+Bender' },
|
||||
{ label: 'Mature', value: '/genre/Mature' },
|
||||
{ label: 'Fantasy', value: '/genre/Fantasy' },
|
||||
{ label: 'Horror', value: '/genre/Horror' },
|
||||
{ label: 'Drama', value: '/genre/Drama' },
|
||||
{ label: 'Tragedy', value: '/genre/Tragedy' },
|
||||
{ label: 'Supernatural', value: '/genre/Supernatural' },
|
||||
{ label: 'Ecchi', value: '/genre/Ecchi' },
|
||||
{ label: 'Xuanhuan', value: '/genre/Xuanhuan' },
|
||||
{ label: 'Adventure', value: '/genre/Adventure' },
|
||||
{ label: 'Action', value: '/genre/Action' },
|
||||
{ label: 'Psychological', value: '/genre/Psychological' },
|
||||
{ label: 'Xianxia', value: '/genre/Xianxia' },
|
||||
{ label: 'Wuxia', value: '/genre/Wuxia' },
|
||||
{ label: 'Historical', value: '/genre/Historical' },
|
||||
{ label: 'Slice of Life', value: '/genre/Slice+of+Life' },
|
||||
{ label: 'Seinen', value: '/genre/Seinen' },
|
||||
{ label: 'Lolicon', value: '/genre/Lolicon' },
|
||||
{ label: 'Adult', value: '/genre/Adult' },
|
||||
{ label: 'Josei', value: '/genre/Josei' },
|
||||
{ label: 'Sports', value: '/genre/Sports' },
|
||||
{ label: 'Smut', value: '/genre/Smut' },
|
||||
{ label: 'Mecha', value: '/genre/Mecha' },
|
||||
{ label: 'Yaoi', value: '/genre/Yaoi' },
|
||||
{ label: 'Shounen Ai', value: '/genre/Shounen+Ai' },
|
||||
{ label: 'Magical Realism', value: '/genre/Magical+Realism' },
|
||||
{ label: 'Video Games', value: '/genre/Video+Games' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+134
-137
@@ -1,30 +1,30 @@
|
||||
import { CheerioAPI, load as parseHTML} from "cheerio";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
|
||||
class BLN implements Plugin.PluginBase {
|
||||
id = "BLN";
|
||||
name = "BestLightNovel";
|
||||
icon = "src/en/bestlightnovel/icon.png";
|
||||
site = "https://bestlightnovel.com/";
|
||||
version = "1.0.0";
|
||||
id = 'BLN';
|
||||
name = 'BestLightNovel';
|
||||
icon = 'src/en/bestlightnovel/icon.png';
|
||||
site = 'https://bestlightnovel.com/';
|
||||
version = '1.0.0';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".update_item.list_category").each((i,el) => {
|
||||
const novelUrl = loadedCheerio(el).find("h3 > a").attr("href");
|
||||
loadedCheerio('.update_item.list_category').each((i, el) => {
|
||||
const novelUrl = loadedCheerio(el).find('h3 > a').attr('href');
|
||||
if (!novelUrl) {
|
||||
// TODO: Handle error
|
||||
console.error("No novel url!");
|
||||
console.error('No novel url!');
|
||||
return;
|
||||
}
|
||||
|
||||
const novelName = loadedCheerio(el).find("h3 > a").text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelName = loadedCheerio(el).find('h3 > a').text();
|
||||
const novelCover = loadedCheerio(el).find('img').attr('src');
|
||||
|
||||
const novel = {
|
||||
name: novelName,
|
||||
@@ -40,9 +40,9 @@ class BLN implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site + "novel_list?"
|
||||
let link = this.site + 'novel_list?';
|
||||
link += 'type=' + filters.type.value;
|
||||
link += '&category=' + filters.category.value;
|
||||
link += '&state=' + filters.status.value;
|
||||
@@ -68,70 +68,68 @@ class BLN implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".truyen_info_right h1").text().trim() || "",
|
||||
cover: loadedCheerio(".info_image img").attr("src") || defaultCover,
|
||||
summary: loadedCheerio("#noidungm").text()?.trim() || "",
|
||||
name: loadedCheerio('.truyen_info_right h1').text().trim() || '',
|
||||
cover: loadedCheerio('.info_image img').attr('src') || defaultCover,
|
||||
summary: loadedCheerio('#noidungm').text()?.trim() || '',
|
||||
chapters: [],
|
||||
};
|
||||
loadedCheerio('ul.truyen_info_right > li').each(function() {
|
||||
loadedCheerio('ul.truyen_info_right > li').each(function () {
|
||||
const detailName = loadedCheerio(this).find('span').text();
|
||||
const detail = loadedCheerio(this)
|
||||
.find('a')
|
||||
.map((a,ex) => loadedCheerio(ex).text())
|
||||
.map((a, ex) => loadedCheerio(ex).text())
|
||||
.toArray()
|
||||
.join(', ');
|
||||
|
||||
switch(detailName) {
|
||||
case "Author(s): ":
|
||||
switch (detailName) {
|
||||
case 'Author(s): ':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "GENRES: ":
|
||||
case 'GENRES: ':
|
||||
novel.genres = detail;
|
||||
break;
|
||||
case "STATUS : ":
|
||||
case 'STATUS : ':
|
||||
novel.status =
|
||||
detail === "Ongoing"
|
||||
detail === 'Ongoing'
|
||||
? NovelStatus.Ongoing
|
||||
: detail === "Completed"
|
||||
: detail === 'Completed'
|
||||
? NovelStatus.Completed
|
||||
: NovelStatus.Unknown;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".chapter-list div.row").each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find("a").text().trim();
|
||||
const releaseDate = loadedCheerio(el)
|
||||
.find("span:last")
|
||||
.text()
|
||||
.trim();
|
||||
loadedCheerio('.chapter-list div.row').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('a').text().trim();
|
||||
const releaseDate = loadedCheerio(el).find('span:last').text().trim();
|
||||
|
||||
const months = [
|
||||
"jan",
|
||||
"feb",
|
||||
"mar",
|
||||
"apr",
|
||||
"may",
|
||||
"jun",
|
||||
"jul",
|
||||
"aug",
|
||||
"sep",
|
||||
"oct",
|
||||
"nov",
|
||||
"dec",
|
||||
].join("|");
|
||||
const rx = new RegExp(`(${months})-(\\d{1,2})-(\\d{2})`,'i')
|
||||
.exec(releaseDate);
|
||||
'jan',
|
||||
'feb',
|
||||
'mar',
|
||||
'apr',
|
||||
'may',
|
||||
'jun',
|
||||
'jul',
|
||||
'aug',
|
||||
'sep',
|
||||
'oct',
|
||||
'nov',
|
||||
'dec',
|
||||
].join('|');
|
||||
const rx = new RegExp(`(${months})-(\\d{1,2})-(\\d{2})`, 'i').exec(
|
||||
releaseDate,
|
||||
);
|
||||
if (!rx) return;
|
||||
const year = 2000 + +rx[3];
|
||||
const month = months.indexOf(rx[1].toLowerCase());
|
||||
const day = +rx[2];
|
||||
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
if (!chapterUrl) {
|
||||
// TODO: Handle error
|
||||
console.error("No chapter url!");
|
||||
console.error('No chapter url!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,14 +151,14 @@ class BLN implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#vung_doc").html() || "";
|
||||
const chapterText = loadedCheerio('#vung_doc').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}search_novels/${searchTerm}?page=${page}`;
|
||||
|
||||
@@ -177,99 +175,98 @@ class BLN implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
status: {
|
||||
label: "Status",
|
||||
value: "all",
|
||||
label: 'Status',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "ALL", value: "all" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: 'ALL', value: 'all' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
value: "topview",
|
||||
label: "Type",
|
||||
value: 'topview',
|
||||
label: 'Type',
|
||||
options: [
|
||||
{ label: "Recently updated", value: "latest" },
|
||||
{ label: "Newest", value: "newest" },
|
||||
{ label: "Top view", value: "topview" },
|
||||
{ label: 'Recently updated', value: 'latest' },
|
||||
{ label: 'Newest', value: 'newest' },
|
||||
{ label: 'Top view', value: 'topview' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
category: {
|
||||
label: "Category",
|
||||
value: "all",
|
||||
label: 'Category',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "ALL", value: "all" },
|
||||
{ label: "Action", value: "1" },
|
||||
{ label: "Adventure", value: "2" },
|
||||
{ label: "Animals", value: "65" },
|
||||
{ label: "Arts", value: "40" },
|
||||
{ label: "Biographies", value: "41" },
|
||||
{ label: "Business", value: "42" },
|
||||
{ label: "Chinese", value: "3" },
|
||||
{ label: "Comedy", value: "4" },
|
||||
{ label: "Computers", value: "43" },
|
||||
{ label: "Crafts, Hobbies", value: "45" },
|
||||
{ label: "Drama", value: "5" },
|
||||
{ label: "Education", value: "46" },
|
||||
{ label: "English", value: "6" },
|
||||
{ label: "Entertainment", value: "47" },
|
||||
{ label: "Fantasy", value: "7" },
|
||||
{ label: "Fiction", value: "48" },
|
||||
{ label: "Gender Bender", value: "8" },
|
||||
{ label: "Harem", value: "9" },
|
||||
{ label: "Historical", value: "10" },
|
||||
{ label: "History", value: "49" },
|
||||
{ label: "Home", value: "50" },
|
||||
{ label: "Horror", value: "11" },
|
||||
{ label: "Humor", value: "51" },
|
||||
{ label: "Investing", value: "52" },
|
||||
{ label: "Josei", value: "12" },
|
||||
{ label: "Korean", value: "13" },
|
||||
{ label: "Literature", value: "53" },
|
||||
{ label: "Lolicon", value: "14" },
|
||||
{ label: "Martial Arts", value: "15" },
|
||||
{ label: "Mature", value: "16" },
|
||||
{ label: "Mecha", value: "17" },
|
||||
{ label: "Memoirs", value: "54" },
|
||||
{ label: "Mystery", value: "18" },
|
||||
{ label: "Original", value: "19" },
|
||||
{ label: "Other Books", value: "66" },
|
||||
{ label: "Philosophy", value: "55" },
|
||||
{ label: "Photography", value: "56" },
|
||||
{ label: "Politics", value: "57" },
|
||||
{ label: "Professional", value: "58" },
|
||||
{ label: "Psychological", value: "20" },
|
||||
{ label: "Reference", value: "59" },
|
||||
{ label: "Reincarnation", value: "21" },
|
||||
{ label: "Religion", value: "60" },
|
||||
{ label: "Romance", value: "22" },
|
||||
{ label: "School Life", value: "23" },
|
||||
{ label: "School Stories", value: "67" },
|
||||
{ label: "Sci-Fi", value: "24" },
|
||||
{ label: "Seinen", value: "25" },
|
||||
{ label: "Short Stories", value: "68" },
|
||||
{ label: "Shotacon", value: "26" },
|
||||
{ label: "Shoujo", value: "27" },
|
||||
{ label: "Shoujo Ai", value: "28" },
|
||||
{ label: "Shounen", value: "29" },
|
||||
{ label: "Shounen Ai", value: "30" },
|
||||
{ label: "Slice Of Life", value: "31" },
|
||||
{ label: "Smut", value: "32" },
|
||||
{ label: "Social Science", value: "61" },
|
||||
{ label: "Spirituality", value: "62" },
|
||||
{ label: "Sports", value: "33" },
|
||||
{ label: "Supernatural", value: "34" },
|
||||
{ label: "Teaser", value: "69" },
|
||||
{ label: "Technical", value: "63" },
|
||||
{ label: "Technology", value: "64" },
|
||||
{ label: "Tragedy", value: "35" },
|
||||
{ label: "Virtual Reality", value: "36" },
|
||||
{ label: "Wuxia", value: "37" },
|
||||
{ label: "Xianxia", value: "38" },
|
||||
{ label: "Xuanhuan", value: "39" },
|
||||
|
||||
{ label: 'ALL', value: 'all' },
|
||||
{ label: 'Action', value: '1' },
|
||||
{ label: 'Adventure', value: '2' },
|
||||
{ label: 'Animals', value: '65' },
|
||||
{ label: 'Arts', value: '40' },
|
||||
{ label: 'Biographies', value: '41' },
|
||||
{ label: 'Business', value: '42' },
|
||||
{ label: 'Chinese', value: '3' },
|
||||
{ label: 'Comedy', value: '4' },
|
||||
{ label: 'Computers', value: '43' },
|
||||
{ label: 'Crafts, Hobbies', value: '45' },
|
||||
{ label: 'Drama', value: '5' },
|
||||
{ label: 'Education', value: '46' },
|
||||
{ label: 'English', value: '6' },
|
||||
{ label: 'Entertainment', value: '47' },
|
||||
{ label: 'Fantasy', value: '7' },
|
||||
{ label: 'Fiction', value: '48' },
|
||||
{ label: 'Gender Bender', value: '8' },
|
||||
{ label: 'Harem', value: '9' },
|
||||
{ label: 'Historical', value: '10' },
|
||||
{ label: 'History', value: '49' },
|
||||
{ label: 'Home', value: '50' },
|
||||
{ label: 'Horror', value: '11' },
|
||||
{ label: 'Humor', value: '51' },
|
||||
{ label: 'Investing', value: '52' },
|
||||
{ label: 'Josei', value: '12' },
|
||||
{ label: 'Korean', value: '13' },
|
||||
{ label: 'Literature', value: '53' },
|
||||
{ label: 'Lolicon', value: '14' },
|
||||
{ label: 'Martial Arts', value: '15' },
|
||||
{ label: 'Mature', value: '16' },
|
||||
{ label: 'Mecha', value: '17' },
|
||||
{ label: 'Memoirs', value: '54' },
|
||||
{ label: 'Mystery', value: '18' },
|
||||
{ label: 'Original', value: '19' },
|
||||
{ label: 'Other Books', value: '66' },
|
||||
{ label: 'Philosophy', value: '55' },
|
||||
{ label: 'Photography', value: '56' },
|
||||
{ label: 'Politics', value: '57' },
|
||||
{ label: 'Professional', value: '58' },
|
||||
{ label: 'Psychological', value: '20' },
|
||||
{ label: 'Reference', value: '59' },
|
||||
{ label: 'Reincarnation', value: '21' },
|
||||
{ label: 'Religion', value: '60' },
|
||||
{ label: 'Romance', value: '22' },
|
||||
{ label: 'School Life', value: '23' },
|
||||
{ label: 'School Stories', value: '67' },
|
||||
{ label: 'Sci-Fi', value: '24' },
|
||||
{ label: 'Seinen', value: '25' },
|
||||
{ label: 'Short Stories', value: '68' },
|
||||
{ label: 'Shotacon', value: '26' },
|
||||
{ label: 'Shoujo', value: '27' },
|
||||
{ label: 'Shoujo Ai', value: '28' },
|
||||
{ label: 'Shounen', value: '29' },
|
||||
{ label: 'Shounen Ai', value: '30' },
|
||||
{ label: 'Slice Of Life', value: '31' },
|
||||
{ label: 'Smut', value: '32' },
|
||||
{ label: 'Social Science', value: '61' },
|
||||
{ label: 'Spirituality', value: '62' },
|
||||
{ label: 'Sports', value: '33' },
|
||||
{ label: 'Supernatural', value: '34' },
|
||||
{ label: 'Teaser', value: '69' },
|
||||
{ label: 'Technical', value: '63' },
|
||||
{ label: 'Technology', value: '64' },
|
||||
{ label: 'Tragedy', value: '35' },
|
||||
{ label: 'Virtual Reality', value: '36' },
|
||||
{ label: 'Wuxia', value: '37' },
|
||||
{ label: 'Xianxia', value: '38' },
|
||||
{ label: 'Xuanhuan', value: '39' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import dayjs from "dayjs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
id = "comrademao";
|
||||
name = "Comrade Mao";
|
||||
site = "https://comrademao.com/";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/comrademao/icon.png";
|
||||
id = 'comrademao';
|
||||
name = 'Comrade Mao';
|
||||
site = 'https://comrademao.com/';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/comrademao/icon.png';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".listupd")
|
||||
.find("div.bs")
|
||||
loadedCheerio('.listupd')
|
||||
.find('div.bs')
|
||||
.each(function () {
|
||||
const novelName = loadedCheerio(this).find(".tt").text().trim();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
const novelName = loadedCheerio(this).find('.tt').text().trim();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -41,16 +41,16 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
|
||||
link += filters.category.value;
|
||||
|
||||
link += "page/" + pageNo + "/?post_type=novel";
|
||||
link += 'page/' + pageNo + '/?post_type=novel';
|
||||
|
||||
const body = await fetchApi(link).then((result) => result.text());
|
||||
const body = await fetchApi(link).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -59,25 +59,25 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio(".entry-title").text().trim();
|
||||
novel.name = loadedCheerio('.entry-title').text().trim();
|
||||
|
||||
novel.cover = loadedCheerio("div.thumbook > div > img").attr("src");
|
||||
novel.cover = loadedCheerio('div.thumbook > div > img').attr('src');
|
||||
|
||||
loadedCheerio(".infox .wd-full").each((i, el) => {
|
||||
const detailName = loadedCheerio(el).find("b").text();
|
||||
const detail = loadedCheerio(el).find("span").text();
|
||||
loadedCheerio('.infox .wd-full').each((i, el) => {
|
||||
const detailName = loadedCheerio(el).find('b').text();
|
||||
const detail = loadedCheerio(el).find('span').text();
|
||||
|
||||
switch (detailName) {
|
||||
case "Publisher: ":
|
||||
case 'Publisher: ':
|
||||
novel.author = detail.trim();
|
||||
break;
|
||||
case "Status: ":
|
||||
case 'Status: ':
|
||||
novel.status = detail.trim();
|
||||
break;
|
||||
case "Genres:":
|
||||
novel.genres = detail.trim().replace(/ , /g, ",");
|
||||
case 'Genres:':
|
||||
novel.genres = detail.trim().replace(/ , /g, ',');
|
||||
break;
|
||||
case "Description: ":
|
||||
case 'Description: ':
|
||||
novel.summary = detail.trim();
|
||||
break;
|
||||
}
|
||||
@@ -85,47 +85,43 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("#chapterlist")
|
||||
.find("li")
|
||||
loadedCheerio('#chapterlist')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||
const chapterUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!chapterUrl) return;
|
||||
|
||||
const chapterName = loadedCheerio(this)
|
||||
.find(".chapternum")
|
||||
.text();
|
||||
let releaseTime = loadedCheerio(this)
|
||||
.find(".chapterdate")
|
||||
.text();
|
||||
const chapterName = loadedCheerio(this).find('.chapternum').text();
|
||||
let releaseTime = loadedCheerio(this).find('.chapterdate').text();
|
||||
|
||||
if (releaseTime?.includes?.("ago")) {
|
||||
const timeAgo = releaseTime.match(/\d+/)?.[0] || "0";
|
||||
if (releaseTime?.includes?.('ago')) {
|
||||
const timeAgo = releaseTime.match(/\d+/)?.[0] || '0';
|
||||
const timeAgoInt = parseInt(timeAgo, 10);
|
||||
|
||||
if (timeAgoInt) {
|
||||
const dayJSDate = dayjs(); // today
|
||||
if (
|
||||
releaseTime.includes("hours ago") ||
|
||||
releaseTime.includes("hour ago")
|
||||
releaseTime.includes('hours ago') ||
|
||||
releaseTime.includes('hour ago')
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||
dayJSDate.subtract(timeAgoInt, 'hours'); // go back N hours
|
||||
}
|
||||
|
||||
if (
|
||||
releaseTime.includes("days ago") ||
|
||||
releaseTime.includes("day ago")
|
||||
releaseTime.includes('days ago') ||
|
||||
releaseTime.includes('day ago')
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||
dayJSDate.subtract(timeAgoInt, 'days'); // go back N days
|
||||
}
|
||||
|
||||
if (
|
||||
releaseTime.includes("months ago") ||
|
||||
releaseTime.includes("month ago")
|
||||
releaseTime.includes('months ago') ||
|
||||
releaseTime.includes('month ago')
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
||||
dayJSDate.subtract(timeAgoInt, 'months'); // go back N months
|
||||
}
|
||||
|
||||
releaseTime = dayJSDate.format("LL");
|
||||
releaseTime = dayJSDate.format('LL');
|
||||
}
|
||||
}
|
||||
chapter.push({
|
||||
@@ -141,10 +137,10 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const body = await fetchApi(chapterUrl).then((result) => result.text());
|
||||
const body = await fetchApi(chapterUrl).then(result => result.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#chaptercontent").html()?.trim() || "";
|
||||
const chapterText = loadedCheerio('#chaptercontent').html()?.trim() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
@@ -155,7 +151,7 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}page/${pageNo}/?s=${searchTerm}&post_type=novel`;
|
||||
|
||||
const body = await fetchApi(url).then((result) => result.text());
|
||||
const body = await fetchApi(url).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -167,14 +163,14 @@ class ComradeMaoPlugin implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
category: {
|
||||
value: "",
|
||||
label: "Type",
|
||||
value: '',
|
||||
label: 'Type',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Chinese", value: "/mtype/chinese/" },
|
||||
{ label: "Japanese", value: "/mtype/japanese/" },
|
||||
{ label: "Korean", value: "/mtype/korean/" },
|
||||
{ label: "Manhua", value: "/mtype/manhua/" },
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Chinese', value: '/mtype/chinese/' },
|
||||
{ label: 'Japanese', value: '/mtype/japanese/' },
|
||||
{ label: 'Korean', value: '/mtype/korean/' },
|
||||
{ label: 'Manhua', value: '/mtype/manhua/' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
|
||||
class DDLPlugin implements Plugin.PluginBase {
|
||||
id = "DDL.com";
|
||||
name = "Divine Dao Library";
|
||||
site = "https://www.divinedaolibrary.com/";
|
||||
version = "1.0.1";
|
||||
icon = "src/en/divinedaolibrary/icon.png";
|
||||
id = 'DDL.com';
|
||||
name = 'Divine Dao Library';
|
||||
site = 'https://www.divinedaolibrary.com/';
|
||||
version = '1.0.1';
|
||||
icon = 'src/en/divinedaolibrary/icon.png';
|
||||
filters?: undefined;
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI, searchTerm?: string){
|
||||
parseNovels(loadedCheerio: CheerioAPI, searchTerm?: string) {
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("#main")
|
||||
.find("li")
|
||||
.each((i,el) => {
|
||||
const novelName = loadedCheerio(el).find("a").text();
|
||||
loadedCheerio('#main')
|
||||
.find('li')
|
||||
.each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('a').text();
|
||||
const novelCover = defaultCover;
|
||||
const novelUrl = loadedCheerio(el).find("a").attr("href");
|
||||
const novelUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -33,20 +33,20 @@ class DDLPlugin implements Plugin.PluginBase {
|
||||
});
|
||||
|
||||
if (searchTerm) {
|
||||
novels = novels.filter((novel) =>
|
||||
novel.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
novels = novels.filter(novel =>
|
||||
novel.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
}
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions
|
||||
options: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site + "novels";
|
||||
let link = this.site + 'novels';
|
||||
|
||||
const body = await fetchApi(link).then((res) => res.text());
|
||||
const body = await fetchApi(link).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -60,29 +60,29 @@ class DDLPlugin implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.entry-title").text().trim() || "Untitled",
|
||||
name: loadedCheerio('h1.entry-title').text().trim() || 'Untitled',
|
||||
cover:
|
||||
loadedCheerio(".entry-content").find("img").attr("data-ezsrc") ||
|
||||
loadedCheerio('.entry-content').find('img').attr('data-ezsrc') ||
|
||||
defaultCover,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.summary = loadedCheerio("#main > article > div > p:nth-child(6)")
|
||||
novel.summary = loadedCheerio('#main > article > div > p:nth-child(6)')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
novel.author = loadedCheerio("#main > article > div > h3:nth-child(2)")
|
||||
novel.author = loadedCheerio('#main > article > div > h3:nth-child(2)')
|
||||
.text()
|
||||
.replace(/Author:/g, "")
|
||||
.replace(/Author:/g, '')
|
||||
.trim();
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("#main")
|
||||
.find("li > span > a")
|
||||
loadedCheerio('#main')
|
||||
.find('li > span > a')
|
||||
.each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).text().trim();
|
||||
const chapterUrl = loadedCheerio(el).attr("href");
|
||||
const chapterUrl = loadedCheerio(el).attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -95,7 +95,7 @@ class DDLPlugin implements Plugin.PluginBase {
|
||||
novel.chapters = chapter;
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
@@ -103,32 +103,31 @@ class DDLPlugin implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterName = loadedCheerio(".entry-title").text().trim();
|
||||
const chapterName = loadedCheerio('.entry-title').text().trim();
|
||||
|
||||
let chapterText = loadedCheerio(".entry-content").html();
|
||||
let chapterText = loadedCheerio('.entry-content').html();
|
||||
|
||||
if (!chapterText) {
|
||||
chapterText = loadedCheerio(".page-header").html();
|
||||
chapterText = loadedCheerio('.page-header').html();
|
||||
}
|
||||
|
||||
chapterText = `<p><h1>${chapterName}</h1></p>` + chapterText;
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "novels";
|
||||
let url = this.site + 'novels';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio, searchTerm);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return await fetchFile(url);
|
||||
|
||||
+95
-102
@@ -1,25 +1,24 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
id = "earlynovel";
|
||||
name = "Early Novel";
|
||||
version = "1.0.0";
|
||||
icon = "multisrc/madara/latestnovel.png";
|
||||
site = "https://earlynovel.net/";
|
||||
id = 'earlynovel';
|
||||
name = 'Early Novel';
|
||||
version = '1.0.0';
|
||||
icon = 'multisrc/madara/latestnovel.png';
|
||||
site = 'https://earlynovel.net/';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".col-truyen-main > .list-truyen > .row").each(
|
||||
(i, el) => {
|
||||
const novelUrl = loadedCheerio(el).find("h3.truyen-title > a").attr("href");
|
||||
const novelName = loadedCheerio(el)
|
||||
.find("h3.truyen-title > a")
|
||||
.text();
|
||||
const novelCover = loadedCheerio(el).find('.lazyimg').attr("data-image");
|
||||
loadedCheerio('.col-truyen-main > .list-truyen > .row').each((i, el) => {
|
||||
const novelUrl = loadedCheerio(el)
|
||||
.find('h3.truyen-title > a')
|
||||
.attr('href');
|
||||
const novelName = loadedCheerio(el).find('h3.truyen-title > a').text();
|
||||
const novelCover = loadedCheerio(el).find('.lazyimg').attr('data-image');
|
||||
|
||||
if (!novelUrl) return;
|
||||
novels.push({
|
||||
@@ -27,51 +26,45 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
parseChapters(loadedCheerio:CheerioAPI){
|
||||
parseChapters(loadedCheerio: CheerioAPI) {
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio("ul.list-chapter > li").each((i,el) => {
|
||||
const chapterName = loadedCheerio(el)
|
||||
.find(".chapter-text")
|
||||
.text()
|
||||
.trim();
|
||||
const chapterUrl = loadedCheerio(el)
|
||||
.find("a")
|
||||
.attr("href")
|
||||
?.slice(1);
|
||||
loadedCheerio('ul.list-chapter > li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('.chapter-text').text().trim();
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href')?.slice(1);
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl
|
||||
})});
|
||||
path: chapterUrl,
|
||||
});
|
||||
});
|
||||
return chapter;
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
|
||||
if (filters.genres.value.length)
|
||||
link += filters.genres.value;
|
||||
else
|
||||
link += filters.order.value;
|
||||
if (filters.genres.value.length) link += filters.genres.value;
|
||||
else link += filters.order.value;
|
||||
|
||||
link += `?page=${pageNo}`;
|
||||
|
||||
const body = await fetchApi(link).then((res) => res.text());
|
||||
const body = await fetchApi(link).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & {totalPages: number}> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const result = await fetchApi(this.site + novelPath);
|
||||
const body = await result.text();
|
||||
|
||||
@@ -79,33 +72,33 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
loadedCheerio('.glyphicon-menu-right').closest('li').remove();
|
||||
const pagenav = loadedCheerio('.page-nav').prev().find('a');
|
||||
const lastPageStr = pagenav.attr('title')?.match(/(\d+)/);
|
||||
const lastPage = Number(lastPageStr?.[1] || "0");
|
||||
const lastPage = Number(lastPageStr?.[1] || '0');
|
||||
|
||||
const novel: Plugin.SourceNovel & {totalPages: number}= {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".book > img").attr("alt") || "Untitled",
|
||||
cover: loadedCheerio(".book > img").attr("src"),
|
||||
summary: loadedCheerio(".desc-text").text().trim(),
|
||||
name: loadedCheerio('.book > img').attr('alt') || 'Untitled',
|
||||
cover: loadedCheerio('.book > img').attr('src'),
|
||||
summary: loadedCheerio('.desc-text').text().trim(),
|
||||
chapters: [],
|
||||
totalPages: lastPage
|
||||
totalPages: lastPage,
|
||||
};
|
||||
|
||||
loadedCheerio(".info > div > h3").each(function () {
|
||||
loadedCheerio('.info > div > h3').each(function () {
|
||||
let detailName = loadedCheerio(this).text();
|
||||
let detail = loadedCheerio(this)
|
||||
.siblings()
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
switch (detailName) {
|
||||
case "Author:":
|
||||
case 'Author:':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Status:":
|
||||
case 'Status:':
|
||||
novel.status = detail;
|
||||
break;
|
||||
case "Genre:":
|
||||
case 'Genre:':
|
||||
novel.genres = detail;
|
||||
break;
|
||||
}
|
||||
@@ -117,10 +110,10 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const url = this.site + novelPath + '?page=' + page;
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapters = this.parseChapters(loadedCheerio);
|
||||
return {chapters}
|
||||
return { chapters };
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
@@ -129,14 +122,14 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#chapter-c").html() || "";
|
||||
const chapterText = loadedCheerio('#chapter-c').html() || '';
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}search?keyword=${searchTerm}`;
|
||||
const result = await fetchApi(url);
|
||||
@@ -144,7 +137,7 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
};
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return await fetchFile(url);
|
||||
@@ -152,58 +145,58 @@ class EarlyNovelPlugin implements Plugin.PagePlugin {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "/most-popular",
|
||||
label: "Order by",
|
||||
value: '/most-popular',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "Latest Release", value: "/latest-release-novel" },
|
||||
{ label: "Hot Novel", value: "/hot-novel" },
|
||||
{ label: "Completed Novel", value: "/completed-novel" },
|
||||
{ label: "Most Popular", value: "/most-popular" },
|
||||
{ label: 'Latest Release', value: '/latest-release-novel' },
|
||||
{ label: 'Hot Novel', value: '/hot-novel' },
|
||||
{ label: 'Completed Novel', value: '/completed-novel' },
|
||||
{ label: 'Most Popular', value: '/most-popular' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
value: "",
|
||||
label: "Genre",
|
||||
value: '',
|
||||
label: 'Genre',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Action", value: "/genre/action-1" },
|
||||
{ label: "Adult", value: "/genre/adult-2" },
|
||||
{ label: "Adventure", value: "/genre/adventure-3" },
|
||||
{ label: "Comedy", value: "/genre/comedy-4" },
|
||||
{ label: "Drama", value: "/genre/drama-5" },
|
||||
{ label: "Ecchi", value: "/genre/ecchi-6" },
|
||||
{ label: "Fantasy", value: "/genre/fantasy-7" },
|
||||
{ label: "Gender Bender", value: "/genre/gender-bender-8" },
|
||||
{ label: "Harem", value: "/genre/harem-9" },
|
||||
{ label: "Historical", value: "/genre/historical-10" },
|
||||
{ label: "Horror", value: "/genre/horror-11" },
|
||||
{ label: "Josei", value: "/genre/josei-12" },
|
||||
{ label: "Martial Arts", value: "/genre/martial-arts-13" },
|
||||
{ label: "Mature", value: "/genre/mature-14" },
|
||||
{ label: "Mecha", value: "/genre/mecha-15" },
|
||||
{ label: "Mystery", value: "/genre/mystery-16" },
|
||||
{ label: "Psychological", value: "/genre/psychological-17" },
|
||||
{ label: "Romance", value: "/genre/romance-18" },
|
||||
{ label: "School Life", value: "/genre/school-life-19" },
|
||||
{ label: "Sci-fi", value: "/genre/sci-fi-20" },
|
||||
{ label: "Seinen", value: "/genre/seinen-21" },
|
||||
{ label: "Shoujo", value: "/genre/shoujo-22" },
|
||||
{ label: "Shoujo Ai", value: "/genre/shoujo-ai-23" },
|
||||
{ label: "Shounen", value: "/genre/shounen-24" },
|
||||
{ label: "Shounen Ai", value: "/genre/shounen-ai-25" },
|
||||
{ label: "Slice of Life", value: "/genre/slice-of-life-26" },
|
||||
{ label: "Smut", value: "/genre/smut-27" },
|
||||
{ label: "Sports", value: "/genre/sports-28" },
|
||||
{ label: "Supernatural", value: "/genre/supernatural-29" },
|
||||
{ label: "Tragedy", value: "/genre/tragedy-30" },
|
||||
{ label: "Wuxia", value: "/genre/wuxia-31" },
|
||||
{ label: "Xianxia", value: "/genre/xianxia-32" },
|
||||
{ label: "Xuanhuan", value: "/genre/xuanhuan-33" },
|
||||
{ label: "Yaoi", value: "/genre/yaoi-34" },
|
||||
{ label: "Yuri", value: "/genre/yuri-35" },
|
||||
{ label: "Video Games", value: "/genre/video-games-36" },
|
||||
{ label: "Magical Realism", value: "/genre/magical-realism-37" },
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Action', value: '/genre/action-1' },
|
||||
{ label: 'Adult', value: '/genre/adult-2' },
|
||||
{ label: 'Adventure', value: '/genre/adventure-3' },
|
||||
{ label: 'Comedy', value: '/genre/comedy-4' },
|
||||
{ label: 'Drama', value: '/genre/drama-5' },
|
||||
{ label: 'Ecchi', value: '/genre/ecchi-6' },
|
||||
{ label: 'Fantasy', value: '/genre/fantasy-7' },
|
||||
{ label: 'Gender Bender', value: '/genre/gender-bender-8' },
|
||||
{ label: 'Harem', value: '/genre/harem-9' },
|
||||
{ label: 'Historical', value: '/genre/historical-10' },
|
||||
{ label: 'Horror', value: '/genre/horror-11' },
|
||||
{ label: 'Josei', value: '/genre/josei-12' },
|
||||
{ label: 'Martial Arts', value: '/genre/martial-arts-13' },
|
||||
{ label: 'Mature', value: '/genre/mature-14' },
|
||||
{ label: 'Mecha', value: '/genre/mecha-15' },
|
||||
{ label: 'Mystery', value: '/genre/mystery-16' },
|
||||
{ label: 'Psychological', value: '/genre/psychological-17' },
|
||||
{ label: 'Romance', value: '/genre/romance-18' },
|
||||
{ label: 'School Life', value: '/genre/school-life-19' },
|
||||
{ label: 'Sci-fi', value: '/genre/sci-fi-20' },
|
||||
{ label: 'Seinen', value: '/genre/seinen-21' },
|
||||
{ label: 'Shoujo', value: '/genre/shoujo-22' },
|
||||
{ label: 'Shoujo Ai', value: '/genre/shoujo-ai-23' },
|
||||
{ label: 'Shounen', value: '/genre/shounen-24' },
|
||||
{ label: 'Shounen Ai', value: '/genre/shounen-ai-25' },
|
||||
{ label: 'Slice of Life', value: '/genre/slice-of-life-26' },
|
||||
{ label: 'Smut', value: '/genre/smut-27' },
|
||||
{ label: 'Sports', value: '/genre/sports-28' },
|
||||
{ label: 'Supernatural', value: '/genre/supernatural-29' },
|
||||
{ label: 'Tragedy', value: '/genre/tragedy-30' },
|
||||
{ label: 'Wuxia', value: '/genre/wuxia-31' },
|
||||
{ label: 'Xianxia', value: '/genre/xianxia-32' },
|
||||
{ label: 'Xuanhuan', value: '/genre/xuanhuan-33' },
|
||||
{ label: 'Yaoi', value: '/genre/yaoi-34' },
|
||||
{ label: 'Yuri', value: '/genre/yuri-35' },
|
||||
{ label: 'Video Games', value: '/genre/video-games-36' },
|
||||
{ label: 'Magical Realism', value: '/genre/magical-realism-37' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class FaqWikiUs implements Plugin.PluginBase {
|
||||
id = "FWK.US";
|
||||
name = "Faq Wiki";
|
||||
site = "https://faqwiki.us/";
|
||||
version = "1.1.1";
|
||||
icon = "src/en/faqwikius/icon.png";
|
||||
id = 'FWK.US';
|
||||
name = 'Faq Wiki';
|
||||
site = 'https://faqwiki.us/';
|
||||
version = '1.1.1';
|
||||
icon = 'src/en/faqwikius/icon.png';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI, searchTerm?: string) {
|
||||
parseNovels(loadedCheerio: CheerioAPI, searchTerm?: string) {
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("figure.wp-block-image").each((index, element) => {
|
||||
loadedCheerio('figure.wp-block-image').each((index, element) => {
|
||||
const name = loadedCheerio(element).text();
|
||||
let cover = loadedCheerio(element).find("img").attr("data-ezsrc");
|
||||
let cover = loadedCheerio(element).find('img').attr('data-ezsrc');
|
||||
|
||||
// Remove the appended query string
|
||||
if (cover) {
|
||||
cover = cover.replace(/\?ezimgfmt=.*$/, ''); // Regular expression magic!
|
||||
} else {
|
||||
cover = loadedCheerio(element).find("img").attr("src");
|
||||
cover = loadedCheerio(element).find('img').attr('src');
|
||||
}
|
||||
|
||||
const path = loadedCheerio(element).find("a").attr("href")?.replace('tp:', 'tps:')?.slice(this.site.length);
|
||||
const path = loadedCheerio(element)
|
||||
.find('a')
|
||||
.attr('href')
|
||||
?.replace('tp:', 'tps:')
|
||||
?.slice(this.site.length);
|
||||
if (!path) return;
|
||||
|
||||
novels.push({ name, cover, path });
|
||||
})
|
||||
});
|
||||
|
||||
if (searchTerm) {
|
||||
novels = novels.filter((novel) =>
|
||||
novel.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
novels = novels.filter(novel =>
|
||||
novel.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,56 +47,72 @@ class FaqWikiUs implements Plugin.PluginBase {
|
||||
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);
|
||||
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(path: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + path).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + path).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path,
|
||||
name: ""
|
||||
name: '',
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio(".entry-title").text().replace("Novel – All Chapters", "").trim();
|
||||
novel.name = loadedCheerio('.entry-title')
|
||||
.text()
|
||||
.replace('Novel – All Chapters', '')
|
||||
.trim();
|
||||
|
||||
novel.cover = loadedCheerio(".wp-block-image")
|
||||
.find("img")
|
||||
.attr("data-ezsrc")
|
||||
novel.cover = loadedCheerio('.wp-block-image')
|
||||
.find('img')
|
||||
.attr('data-ezsrc')
|
||||
?.replace(/\?ezimgfmt=.*$/, ''); // Regular expression magic!
|
||||
|
||||
const status = loadedCheerio("#lcp_instance_0 +:icontains('complete')").text();
|
||||
novel.status = (status ? NovelStatus.Completed : NovelStatus.Ongoing)
|
||||
const status = loadedCheerio(
|
||||
"#lcp_instance_0 +:icontains('complete')",
|
||||
).text();
|
||||
novel.status = status ? NovelStatus.Completed : NovelStatus.Ongoing;
|
||||
|
||||
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>')!);
|
||||
|
||||
loadedCheerio(".book-review-block__meta-item-value strong").each((i,el) => {
|
||||
const key = loadedCheerio(el).text().replace(':', '').trim().toLowerCase();
|
||||
loadedCheerio('.book-review-block__meta-item-value strong').each(
|
||||
(i, el) => {
|
||||
const key = loadedCheerio(el)
|
||||
.text()
|
||||
.replace(':', '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const value = loadedCheerio(el).next().text().replace(':', '').trim();
|
||||
|
||||
switch(key){
|
||||
case "description":
|
||||
switch (key) {
|
||||
case 'description':
|
||||
novel.summary = value;
|
||||
break;
|
||||
case "author(s)":
|
||||
case 'author(s)':
|
||||
novel.author = value;
|
||||
break;
|
||||
case "genre":
|
||||
case 'genre':
|
||||
novel.genres = value.split(' ').join(',');
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio(".lcp_catlist > li > a")
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio(
|
||||
'.lcp_catlist > li > a',
|
||||
)
|
||||
.map((chapterIndex, element) => ({
|
||||
name: loadedCheerio(element).text().replace(novel.name + "", "").replace("Novel" + "", "").trim(),
|
||||
path: ""+loadedCheerio(element).attr("href")?.slice(this.site.length),
|
||||
name: loadedCheerio(element)
|
||||
.text()
|
||||
.replace(novel.name + '', '')
|
||||
.replace('Novel' + '', '')
|
||||
.trim(),
|
||||
path: '' + loadedCheerio(element).attr('href')?.slice(this.site.length),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
}))
|
||||
.get();
|
||||
@@ -102,35 +122,41 @@ class FaqWikiUs implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
loadedCheerio("span").remove();
|
||||
loadedCheerio('span').remove();
|
||||
|
||||
const chapterParagraphs = loadedCheerio(".entry-content p");
|
||||
const chapterParagraphs = loadedCheerio('.entry-content p');
|
||||
|
||||
let chapterContent; // Variable to store the result
|
||||
|
||||
if (chapterParagraphs.length < 5) { //some chapter in this site store their whole text in 1-4 <p>,
|
||||
chapterContent = chapterParagraphs.map((index, element) => {
|
||||
const text = loadedCheerio(element).html()
|
||||
if (chapterParagraphs.length < 5) {
|
||||
//some chapter in this site store their whole text in 1-4 <p>,
|
||||
chapterContent = chapterParagraphs
|
||||
.map((index, element) => {
|
||||
const text = loadedCheerio(element).html();
|
||||
return text;
|
||||
}).get().join('\n\n');
|
||||
}
|
||||
|
||||
else {
|
||||
})
|
||||
.get()
|
||||
.join('\n\n');
|
||||
} else {
|
||||
// Multiple paragraphs case
|
||||
chapterContent = chapterParagraphs.map((index, element) => {
|
||||
chapterContent = chapterParagraphs
|
||||
.map((index, element) => {
|
||||
const text = loadedCheerio(element).text().trim();
|
||||
return `<p>${text}</p>`;
|
||||
}).get().join('\n\n');
|
||||
})
|
||||
.get()
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
return chapterContent;
|
||||
}
|
||||
|
||||
|
||||
async searchNovels(searchTerm: string): 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);
|
||||
|
||||
return this.parseNovels(loadedCheerio, searchTerm);
|
||||
|
||||
@@ -1,73 +1,78 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import qs from "qs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import qs from 'qs';
|
||||
|
||||
class FreeWebNovel implements Plugin.PluginBase {
|
||||
id = "FWN.com";
|
||||
name = "Free Web Novel";
|
||||
site = "https://freewebnovel.com";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/freewebnovel/icon.png";
|
||||
id = 'FWN.com';
|
||||
name = 'Free Web Novel';
|
||||
site = 'https://freewebnovel.com';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/freewebnovel/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const sort = showLatestNovels
|
||||
? "/latest-release-novels/"
|
||||
: "/completed-novels/";
|
||||
? '/latest-release-novels/'
|
||||
: '/completed-novels/';
|
||||
|
||||
const body = await fetchApi(this.site + sort + page).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + sort + page).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row")
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('.li-row')
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find(".tit").text() || "",
|
||||
cover: loadedCheerio(element).find("img").attr("src"),
|
||||
path: loadedCheerio(element).find("h3 > a").attr("href") || "",
|
||||
name: loadedCheerio(element).find('.tit').text() || '',
|
||||
cover: loadedCheerio(element).find('img').attr('src'),
|
||||
path: loadedCheerio(element).find('h3 > a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.tit").text(),
|
||||
cover: loadedCheerio(".pic > img").attr("src"),
|
||||
summary: loadedCheerio(".inner").text().trim(),
|
||||
name: loadedCheerio('h1.tit').text(),
|
||||
cover: loadedCheerio('.pic > img').attr('src'),
|
||||
summary: loadedCheerio('.inner').text().trim(),
|
||||
};
|
||||
|
||||
novel.genres = loadedCheerio("[title=Genre]")
|
||||
novel.genres = loadedCheerio('[title=Genre]')
|
||||
.next()
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
.replace(/[\t\n]/g, '');
|
||||
|
||||
novel.author = loadedCheerio("[title=Author]")
|
||||
novel.author = loadedCheerio('[title=Author]')
|
||||
.next()
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
.replace(/[\t\n]/g, '');
|
||||
|
||||
novel.status = loadedCheerio("[title=Status]")
|
||||
novel.status = loadedCheerio('[title=Status]')
|
||||
.next()
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
.replace(/[\t\n]/g, '');
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio("#idData > li > a")
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio('#idData > li > a')
|
||||
.map((chapterIndex, element) => ({
|
||||
name:
|
||||
loadedCheerio(element).attr("title") ||
|
||||
"Chapter " + (chapterIndex + 1),
|
||||
loadedCheerio(element).attr('title') ||
|
||||
'Chapter ' + (chapterIndex + 1),
|
||||
path:
|
||||
loadedCheerio(element).attr("href") ||
|
||||
novelPath.replace(".html", "/chapter-" + (chapterIndex + 1) + ".html"),
|
||||
loadedCheerio(element).attr('href') ||
|
||||
novelPath.replace(
|
||||
'.html',
|
||||
'/chapter-' + (chapterIndex + 1) + '.html',
|
||||
),
|
||||
releaseTime: null,
|
||||
chapterNumber: chapterIndex + 1,
|
||||
}))
|
||||
@@ -78,33 +83,35 @@ class FreeWebNovel implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("div.txt").html() || "";
|
||||
const chapterText = loadedCheerio('div.txt').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(this.site + "/search/", {
|
||||
const body = await fetchApi(this.site + '/search/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.site,
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({ searchkey: searchTerm }),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio(".li-row > .li > .con")
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('.li-row > .li > .con')
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find(".tit").text() || "",
|
||||
cover: loadedCheerio(element).find(".pic > a > img").attr("src"),
|
||||
path: loadedCheerio(element).find("h3 > a").attr("href") || "",
|
||||
name: loadedCheerio(element).find('.tit').text() || '',
|
||||
cover: loadedCheerio(element).find('.pic > a > img').attr('src'),
|
||||
path: loadedCheerio(element).find('h3 > a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
+111
-110
@@ -1,23 +1,23 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import dayjs from "dayjs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class LightNovelPub implements Plugin.PagePlugin {
|
||||
id = "lightnovelpub";
|
||||
name = "LightNovelPub";
|
||||
version = "1.0.1";
|
||||
icon = "src/en/lightnovelpub/icon.png";
|
||||
site = "https://www.lightnovelpub.com/";
|
||||
id = 'lightnovelpub';
|
||||
name = 'LightNovelPub';
|
||||
version = '1.0.1';
|
||||
icon = 'src/en/lightnovelpub/icon.png';
|
||||
site = 'https://www.lightnovelpub.com/';
|
||||
headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}browse/`;
|
||||
link += `${filters.genres.value}/`;
|
||||
@@ -25,24 +25,20 @@ class LightNovelPub implements Plugin.PagePlugin {
|
||||
link += `${filters.status.value}/`;
|
||||
link += page;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".novel-item.ads").remove();
|
||||
loadedCheerio('.novel-item.ads').remove();
|
||||
|
||||
loadedCheerio(".novel-item").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find(".novel-title")
|
||||
.text()
|
||||
.trim();
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("data-src");
|
||||
const novelUrl =
|
||||
loadedCheerio(ele)
|
||||
.find(".novel-title > a")
|
||||
.attr("href")
|
||||
loadedCheerio('.novel-item').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('.novel-title').text().trim();
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('data-src');
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find('.novel-title > a')
|
||||
.attr('href')
|
||||
?.substring(1);
|
||||
|
||||
if (!novelUrl) return;
|
||||
@@ -58,19 +54,24 @@ class LightNovelPub implements Plugin.PagePlugin {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const body = await fetchApi(this.site + novelPath).then((r) => r.text());
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const body = await fetchApi(this.site + novelPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const totalChapters = parseInt(loadedCheerio(".header-stats span:first strong").text(), 10);
|
||||
const totalChapters = parseInt(
|
||||
loadedCheerio('.header-stats span:first strong').text(),
|
||||
10,
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.novel-title").text().trim() || "Untitled",
|
||||
cover: loadedCheerio("figure.cover > img").attr("data-src"),
|
||||
author: loadedCheerio(".author > a > span").text(),
|
||||
summary: loadedCheerio(".summary > .content").text().trim(),
|
||||
status: loadedCheerio(".header-stats span:last strong").text(),
|
||||
name: loadedCheerio('h1.novel-title').text().trim() || 'Untitled',
|
||||
cover: loadedCheerio('figure.cover > img').attr('data-src'),
|
||||
author: loadedCheerio('.author > a > span').text(),
|
||||
summary: loadedCheerio('.summary > .content').text().trim(),
|
||||
status: loadedCheerio('.header-stats span:last strong').text(),
|
||||
totalPages: Math.ceil(totalChapters / 100),
|
||||
chapters: [],
|
||||
};
|
||||
@@ -85,7 +86,7 @@ class LightNovelPub implements Plugin.PagePlugin {
|
||||
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const url = this.site + novelPath + '/chapters/page-' + page;
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
@@ -99,63 +100,63 @@ class LightNovelPub implements Plugin.PagePlugin {
|
||||
.find('.chapter-update')
|
||||
.attr('datetime');
|
||||
|
||||
const chapterUrl = loadedCheerio(this).find('a').attr('href')?.substring(1);
|
||||
const chapterUrl = loadedCheerio(this)
|
||||
.find('a')
|
||||
.attr('href')
|
||||
?.substring(1);
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl,
|
||||
releaseTime: dayjs(releaseDate).toISOString(),
|
||||
})
|
||||
});
|
||||
});
|
||||
const chapters = chapter;
|
||||
return { chapters };
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio("#chapter-container").html() || '';
|
||||
let chapterText = loadedCheerio('#chapter-container').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}lnsearchlive`;
|
||||
const link = `${this.site}search`;
|
||||
const response = await fetchApi(link).then((r) => r.text());
|
||||
const response = await fetchApi(link).then(r => r.text());
|
||||
const token = parseHTML(response);
|
||||
let verifytoken = token("#novelSearchForm > input").attr("value");
|
||||
let verifytoken = token('#novelSearchForm > input').attr('value');
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("inputContent", searchTerm);
|
||||
formData.append('inputContent', searchTerm);
|
||||
|
||||
const body = await fetchApi(
|
||||
url,
|
||||
{
|
||||
method: "POST",
|
||||
const body = await fetchApi(url, {
|
||||
method: 'POST',
|
||||
headers: { LNRequestVerifyToken: verifytoken! },
|
||||
body: formData,
|
||||
},
|
||||
).then((r) => r.text());
|
||||
}).then(r => r.text());
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
let results = JSON.parse(loadedCheerio("body").text());
|
||||
let results = JSON.parse(loadedCheerio('body').text());
|
||||
|
||||
loadedCheerio = parseHTML(results.resultview);
|
||||
|
||||
loadedCheerio(".novel-item").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find("h4.novel-title")
|
||||
.text()
|
||||
.trim();
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(ele).find("a").attr("href")?.substring(1);
|
||||
loadedCheerio('.novel-item').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('h4.novel-title').text().trim();
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(ele).find('a').attr('href')?.substring(1);
|
||||
if (!novelUrl) return;
|
||||
novels.push({
|
||||
name: novelName,
|
||||
@@ -173,71 +174,71 @@ class LightNovelPub implements Plugin.PagePlugin {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "popular",
|
||||
label: "Order by",
|
||||
value: 'popular',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "New", value: "new" },
|
||||
{ label: "Popular", value: "popular" },
|
||||
{ label: "Updates", value: "updated" },
|
||||
{ label: 'New', value: 'new' },
|
||||
{ label: 'Popular', value: 'popular' },
|
||||
{ label: 'Updates', value: 'updated' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
status: {
|
||||
value: "all",
|
||||
label: "Status",
|
||||
value: 'all',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: "Ongoing", value: "ongoing" }
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
value: "all",
|
||||
label: "Genre",
|
||||
value: 'all',
|
||||
label: 'Genre',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Slice of Life", value: "slice-of-life" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "School Life", value: "school-life" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Sports", value: "sports" },
|
||||
{ label: "Lolicon", value: "lolicon" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Shounen Ai", value: "shounen-ai" },
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: "Video Games", value: "video-games" },
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: "Magical Realism", value: "magical-realism" },
|
||||
{ label: "Eastern Fantasy", value: "eastern-fantasy" },
|
||||
{ label: "Contemporary Romance", value: "contemporary-romance" },
|
||||
{ label: "Fantasy Romance", value: "fantasy-romance" },
|
||||
{ label: "Shoujo Ai", value: "shoujo-ai" },
|
||||
{ label: "Yuri", value: "yuri" },
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
{ label: 'Lolicon', value: 'lolicon' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Shounen Ai', value: 'shounen-ai' },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
{ label: 'Video Games', value: 'video-games' },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
{ label: 'Magical Realism', value: 'magical-realism' },
|
||||
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
|
||||
{ label: 'Contemporary Romance', value: 'contemporary-romance' },
|
||||
{ label: 'Fantasy Romance', value: 'fantasy-romance' },
|
||||
{ label: 'Shoujo Ai', value: 'shoujo-ai' },
|
||||
{ label: 'Yuri', value: 'yuri' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+111
-110
@@ -1,23 +1,23 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import dayjs from "dayjs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class LightNovelWorld implements Plugin.PagePlugin {
|
||||
id = "lightnovelworld";
|
||||
name = "LightNovelWorld";
|
||||
site = "https://www.webnovelworld.org/";
|
||||
version = "1.0.1";
|
||||
icon = "src/en/lightnovelworld/icon.png";
|
||||
id = 'lightnovelworld';
|
||||
name = 'LightNovelWorld';
|
||||
site = 'https://www.webnovelworld.org/';
|
||||
version = '1.0.1';
|
||||
icon = 'src/en/lightnovelworld/icon.png';
|
||||
headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}browse/`;
|
||||
link += `${filters.genres.value}/`;
|
||||
@@ -25,24 +25,20 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
link += `${filters.status.value}/`;
|
||||
link += page;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".novel-item.ads").remove();
|
||||
loadedCheerio('.novel-item.ads').remove();
|
||||
|
||||
loadedCheerio(".novel-item").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find(".novel-title")
|
||||
.text()
|
||||
.trim();
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("data-src");
|
||||
const novelUrl =
|
||||
loadedCheerio(ele)
|
||||
.find(".novel-title > a")
|
||||
.attr("href")
|
||||
loadedCheerio('.novel-item').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('.novel-title').text().trim();
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('data-src');
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find('.novel-title > a')
|
||||
.attr('href')
|
||||
?.substring(1);
|
||||
|
||||
if (!novelUrl) return;
|
||||
@@ -58,19 +54,24 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const body = await fetchApi(this.site + novelPath).then((r) => r.text());
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const body = await fetchApi(this.site + novelPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const totalChapters = parseInt(loadedCheerio(".header-stats span:first strong").text(), 10);
|
||||
const totalChapters = parseInt(
|
||||
loadedCheerio('.header-stats span:first strong').text(),
|
||||
10,
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.novel-title").text().trim() || "Untitled",
|
||||
cover: loadedCheerio("figure.cover > img").attr("data-src"),
|
||||
author: loadedCheerio(".author > a > span").text(),
|
||||
summary: loadedCheerio(".summary > .content").text().trim(),
|
||||
status: loadedCheerio(".header-stats span:last strong").text(),
|
||||
name: loadedCheerio('h1.novel-title').text().trim() || 'Untitled',
|
||||
cover: loadedCheerio('figure.cover > img').attr('data-src'),
|
||||
author: loadedCheerio('.author > a > span').text(),
|
||||
summary: loadedCheerio('.summary > .content').text().trim(),
|
||||
status: loadedCheerio('.header-stats span:last strong').text(),
|
||||
totalPages: Math.ceil(totalChapters / 100),
|
||||
chapters: [],
|
||||
};
|
||||
@@ -85,7 +86,7 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const url = this.site + novelPath + '/chapters/page-' + page;
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('.chapter-list li').each(function () {
|
||||
@@ -99,63 +100,63 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
.find('.chapter-update')
|
||||
.attr('datetime');
|
||||
|
||||
const chapterUrl = loadedCheerio(this).find('a').attr('href')?.substring(1);
|
||||
const chapterUrl = loadedCheerio(this)
|
||||
.find('a')
|
||||
.attr('href')
|
||||
?.substring(1);
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl,
|
||||
releaseTime: dayjs(releaseDate).toISOString(),
|
||||
})
|
||||
});
|
||||
});
|
||||
const chapters = chapter;
|
||||
return { chapters };
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio("#chapter-container").html() || '';
|
||||
let chapterText = loadedCheerio('#chapter-container').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}lnsearchlive`;
|
||||
const link = `${this.site}search`;
|
||||
const response = await fetchApi(link).then((r) => r.text());
|
||||
const response = await fetchApi(link).then(r => r.text());
|
||||
const token = parseHTML(response);
|
||||
let verifytoken = token("#novelSearchForm > input").attr("value");
|
||||
let verifytoken = token('#novelSearchForm > input').attr('value');
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("inputContent", searchTerm);
|
||||
formData.append('inputContent', searchTerm);
|
||||
|
||||
const body = await fetchApi(
|
||||
url,
|
||||
{
|
||||
method: "POST",
|
||||
const body = await fetchApi(url, {
|
||||
method: 'POST',
|
||||
headers: { LNRequestVerifyToken: verifytoken! },
|
||||
body: formData,
|
||||
},
|
||||
).then((r) => r.text());
|
||||
}).then(r => r.text());
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
let results = JSON.parse(loadedCheerio("body").text());
|
||||
let results = JSON.parse(loadedCheerio('body').text());
|
||||
|
||||
loadedCheerio = parseHTML(results.resultview);
|
||||
|
||||
loadedCheerio(".novel-item").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find("h4.novel-title")
|
||||
.text()
|
||||
.trim();
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(ele).find("a").attr("href")?.substring(1);
|
||||
loadedCheerio('.novel-item').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('h4.novel-title').text().trim();
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(ele).find('a').attr('href')?.substring(1);
|
||||
if (!novelUrl) return;
|
||||
novels.push({
|
||||
name: novelName,
|
||||
@@ -173,71 +174,71 @@ class LightNovelWorld implements Plugin.PagePlugin {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "popular",
|
||||
label: "Order by",
|
||||
value: 'popular',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "New", value: "new" },
|
||||
{ label: "Popular", value: "popular" },
|
||||
{ label: "Updates", value: "updated" },
|
||||
{ label: 'New', value: 'new' },
|
||||
{ label: 'Popular', value: 'popular' },
|
||||
{ label: 'Updates', value: 'updated' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
status: {
|
||||
value: "all",
|
||||
label: "Status",
|
||||
value: 'all',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: "Ongoing", value: "ongoing" }
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
value: "all",
|
||||
label: "Genre",
|
||||
value: 'all',
|
||||
label: 'Genre',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Slice of Life", value: "slice-of-life" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "School Life", value: "school-life" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Sports", value: "sports" },
|
||||
{ label: "Lolicon", value: "lolicon" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Shounen Ai", value: "shounen-ai" },
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: "Video Games", value: "video-games" },
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: "Magical Realism", value: "magical-realism" },
|
||||
{ label: "Eastern Fantasy", value: "eastern-fantasy" },
|
||||
{ label: "Contemporary Romance", value: "contemporary-romance" },
|
||||
{ label: "Fantasy Romance", value: "fantasy-romance" },
|
||||
{ label: "Shoujo Ai", value: "shoujo-ai" },
|
||||
{ label: "Yuri", value: "yuri" },
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
{ label: 'Lolicon', value: 'lolicon' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Shounen Ai', value: 'shounen-ai' },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
{ label: 'Video Games', value: 'video-games' },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
{ label: 'Magical Realism', value: 'magical-realism' },
|
||||
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
|
||||
{ label: 'Contemporary Romance', value: 'contemporary-romance' },
|
||||
{ label: 'Fantasy Romance', value: 'fantasy-romance' },
|
||||
{ label: 'Shoujo Ai', value: 'shoujo-ai' },
|
||||
{ label: 'Yuri', value: 'yuri' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+68
-70
@@ -1,40 +1,38 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
id = "lnmtl";
|
||||
name = "LnMTL";
|
||||
icon = "src/en/lnmtl/icon.png";
|
||||
site = "https://lnmtl.com/";
|
||||
version = "1.0.1";
|
||||
id = 'lnmtl';
|
||||
name = 'LnMTL';
|
||||
icon = 'src/en/lnmtl/icon.png';
|
||||
site = 'https://lnmtl.com/';
|
||||
version = '1.0.1';
|
||||
|
||||
async sleep (ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
async sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site + "novel?";
|
||||
let link = this.site + 'novel?';
|
||||
link += `orderBy=${filters.order.value}`;
|
||||
link += `&order=${filters.sort.value}`;
|
||||
link += `&filter=${filters.storyStatus.value}`;
|
||||
link += `&page=${page}`;
|
||||
|
||||
const body = await fetchApi(link).then((result) =>
|
||||
result.text()
|
||||
);
|
||||
const body = await fetchApi(link).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".media").each((i,el) => {
|
||||
const novelName = loadedCheerio(el).find("h4").text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(el).find("h4 > a").attr("href");
|
||||
loadedCheerio('.media').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h4').text();
|
||||
const novelCover = loadedCheerio(el).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(el).find('h4 > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -48,40 +46,42 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & {totalPages: number}> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const result = await fetchApi(this.site + novelPath);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const volumes = JSON.parse(
|
||||
loadedCheerio("main")
|
||||
loadedCheerio('main')
|
||||
.next()
|
||||
.html()
|
||||
?.match(/lnmtl.volumes = \[(.*?)\]/)![0]
|
||||
?.replace("lnmtl.volumes = ", "") || ""
|
||||
?.replace('lnmtl.volumes = ', '') || '',
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel & {totalPages: number} = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".novel-name").text() || 'Untitled',
|
||||
cover: loadedCheerio("div.novel").find("img").attr("src"),
|
||||
summary: loadedCheerio("div.description").text().trim(),
|
||||
name: loadedCheerio('.novel-name').text() || 'Untitled',
|
||||
cover: loadedCheerio('div.novel').find('img').attr('src'),
|
||||
summary: loadedCheerio('div.description').text().trim(),
|
||||
totalPages: volumes.length,
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
loadedCheerio(".panel-body > dl").each(function () {
|
||||
const detailName = loadedCheerio(this).find("dt").text().trim();
|
||||
const detail = loadedCheerio(this).find("dd").text().trim();
|
||||
loadedCheerio('.panel-body > dl').each(function () {
|
||||
const detailName = loadedCheerio(this).find('dt').text().trim();
|
||||
const detail = loadedCheerio(this).find('dd').text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Authors":
|
||||
case 'Authors':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Current status":
|
||||
case 'Current status':
|
||||
novel.status = detail;
|
||||
break;
|
||||
}
|
||||
@@ -90,12 +90,12 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
novel.genres = loadedCheerio('.panel-heading:contains(" Genres ")')
|
||||
.next()
|
||||
.find('a')
|
||||
.map((i,el) => loadedCheerio(el).text())
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const result = await fetchApi(this.site + novelPath);
|
||||
@@ -104,46 +104,44 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const volumes = JSON.parse(
|
||||
loadedCheerio("main")
|
||||
loadedCheerio('main')
|
||||
.next()
|
||||
.html()
|
||||
?.match(/lnmtl.volumes = \[(.*?)\]/)![0]
|
||||
?.replace("lnmtl.volumes = ", "") || ""
|
||||
)[+page-1];
|
||||
?.replace('lnmtl.volumes = ', '') || '',
|
||||
)[+page - 1];
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
await this.sleep(1000);
|
||||
const volumeData = await fetchApi(
|
||||
`${this.site}chapter?volumeId=${volumes.id}`
|
||||
`${this.site}chapter?volumeId=${volumes.id}`,
|
||||
);
|
||||
const volumePage = await volumeData.json();
|
||||
const firstPage = volumePage.data.map(
|
||||
(chapter: ChapterEntry) => ({
|
||||
const firstPage = volumePage.data.map((chapter: ChapterEntry) => ({
|
||||
name: `#${chapter.number} - ${chapter.title}`,
|
||||
path: `chapter/${chapter.slug}`,
|
||||
releaseTime: new Date (chapter.created_at).toISOString(), //converts time obtained to UTC +0, TODO: Make it not convert
|
||||
}))
|
||||
releaseTime: new Date(chapter.created_at).toISOString(), //converts time obtained to UTC +0, TODO: Make it not convert
|
||||
}));
|
||||
chapter.push(...firstPage);
|
||||
|
||||
for (let i = 2; i <= volumePage.last_page; i++) {
|
||||
await this.sleep(1000);
|
||||
const chapterData = await fetchApi(
|
||||
`${this.site}chapter?page=${i}&volumeId=${volumes.id}`
|
||||
`${this.site}chapter?page=${i}&volumeId=${volumes.id}`,
|
||||
);
|
||||
const chapterInfo = await chapterData.json();
|
||||
|
||||
const chapterDetails = chapterInfo.data.map(
|
||||
(chapter: ChapterEntry) => ({
|
||||
const chapterDetails = chapterInfo.data.map((chapter: ChapterEntry) => ({
|
||||
name: `#${chapter.number} ${chapter.title}`,
|
||||
path: `chapter/${chapter.slug}`,
|
||||
releaseTime: new Date (chapter.created_at).toISOString(), //converts time obtained to UTC +0, TODO: Make it not convert
|
||||
releaseTime: new Date(chapter.created_at).toISOString(), //converts time obtained to UTC +0, TODO: Make it not convert
|
||||
}));
|
||||
|
||||
chapter.push(...chapterDetails);
|
||||
}
|
||||
const chapters = chapter;
|
||||
return {chapters};
|
||||
return { chapters };
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
@@ -158,17 +156,17 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
let chapterText = loadedCheerio('.chapter-body').html()?.replace(/„/g, '“');
|
||||
|
||||
if (!chapterText) {
|
||||
chapterText = loadedCheerio(".alert.alert-warning").text();
|
||||
chapterText = loadedCheerio('.alert.alert-warning').text();
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
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 loadedCheerio = parseHTML(body);
|
||||
|
||||
const list = loadedCheerio('footer')
|
||||
@@ -180,12 +178,12 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
const search = await fetch(`${this.site}${list}`);
|
||||
const data = await search.json();
|
||||
|
||||
const nov = data.filter((res: { name: string; }) =>
|
||||
const nov = data.filter((res: { name: string }) =>
|
||||
res.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
nov.map((res: { name: string; slug: string; image: string; }) => {
|
||||
nov.map((res: { name: string; slug: string; image: string }) => {
|
||||
const novelName = res.name;
|
||||
const novelUrl = `novel/${res.slug}`;
|
||||
const novelCover = res.image;
|
||||
@@ -198,7 +196,7 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return await fetchFile(url);
|
||||
@@ -206,31 +204,31 @@ class LnMTLPlugin implements Plugin.PagePlugin {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "favourites",
|
||||
label: "Order by",
|
||||
value: 'favourites',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "Favourites", value: "favourites" },
|
||||
{ label: "Name", value: "name" },
|
||||
{ label: "Addition Date", value: "date" },
|
||||
{ label: 'Favourites', value: 'favourites' },
|
||||
{ label: 'Name', value: 'name' },
|
||||
{ label: 'Addition Date', value: 'date' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "desc",
|
||||
label: "Sort by",
|
||||
value: 'desc',
|
||||
label: 'Sort by',
|
||||
options: [
|
||||
{ label: "Descending", value: "desc" },
|
||||
{ label: "Ascending", value: "asc" },
|
||||
{ label: 'Descending', value: 'desc' },
|
||||
{ label: 'Ascending', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
storyStatus: {
|
||||
value: "all",
|
||||
label: "Status",
|
||||
value: 'all',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: "Finished", value: "finished" },
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
{ label: 'Finished', value: 'finished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+62
-66
@@ -1,39 +1,36 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class MTLNovel implements Plugin.PluginBase {
|
||||
id = "mtlnovel";
|
||||
name = "MTL Novel";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/mtlnovel/icon.png";
|
||||
site = "https://www.mtlnovel.com/";
|
||||
id = 'mtlnovel';
|
||||
name = 'MTL Novel';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/mtlnovel/icon.png';
|
||||
site = 'https://www.mtlnovel.com/';
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}novel-list/?`;
|
||||
link += `orderby=${filters.order.value}`;
|
||||
link += `&order=${filters.sort.value}`;
|
||||
link += `&status=${filters.storyStatus.value}`;
|
||||
link += `&pg=${page}`
|
||||
link += `&pg=${page}`;
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append('Alt-Used', 'www.mtlnovel.com');
|
||||
const body = await fetch(link, {headers}).then((result) => result.text());
|
||||
const body = await fetch(link, { headers }).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("div.box.wide").each((i,el) => {
|
||||
const novelName = loadedCheerio(el)
|
||||
.find("a.list-title")
|
||||
.text()
|
||||
.slice(4);
|
||||
const novelCover = loadedCheerio(el).find("amp-img").attr("src");
|
||||
const novelUrl = loadedCheerio(el).find("a.list-title").attr("href");
|
||||
loadedCheerio('div.box.wide').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('a.list-title').text().slice(4);
|
||||
const novelCover = loadedCheerio(el).find('amp-img').attr('src');
|
||||
const novelUrl = loadedCheerio(el).find('a.list-title').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -50,57 +47,51 @@ class MTLNovel implements Plugin.PluginBase {
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const headers = new Headers();
|
||||
headers.append('Referer', `${this.site}novel-list/`)
|
||||
headers.append('Referer', `${this.site}novel-list/`);
|
||||
headers.append('Alt-Used', 'www.mtlnovel.com');
|
||||
|
||||
const body = await fetch(
|
||||
this.site + novelPath,
|
||||
{
|
||||
headers
|
||||
},
|
||||
).then((result) => result.text());
|
||||
const body = await fetch(this.site + novelPath, {
|
||||
headers,
|
||||
}).then(result => result.text());
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.entry-title").text() || 'Untitled',
|
||||
cover: loadedCheerio(".nov-head > amp-img").attr("src"),
|
||||
summary: loadedCheerio("div.desc > h2").next().text().trim(),
|
||||
author: loadedCheerio("#author").text(),
|
||||
status: loadedCheerio("#status").text(),
|
||||
genres: loadedCheerio("#genre").text().replace(/\s+/g, ""),
|
||||
name: loadedCheerio('h1.entry-title').text() || 'Untitled',
|
||||
cover: loadedCheerio('.nov-head > amp-img').attr('src'),
|
||||
summary: loadedCheerio('div.desc > h2').next().text().trim(),
|
||||
author: loadedCheerio('#author').text(),
|
||||
status: loadedCheerio('#status').text(),
|
||||
genres: loadedCheerio('#genre').text().replace(/\s+/g, ''),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
const chapterListUrl = this.site + novelPath + "chapter-list/";
|
||||
const chapterListUrl = this.site + novelPath + 'chapter-list/';
|
||||
|
||||
const getChapters = async () => {
|
||||
const listResult = await fetch(chapterListUrl, {headers});
|
||||
const listResult = await fetch(chapterListUrl, { headers });
|
||||
const listBody = await listResult.text();
|
||||
|
||||
loadedCheerio = parseHTML(listBody);
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("div.ch-list")
|
||||
.find("a.ch-link")
|
||||
.each((i,el) => {
|
||||
const chapterName = loadedCheerio(el)
|
||||
.text()
|
||||
.replace("~ ", "");
|
||||
loadedCheerio('div.ch-list')
|
||||
.find('a.ch-link')
|
||||
.each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).text().replace('~ ', '');
|
||||
const releaseDate = null;
|
||||
const chapterUrl = loadedCheerio(el).attr("href");
|
||||
const chapterUrl = loadedCheerio(el).attr('href');
|
||||
if (!chapterUrl) return;
|
||||
chapter.push({
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
name: chapterName,
|
||||
releaseTime: releaseDate,
|
||||
});
|
||||
|
||||
});
|
||||
return chapter.reverse();
|
||||
}
|
||||
};
|
||||
|
||||
novel.chapters = await getChapters();
|
||||
|
||||
@@ -110,25 +101,30 @@ class MTLNovel implements Plugin.PluginBase {
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const headers = new Headers();
|
||||
headers.append('Alt-Used', 'www.mtlnovel.com');
|
||||
const body = await fetch(this.site + chapterPath, {headers}).then((r) => r.text());
|
||||
const body = await fetch(this.site + chapterPath, { headers }).then(r =>
|
||||
r.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("div.par").html() || '';
|
||||
const chapterText = loadedCheerio('div.par').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const headers = new Headers();
|
||||
headers.append('Alt-Used', 'www.mtlnovel.com');
|
||||
const searchUrl =
|
||||
this.site +
|
||||
"wp-admin/admin-ajax.php?action=autosuggest&q=" +
|
||||
'wp-admin/admin-ajax.php?action=autosuggest&q=' +
|
||||
searchTerm +
|
||||
"&__amp_source_origin=https%3A%2F%2Fwww.mtlnovel.com";
|
||||
'&__amp_source_origin=https%3A%2F%2Fwww.mtlnovel.com';
|
||||
|
||||
const res = await fetch(searchUrl, {headers});
|
||||
const res = await fetch(searchUrl, { headers });
|
||||
const result = await res.json();
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
@@ -138,7 +134,7 @@ class MTLNovel implements Plugin.PluginBase {
|
||||
permalink: string;
|
||||
}
|
||||
result.items[0].results.map((item: SearchEntry) => {
|
||||
const novelName = item.title.replace(/<\/?strong>/g, "");
|
||||
const novelName = item.title.replace(/<\/?strong>/g, '');
|
||||
const novelCover = item.thumbnail;
|
||||
const novelUrl = item.permalink.replace(this.site, '');
|
||||
|
||||
@@ -152,37 +148,37 @@ class MTLNovel implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
const headers = new Headers();
|
||||
headers.append('Alt-Used', 'www.mtlnovel.com');
|
||||
return fetchFile(url, {headers});
|
||||
return fetchFile(url, { headers });
|
||||
}
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "view",
|
||||
label: "Order by",
|
||||
value: 'view',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "Date", value: "date" },
|
||||
{ label: "Name", value: "name" },
|
||||
{ label: "Rating", value: "rating" },
|
||||
{ label: "View", value: "view" },
|
||||
{ label: 'Date', value: 'date' },
|
||||
{ label: 'Name', value: 'name' },
|
||||
{ label: 'Rating', value: 'rating' },
|
||||
{ label: 'View', value: 'view' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "desc",
|
||||
label: "Sort by",
|
||||
value: 'desc',
|
||||
label: 'Sort by',
|
||||
options: [
|
||||
{ label: "Descending", value: "desc" },
|
||||
{ label: "Ascending", value: "asc" },
|
||||
{ label: 'Descending', value: 'desc' },
|
||||
{ label: 'Ascending', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
storyStatus: {
|
||||
value: "all",
|
||||
label: "Status",
|
||||
value: 'all',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: "Complete", value: "completed" },
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
{ label: 'Complete', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
class MTLReader implements Plugin.PluginBase {
|
||||
id = "mtlreader";
|
||||
name = "MTL Reader";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/mtlreader/icon.png";
|
||||
site = "https://mtlreader.com/";
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
id = 'mtlreader';
|
||||
name = 'MTL Reader';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/mtlreader/icon.png';
|
||||
site = 'https://mtlreader.com/';
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".col-md-4").each((i,el) => {
|
||||
const novelName = loadedCheerio(el).find("h5").text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(el).find("h5 > a").attr("href");
|
||||
loadedCheerio('.col-md-4').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h5').text();
|
||||
const novelCover = loadedCheerio(el).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(el).find('h5 > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -31,39 +31,42 @@ class MTLReader implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
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());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
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 novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".agent-title").text().trim() || "Untitled",
|
||||
cover: loadedCheerio(".agent-p-img > img").attr("src"),
|
||||
summary: loadedCheerio("#editdescription").text().trim(),
|
||||
name: loadedCheerio('.agent-title').text().trim() || 'Untitled',
|
||||
cover: loadedCheerio('.agent-p-img > img').attr('src'),
|
||||
summary: loadedCheerio('#editdescription').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.author = loadedCheerio("i.fa-user")
|
||||
novel.author = loadedCheerio('i.fa-user')
|
||||
.parent()
|
||||
.text()
|
||||
.replace("Author: ", "")
|
||||
.replace('Author: ', '')
|
||||
.trim();
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("tr.spaceUnder").each((i,el) => {
|
||||
const chapterName = loadedCheerio(el).find("a").text().trim();
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
loadedCheerio('tr.spaceUnder').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('a').text().trim();
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -78,22 +81,25 @@ class MTLReader implements Plugin.PluginBase {
|
||||
return novel;
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".container ins,script,p.mtlreader").remove();
|
||||
const chapterText = loadedCheerio(".container").html() || '';
|
||||
loadedCheerio('.container ins,script,p.mtlreader').remove();
|
||||
const chapterText = loadedCheerio('.container').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(this.site).then((r) => r.text());
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(this.site).then(r => r.text());
|
||||
const tokenCheerio = parseHTML(body);
|
||||
const token = tokenCheerio('input[name="_token"]').attr('value');
|
||||
|
||||
const searchUrl = `${this.site}search?_token=${token}&input=${searchTerm}`;
|
||||
const seacrhBody = await fetchApi(searchUrl).then((r) => r.text());
|
||||
const seacrhBody = await fetchApi(searchUrl).then(r => r.text());
|
||||
const loadedCheerio = parseHTML(seacrhBody);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
@@ -101,6 +107,5 @@ class MTLReader implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
export default new MTLReader();
|
||||
|
||||
+113
-111
@@ -1,23 +1,23 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class NovelBin implements Plugin.PluginBase {
|
||||
id = "novelbin";
|
||||
name = "Novel Bin";
|
||||
icon = "src/en/novelbin/icon.png";
|
||||
site = "https://binnovel.com/";
|
||||
version = "1.0.0";
|
||||
parseNovels(loadedCheerio:CheerioAPI){
|
||||
id = 'novelbin';
|
||||
name = 'Novel Bin';
|
||||
icon = 'src/en/novelbin/icon.png';
|
||||
site = 'https://binnovel.com/';
|
||||
version = '1.0.0';
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".col-novel-main .list-novel .row").each((i,el) => {
|
||||
const novelName = loadedCheerio(el).find("h3.novel-title > a").text();
|
||||
const novelCover = loadedCheerio(el).find("img.cover").attr("src");
|
||||
loadedCheerio('.col-novel-main .list-novel .row').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h3.novel-title > a').text();
|
||||
const novelCover = loadedCheerio(el).find('img.cover').attr('src');
|
||||
const novelUrl = loadedCheerio(el)
|
||||
.find("h3.novel-title > a")
|
||||
.attr("href");
|
||||
.find('h3.novel-title > a')
|
||||
.attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -33,63 +33,62 @@ class NovelBin implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(page: number, {filters}: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
if (filters.genre.value !== '')
|
||||
link += `gen/${filters.genre.value}`;
|
||||
else
|
||||
link += `sort/${filters.sort.value}`;
|
||||
if (filters.genre.value !== '') link += `gen/${filters.genre.value}`;
|
||||
else link += `sort/${filters.sort.value}`;
|
||||
|
||||
if (filters.complete.value)
|
||||
link += `/completed`;
|
||||
if (filters.complete.value) link += `/completed`;
|
||||
|
||||
link += `?page=${page}`;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(r => r.text());
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("div.book > img").attr("alt") || "Untitled",
|
||||
cover: loadedCheerio("div.book > img").attr("src"),
|
||||
summary: loadedCheerio("div.desc-text").text().trim(),
|
||||
name: loadedCheerio('div.book > img').attr('alt') || 'Untitled',
|
||||
cover: loadedCheerio('div.book > img').attr('src'),
|
||||
summary: loadedCheerio('div.desc-text').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
loadedCheerio("ul.info > li > h3").each(function () {
|
||||
loadedCheerio('ul.info > li > h3').each(function () {
|
||||
let detailName = loadedCheerio(this).text();
|
||||
let detail = loadedCheerio(this)
|
||||
.siblings()
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
switch (detailName) {
|
||||
case "Author:":
|
||||
case 'Author:':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Status:":
|
||||
case 'Status:':
|
||||
novel.status = detail;
|
||||
break;
|
||||
case "Genre:":
|
||||
case 'Genre:':
|
||||
novel.genres = detail;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const novelId = loadedCheerio("#rating").attr("data-novel-id");
|
||||
const novelId = loadedCheerio('#rating').attr('data-novel-id');
|
||||
|
||||
const getChapters = async (id: string) => {
|
||||
const chapterListUrl =
|
||||
this.site + "ajax/chapter-archive?novelId=" + id;
|
||||
const chapterListUrl = this.site + 'ajax/chapter-archive?novelId=' + id;
|
||||
|
||||
const data = await fetchApi(chapterListUrl);
|
||||
const chapterdata = await data.text();
|
||||
@@ -98,9 +97,9 @@ class NovelBin implements Plugin.PluginBase {
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("ul.list-chapter > li").each((i,el) => {
|
||||
const chapterName = loadedCheerio(el).find("a").attr("title");
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
loadedCheerio('ul.list-chapter > li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('a').attr('title');
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!chapterName || !chapterUrl) return;
|
||||
|
||||
@@ -110,7 +109,7 @@ class NovelBin implements Plugin.PluginBase {
|
||||
});
|
||||
});
|
||||
return chapter;
|
||||
}
|
||||
};
|
||||
|
||||
if (novelId) {
|
||||
novel.chapters = await getChapters(novelId);
|
||||
@@ -120,19 +119,22 @@ class NovelBin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('#chr-content > div,h6,p[style="display: none;"]').remove();
|
||||
const chapterText = loadedCheerio("#chr-content").html() || '';
|
||||
const chapterText = loadedCheerio('#chr-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
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());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -144,84 +146,84 @@ class NovelBin implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
value: "popular-novels",
|
||||
label: "Sort by",
|
||||
value: 'popular-novels',
|
||||
label: 'Sort by',
|
||||
options: [
|
||||
{ label: "Latest Release", value: "update" },
|
||||
{ label: "Hot Novel", value: "hot-novels" },
|
||||
{ label: "Completed Novel", value: "c" },
|
||||
{ label: "Most Popular", value: "popular-novels" },
|
||||
{ label: 'Latest Release', value: 'update' },
|
||||
{ label: 'Hot Novel', value: 'hot-novels' },
|
||||
{ label: 'Completed Novel', value: 'c' },
|
||||
{ label: 'Most Popular', value: 'popular-novels' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
value: "",
|
||||
label: "Genres",
|
||||
value: '',
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Anime", value: "anime" },
|
||||
{ label: "Arts", value: "arts" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Eastern", value: "eastern" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Fan-fiction", value: "fan-fiction" },
|
||||
{ label: "Fanfiction", value: "fanfiction" },
|
||||
{ label: "Fantas", value: "fantas" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Game", value: "game" },
|
||||
{ label: "Gender bender", value: "gender-bender" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Hentai", value: "hentai" },
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Isekai", value: "isekai" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Lgbt+", value: "lgbt+" },
|
||||
{ label: "Litrpg", value: "litrpg" },
|
||||
{ label: "Magic", value: "magic" },
|
||||
{ label: "Magical realism", value: "magical-realism" },
|
||||
{ label: "Manhua", value: "manhua" },
|
||||
{ label: "Martial arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Military", value: "military" },
|
||||
{ label: "Modern life", value: "modern-life" },
|
||||
{ label: "Movies", value: "movies" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Portal fantasy", value: "portal-fantasy" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Realistic fiction", value: "realistic-fiction" },
|
||||
{ label: "Reincarnation", value: "reincarnation" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "School life", value: "school-life" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Shoujo ai", value: "shoujo-ai" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Shounen ai", value: "shounen-ai" },
|
||||
{ label: "Slice of life", value: "slice-of-life" },
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: "Sports", value: "sports" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "System", value: "system" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Urban life", value: "urban-life" },
|
||||
{ label: "Video games", value: "video-games" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: "Yuri", value: "yuri" }
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Anime', value: 'anime' },
|
||||
{ label: 'Arts', value: 'arts' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Eastern', value: 'eastern' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Fan-fiction', value: 'fan-fiction' },
|
||||
{ label: 'Fanfiction', value: 'fanfiction' },
|
||||
{ label: 'Fantas', value: 'fantas' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Game', value: 'game' },
|
||||
{ label: 'Gender bender', value: 'gender-bender' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Hentai', value: 'hentai' },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Isekai', value: 'isekai' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Lgbt+', value: 'lgbt+' },
|
||||
{ label: 'Litrpg', value: 'litrpg' },
|
||||
{ label: 'Magic', value: 'magic' },
|
||||
{ label: 'Magical realism', value: 'magical-realism' },
|
||||
{ label: 'Manhua', value: 'manhua' },
|
||||
{ label: 'Martial arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Military', value: 'military' },
|
||||
{ label: 'Modern life', value: 'modern-life' },
|
||||
{ label: 'Movies', value: 'movies' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Portal fantasy', value: 'portal-fantasy' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Realistic fiction', value: 'realistic-fiction' },
|
||||
{ label: 'Reincarnation', value: 'reincarnation' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'School life', value: 'school-life' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Shoujo ai', value: 'shoujo-ai' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Shounen ai', value: 'shounen-ai' },
|
||||
{ label: 'Slice of life', value: 'slice-of-life' },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'System', value: 'system' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Urban life', value: 'urban-life' },
|
||||
{ label: 'Video games', value: 'video-games' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
{ label: 'Yuri', value: 'yuri' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
complete: {
|
||||
label: "Show Completed Novels Only",
|
||||
label: 'Show Completed Novels Only',
|
||||
value: false,
|
||||
type: FilterTypes.Switch,
|
||||
},
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
class NovelBuddy implements Plugin.PluginBase {
|
||||
id = "novelbuddy";
|
||||
name = "NovelBuddy.io";
|
||||
site = "https://novelbuddy.io/";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/novelbuddy/icon.png";
|
||||
id = 'novelbuddy';
|
||||
name = 'NovelBuddy.io';
|
||||
site = 'https://novelbuddy.io/';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/novelbuddy/icon.png';
|
||||
filters?: undefined; //TODO: Filters, Filters in Filters required
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".book-item").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find(".title").text();
|
||||
loadedCheerio('.book-item').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('.title').text();
|
||||
const novelCover =
|
||||
"https:" + loadedCheerio(ele).find("img").attr("data-src");
|
||||
const novelUrl = loadedCheerio(ele).find(".title a").attr("href")?.substring(1);
|
||||
'https:' + loadedCheerio(ele).find('img').attr('data-src');
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find('.title a')
|
||||
.attr('href')
|
||||
?.substring(1);
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -32,7 +35,7 @@ class NovelBuddy implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions
|
||||
options: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}popular?page=${pageNo}`;
|
||||
|
||||
@@ -51,31 +54,32 @@ class NovelBuddy implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".name h1").text().trim() || "Untitled",
|
||||
cover: "https:" + loadedCheerio(".img-cover img").attr("data-src"),
|
||||
name: loadedCheerio('.name h1').text().trim() || 'Untitled',
|
||||
cover: 'https:' + loadedCheerio('.img-cover img').attr('data-src'),
|
||||
summary: loadedCheerio('.section-body.summary').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
loadedCheerio('.meta.box p').each((i,el) => {
|
||||
loadedCheerio('.meta.box p').each((i, el) => {
|
||||
const detailName = loadedCheerio(el).find('strong').text();
|
||||
const detail = loadedCheerio(el).find('a');
|
||||
|
||||
switch(detailName){
|
||||
case "Authors :":
|
||||
novel.author = detail.find('span')
|
||||
.map((a,ex) => loadedCheerio(ex).text())
|
||||
switch (detailName) {
|
||||
case 'Authors :':
|
||||
novel.author = detail
|
||||
.find('span')
|
||||
.map((a, ex) => loadedCheerio(ex).text())
|
||||
.toArray()
|
||||
.join(', ');
|
||||
break;
|
||||
case "Status :":
|
||||
case 'Status :':
|
||||
novel.status = detail.text();
|
||||
break;
|
||||
case "Genres :":
|
||||
case 'Genres :':
|
||||
novel.genres = detail.text().trim();
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
const novelId = loadedCheerio('script')
|
||||
.text()
|
||||
.match(/bookId = (\d+);/)![1];
|
||||
@@ -88,39 +92,40 @@ class NovelBuddy implements Plugin.PluginBase {
|
||||
|
||||
loadedCheerio = parseHTML(chapterlist);
|
||||
|
||||
loadedCheerio("li").each((i,el) => {
|
||||
loadedCheerio('li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el)
|
||||
.find(".chapter-title")
|
||||
.find('.chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(el)
|
||||
.find(".chapter-update")
|
||||
.find('.chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const months = [
|
||||
"jan",
|
||||
"feb",
|
||||
"mar",
|
||||
"apr",
|
||||
"may",
|
||||
"jun",
|
||||
"jul",
|
||||
"aug",
|
||||
"sep",
|
||||
"oct",
|
||||
"nov",
|
||||
"dec",
|
||||
].join("|");
|
||||
const rx = new RegExp(`(${months}) (\\d{1,2}), (\\d{4})`,'i')
|
||||
.exec(releaseDate);
|
||||
'jan',
|
||||
'feb',
|
||||
'mar',
|
||||
'apr',
|
||||
'may',
|
||||
'jun',
|
||||
'jul',
|
||||
'aug',
|
||||
'sep',
|
||||
'oct',
|
||||
'nov',
|
||||
'dec',
|
||||
].join('|');
|
||||
const rx = new RegExp(`(${months}) (\\d{1,2}), (\\d{4})`, 'i').exec(
|
||||
releaseDate,
|
||||
);
|
||||
if (!rx) return;
|
||||
const year = +rx[3];
|
||||
const month = months.indexOf(rx[1].toLowerCase());
|
||||
const day = +rx[2];
|
||||
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href")?.slice(1);
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href')?.slice(1);
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -131,7 +136,7 @@ class NovelBuddy implements Plugin.PluginBase {
|
||||
});
|
||||
});
|
||||
return chapter;
|
||||
}
|
||||
};
|
||||
|
||||
novel.chapters = (await getChapters(novelId)).reverse();
|
||||
|
||||
@@ -144,15 +149,18 @@ class NovelBuddy implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("#listen-chapter").remove();
|
||||
loadedCheerio("#google_translate_element").remove();
|
||||
loadedCheerio('#listen-chapter').remove();
|
||||
loadedCheerio('#google_translate_element').remove();
|
||||
|
||||
const chapterText = loadedCheerio(".chapter__content").html() || '';
|
||||
const chapterText = loadedCheerio('.chapter__content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}search?q=${searchTerm}&page=${page}`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
@@ -165,7 +173,6 @@ class NovelBuddy implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new NovelBuddy();
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class NovelFull implements Plugin.PluginBase {
|
||||
id = "novelfull";
|
||||
name = "NovelFull";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/novelfull/icon.png";
|
||||
site = "https://novelfull.com/";
|
||||
id = 'novelfull';
|
||||
name = 'NovelFull';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/novelfull/icon.png';
|
||||
site = 'https://novelfull.com/';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".col-truyen-main .list-truyen .row").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find("h3.truyen-title > a")
|
||||
.text();
|
||||
loadedCheerio('.col-truyen-main .list-truyen .row').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('h3.truyen-title > a').text();
|
||||
|
||||
const novelCover =
|
||||
this.site + loadedCheerio(ele).find("img").attr("src")?.slice(1);
|
||||
this.site + loadedCheerio(ele).find('img').attr('src')?.slice(1);
|
||||
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find("h3.truyen-title > a")
|
||||
.attr("href")
|
||||
.find('h3.truyen-title > a')
|
||||
.attr('href')
|
||||
?.slice(1);
|
||||
|
||||
if (!novelUrl) return;
|
||||
@@ -41,16 +38,17 @@ class NovelFull implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(page: number, {filters}: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
if (filters.genre.value !== '')
|
||||
link += `genre/${filters.genre.value}`;
|
||||
else
|
||||
link += filters.sort.value
|
||||
if (filters.genre.value !== '') link += `genre/${filters.genre.value}`;
|
||||
else link += filters.sort.value;
|
||||
|
||||
link += `?page=${page}`;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -64,9 +62,9 @@ class NovelFull implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("div.book > img").attr("alt") || "Untitled",
|
||||
cover: this.site + loadedCheerio("div.book > img").attr("src"),
|
||||
summary: loadedCheerio("div.desc-text").text().trim(),
|
||||
name: loadedCheerio('div.book > img').attr('alt') || 'Untitled',
|
||||
cover: this.site + loadedCheerio('div.book > img').attr('src'),
|
||||
summary: loadedCheerio('div.desc-text').text().trim(),
|
||||
status: loadedCheerio('h3:contains("Status")').next().text(),
|
||||
chapters: [],
|
||||
};
|
||||
@@ -75,28 +73,28 @@ class NovelFull implements Plugin.PluginBase {
|
||||
.parent()
|
||||
.contents()
|
||||
.text()
|
||||
.replace("Author:", "");
|
||||
.replace('Author:', '');
|
||||
|
||||
novel.genres = loadedCheerio('h3:contains("Genre")')
|
||||
.siblings()
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
const novelId = loadedCheerio("#rating").attr("data-novel-id")!;
|
||||
const novelId = loadedCheerio('#rating').attr('data-novel-id')!;
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
const getChapters = async (id: string) => {
|
||||
const chapterListUrl = this.site + "ajax/chapter-option?novelId=" + id;
|
||||
const chapterListUrl = this.site + 'ajax/chapter-option?novelId=' + id;
|
||||
|
||||
const data = await fetchApi(chapterListUrl);
|
||||
const chapterlist = await data.text();
|
||||
|
||||
loadedCheerio = parseHTML(chapterlist);
|
||||
|
||||
loadedCheerio("select > option").each(function () {
|
||||
loadedCheerio('select > option').each(function () {
|
||||
const chapterName = loadedCheerio(this).text();
|
||||
const chapterUrl = loadedCheerio(this).attr("value")?.slice(1);
|
||||
const chapterUrl = loadedCheerio(this).attr('value')?.slice(1);
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
@@ -105,7 +103,7 @@ class NovelFull implements Plugin.PluginBase {
|
||||
});
|
||||
});
|
||||
return chapter;
|
||||
}
|
||||
};
|
||||
|
||||
novel.chapters = await getChapters(novelId);
|
||||
|
||||
@@ -118,13 +116,16 @@ class NovelFull implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("#chapter-content div.ads").remove();
|
||||
const chapterText = loadedCheerio("#chapter-content").html() || '';
|
||||
loadedCheerio('#chapter-content div.ads').remove();
|
||||
const chapterText = loadedCheerio('#chapter-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = `${this.site}search?keyword=${searchTerm}&page=${page}`;
|
||||
|
||||
const result = await fetchApi(searchUrl);
|
||||
@@ -140,57 +141,57 @@ class NovelFull implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
value: "most-popular",
|
||||
label: "Sort by",
|
||||
value: 'most-popular',
|
||||
label: 'Sort by',
|
||||
options: [
|
||||
{ label: "Latest Release", value: "latest-release-novel" },
|
||||
{ label: "Hot Novel", value: "hot-novel" },
|
||||
{ label: "Completed Novel", value: "completed-novel" },
|
||||
{ label: "Most Popular", value: "most-popular" },
|
||||
{ label: 'Latest Release', value: 'latest-release-novel' },
|
||||
{ label: 'Hot Novel', value: 'hot-novel' },
|
||||
{ label: 'Completed Novel', value: 'completed-novel' },
|
||||
{ label: 'Most Popular', value: 'most-popular' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
value: "",
|
||||
label: "Genres",
|
||||
value: '',
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Shounen", value: "Shounen" },
|
||||
{ label: "Harem", value: "Harem" },
|
||||
{ label: "Comedy", value: "Comedy" },
|
||||
{ label: "Martial Arts", value: "Martial+Arts" },
|
||||
{ label: "School Life", value: "School+Life" },
|
||||
{ label: "Mystery", value: "Mystery" },
|
||||
{ label: "Shoujo", value: "Shoujo" },
|
||||
{ label: "Romance", value: "Romance" },
|
||||
{ label: "Sci-fi", value: "Sci-fi" },
|
||||
{ label: "Gender Bender", value: "Gender+Bender" },
|
||||
{ label: "Mature", value: "Mature" },
|
||||
{ label: "Fantasy", value: "Fantasy" },
|
||||
{ label: "Horror", value: "Horror" },
|
||||
{ label: "Drama", value: "Drama" },
|
||||
{ label: "Tragedy", value: "Tragedy" },
|
||||
{ label: "Supernatural", value: "Supernatural" },
|
||||
{ label: "Ecchi", value: "Ecchi" },
|
||||
{ label: "Xuanhuan", value: "Xuanhuan" },
|
||||
{ label: "Adventure", value: "Adventure" },
|
||||
{ label: "Action", value: "Action" },
|
||||
{ label: "Psychological", value: "Psychological" },
|
||||
{ label: "Xianxia", value: "Xianxia" },
|
||||
{ label: "Wuxia", value: "Wuxia" },
|
||||
{ label: "Historical", value: "Historical" },
|
||||
{ label: "Slice of Life", value: "Slice+of+Life" },
|
||||
{ label: "Seinen", value: "Seinen" },
|
||||
{ label: "Lolicon", value: "Lolicon" },
|
||||
{ label: "Adult", value: "Adult" },
|
||||
{ label: "Josei", value: "Josei" },
|
||||
{ label: "Sports", value: "Sports" },
|
||||
{ label: "Smut", value: "Smut" },
|
||||
{ label: "Mecha", value: "Mecha" },
|
||||
{ label: "Yaoi", value: "Yaoi" },
|
||||
{ label: "Shounen Ai", value: "Shounen+Ai" },
|
||||
{ label: "History", value: "History" },
|
||||
{ label: "Martial", value: "Martial" }
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Shounen', value: 'Shounen' },
|
||||
{ label: 'Harem', value: 'Harem' },
|
||||
{ label: 'Comedy', value: 'Comedy' },
|
||||
{ label: 'Martial Arts', value: 'Martial+Arts' },
|
||||
{ label: 'School Life', value: 'School+Life' },
|
||||
{ label: 'Mystery', value: 'Mystery' },
|
||||
{ label: 'Shoujo', value: 'Shoujo' },
|
||||
{ label: 'Romance', value: 'Romance' },
|
||||
{ label: 'Sci-fi', value: 'Sci-fi' },
|
||||
{ label: 'Gender Bender', value: 'Gender+Bender' },
|
||||
{ label: 'Mature', value: 'Mature' },
|
||||
{ label: 'Fantasy', value: 'Fantasy' },
|
||||
{ label: 'Horror', value: 'Horror' },
|
||||
{ label: 'Drama', value: 'Drama' },
|
||||
{ label: 'Tragedy', value: 'Tragedy' },
|
||||
{ label: 'Supernatural', value: 'Supernatural' },
|
||||
{ label: 'Ecchi', value: 'Ecchi' },
|
||||
{ label: 'Xuanhuan', value: 'Xuanhuan' },
|
||||
{ label: 'Adventure', value: 'Adventure' },
|
||||
{ label: 'Action', value: 'Action' },
|
||||
{ label: 'Psychological', value: 'Psychological' },
|
||||
{ label: 'Xianxia', value: 'Xianxia' },
|
||||
{ label: 'Wuxia', value: 'Wuxia' },
|
||||
{ label: 'Historical', value: 'Historical' },
|
||||
{ label: 'Slice of Life', value: 'Slice+of+Life' },
|
||||
{ label: 'Seinen', value: 'Seinen' },
|
||||
{ label: 'Lolicon', value: 'Lolicon' },
|
||||
{ label: 'Adult', value: 'Adult' },
|
||||
{ label: 'Josei', value: 'Josei' },
|
||||
{ label: 'Sports', value: 'Sports' },
|
||||
{ label: 'Smut', value: 'Smut' },
|
||||
{ label: 'Mecha', value: 'Mecha' },
|
||||
{ label: 'Yaoi', value: 'Yaoi' },
|
||||
{ label: 'Shounen Ai', value: 'Shounen+Ai' },
|
||||
{ label: 'History', value: 'History' },
|
||||
{ label: 'Martial', value: 'Martial' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
import { load, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { load, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
|
||||
class NovelHall implements Plugin.PluginBase {
|
||||
id = "novelhall";
|
||||
name = "Novel Hall";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/novelhall/icon.png";
|
||||
id = 'novelhall';
|
||||
name = 'Novel Hall';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/novelhall/icon.png';
|
||||
filters?: Filters | undefined; //TODO: Filters Requires hideOnSelect
|
||||
site = "https://novelhall.com/";
|
||||
site = 'https://novelhall.com/';
|
||||
|
||||
async popularNovels(page: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
page: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
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());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("li.btm").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).text().trim()
|
||||
const novelUrl = loadedCheerio(ele).find("a").attr("href");
|
||||
if(!novelUrl) return;
|
||||
loadedCheerio('li.btm').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).text().trim();
|
||||
const novelUrl = loadedCheerio(ele).find('a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
|
||||
const novel = {
|
||||
name: novelName,
|
||||
@@ -38,40 +41,40 @@ class NovelHall implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
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 novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".book-info > h1").text() || 'Untitled',
|
||||
name: loadedCheerio('.book-info > h1').text() || 'Untitled',
|
||||
cover: loadedCheerio('meta[property="og:image"]').attr('content'),
|
||||
summary: loadedCheerio(".intro").text().trim(),
|
||||
summary: loadedCheerio('.intro').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
loadedCheerio('.total').find('p').remove();
|
||||
novel.author = loadedCheerio('.total span:contains("Author")')
|
||||
.text()
|
||||
.replace("Author:", "")
|
||||
.replace('Author:', '')
|
||||
.trim();
|
||||
|
||||
novel.status = loadedCheerio('.total span:contains("Status")')
|
||||
.text()
|
||||
.replace("Status:", "")
|
||||
.replace("Active", "Ongoing")
|
||||
.replace('Status:', '')
|
||||
.replace('Active', 'Ongoing')
|
||||
.trim();
|
||||
|
||||
novel.genres = loadedCheerio('.total a')
|
||||
.map((a,ex) => loadedCheerio(ex).text())
|
||||
.map((a, ex) => loadedCheerio(ex).text())
|
||||
.toArray()
|
||||
.join(',');
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("#morelist ul > li").each((idx, ele) => {
|
||||
const chapterName = loadedCheerio(ele).find("a").text().trim();
|
||||
const chapterUrl = loadedCheerio(ele).find("a").attr("href");
|
||||
loadedCheerio('#morelist ul > li').each((idx, ele) => {
|
||||
const chapterName = loadedCheerio(ele).find('a').text().trim();
|
||||
const chapterUrl = loadedCheerio(ele).find('a').attr('href');
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
@@ -85,27 +88,30 @@ class NovelHall implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapterText = loadedCheerio(".content").html() || '';
|
||||
const chapterText = loadedCheerio('.content').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
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 novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("#article_list_content > li").each((idx, ele) => {
|
||||
loadedCheerio('#article_list_content > li').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find("h3")
|
||||
.find('h3')
|
||||
.text()
|
||||
.replace(/\t+/g, "")
|
||||
.replace(/\n/g, " ");
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("data-src");
|
||||
const novelUrl = loadedCheerio(ele).find("a").attr("href")?.slice(1);
|
||||
.replace(/\t+/g, '')
|
||||
.replace(/\n/g, ' ');
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('data-src');
|
||||
const novelUrl = loadedCheerio(ele).find('a').attr('href')?.slice(1);
|
||||
if (!novelUrl) return;
|
||||
|
||||
const novel = {
|
||||
@@ -122,7 +128,6 @@ class NovelHall implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new NovelHall();
|
||||
|
||||
+192
-185
@@ -1,24 +1,24 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
class NovelUpdates implements Plugin.PluginBase {
|
||||
id = "novelupdates";
|
||||
name = "Novel Updates";
|
||||
version = "0.5.1";
|
||||
icon = "src/en/novelupdates/icon.png";
|
||||
site = "https://www.novelupdates.com/";
|
||||
id = 'novelupdates';
|
||||
name = 'Novel Updates';
|
||||
version = '0.5.1';
|
||||
icon = 'src/en/novelupdates/icon.png';
|
||||
site = 'https://www.novelupdates.com/';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("div.search_main_box_nu").each((idx, ele) => {
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("src");
|
||||
const novelName = loadedCheerio(ele).find(".search_title > a").text();
|
||||
loadedCheerio('div.search_main_box_nu').each((idx, ele) => {
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('src');
|
||||
const novelName = loadedCheerio(ele).find('.search_title > a').text();
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find(".search_title > a")
|
||||
.attr("href");
|
||||
.find('.search_title > a')
|
||||
.attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -34,19 +34,27 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(page: number, { showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}`;
|
||||
if (filters.language.value.length ||
|
||||
if (
|
||||
filters.language.value.length ||
|
||||
filters.novelType.value.length ||
|
||||
filters.genres.value.include?.length ||
|
||||
filters.genres.value.exclude?.length ||
|
||||
filters.storyStatus.value !== '' ){
|
||||
link += "series-finder/?sf=1"
|
||||
} else if (showLatestNovels){
|
||||
link += "latest-series/?st=1"
|
||||
filters.storyStatus.value !== ''
|
||||
) {
|
||||
link += 'series-finder/?sf=1';
|
||||
} else if (showLatestNovels) {
|
||||
link += 'latest-series/?st=1';
|
||||
} else {
|
||||
link += "series-ranking/?rank=week"
|
||||
};
|
||||
link += 'series-ranking/?rank=week';
|
||||
}
|
||||
|
||||
if (filters.language.value.length)
|
||||
link += '&org=' + filters.language.value.join(',');
|
||||
@@ -60,7 +68,10 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
if (filters.genres.value.exclude?.length)
|
||||
link += '&ge=' + filters.genres.value.exclude.join(',');
|
||||
|
||||
if (filters.genres.value.include?.length || filters.genres.value.exclude?.length)
|
||||
if (
|
||||
filters.genres.value.include?.length ||
|
||||
filters.genres.value.exclude?.length
|
||||
)
|
||||
link += '&mgi=' + filters.genre_operator.value;
|
||||
|
||||
if (filters.storyStatus.value.length)
|
||||
@@ -72,9 +83,7 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
link += '&pg=' + page;
|
||||
|
||||
const body = await fetchApi(link).then((result) =>
|
||||
result.text()
|
||||
);
|
||||
const body = await fetchApi(link).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -89,7 +98,7 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".seriestitlenu").text() || "Untitled",
|
||||
name: loadedCheerio('.seriestitlenu').text() || 'Untitled',
|
||||
cover: loadedCheerio('.wpb_wrapper img').attr('src'),
|
||||
chapters: [],
|
||||
};
|
||||
@@ -99,48 +108,44 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
.toArray()
|
||||
.join(', ');
|
||||
|
||||
novel.genres = loadedCheerio("#seriesgenre")
|
||||
.children("a")
|
||||
novel.genres = loadedCheerio('#seriesgenre')
|
||||
.children('a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
novel.status = loadedCheerio("#editstatus").text().includes("Ongoing")
|
||||
? "Ongoing"
|
||||
: "Completed";
|
||||
novel.status = loadedCheerio('#editstatus').text().includes('Ongoing')
|
||||
? 'Ongoing'
|
||||
: 'Completed';
|
||||
|
||||
const type = loadedCheerio("#showtype").text().trim();
|
||||
const type = loadedCheerio('#showtype').text().trim();
|
||||
|
||||
const summary = loadedCheerio("#editdescription").text().trim();
|
||||
const summary = loadedCheerio('#editdescription').text().trim();
|
||||
|
||||
novel.summary = summary + `\n\nType: ${type}`;
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
const novelId = loadedCheerio("input#mypostid").attr("value")!;
|
||||
const novelId = loadedCheerio('input#mypostid').attr('value')!;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("action", "nd_getchapters");
|
||||
formData.append("mygrr", "0");
|
||||
formData.append("mypostid", novelId);
|
||||
formData.append('action', 'nd_getchapters');
|
||||
formData.append('mygrr', '0');
|
||||
formData.append('mypostid', novelId);
|
||||
|
||||
const link = `${this.site}wp-admin/admin-ajax.php`;
|
||||
|
||||
const text = await fetchApi(
|
||||
link,
|
||||
{
|
||||
method: "POST",
|
||||
const text = await fetchApi(link, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
},
|
||||
).then((data) => data.text());
|
||||
}).then(data => data.text());
|
||||
|
||||
loadedCheerio = parseHTML(text);
|
||||
|
||||
loadedCheerio("li.sp_li_chp").each((i,el) => {
|
||||
loadedCheerio('li.sp_li_chp').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).text().trim();
|
||||
const chapterUrl =
|
||||
"https:" +
|
||||
loadedCheerio(el).find("a").first().next().attr("href");
|
||||
'https:' + loadedCheerio(el).find('a').first().next().attr('href');
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
@@ -155,17 +160,15 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
getLocation(href: string) {
|
||||
var match = href.match(
|
||||
/^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/
|
||||
/^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/,
|
||||
);
|
||||
return match && `${match[1]}//${match[3]}`;
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
let chapterText = "";
|
||||
let chapterText = '';
|
||||
|
||||
const result = await fetchApi(
|
||||
this.site + chapterPath,
|
||||
);
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
const body = await result.text();
|
||||
|
||||
// console.log(result.chapterUrl);
|
||||
@@ -174,13 +177,13 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let isWuxiaWorld = result.url.toLowerCase().includes("wuxiaworld");
|
||||
let isWuxiaWorld = result.url.toLowerCase().includes('wuxiaworld');
|
||||
|
||||
let isBlogspot = result.url.toLowerCase().includes("blogspot");
|
||||
let isBlogspot = result.url.toLowerCase().includes('blogspot');
|
||||
|
||||
let isTumblr = result.url.toLowerCase().includes("tumblr");
|
||||
let isTumblr = result.url.toLowerCase().includes('tumblr');
|
||||
|
||||
let isWattpad = result.url.toLowerCase().includes("wattpad");
|
||||
let isWattpad = result.url.toLowerCase().includes('wattpad');
|
||||
|
||||
let isAnomalously = result.url.toLowerCase().includes('anotivereads');
|
||||
|
||||
@@ -192,62 +195,59 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
let isLightNovelsTls = result.url
|
||||
.toLowerCase()
|
||||
.includes("lightnovelstranslations");
|
||||
.includes('lightnovelstranslations');
|
||||
|
||||
let isiNovelTranslation = result.url
|
||||
.toLowerCase()
|
||||
.includes("inoveltranslation");
|
||||
.includes('inoveltranslation');
|
||||
|
||||
let isTravisTranslation = result.url
|
||||
.toLowerCase()
|
||||
.includes("travistranslations");
|
||||
.includes('travistranslations');
|
||||
|
||||
/**
|
||||
* Checks if its a wordpress site
|
||||
*/
|
||||
let isWordPressStr =
|
||||
loadedCheerio('meta[name="generator"]').attr("content") ||
|
||||
loadedCheerio("footer").text();
|
||||
loadedCheerio('meta[name="generator"]').attr('content') ||
|
||||
loadedCheerio('footer').text();
|
||||
|
||||
let isWordPress = false;
|
||||
|
||||
if (isWordPressStr) {
|
||||
isWordPress =
|
||||
isWordPressStr.toLowerCase().includes("wordpress") ||
|
||||
isWordPressStr.includes("Site Kit by Google") ||
|
||||
loadedCheerio(".powered-by")
|
||||
.text()
|
||||
.toLowerCase()
|
||||
.includes("wordpress");
|
||||
isWordPressStr.toLowerCase().includes('wordpress') ||
|
||||
isWordPressStr.includes('Site Kit by Google') ||
|
||||
loadedCheerio('.powered-by').text().toLowerCase().includes('wordpress');
|
||||
}
|
||||
|
||||
let isRainOfSnow = result.url.toLowerCase().includes("rainofsnow");
|
||||
let isRainOfSnow = result.url.toLowerCase().includes('rainofsnow');
|
||||
|
||||
let isWebNovel = result.url.toLowerCase().includes("webnovel");
|
||||
let isWebNovel = result.url.toLowerCase().includes('webnovel');
|
||||
|
||||
let isHostedNovel = result.url.toLowerCase().includes("hostednovel");
|
||||
let isHostedNovel = result.url.toLowerCase().includes('hostednovel');
|
||||
|
||||
let isScribbleHub = result.url.toLowerCase().includes("scribblehub");
|
||||
let isScribbleHub = result.url.toLowerCase().includes('scribblehub');
|
||||
|
||||
if (isWuxiaWorld) {
|
||||
chapterText = loadedCheerio("#chapter-content").html()!;
|
||||
chapterText = loadedCheerio('#chapter-content').html()!;
|
||||
} else if (isRainOfSnow) {
|
||||
chapterText = loadedCheerio("div.content").html()!;
|
||||
chapterText = loadedCheerio('div.content').html()!;
|
||||
} else if (isTumblr) {
|
||||
chapterText = loadedCheerio(".post").html()!;
|
||||
chapterText = loadedCheerio('.post').html()!;
|
||||
} else if (isBlogspot) {
|
||||
loadedCheerio('.post-share-buttons').remove();
|
||||
chapterText =
|
||||
loadedCheerio('.entry-content').html() ||
|
||||
loadedCheerio('#post-body').html()!;
|
||||
} else if (isHostedNovel) {
|
||||
chapterText = loadedCheerio(".chapter").html()!;
|
||||
chapterText = loadedCheerio('.chapter').html()!;
|
||||
} else if (isScribbleHub) {
|
||||
chapterText = loadedCheerio("div.chp_raw").html()!;
|
||||
chapterText = loadedCheerio('div.chp_raw').html()!;
|
||||
} else if (isWattpad) {
|
||||
chapterText = loadedCheerio(".container pre").html()!;
|
||||
chapterText = loadedCheerio('.container pre').html()!;
|
||||
} else if (isTravisTranslation) {
|
||||
chapterText = loadedCheerio(".reader-content").html()!;
|
||||
chapterText = loadedCheerio('.reader-content').html()!;
|
||||
} else if (isLightNovelsTls) {
|
||||
chapterText = loadedCheerio('.text_story').html()!;
|
||||
} else if (isBlossomTranslation) {
|
||||
@@ -272,48 +272,48 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
*/
|
||||
|
||||
const bloatClasses = [
|
||||
".c-ads",
|
||||
"#madara-comments",
|
||||
"#comments",
|
||||
".content-comments",
|
||||
".sharedaddy",
|
||||
".wp-dark-mode-switcher",
|
||||
".wp-next-post-navi",
|
||||
".wp-block-buttons",
|
||||
".wp-block-columns",
|
||||
".post-cats",
|
||||
".sidebar",
|
||||
".author-avatar",
|
||||
".ezoic-ad",
|
||||
'.c-ads',
|
||||
'#madara-comments',
|
||||
'#comments',
|
||||
'.content-comments',
|
||||
'.sharedaddy',
|
||||
'.wp-dark-mode-switcher',
|
||||
'.wp-next-post-navi',
|
||||
'.wp-block-buttons',
|
||||
'.wp-block-columns',
|
||||
'.post-cats',
|
||||
'.sidebar',
|
||||
'.author-avatar',
|
||||
'.ezoic-ad',
|
||||
];
|
||||
|
||||
bloatClasses.map((tag) => loadedCheerio(tag).remove());
|
||||
bloatClasses.map(tag => loadedCheerio(tag).remove());
|
||||
|
||||
chapterText =
|
||||
loadedCheerio(".entry-content").html() ||
|
||||
loadedCheerio(".single_post").html() ||
|
||||
loadedCheerio(".post-entry").html() ||
|
||||
loadedCheerio(".main-content").html() ||
|
||||
loadedCheerio("article.post").html() ||
|
||||
loadedCheerio(".content").html() ||
|
||||
loadedCheerio("#content").html() ||
|
||||
loadedCheerio(".page-body").html() ||
|
||||
loadedCheerio(".td-page-content").html()!;
|
||||
loadedCheerio('.entry-content').html() ||
|
||||
loadedCheerio('.single_post').html() ||
|
||||
loadedCheerio('.post-entry').html() ||
|
||||
loadedCheerio('.main-content').html() ||
|
||||
loadedCheerio('article.post').html() ||
|
||||
loadedCheerio('.content').html() ||
|
||||
loadedCheerio('#content').html() ||
|
||||
loadedCheerio('.page-body').html() ||
|
||||
loadedCheerio('.td-page-content').html()!;
|
||||
} else if (isWebNovel) {
|
||||
chapterText = loadedCheerio(".cha-words").html()!;
|
||||
chapterText = loadedCheerio('.cha-words').html()!;
|
||||
|
||||
if (!chapterText) {
|
||||
chapterText = loadedCheerio("._content").html()!;
|
||||
chapterText = loadedCheerio('._content').html()!;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Remove unnecessary tags
|
||||
*/
|
||||
const tags = ["nav", "header", "footer", ".hidden"];
|
||||
const tags = ['nav', 'header', 'footer', '.hidden'];
|
||||
|
||||
tags.map((tag) => loadedCheerio(tag).remove());
|
||||
tags.map(tag => loadedCheerio(tag).remove());
|
||||
|
||||
chapterText = loadedCheerio("body").html()!;
|
||||
chapterText = loadedCheerio('body').html()!;
|
||||
}
|
||||
|
||||
if (chapterText) {
|
||||
@@ -322,15 +322,22 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
*/
|
||||
chapterText = chapterText.replace(
|
||||
/href="\//g,
|
||||
`href="${this.getLocation(result.url)}/`
|
||||
`href="${this.getLocation(result.url)}/`,
|
||||
);
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + '?s=' + searchTerm.replace(/\s+/g, '+') + '&post_type=seriesplans';
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site +
|
||||
'?s=' +
|
||||
searchTerm.replace(/\s+/g, '+') +
|
||||
'&post_type=seriesplans';
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
@@ -343,117 +350,117 @@ class NovelUpdates implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Sort Results By",
|
||||
value: "sdate",
|
||||
label: 'Sort Results By',
|
||||
value: 'sdate',
|
||||
options: [
|
||||
{ label: "Last Updated", value: "sdate" },
|
||||
{ label: "Rating", value: "srate" },
|
||||
{ label: "Rank", value: "srank" },
|
||||
{ label: "Reviews", value: "sreview" },
|
||||
{ label: "Chapters", value: "srel" },
|
||||
{ label: "Title", value: "abc" },
|
||||
{ label: "Readers", value: "sread" },
|
||||
{ label: "Frequency", value: "sfrel" },
|
||||
{ label: 'Last Updated', value: 'sdate' },
|
||||
{ label: 'Rating', value: 'srate' },
|
||||
{ label: 'Rank', value: 'srank' },
|
||||
{ label: 'Reviews', value: 'sreview' },
|
||||
{ label: 'Chapters', value: 'srel' },
|
||||
{ label: 'Title', value: 'abc' },
|
||||
{ label: 'Readers', value: 'sread' },
|
||||
{ label: 'Frequency', value: 'sfrel' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
order: {
|
||||
label: "Order",
|
||||
value: "desc",
|
||||
label: 'Order',
|
||||
value: 'desc',
|
||||
options: [
|
||||
{ label: "Descending", value: "desc" },
|
||||
{ label: "Ascending", value: "asc" },
|
||||
{ label: 'Descending', value: 'desc' },
|
||||
{ label: 'Ascending', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
storyStatus: {
|
||||
label: "Story Status (Translation)",
|
||||
value: "",
|
||||
label: 'Story Status (Translation)',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Completed", value: "2" },
|
||||
{ label: "Ongoing", value: "3" },
|
||||
{ label: "Hiatus", value: "4" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Completed', value: '2' },
|
||||
{ label: 'Ongoing', value: '3' },
|
||||
{ label: 'Hiatus', value: '4' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
language: {
|
||||
label: "Language",
|
||||
label: 'Language',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Chinese", value: "495" },
|
||||
{ label: "Filipino", value: "9181" },
|
||||
{ label: "Indonesian", value: "9179" },
|
||||
{ label: "Japanese", value: "496" },
|
||||
{ label: "Khmer", value: "18657" },
|
||||
{ label: "Korean", value: "497" },
|
||||
{ label: "Malaysian", value: "9183" },
|
||||
{ label: "Thai", value: "9954" },
|
||||
{ label: "Vietnamese", value: "9177" },
|
||||
{ label: 'Chinese', value: '495' },
|
||||
{ label: 'Filipino', value: '9181' },
|
||||
{ label: 'Indonesian', value: '9179' },
|
||||
{ label: 'Japanese', value: '496' },
|
||||
{ label: 'Khmer', value: '18657' },
|
||||
{ label: 'Korean', value: '497' },
|
||||
{ label: 'Malaysian', value: '9183' },
|
||||
{ label: 'Thai', value: '9954' },
|
||||
{ label: 'Vietnamese', value: '9177' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
novelType: {
|
||||
label: "Novel Type",
|
||||
label: 'Novel Type',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Light Novel", value: "2443" },
|
||||
{ label: "Published Novel", value: "26874" },
|
||||
{ label: "Web Novel", value: "2444" },
|
||||
{ label: 'Light Novel', value: '2443' },
|
||||
{ label: 'Published Novel', value: '26874' },
|
||||
{ label: 'Web Novel', value: '2444' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genres: {
|
||||
label: "Genres",
|
||||
label: 'Genres',
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
value: {
|
||||
include: [],
|
||||
exclude: [],
|
||||
},
|
||||
options: [
|
||||
{ label: "Action", value: "8" },
|
||||
{ label: "Adult", value: "280" },
|
||||
{ label: "Adventure", value: "13" },
|
||||
{ label: "Comedy", value: "17" },
|
||||
{ label: "Drama", value: "9" },
|
||||
{ label: "Ecchi", value: "292" },
|
||||
{ label: "Fantasy", value: "5" },
|
||||
{ label: "Gender Bender", value: "168" },
|
||||
{ label: "Harem", value: "3" },
|
||||
{ label: "Historical", value: "330" },
|
||||
{ label: "Horror", value: "343" },
|
||||
{ label: "Josei", value: "324" },
|
||||
{ label: "Martial Arts", value: "14" },
|
||||
{ label: "Mature", value: "4" },
|
||||
{ label: "Mecha", value: "10" },
|
||||
{ label: "Mystery", value: "245" },
|
||||
{ label: "Psychoical", value: "486" },
|
||||
{ label: "Romance", value: "15" },
|
||||
{ label: "School Life", value: "6" },
|
||||
{ label: "Sci-fi", value: "11" },
|
||||
{ label: "Seinen", value: "18" },
|
||||
{ label: "Shoujo", value: "157" },
|
||||
{ label: "Shoujo Ai", value: "851" },
|
||||
{ label: "Shounen", value: "12" },
|
||||
{ label: "Shounen Ai", value: "1692" },
|
||||
{ label: "Slice of Life", value: "7" },
|
||||
{ label: "Smut", value: "281" },
|
||||
{ label: "Sports", value: "1357" },
|
||||
{ label: "Supernatural", value: "16" },
|
||||
{ label: "Tragedy", value: "132" },
|
||||
{ label: "Wuxia", value: "479" },
|
||||
{ label: "Xianxia", value: "480" },
|
||||
{ label: "Xuanhuan", value: "3954" },
|
||||
{ label: "Yaoi", value: "560" },
|
||||
{ label: "Yuri", value: "922" },
|
||||
{ label: 'Action', value: '8' },
|
||||
{ label: 'Adult', value: '280' },
|
||||
{ label: 'Adventure', value: '13' },
|
||||
{ label: 'Comedy', value: '17' },
|
||||
{ label: 'Drama', value: '9' },
|
||||
{ label: 'Ecchi', value: '292' },
|
||||
{ label: 'Fantasy', value: '5' },
|
||||
{ label: 'Gender Bender', value: '168' },
|
||||
{ label: 'Harem', value: '3' },
|
||||
{ label: 'Historical', value: '330' },
|
||||
{ label: 'Horror', value: '343' },
|
||||
{ label: 'Josei', value: '324' },
|
||||
{ label: 'Martial Arts', value: '14' },
|
||||
{ label: 'Mature', value: '4' },
|
||||
{ label: 'Mecha', value: '10' },
|
||||
{ label: 'Mystery', value: '245' },
|
||||
{ label: 'Psychoical', value: '486' },
|
||||
{ label: 'Romance', value: '15' },
|
||||
{ label: 'School Life', value: '6' },
|
||||
{ label: 'Sci-fi', value: '11' },
|
||||
{ label: 'Seinen', value: '18' },
|
||||
{ label: 'Shoujo', value: '157' },
|
||||
{ label: 'Shoujo Ai', value: '851' },
|
||||
{ label: 'Shounen', value: '12' },
|
||||
{ label: 'Shounen Ai', value: '1692' },
|
||||
{ label: 'Slice of Life', value: '7' },
|
||||
{ label: 'Smut', value: '281' },
|
||||
{ label: 'Sports', value: '1357' },
|
||||
{ label: 'Supernatural', value: '16' },
|
||||
{ label: 'Tragedy', value: '132' },
|
||||
{ label: 'Wuxia', value: '479' },
|
||||
{ label: 'Xianxia', value: '480' },
|
||||
{ label: 'Xuanhuan', value: '3954' },
|
||||
{ label: 'Yaoi', value: '560' },
|
||||
{ label: 'Yuri', value: '922' },
|
||||
],
|
||||
},
|
||||
genre_operator: {
|
||||
label: "Genre (AND/OR)",
|
||||
value: "and",
|
||||
label: 'Genre (AND/OR)',
|
||||
value: 'and',
|
||||
options: [
|
||||
{ label: "AND", value: "and" },
|
||||
{ label: "OR", value: "or" },
|
||||
{ label: 'AND', value: 'and' },
|
||||
{ label: 'OR', value: 'or' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+92
-88
@@ -1,19 +1,19 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class PawRead implements Plugin.PluginBase {
|
||||
id = "pawread";
|
||||
name = "PawRead";
|
||||
version = "2.0.0";
|
||||
icon = "src/en/pawread/icon.png";
|
||||
site = "https://m.pawread.com/";
|
||||
id = 'pawread';
|
||||
name = 'PawRead';
|
||||
version = '2.0.0';
|
||||
icon = 'src/en/pawread/icon.png';
|
||||
site = 'https://m.pawread.com/';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".list-comic, .itemBox").each((idx, ele) => {
|
||||
loadedCheerio('.list-comic, .itemBox').each((idx, ele) => {
|
||||
loadedCheerio(ele).find('.serialise').remove();
|
||||
const novelName = loadedCheerio(ele).find('a').text().trim();
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('src');
|
||||
@@ -35,27 +35,25 @@ class PawRead implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}list/`;
|
||||
|
||||
const filterValues = [
|
||||
filters.status.value,
|
||||
filters.lang.value,
|
||||
filters.genre.value
|
||||
filters.genre.value,
|
||||
];
|
||||
|
||||
link += filterValues.filter(value => value !== '').join('-');
|
||||
if (filterValues.some(value => value !== ''))
|
||||
link += '/';
|
||||
if (filterValues.some(value => value !== '')) link += '/';
|
||||
|
||||
if (filters.order.value)
|
||||
link += '-';
|
||||
if (filters.order.value) link += '-';
|
||||
|
||||
link += filters.sort.value;
|
||||
link += `/?page=${page}`;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -69,14 +67,14 @@ class PawRead implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("#Cover img").attr('title') || "Untitled",
|
||||
cover: loadedCheerio("#Cover img").attr('src'),
|
||||
name: loadedCheerio('#Cover img').attr('title') || 'Untitled',
|
||||
cover: loadedCheerio('#Cover img').attr('src'),
|
||||
author: loadedCheerio('.icon01 <').text().trim(),
|
||||
status: loadedCheerio('.txtItme:first').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.summary = loadedCheerio("#full-des")
|
||||
novel.summary = loadedCheerio('#full-des')
|
||||
.find('br')
|
||||
.replaceWith('\n')
|
||||
.end()
|
||||
@@ -86,22 +84,26 @@ class PawRead implements Plugin.PluginBase {
|
||||
novel.genres = loadedCheerio('a.btn-default')
|
||||
.map((i, el) => loadedCheerio(el).text().trim())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".item-box").each((idx, ele) => {
|
||||
loadedCheerio('.item-box').each((idx, ele) => {
|
||||
const smallText = loadedCheerio(ele).find('span:last').text().trim();
|
||||
if (smallText === "Advanced Chapter") return;
|
||||
if (smallText === 'Advanced Chapter') return;
|
||||
const releaseDate = smallText.split('.').map(x => Number(x));
|
||||
const chapterName = loadedCheerio(ele).find("span:first").text().trim();
|
||||
const chapterUrl = loadedCheerio(ele).attr("onclick")?.match(/\d+/)![0];
|
||||
const chapterName = loadedCheerio(ele).find('span:first').text().trim();
|
||||
const chapterUrl = loadedCheerio(ele).attr('onclick')?.match(/\d+/)![0];
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
path: `${novelPath}${chapterUrl}.html`,
|
||||
releaseTime: new Date(releaseDate[0], releaseDate[1], releaseDate[2]).toISOString(),
|
||||
releaseTime: new Date(
|
||||
releaseDate[0],
|
||||
releaseDate[1],
|
||||
releaseDate[2],
|
||||
).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,7 +111,6 @@ class PawRead implements Plugin.PluginBase {
|
||||
return novel;
|
||||
}
|
||||
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
const body = await result.text();
|
||||
@@ -118,12 +119,15 @@ class PawRead implements Plugin.PluginBase {
|
||||
|
||||
const steal = ['bit.ly', 'tinyurl', 'pawread'];
|
||||
steal.map(tag => loadedCheerio(`p:icontains(${tag})`).remove());
|
||||
const chapterText = loadedCheerio('.main').html() || "";
|
||||
const chapterText = loadedCheerio('.main').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = `${this.site}search/?keywords=${searchTerm}&page=${page}`;
|
||||
|
||||
const result = await fetchApi(searchUrl);
|
||||
@@ -139,85 +143,85 @@ class PawRead implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
status: {
|
||||
value: "",
|
||||
label: "Status",
|
||||
value: '',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Completed", value: "wanjie" },
|
||||
{ label: "Ongoing", value: "lianzai" },
|
||||
{ label: "Hiatus", value: "hiatus" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Completed', value: 'wanjie' },
|
||||
{ label: 'Ongoing', value: 'lianzai' },
|
||||
{ label: 'Hiatus', value: 'hiatus' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
lang: {
|
||||
value: "",
|
||||
label: "Languages",
|
||||
value: '',
|
||||
label: 'Languages',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Chinese", value: "chinese" },
|
||||
{ label: "Korean", value: "korean" },
|
||||
{ label: "Japanese", value: "japanese" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Chinese', value: 'chinese' },
|
||||
{ label: 'Korean', value: 'korean' },
|
||||
{ label: 'Japanese', value: 'japanese' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
value: "",
|
||||
label: "Genres",
|
||||
value: '',
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Fantasy", value: "Fantasy" },
|
||||
{ label: "Action", value: "Action" },
|
||||
{ label: "Xuanhuan", value: "Xuanhuan" },
|
||||
{ label: "Romance", value: "Romance" },
|
||||
{ label: "Comedy", value: "Comedy" },
|
||||
{ label: "Mystery", value: "Mystery" },
|
||||
{ label: "Mature", value: "Mature" },
|
||||
{ label: "Harem", value: "Harem" },
|
||||
{ label: "Wuxia", value: "Wuxia" },
|
||||
{ label: "Xianxia", value: "Xianxia" },
|
||||
{ label: "Tragedy", value: "Tragedy" },
|
||||
{ label: "Sci-fi", value: "Scifi" },
|
||||
{ label: "Historical", value: "Historical" },
|
||||
{ label: "Ecchi", value: "Ecchi" },
|
||||
{ label: "Adventure", value: "Adventure" },
|
||||
{ label: "Adult", value: "Adult" },
|
||||
{ label: "Supernatural", value: "Supernatural" },
|
||||
{ label: "Psychological", value: "Psychological" },
|
||||
{ label: "Drama", value: "Drama" },
|
||||
{ label: "Horror", value: "Horror" },
|
||||
{ label: "Josei", value: "Josei" },
|
||||
{ label: "Mecha", value: "Mecha" },
|
||||
{ label: "Seinen", value: "Seinen" },
|
||||
{ label: "Shoujo", value: "Shoujo" },
|
||||
{ label: "Shounen", value: "Shounen" },
|
||||
{ label: "Smut", value: "Smut" },
|
||||
{ label: "Yaoi", value: "Yaoi" },
|
||||
{ label: "Yuri", value: "Yuri" },
|
||||
{ label: "Martial Arts", value: "MartialArts" },
|
||||
{ label: "School Life", value: "SchoolLife" },
|
||||
{ label: "Shoujo Ai", value: "ShoujoAi" },
|
||||
{ label: "Shounen Ai", value: "ShounenAi" },
|
||||
{ label: "Slice of Life", value: "SliceofLife" },
|
||||
{ label: "Gender Bender", value: "GenderBender" },
|
||||
{ label: "Sports", value: "Sports" },
|
||||
{ label: "Urban", value: "Urban" },
|
||||
{ label: "Adventurer", value: "Adventurer" }
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Fantasy', value: 'Fantasy' },
|
||||
{ label: 'Action', value: 'Action' },
|
||||
{ label: 'Xuanhuan', value: 'Xuanhuan' },
|
||||
{ label: 'Romance', value: 'Romance' },
|
||||
{ label: 'Comedy', value: 'Comedy' },
|
||||
{ label: 'Mystery', value: 'Mystery' },
|
||||
{ label: 'Mature', value: 'Mature' },
|
||||
{ label: 'Harem', value: 'Harem' },
|
||||
{ label: 'Wuxia', value: 'Wuxia' },
|
||||
{ label: 'Xianxia', value: 'Xianxia' },
|
||||
{ label: 'Tragedy', value: 'Tragedy' },
|
||||
{ label: 'Sci-fi', value: 'Scifi' },
|
||||
{ label: 'Historical', value: 'Historical' },
|
||||
{ label: 'Ecchi', value: 'Ecchi' },
|
||||
{ label: 'Adventure', value: 'Adventure' },
|
||||
{ label: 'Adult', value: 'Adult' },
|
||||
{ label: 'Supernatural', value: 'Supernatural' },
|
||||
{ label: 'Psychological', value: 'Psychological' },
|
||||
{ label: 'Drama', value: 'Drama' },
|
||||
{ label: 'Horror', value: 'Horror' },
|
||||
{ label: 'Josei', value: 'Josei' },
|
||||
{ label: 'Mecha', value: 'Mecha' },
|
||||
{ label: 'Seinen', value: 'Seinen' },
|
||||
{ label: 'Shoujo', value: 'Shoujo' },
|
||||
{ label: 'Shounen', value: 'Shounen' },
|
||||
{ label: 'Smut', value: 'Smut' },
|
||||
{ label: 'Yaoi', value: 'Yaoi' },
|
||||
{ label: 'Yuri', value: 'Yuri' },
|
||||
{ label: 'Martial Arts', value: 'MartialArts' },
|
||||
{ label: 'School Life', value: 'SchoolLife' },
|
||||
{ label: 'Shoujo Ai', value: 'ShoujoAi' },
|
||||
{ label: 'Shounen Ai', value: 'ShounenAi' },
|
||||
{ label: 'Slice of Life', value: 'SliceofLife' },
|
||||
{ label: 'Gender Bender', value: 'GenderBender' },
|
||||
{ label: 'Sports', value: 'Sports' },
|
||||
{ label: 'Urban', value: 'Urban' },
|
||||
{ label: 'Adventurer', value: 'Adventurer' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "click",
|
||||
label: "Sort By",
|
||||
value: 'click',
|
||||
label: 'Sort By',
|
||||
options: [
|
||||
{ label: "Time Updated", value: "update" },
|
||||
{ label: "Time Posted", value: "post" },
|
||||
{ label: "Clicks", value: "click" },
|
||||
{ label: 'Time Updated', value: 'update' },
|
||||
{ label: 'Time Posted', value: 'post' },
|
||||
{ label: 'Clicks', value: 'click' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
order: {
|
||||
value: false,
|
||||
label: "Order ↑ ↓",
|
||||
label: 'Order ↑ ↓',
|
||||
type: FilterTypes.Switch,
|
||||
},
|
||||
} satisfies Filters;
|
||||
|
||||
+38
-34
@@ -1,7 +1,7 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
interface ChapterEntry {
|
||||
id: number;
|
||||
@@ -18,14 +18,14 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
filters?: Filters | undefined;
|
||||
version = '1.0.0';
|
||||
|
||||
async sleep (ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
async sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('.short-cont').each((i,el) => {
|
||||
loadedCheerio('.short-cont').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('h2.title').text();
|
||||
const novelUrl = loadedCheerio(el).find('h2.title a').attr('href');
|
||||
const novelCover = loadedCheerio(el)
|
||||
@@ -39,13 +39,13 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl?.replace(this.site, ''),
|
||||
}
|
||||
};
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseChapters(data: {chapters: ChapterEntry[]}){
|
||||
parseChapters(data: { chapters: ChapterEntry[] }) {
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
data.chapters.map((item: ChapterEntry) => {
|
||||
chapter.push({
|
||||
@@ -59,27 +59,30 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}/novels/page/${page}/`;
|
||||
const body = await fetchApi(link).then((result) =>
|
||||
result.text()
|
||||
);
|
||||
const body = await fetchApi(link).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number; }> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const url = this.site + novelPath;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel & { totalPages: number; } = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio('.poster img').attr('alt') || "Untitled",
|
||||
name: loadedCheerio('.poster img').attr('alt') || 'Untitled',
|
||||
cover: this.site + loadedCheerio('.poster img').attr('src'),
|
||||
author: loadedCheerio('[itemprop="creator"]').text(),
|
||||
chapters: [],
|
||||
@@ -93,8 +96,8 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
novel.status = loadedCheerio('li:contains("Status") span')
|
||||
.text() === 'Ongoing'
|
||||
novel.status =
|
||||
loadedCheerio('li:contains("Status") span').text() === 'Ongoing'
|
||||
? 'Ongoing'
|
||||
: 'Completed';
|
||||
|
||||
@@ -106,7 +109,9 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
chapterListUrl = this.site + chapterListUrl;
|
||||
}
|
||||
|
||||
const chaptersHtmlToString = await fetchApi(chapterListUrl).then(r => r.text());
|
||||
const chaptersHtmlToString = await fetchApi(chapterListUrl).then(r =>
|
||||
r.text(),
|
||||
);
|
||||
const pageCheerio = parseHTML(chaptersHtmlToString);
|
||||
|
||||
const json = pageCheerio('#dle-content main').next().html()!;
|
||||
@@ -115,13 +120,11 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
novel.chapters = this.parseChapters(data);
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const pagePath = novelPath.split('-')[0];
|
||||
const firstUrl = this.site +
|
||||
'/chapters' +
|
||||
pagePath.replace('novels/', '');
|
||||
const firstUrl = this.site + '/chapters' + pagePath.replace('novels/', '');
|
||||
const pageUrl = firstUrl + '/page/' + page;
|
||||
|
||||
const body = await fetchApi(firstUrl).then(r => r.text());
|
||||
@@ -134,11 +137,13 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
const latestChapterName = data.chapters[0].title;
|
||||
const latestChapterDate = data.chapters[0].date;
|
||||
|
||||
const latestChapter: Plugin.ChapterItem | undefined = latestChapterUrl ? {
|
||||
const latestChapter: Plugin.ChapterItem | undefined = latestChapterUrl
|
||||
? {
|
||||
path: latestChapterUrl.replace(this.site, ''),
|
||||
name: latestChapterName,
|
||||
releaseTime: new Date(latestChapterDate).toISOString(),
|
||||
} : undefined
|
||||
}
|
||||
: undefined;
|
||||
|
||||
await this.sleep(1000);
|
||||
|
||||
@@ -149,8 +154,8 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
const chapters = this.parseChapters(data);
|
||||
return {
|
||||
chapters,
|
||||
latestChapter
|
||||
}
|
||||
latestChapter,
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
@@ -159,26 +164,25 @@ class RanobesPlugin implements Plugin.PagePlugin {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio('#arrticle').html() || "";
|
||||
const chapterText = loadedCheerio('#arrticle').html() || '';
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}/search/${searchTerm}/page/${page}`;
|
||||
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio)
|
||||
};
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return await fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
export default new RanobesPlugin();
|
||||
|
||||
@@ -1,49 +1,50 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { isUrlAbsolute } from "@libs/isAbsoluteUrl";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import dayjs from "dayjs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class ReadLiteNovel implements Plugin.PluginBase {
|
||||
id = "rln.app";
|
||||
name = "ReadLiteNovel";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/readlitenovel/icon.png";
|
||||
site = "https://rln.app";
|
||||
id = 'rln.app';
|
||||
name = 'ReadLiteNovel';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/readlitenovel/icon.png';
|
||||
site = 'https://rln.app';
|
||||
|
||||
parseAgoDate(date: string | undefined){ //parseMadaraDate
|
||||
if (date?.includes("ago")) {
|
||||
parseAgoDate(date: string | undefined) {
|
||||
//parseMadaraDate
|
||||
if (date?.includes('ago')) {
|
||||
const dayJSDate = dayjs(new Date()); // today
|
||||
const timeAgo = date.match(/\d+/)?.[0] || "";
|
||||
const timeAgo = date.match(/\d+/)?.[0] || '';
|
||||
const timeAgoInt = parseInt(timeAgo, 10);
|
||||
|
||||
if (!timeAgo) return null; // there is no number!
|
||||
|
||||
if (date.includes("hours ago") || date.includes("hour ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||
if (date.includes('hours ago') || date.includes('hour ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'hours'); // go back N hours
|
||||
}
|
||||
|
||||
if (date.includes("days ago") || date.includes("day ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||
if (date.includes('days ago') || date.includes('day ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'days'); // go back N days
|
||||
}
|
||||
|
||||
if (date.includes("months ago") || date.includes("month ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
||||
if (date.includes('months ago') || date.includes('month ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'months'); // go back N months
|
||||
}
|
||||
|
||||
if (date.includes("years ago") || date.includes("year ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "years"); // go back N years
|
||||
if (date.includes('years ago') || date.includes('year ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'years'); // go back N years
|
||||
}
|
||||
|
||||
return dayJSDate.toISOString();
|
||||
}
|
||||
return null; // there is no "ago" so give up
|
||||
};
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const link = `${this.site}/ranking/${filters.order.value}/${page}`;
|
||||
const result = await fetchApi(link);
|
||||
@@ -53,19 +54,15 @@ class ReadLiteNovel implements Plugin.PluginBase {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".category-items li").each((i,el) => {
|
||||
const novelUrl = loadedCheerio(el)
|
||||
.find(".category-name a")
|
||||
.attr("href");
|
||||
loadedCheerio('.category-items li').each((i, el) => {
|
||||
const novelUrl = loadedCheerio(el).find('.category-name a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
const novelName = loadedCheerio(el)
|
||||
.find(".category-name a")
|
||||
.find('.category-name a')
|
||||
.text()
|
||||
.trim();
|
||||
let novelCover = loadedCheerio(el)
|
||||
.find(".category-img img")
|
||||
.attr("src");
|
||||
let novelCover = loadedCheerio(el).find('.category-img img').attr('src');
|
||||
|
||||
if (novelCover && !isUrlAbsolute(novelCover)) {
|
||||
novelCover = this.site + novelCover;
|
||||
@@ -77,9 +74,9 @@ class ReadLiteNovel implements Plugin.PluginBase {
|
||||
cover: novelCover,
|
||||
};
|
||||
novels.push(novel);
|
||||
})
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.site + novelPath);
|
||||
@@ -89,55 +86,60 @@ class ReadLiteNovel implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".novel-title").text() || "Untitled",
|
||||
cover: loadedCheerio(".novels-detail img").attr("src"),
|
||||
name: loadedCheerio('.novel-title').text() || 'Untitled',
|
||||
cover: loadedCheerio('.novels-detail img').attr('src'),
|
||||
summary: loadedCheerio('.empty-box').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
loadedCheerio('.novels-detail-right li').each((i,el) => {
|
||||
loadedCheerio('.novels-detail-right li').each((i, el) => {
|
||||
const detailName = loadedCheerio(el).find('div:first').text();
|
||||
const detail = loadedCheerio(el).find('div:last');
|
||||
|
||||
switch(detailName){
|
||||
case "Status:":
|
||||
switch (detailName) {
|
||||
case 'Status:':
|
||||
novel.status = detail.text();
|
||||
break;
|
||||
case "Genres:":
|
||||
novel.genres = detail.find('a')
|
||||
.map((i,el) => loadedCheerio(el).text())
|
||||
case 'Genres:':
|
||||
novel.genres =
|
||||
detail
|
||||
.find('a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(', ') || detail.text();
|
||||
break;
|
||||
case "Author(s):":
|
||||
novel.author = detail.find('a')
|
||||
.map((i,el) => loadedCheerio(el).text())
|
||||
case 'Author(s):':
|
||||
novel.author =
|
||||
detail
|
||||
.find('a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(', ') || detail.text();
|
||||
break;
|
||||
case "Translator:":
|
||||
case 'Translator:':
|
||||
novel.artist = detail.text();
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio(".cm-tabs-content li").each((i,el) => {
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
loadedCheerio('.cm-tabs-content li').each((i, el) => {
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
if (!chapterUrl) return;
|
||||
const chapterName = loadedCheerio(el).find("a").text().trim();
|
||||
const chapterName = loadedCheerio(el).find('a').text().trim();
|
||||
const releaseDate = this.parseAgoDate(
|
||||
loadedCheerio(el).find('svg').attr('data-bs-original-title')!
|
||||
loadedCheerio(el).find('svg').attr('data-bs-original-title')!,
|
||||
);
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl?.replace(this.site, ''),
|
||||
releaseTime: releaseDate,
|
||||
})})
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = chapter;
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
@@ -145,27 +147,32 @@ class ReadLiteNovel implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#chapterText").html() || "";
|
||||
const chapterText = loadedCheerio('#chapterText').html() || '';
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + `/search/autocomplete?dataType=json&query=${searchTerm}`;
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site + `/search/autocomplete?dataType=json&query=${searchTerm}`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json();
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.results.forEach((item: {link: string; original_title:string; image:string;}) =>
|
||||
body.results.forEach(
|
||||
(item: { link: string; original_title: string; image: string }) =>
|
||||
novels.push({
|
||||
path: item.link,
|
||||
name: item.original_title,
|
||||
cover: item.image,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
@@ -173,13 +180,13 @@ class ReadLiteNovel implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "top-rated",
|
||||
label: "Order by",
|
||||
value: 'top-rated',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "MOST VIEWED", value: "most-viewed" },
|
||||
{ label: "TOP RATED", value: "top-rated" },
|
||||
{ label: "BOOKMARKS", value: "subscribers" },
|
||||
{ label: "NEW", value: "new" },
|
||||
{ label: 'MOST VIEWED', value: 'most-viewed' },
|
||||
{ label: 'TOP RATED', value: 'top-rated' },
|
||||
{ label: 'BOOKMARKS', value: 'subscribers' },
|
||||
{ label: 'NEW', value: 'new' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class RoyalRoad implements Plugin.PluginBase {
|
||||
id = "royalroad";
|
||||
name = "Royal Road";
|
||||
version = "1.0.1";
|
||||
icon = "src/en/royalroad/icon.png";
|
||||
site = "https://www.royalroad.com/";
|
||||
id = 'royalroad';
|
||||
name = 'Royal Road';
|
||||
version = '1.0.1';
|
||||
icon = 'src/en/royalroad/icon.png';
|
||||
site = 'https://www.royalroad.com/';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".fiction-list-item").each((idx, ele) => {
|
||||
loadedCheerio('.fiction-list-item').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('a.bold').text();
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(ele).find('a.bold').attr('href')?.slice(1);
|
||||
@@ -34,17 +34,16 @@ class RoyalRoad implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}fictions/`;
|
||||
|
||||
link += filters.order.value;
|
||||
link += `?page=${page}`;
|
||||
|
||||
if (filters.genre.value !== '')
|
||||
link += `&genre=${filters.genre.value}`;
|
||||
if (filters.genre.value !== '') link += `&genre=${filters.genre.value}`;
|
||||
|
||||
const body = await fetchApi(link).then((r) => r.text());
|
||||
const body = await fetchApi(link).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -58,10 +57,10 @@ class RoyalRoad implements Plugin.PluginBase {
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1").text() || "Untitled",
|
||||
name: loadedCheerio('h1').text() || 'Untitled',
|
||||
cover: loadedCheerio('.cover-art-container img').attr('src'),
|
||||
author: loadedCheerio('h4 a').text(),
|
||||
summary: loadedCheerio(".description").text().trim(),
|
||||
summary: loadedCheerio('.description').text().trim(),
|
||||
status: loadedCheerio('span.label-sm:last').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
@@ -69,20 +68,25 @@ class RoyalRoad implements Plugin.PluginBase {
|
||||
novel.genres = loadedCheerio('span.tags a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
const chapterJson = JSON.parse(loadedCheerio('script:contains("window.chapters")')
|
||||
const chapterJson = JSON.parse(
|
||||
loadedCheerio('script:contains("window.chapters")')
|
||||
.html()
|
||||
?.match(/window.chapters = (.+])(?=;)/)![1] || "");
|
||||
?.match(/window.chapters = (.+])(?=;)/)![1] || '',
|
||||
);
|
||||
|
||||
const volumeJson = JSON.parse(loadedCheerio('script:contains("window.chapters")')
|
||||
const volumeJson = JSON.parse(
|
||||
loadedCheerio('script:contains("window.chapters")')
|
||||
.html()
|
||||
?.match(/window.volumes = (.+])(?=;)/)![1] || "");
|
||||
?.match(/window.volumes = (.+])(?=;)/)![1] || '',
|
||||
);
|
||||
|
||||
const chapter : Plugin.ChapterItem[] = chapterJson.map(
|
||||
const chapter: Plugin.ChapterItem[] = chapterJson.map(
|
||||
(chapter: ChapterEntry) => {
|
||||
const matchingVolume = volumeJson.find(
|
||||
(volume: VolumeEntry) => volume.id === chapter.volumeId);
|
||||
(volume: VolumeEntry) => volume.id === chapter.volumeId,
|
||||
);
|
||||
return {
|
||||
name: chapter.title,
|
||||
path: chapter.url.slice(1),
|
||||
@@ -90,26 +94,28 @@ class RoyalRoad implements Plugin.PluginBase {
|
||||
chapterNumber: chapter?.order,
|
||||
page: matchingVolume ? matchingVolume.title : null,
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
novel.chapters = chapter;
|
||||
return novel;
|
||||
}
|
||||
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio(".chapter-content").html() || '';
|
||||
const chapterText = loadedCheerio('.chapter-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = `${this.site}fictions/search?page=${page}&title=${searchTerm}`;
|
||||
|
||||
const result = await fetchApi(searchUrl);
|
||||
@@ -125,44 +131,44 @@ class RoyalRoad implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "weekly-popular",
|
||||
label: "Order By",
|
||||
value: 'weekly-popular',
|
||||
label: 'Order By',
|
||||
options: [
|
||||
{ label: "Best Rated", value: "best-rated" },
|
||||
{ label: "Trending", value: "trending" },
|
||||
{ label: "Ongoing Fictions", value: "active-popular" },
|
||||
{ label: "Complete", value: "complete" },
|
||||
{ label: "Popular this week", value: "weekly-popular" },
|
||||
{ label: "Latest Updates", value: "latest-updates" },
|
||||
{ label: "Newest Fictions", value: "new" },
|
||||
{ label: "Rising Stars", value: "rising-stars" },
|
||||
{ label: "Writathon", value: "writathon" },
|
||||
{ label: 'Best Rated', value: 'best-rated' },
|
||||
{ label: 'Trending', value: 'trending' },
|
||||
{ label: 'Ongoing Fictions', value: 'active-popular' },
|
||||
{ label: 'Complete', value: 'complete' },
|
||||
{ label: 'Popular this week', value: 'weekly-popular' },
|
||||
{ label: 'Latest Updates', value: 'latest-updates' },
|
||||
{ label: 'Newest Fictions', value: 'new' },
|
||||
{ label: 'Rising Stars', value: 'rising-stars' },
|
||||
{ label: 'Writathon', value: 'writathon' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
value: "",
|
||||
label: "Genres",
|
||||
value: '',
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "ALL", value: "" },
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Contemporary", value: "contemporary" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "Satire", value: "satire" },
|
||||
{ label: "Sci-fi", value: "sci_fi" },
|
||||
{ label: "Short Story", value: "one_shot" },
|
||||
{ label: "Tragedy", value: "tragedy" }
|
||||
{ label: 'ALL', value: '' },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Contemporary', value: 'contemporary' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'Satire', value: 'satire' },
|
||||
{ label: 'Sci-fi', value: 'sci_fi' },
|
||||
{ label: 'Short Story', value: 'one_shot' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
}
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import dayjs from "dayjs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
id = "scribblehub";
|
||||
name = "ScribbleHub";
|
||||
icon = "src/en/scribblehub/icon.png";
|
||||
site = "https://www.scribblehub.com/";
|
||||
version = "1.0.0";
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
id = 'scribblehub';
|
||||
name = 'ScribbleHub';
|
||||
icon = 'src/en/scribblehub/icon.png';
|
||||
site = 'https://www.scribblehub.com/';
|
||||
version = '1.0.0';
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('.search_main_box').each((i,el) => {
|
||||
loadedCheerio('.search_main_box').each((i, el) => {
|
||||
const novelName = loadedCheerio(el).find('.search_title > a').text();
|
||||
const novelCover = loadedCheerio(el).find('.search_img > img').attr('src');
|
||||
const novelCover = loadedCheerio(el)
|
||||
.find('.search_img > img')
|
||||
.attr('src');
|
||||
const novelUrl = loadedCheerio(el).find('.search_title > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
@@ -24,7 +26,7 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
}
|
||||
};
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
@@ -32,14 +34,16 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}`;
|
||||
if (showLatestNovels)
|
||||
link += "latest-series/"
|
||||
else // if (filters.genres.value.include?.length ||
|
||||
if (showLatestNovels) link += 'latest-series/';
|
||||
// if (filters.genres.value.include?.length ||
|
||||
// filters.genres.value.exclude?.length)
|
||||
link += "series-finder/?sf=1"
|
||||
else link += 'series-finder/?sf=1';
|
||||
// else
|
||||
// link += "series-ranking/";
|
||||
// TODO, series-ranking filters when hideOnSelect come out
|
||||
@@ -50,7 +54,10 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
if (filters.genres.value.exclude?.length)
|
||||
link += '&ge=' + filters.genres.value.exclude.join(',');
|
||||
|
||||
if (filters.genres.value.include?.length || filters.genres.value.exclude?.length)
|
||||
if (
|
||||
filters.genres.value.include?.length ||
|
||||
filters.genres.value.exclude?.length
|
||||
)
|
||||
link += '&mgi=' + filters.genre_operator.value;
|
||||
|
||||
if (filters.content_warning.value.include?.length)
|
||||
@@ -59,16 +66,16 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
if (filters.content_warning.value.exclude?.length)
|
||||
link += '&cte=' + filters.content_warning.value.exclude.join(',');
|
||||
|
||||
if (filters.content_warning.value.include?.length ||
|
||||
filters.content_warning.value.exclude?.length)
|
||||
if (
|
||||
filters.content_warning.value.include?.length ||
|
||||
filters.content_warning.value.exclude?.length
|
||||
)
|
||||
link += '&mct' + filters.content_warning_operator.value;
|
||||
|
||||
link += '&sort=' + filters.sort.value;
|
||||
link += '&order=' + filters.order.value;
|
||||
|
||||
const body = await fetchApi(link).then((result) =>
|
||||
result.text()
|
||||
);
|
||||
const body = await fetchApi(link).then(result => result.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
@@ -92,12 +99,9 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
novel.genres = loadedCheerio('.fic_genre')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
novel.status = loadedCheerio('.rnd_stats')
|
||||
.next()
|
||||
.text()
|
||||
.includes('Ongoing')
|
||||
novel.status = loadedCheerio('.rnd_stats').next().text().includes('Ongoing')
|
||||
? 'Ongoing'
|
||||
: 'Completed';
|
||||
|
||||
@@ -106,13 +110,10 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
formData.append('pagenum', '-1');
|
||||
formData.append('mypostid', novelPath.split('/')[1]);
|
||||
|
||||
const data = await fetchApi(
|
||||
`${this.site}wp-admin/admin-ajax.php`,
|
||||
{
|
||||
const data = await fetchApi(`${this.site}wp-admin/admin-ajax.php`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
},
|
||||
);
|
||||
});
|
||||
const text = await data.text();
|
||||
|
||||
loadedCheerio = parseHTML(text);
|
||||
@@ -120,23 +121,23 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
const parseISODate = (date: string) => {
|
||||
if (date.includes("ago")) {
|
||||
if (date.includes('ago')) {
|
||||
const dayJSDate = dayjs(new Date()); // today
|
||||
const timeAgo = date.match(/\d+/)?.[0] || "";
|
||||
const timeAgo = date.match(/\d+/)?.[0] || '';
|
||||
const timeAgoInt = parseInt(timeAgo, 10);
|
||||
|
||||
if (!timeAgo) return null; // there is no number!
|
||||
|
||||
if (date.includes("hours ago") || date.includes("hour ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||
if (date.includes('hours ago') || date.includes('hour ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'hours'); // go back N hours
|
||||
}
|
||||
|
||||
if (date.includes("days ago") || date.includes("day ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||
if (date.includes('days ago') || date.includes('day ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'days'); // go back N days
|
||||
}
|
||||
|
||||
if (date.includes("months ago") || date.includes("month ago")) {
|
||||
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
||||
if (date.includes('months ago') || date.includes('month ago')) {
|
||||
dayJSDate.subtract(timeAgoInt, 'months'); // go back N months
|
||||
}
|
||||
|
||||
return dayJSDate.toISOString();
|
||||
@@ -144,7 +145,7 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
return null;
|
||||
};
|
||||
|
||||
loadedCheerio('.toc_w').each((i,el) => {
|
||||
loadedCheerio('.toc_w').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('.toc_a').text();
|
||||
const releaseDate = loadedCheerio(el).find('.fic_date_pub').text();
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
@@ -174,9 +175,9 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
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 body = await result.text();
|
||||
|
||||
@@ -190,41 +191,41 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Sort Results By",
|
||||
value: "ratings",
|
||||
label: 'Sort Results By',
|
||||
value: 'ratings',
|
||||
options: [
|
||||
{ label: "Chapters", value: "chapters" },
|
||||
{ label: "Chapters per week", value: "frequency" },
|
||||
{ label: "Date Added", value: "dateadded" },
|
||||
{ label: "Favorites", value: "favorites" },
|
||||
{ label: "Last Updated", value: "lastchdate" },
|
||||
{ label: "Number of Ratings", value: "numofrate" },
|
||||
{ label: "Pages", value: "pages" },
|
||||
{ label: "Pageviews", value: "pageviews" },
|
||||
{ label: "Ratings", value: "ratings" },
|
||||
{ label: "Readers", value: "readers" },
|
||||
{ label: "Reviews", value: "reviews" },
|
||||
{ label: "Total Words", value: "totalwords" },
|
||||
{ label: 'Chapters', value: 'chapters' },
|
||||
{ label: 'Chapters per week', value: 'frequency' },
|
||||
{ label: 'Date Added', value: 'dateadded' },
|
||||
{ label: 'Favorites', value: 'favorites' },
|
||||
{ label: 'Last Updated', value: 'lastchdate' },
|
||||
{ label: 'Number of Ratings', value: 'numofrate' },
|
||||
{ label: 'Pages', value: 'pages' },
|
||||
{ label: 'Pageviews', value: 'pageviews' },
|
||||
{ label: 'Ratings', value: 'ratings' },
|
||||
{ label: 'Readers', value: 'readers' },
|
||||
{ label: 'Reviews', value: 'reviews' },
|
||||
{ label: 'Total Words', value: 'totalwords' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
order: {
|
||||
label: "Order by",
|
||||
value: "desc",
|
||||
label: 'Order by',
|
||||
value: 'desc',
|
||||
options: [
|
||||
{ label: "Descending", value: "desc" },
|
||||
{ label: "Ascending", value: "asc" },
|
||||
{ label: 'Descending', value: 'desc' },
|
||||
{ label: 'Ascending', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
storyStatus: {
|
||||
label: "Story Status (Translation)",
|
||||
value: "",
|
||||
label: 'Story Status (Translation)',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: "Hiatus", value: "hiatus" },
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
{ label: 'Hiatus', value: 'hiatus' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
@@ -269,7 +270,7 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
genre_operator : {
|
||||
genre_operator: {
|
||||
value: 'and',
|
||||
label: 'Genre And/Or',
|
||||
options: [
|
||||
@@ -283,13 +284,13 @@ class ScribbleHubPlugin implements Plugin.PluginBase {
|
||||
include: [],
|
||||
exclude: [],
|
||||
},
|
||||
label: "Mature Content",
|
||||
label: 'Mature Content',
|
||||
options: [
|
||||
{ label: 'Gore', value: '48' },
|
||||
{ label: 'Sexual Content', value: '50' },
|
||||
{ label: 'Strong Language', value: '49' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
content_warning_operator: {
|
||||
value: 'and',
|
||||
|
||||
+95
-82
@@ -1,119 +1,143 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
|
||||
class WTRLAB implements Plugin.PluginBase {
|
||||
id = "WTRLAB";
|
||||
name = "WTR-LAB";
|
||||
site = "https://wtr-lab.com/";
|
||||
version = "1.0.0";
|
||||
icon = "src/en/wtrlab/icon.png";
|
||||
sourceLang = "en/"
|
||||
id = 'WTRLAB';
|
||||
name = 'WTR-LAB';
|
||||
site = 'https://wtr-lab.com/';
|
||||
version = '1.0.0';
|
||||
icon = 'src/en/wtrlab/icon.png';
|
||||
sourceLang = 'en/';
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site + this.sourceLang + "novel-list?";
|
||||
let link = this.site + this.sourceLang + 'novel-list?';
|
||||
link += `orderBy=${filters.order.value}`;
|
||||
link += `&order=${filters.sort.value}`;
|
||||
link += `&filter=${filters.storyStatus.value}`;
|
||||
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', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ page: page })
|
||||
body: JSON.stringify({ page: page }),
|
||||
});
|
||||
|
||||
const recentNovel: JsonNovel = await response.json();
|
||||
|
||||
// Parse novels from JSON
|
||||
const novels: Plugin.NovelItem[] = recentNovel.data.map((datum: Datum) => ({
|
||||
name: datum.serie.data.title || "",
|
||||
const novels: Plugin.NovelItem[] = recentNovel.data.map(
|
||||
(datum: Datum) => ({
|
||||
name: datum.serie.data.title || '',
|
||||
cover: datum.serie.data.image,
|
||||
path: this.sourceLang + "serie-" + datum.serie.raw_id + "/" + datum.serie.slug || "",
|
||||
}));
|
||||
path:
|
||||
this.sourceLang +
|
||||
'serie-' +
|
||||
datum.serie.raw_id +
|
||||
'/' +
|
||||
datum.serie.slug || '',
|
||||
}),
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
else {
|
||||
const body = await fetchApi(link).then((res) => res.text());
|
||||
} else {
|
||||
const body = await fetchApi(link).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
//console.log(link);
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio(".serie-item")
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('.serie-item')
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find(".title-wrap > a").text().replace(loadedCheerio(element).find(".rawtitle").text(), "") || "",
|
||||
cover: loadedCheerio(element).find("img").attr("src"),
|
||||
path: loadedCheerio(element).find("a").attr("href") || "",
|
||||
name:
|
||||
loadedCheerio(element)
|
||||
.find('.title-wrap > a')
|
||||
.text()
|
||||
.replace(loadedCheerio(element).find('.rawtitle').text(), '') ||
|
||||
'',
|
||||
cover: loadedCheerio(element).find('img').attr('src'),
|
||||
path: loadedCheerio(element).find('a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
return novels;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
//console.log(this.site + novelPath);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.text-uppercase").text(),
|
||||
cover: loadedCheerio(".img-wrap > img").attr("src"),
|
||||
summary: loadedCheerio(".lead").text().trim(),
|
||||
name: loadedCheerio('h1.text-uppercase').text(),
|
||||
cover: loadedCheerio('.img-wrap > img').attr('src'),
|
||||
summary: loadedCheerio('.lead').text().trim(),
|
||||
};
|
||||
|
||||
novel.genres = loadedCheerio('td:contains("Genre")')
|
||||
.next()
|
||||
.find('a')
|
||||
.map((i,el) => loadedCheerio(el).text())
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
novel.author = loadedCheerio('td:contains("Author")')
|
||||
.next()
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
.replace(/[\t\n]/g, '');
|
||||
|
||||
novel.status = loadedCheerio('td:contains("Status")')
|
||||
.next()
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
.replace(/[\t\n]/g, '');
|
||||
|
||||
|
||||
const chapterJson = loadedCheerio('#__NEXT_DATA__').html() + "";
|
||||
const chapterJson = loadedCheerio('#__NEXT_DATA__').html() + '';
|
||||
const jsonData: NovelJson = JSON.parse(chapterJson);
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = jsonData.props.pageProps.serie.chapters.map((jsonChapter, chapterIndex) => ({
|
||||
name: "Chapter " + jsonChapter.slug + " " + jsonChapter.title,
|
||||
path: this.sourceLang + "serie-" +jsonData.props.pageProps.serie.serie_data.raw_id + "/" + jsonData.props.pageProps.serie.serie_data.slug + "/chapter-" + jsonChapter.slug, // Assuming 'slug' is the intended path
|
||||
const chapters: Plugin.ChapterItem[] =
|
||||
jsonData.props.pageProps.serie.chapters.map(
|
||||
(jsonChapter, chapterIndex) => ({
|
||||
name: 'Chapter ' + jsonChapter.slug + ' ' + jsonChapter.title,
|
||||
path:
|
||||
this.sourceLang +
|
||||
'serie-' +
|
||||
jsonData.props.pageProps.serie.serie_data.raw_id +
|
||||
'/' +
|
||||
jsonData.props.pageProps.serie.serie_data.slug +
|
||||
'/chapter-' +
|
||||
jsonChapter.slug, // Assuming 'slug' is the intended path
|
||||
releaseTime: jsonChapter.created_at.substring(0, 10),
|
||||
chapterNumber: chapterIndex + 1
|
||||
}));
|
||||
chapterNumber: chapterIndex + 1,
|
||||
}),
|
||||
);
|
||||
|
||||
novel.chapters = chapters;
|
||||
|
||||
return novel;
|
||||
}
|
||||
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapterJson = loadedCheerio('#__NEXT_DATA__').html() + "";
|
||||
const chapterJson = loadedCheerio('#__NEXT_DATA__').html() + '';
|
||||
const jsonData: NovelJson = JSON.parse(chapterJson);
|
||||
|
||||
//console.log(body);
|
||||
|
||||
const chapterContent = JSON.stringify(jsonData.props.pageProps.serie.chapter_data.data.body);
|
||||
const chapterContent = JSON.stringify(
|
||||
jsonData.props.pageProps.serie.chapter_data.data.body,
|
||||
);
|
||||
//console.log(chapterContent);
|
||||
const parsedArray = JSON.parse(chapterContent);
|
||||
let htmlString = '';
|
||||
@@ -122,75 +146,68 @@ class WTRLAB implements Plugin.PluginBase {
|
||||
htmlString += `<p>${text}</p>`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return htmlString;
|
||||
}
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const response = await fetch(this.site + "api/search", {
|
||||
const response = await fetch(this.site + 'api/search', {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
Referer: this.site + this.sourceLang,
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({ text: searchTerm}),
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ text: searchTerm }),
|
||||
});
|
||||
|
||||
const recentNovel: JsonNovel = await response.json();
|
||||
|
||||
|
||||
// Parse novels from JSON
|
||||
const novels: Plugin.NovelItem[] = recentNovel.data.map((datum: Datum) => ({
|
||||
name: datum.data.title || "",
|
||||
name: datum.data.title || '',
|
||||
cover: datum.data.image,
|
||||
path: this.sourceLang + "serie-" + datum.raw_id + "/" + datum.slug || "",
|
||||
path: this.sourceLang + 'serie-' + datum.raw_id + '/' + datum.slug || '',
|
||||
}));
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
|
||||
fetchImage = fetchFile;
|
||||
filters = {
|
||||
order: {
|
||||
value: "chapter",
|
||||
label: "Order by",
|
||||
value: 'chapter',
|
||||
label: 'Order by',
|
||||
options: [
|
||||
{ label: "View", value: "view" },
|
||||
{ label: "Name", value: "name" },
|
||||
{ label: "Addition Date", value: "date" },
|
||||
{ label: "Reader", value: "reader" },
|
||||
{ label: "Chapter", value: "chapter" },
|
||||
{ label: 'View', value: 'view' },
|
||||
{ label: 'Name', value: 'name' },
|
||||
{ label: 'Addition Date', value: 'date' },
|
||||
{ label: 'Reader', value: 'reader' },
|
||||
{ label: 'Chapter', value: 'chapter' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "desc",
|
||||
label: "Sort by",
|
||||
value: 'desc',
|
||||
label: 'Sort by',
|
||||
options: [
|
||||
{ label: "Descending", value: "desc" },
|
||||
{ label: "Ascending", value: "asc" },
|
||||
{ label: 'Descending', value: 'desc' },
|
||||
{ label: 'Ascending', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
storyStatus: {
|
||||
value: "all",
|
||||
label: "Status",
|
||||
value: 'all',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface NovelJson {
|
||||
props: Props;
|
||||
page: string;
|
||||
@@ -277,10 +294,6 @@ interface Datum {
|
||||
raw_id: number;
|
||||
slug: string;
|
||||
data: Data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default new WTRLAB();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile, fetchProto } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile, fetchProto } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
interface StringValue {
|
||||
value: string;
|
||||
@@ -71,7 +71,7 @@ enum NovelItem_Status {
|
||||
Finished = 0,
|
||||
Active = 1,
|
||||
Hiatus = 2,
|
||||
All = -1
|
||||
All = -1,
|
||||
}
|
||||
|
||||
interface NovelKarmaInfo {
|
||||
@@ -101,14 +101,14 @@ interface GetNovelResponse {
|
||||
}
|
||||
|
||||
class WuxiaWorld implements Plugin.PluginBase {
|
||||
id = "wuxiaworld";
|
||||
name = "Wuxia World";
|
||||
icon = "src/en/wuxiaworld/icon.png";
|
||||
site = "https://www.wuxiaworld.com/";
|
||||
id = 'wuxiaworld';
|
||||
name = 'Wuxia World';
|
||||
icon = 'src/en/wuxiaworld/icon.png';
|
||||
site = 'https://www.wuxiaworld.com/';
|
||||
filters?: Filters | undefined;
|
||||
version = "0.5.0";
|
||||
version = '0.5.0';
|
||||
|
||||
parseNovels(data: {items: NovelEntry[]}){
|
||||
parseNovels(data: { items: NovelEntry[] }) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
data.items.map((novel: NovelEntry) => {
|
||||
@@ -126,13 +126,16 @@ class WuxiaWorld implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const link = `${this.site}api/novels`;
|
||||
|
||||
const result = await fetchApi(link);
|
||||
const data = await result.json();
|
||||
|
||||
return this.parseNovels(data)
|
||||
return this.parseNovels(data);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
@@ -141,28 +144,30 @@ class WuxiaWorld implements Plugin.PluginBase {
|
||||
proto: this.proto,
|
||||
requestType: 'GetNovelRequest',
|
||||
responseType: 'GetNovelResponse',
|
||||
requestData: {slug: novelPath.split('/')[1]},
|
||||
requestData: { slug: novelPath.split('/')[1] },
|
||||
},
|
||||
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Novels/GetNovel',
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/grpc-web+proto",
|
||||
}
|
||||
}
|
||||
)
|
||||
'Content-Type': 'application/grpc-web+proto',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: data.item?.name || "Untitled",
|
||||
name: data.item?.name || 'Untitled',
|
||||
cover: data.item?.coverUrl?.value,
|
||||
summary: parseHTML(data.item?.description?.value + '\n\n' + data.item?.synopsis?.value).text(),
|
||||
summary: parseHTML(
|
||||
data.item?.description?.value + '\n\n' + data.item?.synopsis?.value,
|
||||
).text(),
|
||||
author: data.item?.authorName?.value,
|
||||
genres: data.item?.genres.join(','),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
const status = data.item?.status;
|
||||
switch (status){
|
||||
switch (status) {
|
||||
case NovelItem_Status.Active:
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
break;
|
||||
@@ -172,7 +177,8 @@ class WuxiaWorld implements Plugin.PluginBase {
|
||||
case NovelItem_Status.All:
|
||||
novel.status = NovelStatus.Unknown;
|
||||
break;
|
||||
default: novel.status = NovelStatus.Completed;
|
||||
default:
|
||||
novel.status = NovelStatus.Completed;
|
||||
}
|
||||
|
||||
// novel.summary = loadedCheerio('.relative > .absolute:first')
|
||||
@@ -193,38 +199,41 @@ class WuxiaWorld implements Plugin.PluginBase {
|
||||
proto: this.proto,
|
||||
requestType: 'GetChapterListRequest',
|
||||
responseType: 'GetChapterListResponse',
|
||||
requestData: {novelId: data.item?.id},
|
||||
requestData: { novelId: data.item?.id },
|
||||
},
|
||||
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapterList',
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/grpc-web+proto",
|
||||
}
|
||||
}
|
||||
)
|
||||
'Content-Type': 'application/grpc-web+proto',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const freeChapter = Number(data.item?.karmaInfo?.maxFreeChapter?.units) +
|
||||
const freeChapter =
|
||||
Number(data.item?.karmaInfo?.maxFreeChapter?.units) +
|
||||
(data.item?.karmaInfo?.maxFreeChapter?.nanos || 0) / 1000000000 || 50;
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = list.items.flatMap((ChapterGroupItem: ChapterGroupItem) =>
|
||||
const chapter: Plugin.ChapterItem[] = list.items.flatMap(
|
||||
(ChapterGroupItem: ChapterGroupItem) =>
|
||||
ChapterGroupItem.chapterList.map((chapterItem: ChapterItem) => ({
|
||||
page: ChapterGroupItem.title,
|
||||
name: chapterItem.name + (
|
||||
(chapterItem.relatedUserInfo?.isChapterUnlocked?.value === false) ||
|
||||
name:
|
||||
chapterItem.name +
|
||||
(chapterItem.relatedUserInfo?.isChapterUnlocked?.value === false ||
|
||||
(!chapterItem.relatedUserInfo &&
|
||||
(Number(chapterItem.number?.units) +
|
||||
(chapterItem.number?.nanos || 0) / 1000000000) > freeChapter)
|
||||
Number(chapterItem.number?.units) +
|
||||
(chapterItem.number?.nanos || 0) / 1000000000 >
|
||||
freeChapter)
|
||||
? ' 🔒'
|
||||
: ''
|
||||
),
|
||||
: ''),
|
||||
path: novelPath + chapterItem.slug,
|
||||
chapterNumber: chapterItem.offset,
|
||||
releaseTime: new Date(
|
||||
(chapterItem.publishedAt?.seconds || 0) * 1000 +
|
||||
(chapterItem.publishedAt?.nanos || 0) / 1000000
|
||||
(chapterItem.publishedAt?.nanos || 0) / 1000000,
|
||||
).toISOString(),
|
||||
})
|
||||
));
|
||||
})),
|
||||
);
|
||||
|
||||
novel.chapters = chapter;
|
||||
|
||||
@@ -243,27 +252,30 @@ class WuxiaWorld implements Plugin.PluginBase {
|
||||
slugs: {
|
||||
novelSlug: paths[1],
|
||||
chapterSlug: paths[2],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'https://api2.wuxiaworld.com/wuxiaworld.api.v2.Chapters/GetChapter',
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/grpc-web+proto",
|
||||
}
|
||||
}
|
||||
)
|
||||
'Content-Type': 'application/grpc-web+proto',
|
||||
},
|
||||
},
|
||||
);
|
||||
// loadedCheerio(".chapter-nav").remove();
|
||||
// loadedCheerio("#chapter-content > script").remove();
|
||||
// const chapterText = loadedCheerio("#chapter-content").html() || '';
|
||||
|
||||
const chapterText = data.item?.content?.value || "";
|
||||
return chapterText
|
||||
const chapterText = data.item?.content?.value || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = "https://www.wuxiaworld.com/api/novels/search?query=";
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = 'https://www.wuxiaworld.com/api/novels/search?query=';
|
||||
|
||||
const url = searchUrl + searchTerm;
|
||||
|
||||
@@ -460,7 +472,7 @@ class WuxiaWorld implements Plugin.PluginBase {
|
||||
service Novels {
|
||||
rpc GetNovel(GetNovelRequest) returns (GetNovelResponse);
|
||||
}
|
||||
`
|
||||
`;
|
||||
}
|
||||
|
||||
export default new WuxiaWorld();
|
||||
+226
-150
@@ -1,128 +1,176 @@
|
||||
import { CheerioAPI, load } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import dayjs from "dayjs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { CheerioAPI, load } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import dayjs from 'dayjs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class ChireadsPlugin implements Plugin.PluginBase {
|
||||
id = "chireads";
|
||||
name = "Chireads";
|
||||
icon = "src/fr/chireads/icon.png";
|
||||
site = "https://chireads.com";
|
||||
version = "1.0.2";
|
||||
id = 'chireads';
|
||||
name = 'Chireads';
|
||||
icon = 'src/fr/chireads/icon.png';
|
||||
site = 'https://chireads.com';
|
||||
version = '1.0.2';
|
||||
|
||||
async getCheerio(url: string): Promise<CheerioAPI> {
|
||||
const r = await fetchApi(url, {headers: { 'Accept-Encoding': 'deflate' }});
|
||||
const r = await fetchApi(url, {
|
||||
headers: { 'Accept-Encoding': 'deflate' },
|
||||
});
|
||||
const body = await r.text();
|
||||
const $ = load(body);
|
||||
return ($);
|
||||
return $;
|
||||
}
|
||||
|
||||
async popularNovels(pageNo: number, { filters, showLatestNovels }: Plugin.PopularNovelsOptions): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site;
|
||||
let tag: string = "all";
|
||||
if (showLatestNovels)
|
||||
url += "/category/translatedtales/page/" + pageNo;
|
||||
let tag: string = 'all';
|
||||
if (showLatestNovels) url += '/category/translatedtales/page/' + pageNo;
|
||||
else {
|
||||
if (filters && typeof filters.tag.value === "string" && filters.tag.value !== "all")
|
||||
if (
|
||||
filters &&
|
||||
typeof filters.tag.value === 'string' &&
|
||||
filters.tag.value !== 'all'
|
||||
)
|
||||
tag = filters.tag.value;
|
||||
if (tag !== "all")
|
||||
url += "/tag/" + tag + "/page/" + pageNo;
|
||||
else if (pageNo > 1)
|
||||
return [];
|
||||
if (tag !== 'all') url += '/tag/' + tag + '/page/' + pageNo;
|
||||
else if (pageNo > 1) return [];
|
||||
}
|
||||
let $ = await this.getCheerio(url);
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
let novel: Plugin.NovelItem;
|
||||
|
||||
if (showLatestNovels || tag !== "all") {
|
||||
if (showLatestNovels || tag !== 'all') {
|
||||
let loop = 1;
|
||||
if (showLatestNovels)
|
||||
loop = 2;
|
||||
if (showLatestNovels) loop = 2;
|
||||
for (let i = 0; i < loop; i++) {
|
||||
if (i === 1)
|
||||
$ = await this.getCheerio(this.site + "/category/original/page/" + pageNo);
|
||||
let romans = $(".romans-content li");
|
||||
if (!romans.length)
|
||||
romans = $("#content li");
|
||||
$ = await this.getCheerio(
|
||||
this.site + '/category/original/page/' + pageNo,
|
||||
);
|
||||
let romans = $('.romans-content li');
|
||||
if (!romans.length) romans = $('#content li');
|
||||
romans.each((i, elem) => {
|
||||
const novelName = $(elem).contents().find("div").first().text().trim();
|
||||
const novelCover = $(elem).find("div").first().find("img").attr("src");
|
||||
const novelUrl = $(elem).find("div").first().find("a").attr("href")
|
||||
const novelName = $(elem)
|
||||
.contents()
|
||||
.find('div')
|
||||
.first()
|
||||
.text()
|
||||
.trim();
|
||||
const novelCover = $(elem)
|
||||
.find('div')
|
||||
.first()
|
||||
.find('img')
|
||||
.attr('src');
|
||||
const novelUrl = $(elem).find('div').first().find('a').attr('href');
|
||||
|
||||
if (novelUrl){
|
||||
novel = { name: novelName, cover: novelCover, path: novelUrl.replace(this.site, "") };
|
||||
if (novelUrl) {
|
||||
novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
};
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const populaire = $(":contains(\"Populaire\")").last().parent().next().find("li > div")
|
||||
if (populaire.length === 12) // pc
|
||||
{
|
||||
let novelCover : string | undefined;
|
||||
let novelName : string | undefined;
|
||||
let novelUrl : string | undefined;
|
||||
} else {
|
||||
const populaire = $(':contains("Populaire")')
|
||||
.last()
|
||||
.parent()
|
||||
.next()
|
||||
.find('li > div');
|
||||
if (populaire.length === 12) {
|
||||
// pc
|
||||
let novelCover: string | undefined;
|
||||
let novelName: string | undefined;
|
||||
let novelUrl: string | undefined;
|
||||
populaire.each((i, elem) => {
|
||||
if (i % 2 === 0)
|
||||
novelCover = $(elem).find("img").attr("src");
|
||||
if (i % 2 === 0) novelCover = $(elem).find('img').attr('src');
|
||||
else {
|
||||
novelName = $(elem).text().trim();
|
||||
novelUrl = $(elem).find("a").attr("href");
|
||||
novelUrl = $(elem).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
novel = { name: novelName, cover: novelCover || defaultCover, path: novelUrl.replace(this.site, "") };
|
||||
novel = {
|
||||
name: novelName,
|
||||
cover: novelCover || defaultCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
}
|
||||
else // mobile
|
||||
{
|
||||
const imgs = populaire.find("div.popular-list-img img");
|
||||
const txts = populaire.find("div.popular-list-name");
|
||||
} // mobile
|
||||
else {
|
||||
const imgs = populaire.find('div.popular-list-img img');
|
||||
const txts = populaire.find('div.popular-list-name');
|
||||
|
||||
txts.each((i, elem) => {
|
||||
const novelName = $(elem).text().trim();
|
||||
const novelCover = $(imgs[i]).attr("src");
|
||||
const novelUrl = $(elem).find("a").attr("href");
|
||||
const novelCover = $(imgs[i]).attr('src');
|
||||
const novelUrl = $(elem).find('a').attr('href');
|
||||
|
||||
if (novelUrl){
|
||||
novel = { name: novelName, cover: novelCover, path: novelUrl.replace(this.site, "") };
|
||||
if (novelUrl) {
|
||||
novel = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
};
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
novel.name = $(".inform-product-txt").first().text().trim() || $(".inform-title").text().trim();
|
||||
novel.cover = $(".inform-product img").attr("src") || $(".inform-product-img img").attr("src") || defaultCover;
|
||||
novel.summary = $(".inform-inform-txt").text().trim() || $(".inform-intr-txt").text().trim();
|
||||
novel.name =
|
||||
$('.inform-product-txt').first().text().trim() ||
|
||||
$('.inform-title').text().trim();
|
||||
novel.cover =
|
||||
$('.inform-product img').attr('src') ||
|
||||
$('.inform-product-img img').attr('src') ||
|
||||
defaultCover;
|
||||
novel.summary =
|
||||
$('.inform-inform-txt').text().trim() ||
|
||||
$('.inform-intr-txt').text().trim();
|
||||
|
||||
let infos = $("div.inform-product-txt > div.inform-intr-col").text().trim() || $("div.inform-inform-data > h6").text().trim();
|
||||
if (infos.includes("Auteur : "))
|
||||
novel.author = infos.substring(infos.indexOf("Auteur : ") + 9, infos.indexOf("Statut de Parution : ")).trim();
|
||||
else if (infos.includes("Fantrad : "))
|
||||
novel.author = infos.substring(infos.indexOf("Fantrad : ") + 10, infos.indexOf("Statut de Parution : ")).trim();
|
||||
else
|
||||
novel.author = "Inconnu";
|
||||
switch (infos.substring(infos.indexOf("Statut de Parution : ") + 21).toLowerCase()) {
|
||||
case "en pause":
|
||||
let infos =
|
||||
$('div.inform-product-txt > div.inform-intr-col').text().trim() ||
|
||||
$('div.inform-inform-data > h6').text().trim();
|
||||
if (infos.includes('Auteur : '))
|
||||
novel.author = infos
|
||||
.substring(
|
||||
infos.indexOf('Auteur : ') + 9,
|
||||
infos.indexOf('Statut de Parution : '),
|
||||
)
|
||||
.trim();
|
||||
else if (infos.includes('Fantrad : '))
|
||||
novel.author = infos
|
||||
.substring(
|
||||
infos.indexOf('Fantrad : ') + 10,
|
||||
infos.indexOf('Statut de Parution : '),
|
||||
)
|
||||
.trim();
|
||||
else novel.author = 'Inconnu';
|
||||
switch (
|
||||
infos.substring(infos.indexOf('Statut de Parution : ') + 21).toLowerCase()
|
||||
) {
|
||||
case 'en pause':
|
||||
novel.status = NovelStatus.OnHiatus;
|
||||
break;
|
||||
case "complet":
|
||||
case 'complet':
|
||||
novel.status = NovelStatus.Completed;
|
||||
break;
|
||||
default:
|
||||
@@ -132,22 +180,23 @@ class ChireadsPlugin implements Plugin.PluginBase {
|
||||
|
||||
let chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
let chapterList = $(".chapitre-table a");
|
||||
if (!chapterList.length)
|
||||
{
|
||||
$("div.inform-annexe-list").first().remove();
|
||||
chapterList = $(".inform-annexe-list").find("a");
|
||||
let chapterList = $('.chapitre-table a');
|
||||
if (!chapterList.length) {
|
||||
$('div.inform-annexe-list').first().remove();
|
||||
chapterList = $('.inform-annexe-list').find('a');
|
||||
}
|
||||
chapterList.each((i, elem) => {
|
||||
const chapterName = $(elem).text().trim();
|
||||
const chapterUrl = $(elem).attr("href");
|
||||
const releaseDate = dayjs(chapterUrl?.substring(chapterUrl.length - 11, chapterUrl.length - 1)).format("DD MMMM YYYY");
|
||||
const chapterUrl = $(elem).attr('href');
|
||||
const releaseDate = dayjs(
|
||||
chapterUrl?.substring(chapterUrl.length - 11, chapterUrl.length - 1),
|
||||
).format('DD MMMM YYYY');
|
||||
|
||||
if (chapterUrl){
|
||||
if (chapterUrl) {
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
releaseTime: releaseDate,
|
||||
path: chapterUrl.replace(this.site, ""),
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -155,34 +204,37 @@ class ChireadsPlugin implements Plugin.PluginBase {
|
||||
novel.chapters = chapters;
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
const $ = await this.getCheerio(this.site + chapterUrl);
|
||||
|
||||
const chapterText = $("#content").html() || "";
|
||||
const chapterText = $('#content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
if (pageNo !== 1)
|
||||
return [];
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
if (pageNo !== 1) return [];
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
let i = 1;
|
||||
let finised = false;
|
||||
while ( !finised )
|
||||
{
|
||||
await this.popularNovels(i, { showLatestNovels: true, filters: undefined } ).then((res) => {
|
||||
if (res.length === 0)
|
||||
finised = true;
|
||||
while (!finised) {
|
||||
await this.popularNovels(i, {
|
||||
showLatestNovels: true,
|
||||
filters: undefined,
|
||||
}).then(res => {
|
||||
if (res.length === 0) finised = true;
|
||||
novels.push(...res);
|
||||
});
|
||||
i++;
|
||||
}
|
||||
|
||||
novels = novels.filter((novel) =>
|
||||
novels = novels.filter(novel =>
|
||||
novel.name
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
@@ -191,75 +243,99 @@ class ChireadsPlugin implements Plugin.PluginBase {
|
||||
searchTerm
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
)
|
||||
.replace(/[\u0300-\u036f]/g, ''),
|
||||
),
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
fetchImage = fetchFile;
|
||||
|
||||
filters = {
|
||||
tag: {
|
||||
type: FilterTypes.Picker,
|
||||
label: "Tag",
|
||||
value: "all",
|
||||
label: 'Tag',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "Tous", value: "all" },
|
||||
{ label: "Arts martiaux", value: "arts-martiaux" },
|
||||
{ label: "De faible à fort", value: "de-faible-a-fort" },
|
||||
{ label: "Adapté en manhua", value: "adapte-en-manhua" },
|
||||
{ label: "Cultivation", value: "cultivation" },
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Aventure", value: "aventure" },
|
||||
{ label: "Monstres", value: "monstres" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Fantastique", value: "fantastique" },
|
||||
{ label: "Adapté en Animé", value: "adapte-en-anime" },
|
||||
{ label: "Alchimie", value: "alchimie" },
|
||||
{ label: "Éléments de jeux", value: "elements-de-jeux" },
|
||||
{ label: "Calme Protagoniste", value: "calme-protagoniste" },
|
||||
{ label: "Protagoniste intelligent", value: "protagoniste-intelligent" },
|
||||
{ label: "Polygamie", value: "polygamie" },
|
||||
{ label: "Belle femelle Lea", value: "belle-femelle-lea" },
|
||||
{ label: "Personnages arrogants", value: "personnages-arrogants" },
|
||||
{ label: "Système de niveau", value: "systeme-de-niveau" },
|
||||
{ label: "Cheat", value: "cheat" },
|
||||
{ label: "Protagoniste génie", value: "protagoniste-genie" },
|
||||
{ label: "Comédie", value: "comedie" },
|
||||
{ label: "Gamer", value: "gamer" },
|
||||
{ label: "Mariage", value: "mariage" },
|
||||
{ label: "seeking Protag", value: "seeking-protag" },
|
||||
{ label: "Romance précoce", value: "romance-precoce" },
|
||||
{ label: "Croissance accélérée", value: "croissance-acceleree" },
|
||||
{ label: "Artefacts", value: "artefacts" },
|
||||
{ label: "Intelligence artificielle", value: "intelligence-artificielle" },
|
||||
{ label: "Mariage arrangé", value: "mariage-arrange" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Adulte", value: "adulte" },
|
||||
{ label: "Administrateur de système", value: "administrateur-de-systeme" },
|
||||
{ label: "Beau protagoniste", value: "beau-protagoniste" },
|
||||
{ label: "Protagoniste charismatique", value: "protagoniste-charismatique" },
|
||||
{ label: "Protagoniste masculin", value: "protagoniste-masculin" },
|
||||
{ label: "Démons", value: "demons" },
|
||||
{ label: "Reincarnation", value: "reincarnation" },
|
||||
{ label: "Académie", value: "academie" },
|
||||
{ label: "Cacher les vraies capacités", value: "cacher-les-vraies-capacites" },
|
||||
{ label: "Protagoniste surpuissant", value: "protagoniste-surpuissant" },
|
||||
{ label: "Joueur", value: "joueur" },
|
||||
{ label: "Protagoniste fort dès le départ", value: "protagoniste-fort-des-le-depart" },
|
||||
{ label: "Immortels", value: "immortels" },
|
||||
{ label: "Cultivation rapide", value: "cultivation-rapide" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Assasins", value: "assasins" },
|
||||
{ label: "De pauvre à riche", value: "de-pauvre-a-riche" },
|
||||
{ label: "Système de classement de jeux", value: "systeme-de-classement-de-jeux" },
|
||||
{ label: "Capacités spéciales", value: "capacites-speciales" },
|
||||
{ label: "Vengeance", value: "vengeance" },
|
||||
{ label: 'Tous', value: 'all' },
|
||||
{ label: 'Arts martiaux', value: 'arts-martiaux' },
|
||||
{ label: 'De faible à fort', value: 'de-faible-a-fort' },
|
||||
{ label: 'Adapté en manhua', value: 'adapte-en-manhua' },
|
||||
{ label: 'Cultivation', value: 'cultivation' },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Aventure', value: 'aventure' },
|
||||
{ label: 'Monstres', value: 'monstres' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Fantastique', value: 'fantastique' },
|
||||
{ label: 'Adapté en Animé', value: 'adapte-en-anime' },
|
||||
{ label: 'Alchimie', value: 'alchimie' },
|
||||
{ label: 'Éléments de jeux', value: 'elements-de-jeux' },
|
||||
{ label: 'Calme Protagoniste', value: 'calme-protagoniste' },
|
||||
{
|
||||
label: 'Protagoniste intelligent',
|
||||
value: 'protagoniste-intelligent',
|
||||
},
|
||||
{ label: 'Polygamie', value: 'polygamie' },
|
||||
{ label: 'Belle femelle Lea', value: 'belle-femelle-lea' },
|
||||
{ label: 'Personnages arrogants', value: 'personnages-arrogants' },
|
||||
{ label: 'Système de niveau', value: 'systeme-de-niveau' },
|
||||
{ label: 'Cheat', value: 'cheat' },
|
||||
{ label: 'Protagoniste génie', value: 'protagoniste-genie' },
|
||||
{ label: 'Comédie', value: 'comedie' },
|
||||
{ label: 'Gamer', value: 'gamer' },
|
||||
{ label: 'Mariage', value: 'mariage' },
|
||||
{ label: 'seeking Protag', value: 'seeking-protag' },
|
||||
{ label: 'Romance précoce', value: 'romance-precoce' },
|
||||
{ label: 'Croissance accélérée', value: 'croissance-acceleree' },
|
||||
{ label: 'Artefacts', value: 'artefacts' },
|
||||
{
|
||||
label: 'Intelligence artificielle',
|
||||
value: 'intelligence-artificielle',
|
||||
},
|
||||
{ label: 'Mariage arrangé', value: 'mariage-arrange' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Adulte', value: 'adulte' },
|
||||
{
|
||||
label: 'Administrateur de système',
|
||||
value: 'administrateur-de-systeme',
|
||||
},
|
||||
{ label: 'Beau protagoniste', value: 'beau-protagoniste' },
|
||||
{
|
||||
label: 'Protagoniste charismatique',
|
||||
value: 'protagoniste-charismatique',
|
||||
},
|
||||
{ label: 'Protagoniste masculin', value: 'protagoniste-masculin' },
|
||||
{ label: 'Démons', value: 'demons' },
|
||||
{ label: 'Reincarnation', value: 'reincarnation' },
|
||||
{ label: 'Académie', value: 'academie' },
|
||||
{
|
||||
label: 'Cacher les vraies capacités',
|
||||
value: 'cacher-les-vraies-capacites',
|
||||
},
|
||||
{
|
||||
label: 'Protagoniste surpuissant',
|
||||
value: 'protagoniste-surpuissant',
|
||||
},
|
||||
{ label: 'Joueur', value: 'joueur' },
|
||||
{
|
||||
label: 'Protagoniste fort dès le départ',
|
||||
value: 'protagoniste-fort-des-le-depart',
|
||||
},
|
||||
{ label: 'Immortels', value: 'immortels' },
|
||||
{ label: 'Cultivation rapide', value: 'cultivation-rapide' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Assasins', value: 'assasins' },
|
||||
{ label: 'De pauvre à riche', value: 'de-pauvre-a-riche' },
|
||||
{
|
||||
label: 'Système de classement de jeux',
|
||||
value: 'systeme-de-classement-de-jeux',
|
||||
},
|
||||
{ label: 'Capacités spéciales', value: 'capacites-speciales' },
|
||||
{ label: 'Vengeance', value: 'vengeance' },
|
||||
],
|
||||
}
|
||||
},
|
||||
} satisfies Filters;
|
||||
};
|
||||
}
|
||||
|
||||
export default new ChireadsPlugin();
|
||||
+196
-182
@@ -1,49 +1,69 @@
|
||||
import { load, CheerioAPI } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import dayjs from "dayjs";
|
||||
import { load, CheerioAPI } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class NovelDeGlacePlugin implements Plugin.PluginBase{
|
||||
id = "noveldeglace";
|
||||
name = "NovelDeGlace";
|
||||
icon = "src/fr/noveldeglace/icon.png";
|
||||
site = "https://noveldeglace.com/";
|
||||
version = "1.0.2";
|
||||
class NovelDeGlacePlugin implements Plugin.PluginBase {
|
||||
id = 'noveldeglace';
|
||||
name = 'NovelDeGlace';
|
||||
icon = 'src/fr/noveldeglace/icon.png';
|
||||
site = 'https://noveldeglace.com/';
|
||||
version = '1.0.2';
|
||||
|
||||
async getCheerio(url: string): Promise<CheerioAPI | undefined> {
|
||||
const r = await fetchApi(url, {headers: { 'Accept-Encoding': 'deflate' }});
|
||||
if (!r.ok) return (undefined);
|
||||
const r = await fetchApi(url, {
|
||||
headers: { 'Accept-Encoding': 'deflate' },
|
||||
});
|
||||
if (!r.ok) return undefined;
|
||||
const body = await r.text();
|
||||
const loadedCheerio = load(body);
|
||||
return (loadedCheerio);
|
||||
return loadedCheerio;
|
||||
}
|
||||
|
||||
parseDate(date: string): string {
|
||||
const monthMapping: { [key: string]: string } = { janvier: "January", fevrier: "February", mars: "March", avril: "April", mai: "May", juin: "June", juillet: "July", aout: "August", septembre: "September", octobre: "October", novembre: "November", decembre: "December"};
|
||||
const [day, month, year] = date.split(" ");
|
||||
return dayjs(`${day} ${monthMapping[month.normalize('NFD').replace(/[\u0300-\u036f]/g, '')]} ${year}`, "D MMMM YYYY").format("YYYY-MM-DD");
|
||||
const monthMapping: { [key: string]: string } = {
|
||||
janvier: 'January',
|
||||
fevrier: 'February',
|
||||
mars: 'March',
|
||||
avril: 'April',
|
||||
mai: 'May',
|
||||
juin: 'June',
|
||||
juillet: 'July',
|
||||
aout: 'August',
|
||||
septembre: 'September',
|
||||
octobre: 'October',
|
||||
novembre: 'November',
|
||||
decembre: 'December',
|
||||
};
|
||||
const [day, month, year] = date.split(' ');
|
||||
return dayjs(
|
||||
`${day} ${monthMapping[month.normalize('NFD').replace(/[\u0300-\u036f]/g, '')]} ${year}`,
|
||||
'D MMMM YYYY',
|
||||
).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
parseNovels($: CheerioAPI, showLatestNovels: boolean | undefined): Plugin.NovelItem[] {
|
||||
parseNovels(
|
||||
$: CheerioAPI,
|
||||
showLatestNovels: boolean | undefined,
|
||||
): Plugin.NovelItem[] {
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
$("article").each((i, el) => {
|
||||
$('article').each((i, el) => {
|
||||
const cheerio = $(el);
|
||||
const novelName = cheerio.find("h2").text().trim();
|
||||
const novelCover = cheerio.find("img").attr("src");
|
||||
const novelName = cheerio.find('h2').text().trim();
|
||||
const novelCover = cheerio.find('img').attr('src');
|
||||
let novelUrl: string | undefined;
|
||||
if (showLatestNovels)
|
||||
novelUrl = cheerio.find("span.Roman > a").attr("href");
|
||||
else
|
||||
novelUrl = cheerio.find("h2 > a").attr("href");
|
||||
novelUrl = cheerio.find('span.Roman > a').attr('href');
|
||||
else novelUrl = cheerio.find('h2 > a').attr('href');
|
||||
|
||||
if (novelUrl){
|
||||
const novel:Plugin.NovelItem = {
|
||||
if (novelUrl) {
|
||||
const novel: Plugin.NovelItem = {
|
||||
name: novelName,
|
||||
path: novelUrl.replace(this.site, ""),
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
cover: novelCover,
|
||||
};
|
||||
novels.push(novel);
|
||||
@@ -53,98 +73,95 @@ class NovelDeGlacePlugin implements Plugin.PluginBase{
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(pageNo: number, { filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site;
|
||||
if (showLatestNovels)
|
||||
url += "chapitre"
|
||||
else
|
||||
{
|
||||
let cat_gen: string = "all";
|
||||
if (filters && typeof filters.categorie_genre.value == "string")
|
||||
if (showLatestNovels) url += 'chapitre';
|
||||
else {
|
||||
let cat_gen: string = 'all';
|
||||
if (filters && typeof filters.categorie_genre.value == 'string')
|
||||
cat_gen = filters.categorie_genre.value;
|
||||
if (cat_gen != "all" && cat_gen != "categorie_roman" && cat_gen != "genre")
|
||||
{
|
||||
if (cat_gen[0] == "c")
|
||||
url += "categorie_roman/" + cat_gen.substring(2);
|
||||
else if (cat_gen[0] == "g")
|
||||
url += "genre/" + cat_gen.substring(2);
|
||||
if (
|
||||
cat_gen != 'all' &&
|
||||
cat_gen != 'categorie_roman' &&
|
||||
cat_gen != 'genre'
|
||||
) {
|
||||
if (cat_gen[0] == 'c') url += 'categorie_roman/' + cat_gen.substring(2);
|
||||
else if (cat_gen[0] == 'g') url += 'genre/' + cat_gen.substring(2);
|
||||
} else if (pageNo > 1)
|
||||
return []; // when asking for all novels, there is only 1 page
|
||||
else url += 'roman';
|
||||
}
|
||||
else if (pageNo > 1)
|
||||
return ([]); // when asking for all novels, there is only 1 page
|
||||
else
|
||||
url += "roman";
|
||||
}
|
||||
url += "/page/" + pageNo;
|
||||
url += '/page/' + pageNo;
|
||||
let $ = await this.getCheerio(url);
|
||||
if (!$)
|
||||
return ([]);
|
||||
if (!$) return [];
|
||||
return this.parseNovels($, showLatestNovels);
|
||||
};
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
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" };
|
||||
let novel: Plugin.SourceNovel = { path: novelPath, name: 'Untitled' };
|
||||
|
||||
novel.name = ($("div.entry-content > div > strong")[0]
|
||||
.nextSibling as Text | null)
|
||||
?.nodeValue
|
||||
?.trim() || "Untitled";
|
||||
novel.name =
|
||||
(
|
||||
$('div.entry-content > div > strong')[0].nextSibling as Text | null
|
||||
)?.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();
|
||||
let novelInfos = $('div[data-title=Tomes] >').toArray();
|
||||
novelInfos.pop();
|
||||
novelInfos.shift();
|
||||
novel.summary = $("div[data-title=Synopsis]")
|
||||
.text().trim() +
|
||||
"\n\n" + novelInfos.map((el) => $(el).text()).join("\n").trim();
|
||||
novel.summary =
|
||||
$('div[data-title=Synopsis]').text().trim() +
|
||||
'\n\n' +
|
||||
novelInfos
|
||||
.map(el => $(el).text())
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
novel.author = $("strong:contains('Auteur :')")
|
||||
.parent()
|
||||
.text()
|
||||
.replace("Auteur : ", "")
|
||||
.replace('Auteur : ', '')
|
||||
.trim();
|
||||
|
||||
novel.artist = $("strong:contains('Illustrateur :')")
|
||||
.parent()
|
||||
.text()
|
||||
.replace("Illustrateur :", "")
|
||||
.replace('Illustrateur :', '')
|
||||
.trim();
|
||||
|
||||
const categorie = $(".categorie")
|
||||
const categorie = $('.categorie').text().replace('Catégorie :', '').trim();
|
||||
const genres = $('.genre')
|
||||
.text()
|
||||
.replace("Catégorie :", "")
|
||||
.replace('Genre :', '')
|
||||
.replace(/, /g, ',')
|
||||
.trim();
|
||||
const genres = $(".genre")
|
||||
.text()
|
||||
.replace("Genre :", "")
|
||||
.replace(/, /g, ",")
|
||||
.trim();
|
||||
if (categorie && categorie != "Autre")
|
||||
novel.genres = categorie;
|
||||
if (categorie && categorie != 'Autre') novel.genres = categorie;
|
||||
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");
|
||||
let status = $("strong:contains('Statut :')").parent().attr('class');
|
||||
switch (status) {
|
||||
case "type etat0":
|
||||
case "type etat1":
|
||||
case 'type etat0':
|
||||
case 'type etat1':
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
break;
|
||||
case "type etat4":
|
||||
case 'type etat4':
|
||||
novel.status = NovelStatus.OnHiatus;
|
||||
break;
|
||||
case "type etat5":
|
||||
case 'type etat5':
|
||||
novel.status = NovelStatus.Completed;
|
||||
break;
|
||||
case "type etat6":
|
||||
case 'type etat6':
|
||||
novel.status = NovelStatus.Cancelled;
|
||||
break;
|
||||
default:
|
||||
@@ -154,66 +171,64 @@ class NovelDeGlacePlugin implements Plugin.PluginBase{
|
||||
|
||||
let 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;
|
||||
|
||||
let chapterName = ""
|
||||
let chapterName = '';
|
||||
let site = this.site;
|
||||
volumes.each((volumeIndex: number, el) => {
|
||||
if (hasMultipleVolumes)
|
||||
chapterName = "T." + (volumeIndex + 1) + " ";
|
||||
if (hasMultipleVolumes) chapterName = 'T.' + (volumeIndex + 1) + ' ';
|
||||
$(el)
|
||||
.find(".chpt")
|
||||
.find('.chpt')
|
||||
.each((chapterIndex: number, el) => {
|
||||
const cheerio = $(el);
|
||||
const newChapterName = chapterName + cheerio
|
||||
.find("a")
|
||||
.first()
|
||||
.text().trim() || "";
|
||||
if (!cheerio.find("i.fa").length) // no parts
|
||||
{
|
||||
const dateHtml = cheerio.html()?.substring(cheerio.html()?.indexOf("</a>") || 0) || "";
|
||||
let releaseDate = dateHtml?.substring(dateHtml.indexOf("(") + 1, dateHtml.indexOf(")")) || undefined;
|
||||
const newChapterName =
|
||||
chapterName + cheerio.find('a').first().text().trim() || '';
|
||||
if (!cheerio.find('i.fa').length) {
|
||||
// no parts
|
||||
const dateHtml =
|
||||
cheerio.html()?.substring(cheerio.html()?.indexOf('</a>') || 0) ||
|
||||
'';
|
||||
let releaseDate =
|
||||
dateHtml?.substring(
|
||||
dateHtml.indexOf('(') + 1,
|
||||
dateHtml.indexOf(')'),
|
||||
) || undefined;
|
||||
if (releaseDate)
|
||||
try {
|
||||
releaseDate = this.parseDate(releaseDate);
|
||||
} catch (e) {}
|
||||
const chapterUrl = cheerio.find("a").attr("href");
|
||||
if (chapterUrl)
|
||||
{
|
||||
const chapterUrl = cheerio.find('a').attr('href');
|
||||
if (chapterUrl) {
|
||||
const chapter: Plugin.ChapterItem = {
|
||||
name: newChapterName,
|
||||
releaseTime: releaseDate,
|
||||
path: chapterUrl.replace(site, ""),
|
||||
path: chapterUrl.replace(site, ''),
|
||||
chapterNumber: chapterIndex,
|
||||
};
|
||||
|
||||
novelChapters.push(chapter);
|
||||
}
|
||||
}
|
||||
else // has parts that needs to be added individually
|
||||
{
|
||||
const items = cheerio
|
||||
.find("i")
|
||||
.parent()
|
||||
.next()
|
||||
.html()
|
||||
?.split("</a>") || [];
|
||||
} // has parts that needs to be added individually
|
||||
else {
|
||||
const items =
|
||||
cheerio.find('i').parent().next().html()?.split('</a>') || [];
|
||||
items?.shift();
|
||||
const dates: string[] = [];
|
||||
items?.forEach((item) => { // there is a date on every publish parts
|
||||
dates.push(item.substring(item.indexOf("(") + 1, item.indexOf(")")));
|
||||
items?.forEach(item => {
|
||||
// there is a date on every publish parts
|
||||
dates.push(
|
||||
item.substring(item.indexOf('(') + 1, item.indexOf(')')),
|
||||
);
|
||||
});
|
||||
const hrefs: string[] = [];
|
||||
cheerio
|
||||
.find("i")
|
||||
.find('i')
|
||||
.parent()
|
||||
.next()
|
||||
.find("a")
|
||||
.find('a')
|
||||
.each(function () {
|
||||
hrefs.push(this["attribs"]["href"]);
|
||||
hrefs.push(this['attribs']['href']);
|
||||
});
|
||||
if (dates.length == hrefs.length)
|
||||
dates.forEach((date, index) => {
|
||||
@@ -222,14 +237,13 @@ class NovelDeGlacePlugin implements Plugin.PluginBase{
|
||||
releaseDate = this.parseDate(date);
|
||||
} catch (e) {}
|
||||
const chapter: Plugin.ChapterItem = {
|
||||
name: newChapterName + " (" + (index + 1) + ")",
|
||||
name: newChapterName + ' (' + (index + 1) + ')',
|
||||
releaseTime: releaseDate,
|
||||
path: hrefs[index].replace(site, ""),
|
||||
path: hrefs[index].replace(site, ''),
|
||||
chapterNumber: chapterIndex + (index + 1) / 1000,
|
||||
};
|
||||
novelChapters.push(chapter);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -237,30 +251,30 @@ class NovelDeGlacePlugin implements Plugin.PluginBase{
|
||||
novel.chapters = novelChapters;
|
||||
|
||||
return novel;
|
||||
};
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const $ = await this.getCheerio(this.site + chapterPath);
|
||||
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();
|
||||
let chapterText = $(".chapter-content").html()
|
||||
|| $(".entry-content").html()
|
||||
|| "";
|
||||
$('.mistape_caption').remove();
|
||||
let chapterText =
|
||||
$('.chapter-content').html() || $('.entry-content').html() || '';
|
||||
return chapterText;
|
||||
};
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, num: number): Promise<Plugin.NovelItem[]> {
|
||||
if (num !== 1) return ([]); // only 1 page of results
|
||||
let url = this.site + "roman";
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
num: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
if (num !== 1) return []; // only 1 page of results
|
||||
let url = this.site + 'roman';
|
||||
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)');
|
||||
|
||||
let novels = this.parseNovels($, false);
|
||||
|
||||
novels = novels.filter((novel) =>
|
||||
novels = novels.filter(novel =>
|
||||
novel.name
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
@@ -271,62 +285,62 @@ class NovelDeGlacePlugin implements Plugin.PluginBase{
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.trim()
|
||||
)
|
||||
.trim(),
|
||||
),
|
||||
);
|
||||
|
||||
return novels;
|
||||
};
|
||||
}
|
||||
|
||||
fetchImage = fetchFile;
|
||||
|
||||
filters = {
|
||||
categorie_genre: {
|
||||
type: FilterTypes.Picker,
|
||||
label: "Catégorie/Genre",
|
||||
value: "all",
|
||||
label: 'Catégorie/Genre',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "Tous", value: "all" },
|
||||
{ label: "═══CATÉGORIES═══", value: "categorie_roman" },
|
||||
{ label: "Seinen", value: "c_seinen" },
|
||||
{ label: "Shonen", value: "c_shonen" },
|
||||
{ label: "Original", value: "c_original" },
|
||||
{ label: "Yuri", value: "c_yuri" },
|
||||
{ label: "Autre", value: "c_autre" },
|
||||
{ label: "Fille", value: "c_fille" },
|
||||
{ label: "Roman pour Adulte", value: "c_roman-pour-adulte" },
|
||||
{ label: "Xuanhuan", value: "c_xuanhuan" },
|
||||
{ label: "Yaoi", value: "c_yaoi" },
|
||||
{ label: "═══GENRES═══", value: "genre" },
|
||||
{ label: "Action", value: "g_action" },
|
||||
{ label: "Aventure", value: "g_aventure" },
|
||||
{ label: "Comédie", value: "g_comedie" },
|
||||
{ label: "Drame", value: "g_drame" },
|
||||
{ label: "Fantastique", value: "g_fantastique" },
|
||||
{ label: "Harem", value: "g_harem" },
|
||||
{ label: "Psychologique", value: "g_psychologique" },
|
||||
{ label: "Romance", value: "g_romance" },
|
||||
{ label: "Ecchi", value: "g_ecchi" },
|
||||
{ label: "Mature", value: "g_mature" },
|
||||
{ label: "Surnaturel", value: "g_surnaturel" },
|
||||
{ label: "Vie scolaire", value: "g_vie-scolaire" },
|
||||
{ label: "Adulte", value: "g_adulte" },
|
||||
{ label: "Tragédie", value: "g_tragedie" },
|
||||
{ label: "Arts Martiaux", value: "g_arts-martiaux" },
|
||||
{ label: "Pas de harem", value: "g_pas-de-harem" },
|
||||
{ label: "Tranche de vie", value: "g_tranche-de-vie" },
|
||||
{ label: "Mecha", value: "g_mecha" },
|
||||
{ label: "Sci-fi", value: "g_sci-fi" },
|
||||
{ label: "Science-Fiction", value: "g_science-fiction" },
|
||||
{ label: "Anti-Héros", value: "g_anti-heros" },
|
||||
{ label: "Horreur", value: "g_horreur" },
|
||||
{ label: "Insectes", value: "g_insectes" },
|
||||
{ label: "Mystère", value: "g_mystere" },
|
||||
{ label: "Lolicon", value: "g_lolicon" },
|
||||
{ label: "Shoujo Ai", value: "g_shoujo-ai" },
|
||||
{ label: "Smut", value: "g_smut" },
|
||||
{ label: "Xuanhuan", value: "g_xuanhuan" },
|
||||
{ label: "Shotacon", value: "g_shotacon" },
|
||||
{ label: 'Tous', value: 'all' },
|
||||
{ label: '═══CATÉGORIES═══', value: 'categorie_roman' },
|
||||
{ label: 'Seinen', value: 'c_seinen' },
|
||||
{ label: 'Shonen', value: 'c_shonen' },
|
||||
{ label: 'Original', value: 'c_original' },
|
||||
{ label: 'Yuri', value: 'c_yuri' },
|
||||
{ label: 'Autre', value: 'c_autre' },
|
||||
{ label: 'Fille', value: 'c_fille' },
|
||||
{ label: 'Roman pour Adulte', value: 'c_roman-pour-adulte' },
|
||||
{ label: 'Xuanhuan', value: 'c_xuanhuan' },
|
||||
{ label: 'Yaoi', value: 'c_yaoi' },
|
||||
{ label: '═══GENRES═══', value: 'genre' },
|
||||
{ label: 'Action', value: 'g_action' },
|
||||
{ label: 'Aventure', value: 'g_aventure' },
|
||||
{ label: 'Comédie', value: 'g_comedie' },
|
||||
{ label: 'Drame', value: 'g_drame' },
|
||||
{ label: 'Fantastique', value: 'g_fantastique' },
|
||||
{ label: 'Harem', value: 'g_harem' },
|
||||
{ label: 'Psychologique', value: 'g_psychologique' },
|
||||
{ label: 'Romance', value: 'g_romance' },
|
||||
{ label: 'Ecchi', value: 'g_ecchi' },
|
||||
{ label: 'Mature', value: 'g_mature' },
|
||||
{ label: 'Surnaturel', value: 'g_surnaturel' },
|
||||
{ label: 'Vie scolaire', value: 'g_vie-scolaire' },
|
||||
{ label: 'Adulte', value: 'g_adulte' },
|
||||
{ label: 'Tragédie', value: 'g_tragedie' },
|
||||
{ label: 'Arts Martiaux', value: 'g_arts-martiaux' },
|
||||
{ label: 'Pas de harem', value: 'g_pas-de-harem' },
|
||||
{ label: 'Tranche de vie', value: 'g_tranche-de-vie' },
|
||||
{ label: 'Mecha', value: 'g_mecha' },
|
||||
{ label: 'Sci-fi', value: 'g_sci-fi' },
|
||||
{ label: 'Science-Fiction', value: 'g_science-fiction' },
|
||||
{ label: 'Anti-Héros', value: 'g_anti-heros' },
|
||||
{ label: 'Horreur', value: 'g_horreur' },
|
||||
{ label: 'Insectes', value: 'g_insectes' },
|
||||
{ label: 'Mystère', value: 'g_mystere' },
|
||||
{ label: 'Lolicon', value: 'g_lolicon' },
|
||||
{ label: 'Shoujo Ai', value: 'g_shoujo-ai' },
|
||||
{ label: 'Smut', value: 'g_smut' },
|
||||
{ label: 'Xuanhuan', value: 'g_xuanhuan' },
|
||||
{ label: 'Shotacon', value: 'g_shotacon' },
|
||||
],
|
||||
},
|
||||
} satisfies Filters;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class IndoWebNovel implements Plugin.PluginBase {
|
||||
id = "IDWN.id";
|
||||
name = "IndoWebNovel";
|
||||
icon = "src/id/indowebnovel/icon.png";
|
||||
site = "https://indowebnovel.id/";
|
||||
version = "1.2.0";
|
||||
id = 'IDWN.id';
|
||||
name = 'IndoWebNovel';
|
||||
icon = 'src/id/indowebnovel/icon.png';
|
||||
site = 'https://indowebnovel.id/';
|
||||
version = '1.2.0';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".flexbox2-item").each((i,el) => {
|
||||
loadedCheerio('.flexbox2-item').each((i, el) => {
|
||||
const novelName = loadedCheerio(el)
|
||||
.find(".flexbox2-title span")
|
||||
.find('.flexbox2-title span')
|
||||
.first()
|
||||
.text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(el).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(el)
|
||||
.find(".flexbox2-content > a")
|
||||
.attr("href");
|
||||
.find('.flexbox2-content > a')
|
||||
.attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -37,23 +37,23 @@ class IndoWebNovel implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}advanced-search/page/${page}/?title=&author=&yearx=`;
|
||||
link += `&status=${filters.status.value}`
|
||||
link += `&status=${filters.status.value}`;
|
||||
link += `&type=${filters.type.value}`;
|
||||
link += `&order=${filters.sort.value}`;
|
||||
|
||||
if (filters.lang.value.length)
|
||||
link += filters.lang.value.map((i) => `&country[]=${i}`).join("");
|
||||
link += filters.lang.value.map(i => `&country[]=${i}`).join('');
|
||||
if (filters.genre.value.length)
|
||||
link += filters.genre.value.map((i) => `&genre[]=${i}`).join("");
|
||||
link += filters.genre.value.map(i => `&genre[]=${i}`).join('');
|
||||
|
||||
const result = await fetchApi(link);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio)
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
@@ -61,28 +61,30 @@ class IndoWebNovel implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
loadedCheerio(".series-synops div").remove();
|
||||
loadedCheerio('.series-synops div').remove();
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".series-title").text().trim() || "Untitled",
|
||||
cover: loadedCheerio(".series-thumb img").attr("src"),
|
||||
author: loadedCheerio("ul.series-infolist b:contains('Author') +").text().trim(),
|
||||
status: loadedCheerio(".status").text().trim(),
|
||||
summary: loadedCheerio(".series-synops").text().trim(),
|
||||
name: loadedCheerio('.series-title').text().trim() || 'Untitled',
|
||||
cover: loadedCheerio('.series-thumb img').attr('src'),
|
||||
author: loadedCheerio("ul.series-infolist b:contains('Author') +")
|
||||
.text()
|
||||
.trim(),
|
||||
status: loadedCheerio('.status').text().trim(),
|
||||
summary: loadedCheerio('.series-synops').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.genres = loadedCheerio(".series-genres a")
|
||||
.map((i,el) => loadedCheerio(el).text().trim())
|
||||
novel.genres = loadedCheerio('.series-genres a')
|
||||
.map((i, el) => loadedCheerio(el).text().trim())
|
||||
.toArray()
|
||||
.join(',');
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".series-chapterlist li").each((i,el) => {
|
||||
const chapterName = loadedCheerio(el).find("a").text().trim();
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
loadedCheerio('.series-chapterlist li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('a').text().trim();
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -103,23 +105,26 @@ class IndoWebNovel implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio(".readerss").html() || '';
|
||||
const chapterText = loadedCheerio('.readerss').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}advanced-search/page/${page}/?title=${searchTerm}&author=&yearx=`;
|
||||
link += `&status=${this.filters.status.value}`
|
||||
link += `&status=${this.filters.status.value}`;
|
||||
link += `&type=${this.filters.type.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('');
|
||||
|
||||
const result = await fetchApi(link);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio)
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
@@ -128,83 +133,83 @@ class IndoWebNovel implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
status: {
|
||||
value: "",
|
||||
label: "Status",
|
||||
value: '',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
value: "",
|
||||
label: "Type",
|
||||
value: '',
|
||||
label: 'Type',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Web Novel", value: "Web+Novel" },
|
||||
{ label: "Light Novel", value: "Light+Novel" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Web Novel', value: 'Web+Novel' },
|
||||
{ label: 'Light Novel', value: 'Light+Novel' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "rating",
|
||||
label: "Order By",
|
||||
value: 'rating',
|
||||
label: 'Order By',
|
||||
options: [
|
||||
{ label: "A-Z", value: "title" },
|
||||
{ label: "Z-A", value: "titlereverse" },
|
||||
{ label: "Latest Update", value: "update" },
|
||||
{ label: "Latest Added", value: "latest" },
|
||||
{ label: "Popular", value: "popular" },
|
||||
{ label: "Rating", value: "rating" },
|
||||
{ label: 'A-Z', value: 'title' },
|
||||
{ label: 'Z-A', value: 'titlereverse' },
|
||||
{ label: 'Latest Update', value: 'update' },
|
||||
{ label: 'Latest Added', value: 'latest' },
|
||||
{ label: 'Popular', value: 'popular' },
|
||||
{ label: 'Rating', value: 'rating' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
lang: {
|
||||
value: ['china','jepang','korea','unknown'],
|
||||
label: "Country",
|
||||
value: ['china', 'jepang', 'korea', 'unknown'],
|
||||
label: 'Country',
|
||||
options: [
|
||||
{ label: "China", value: "china" },
|
||||
{ label: "Jepang", value: "jepang" },
|
||||
{ label: "Korea", value: "korea" },
|
||||
{ label: "Unknown", value: "unknown" },
|
||||
{ label: 'China', value: 'china' },
|
||||
{ label: 'Jepang', value: 'jepang' },
|
||||
{ label: 'Korea', value: 'korea' },
|
||||
{ label: 'Unknown', value: 'unknown' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genre: {
|
||||
value: [],
|
||||
label: "Genres",
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "School Life", value: "school-life" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Slice of Life", value: "slice-of-life" },
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" }
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+126
-127
@@ -1,27 +1,24 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class NovelRingan implements Plugin.PluginBase {
|
||||
id = "novelringan.com";
|
||||
name = "NovelRingan";
|
||||
icon = "src/id/novelringan/icon.png";
|
||||
site = "https://novelringan.com/";
|
||||
version = "1.0.0";
|
||||
coverUriPrefix = "https://i0.wp.com/novelringan.com/wp-content/uploads/";
|
||||
id = 'novelringan.com';
|
||||
name = 'NovelRingan';
|
||||
icon = 'src/id/novelringan/icon.png';
|
||||
site = 'https://novelringan.com/';
|
||||
version = '1.0.0';
|
||||
coverUriPrefix = 'https://i0.wp.com/novelringan.com/wp-content/uploads/';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI) {
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("article.post").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find(".entry-title")
|
||||
.text()
|
||||
?.trim();
|
||||
loadedCheerio('article.post').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('.entry-title').text()?.trim();
|
||||
const novelCover =
|
||||
this.coverUriPrefix + loadedCheerio(ele).find("img").attr("data-sxrx");
|
||||
const novelUrl = loadedCheerio(ele).find("h2 > a").attr("href");
|
||||
this.coverUriPrefix + loadedCheerio(ele).find('img').attr('data-sxrx');
|
||||
const novelUrl = loadedCheerio(ele).find('h2 > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -39,17 +36,16 @@ class NovelRingan implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}advanced-search/page/${page}/?title`;
|
||||
link += `&status=${filters.status.value}`
|
||||
link += `&status=${filters.status.value}`;
|
||||
link += `&order=${filters.sort.value}`;
|
||||
|
||||
if (filters.type.value.length)
|
||||
link += filters.type.value.map((i) => `&type[]=${i}`).join("");
|
||||
link += filters.type.value.map(i => `&type[]=${i}`).join('');
|
||||
if (filters.genre.value.length)
|
||||
link += filters.genre.value.map((i) => `&genre[]=${i}`).join("");
|
||||
|
||||
link += filters.genre.value.map(i => `&genre[]=${i}`).join('');
|
||||
|
||||
const result = await fetchApi(link);
|
||||
const body = await result.text();
|
||||
@@ -58,7 +54,6 @@ class NovelRingan implements Plugin.PluginBase {
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.site + novelPath);
|
||||
const body = await result.text();
|
||||
@@ -66,17 +61,15 @@ class NovelRingan implements Plugin.PluginBase {
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const styletag = Array.from(
|
||||
(
|
||||
loadedCheerio('meta[name=msapplication-TileImage] + style')
|
||||
.html()
|
||||
?.matchAll(/"(.*?)"/g)
|
||||
) || [],
|
||||
m => m[1]
|
||||
?.matchAll(/"(.*?)"/g) || [],
|
||||
m => m[1],
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: styletag[0] || loadedCheerio('.entry-title').text() || "Untitled",
|
||||
name: styletag[0] || loadedCheerio('.entry-title').text() || 'Untitled',
|
||||
author: styletag[1],
|
||||
summary: loadedCheerio('.maininfo span p').text(),
|
||||
chapters: [],
|
||||
@@ -84,27 +77,27 @@ class NovelRingan implements Plugin.PluginBase {
|
||||
|
||||
novel.cover =
|
||||
this.coverUriPrefix +
|
||||
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();
|
||||
let detail = loadedCheerio(this).find('b').remove().end().text().trim()
|
||||
let detail = loadedCheerio(this).find('b').remove().end().text().trim();
|
||||
|
||||
switch(detailName) {
|
||||
case "Status:":
|
||||
switch (detailName) {
|
||||
case 'Status:':
|
||||
novel.status = detail;
|
||||
break;
|
||||
case "Genre:":
|
||||
case 'Genre:':
|
||||
novel.genres = detail;
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".bxcl > ul > li").each((i,el) => {
|
||||
const chapterName = loadedCheerio(el).find("a").text();
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
loadedCheerio('.bxcl > ul > li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el).find('a').text();
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -129,19 +122,22 @@ class NovelRingan implements Plugin.PluginBase {
|
||||
|
||||
loadedCheerio('.entry-content div[style="display:none"]').remove();
|
||||
|
||||
const chapterText = loadedCheerio(".entry-content").html() || '';
|
||||
const chapterText = loadedCheerio('.entry-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + "page/" + page + "/?s=" + searchTerm;
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + 'page/' + page + '/?s=' + searchTerm;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio)
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
@@ -150,108 +146,111 @@ class NovelRingan implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
status: {
|
||||
value: "",
|
||||
label: "Status",
|
||||
value: '',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Ongoing", value: "Ongoing" },
|
||||
{ label: "Completed", value: "Completed" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Ongoing', value: 'Ongoing' },
|
||||
{ label: 'Completed', value: 'Completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "popular",
|
||||
label: "Urutkan",
|
||||
value: 'popular',
|
||||
label: 'Urutkan',
|
||||
options: [
|
||||
{ label: "A-Z", value: "title" },
|
||||
{ label: "Z-A", value: "titlereverse" },
|
||||
{ label: "Terbarui", value: "update" },
|
||||
{ label: "Ditambahkan", value: "latest" },
|
||||
{ label: "Terpopuler", value: "popular" },
|
||||
{ label: 'A-Z', value: 'title' },
|
||||
{ label: 'Z-A', value: 'titlereverse' },
|
||||
{ label: 'Terbarui', value: 'update' },
|
||||
{ label: 'Ditambahkan', value: 'latest' },
|
||||
{ label: 'Terpopuler', value: 'popular' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
value: [],
|
||||
label: "Tipe",
|
||||
label: 'Tipe',
|
||||
options: [
|
||||
{ label: "Chinese Novel", value: "chinese-novel" },
|
||||
{ label: "Chinese Web Novel", value: "chinese-web-novel" },
|
||||
{ label: "Filipino Novel", value: "filipino-novel" },
|
||||
{ label: "Indonesia Novel", value: "indonesia-novel" },
|
||||
{ label: "Korean Novel", value: "korean-novel" },
|
||||
{ label: "Light Novel", value: "light-novel" },
|
||||
{ label: "Light Novel (CN)", value: "light-novel-cn" },
|
||||
{ label: "Light Novel (JP)", value: "light-novel-jp" },
|
||||
{ label: "Light Novel (KR)", value: "light-novel-kr" },
|
||||
{ label: "Malaysian Novel", value: "malaysian-novel" },
|
||||
{ label: "Published Novel (CN)", value: "published-novel-cn" },
|
||||
{ label: "Published Novel (JP)", value: "published-novel-jp" },
|
||||
{ label: "Published Novel (KR)", value: "published-novel-kr" },
|
||||
{ label: "Published Novel (TH)", value: "published-novel-th" },
|
||||
{ label: "Thai Novel", value: "thai-novel" },
|
||||
{ label: "Vietnamese Novel", value: "vietnamese-novel" },
|
||||
{ label: "Web Novel", value: "web-novel" },
|
||||
{ label: "Webnovel", value: "webnovel" },
|
||||
{ label: 'Chinese Novel', value: 'chinese-novel' },
|
||||
{ label: 'Chinese Web Novel', value: 'chinese-web-novel' },
|
||||
{ label: 'Filipino Novel', value: 'filipino-novel' },
|
||||
{ label: 'Indonesia Novel', value: 'indonesia-novel' },
|
||||
{ label: 'Korean Novel', value: 'korean-novel' },
|
||||
{ label: 'Light Novel', value: 'light-novel' },
|
||||
{ label: 'Light Novel (CN)', value: 'light-novel-cn' },
|
||||
{ label: 'Light Novel (JP)', value: 'light-novel-jp' },
|
||||
{ label: 'Light Novel (KR)', value: 'light-novel-kr' },
|
||||
{ label: 'Malaysian Novel', value: 'malaysian-novel' },
|
||||
{ label: 'Published Novel (CN)', value: 'published-novel-cn' },
|
||||
{ label: 'Published Novel (JP)', value: 'published-novel-jp' },
|
||||
{ label: 'Published Novel (KR)', value: 'published-novel-kr' },
|
||||
{ label: 'Published Novel (TH)', value: 'published-novel-th' },
|
||||
{ label: 'Thai Novel', value: 'thai-novel' },
|
||||
{ label: 'Vietnamese Novel', value: 'vietnamese-novel' },
|
||||
{ label: 'Web Novel', value: 'web-novel' },
|
||||
{ label: 'Webnovel', value: 'webnovel' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genre: {
|
||||
value: [],
|
||||
label: "Genres",
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Celebrity", value: "celebrity" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "ction", value: "ction" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Eastern", value: "eastern" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Game", value: "game" },
|
||||
{ label: "Games", value: "games" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Isekai", value: "isekai" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Life", value: "life" },
|
||||
{ label: "LitRPG", value: "litrpg" },
|
||||
{ label: "Magical Realism", value: "magical-realism" },
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Psychologic", value: "psychologic" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Recarnation", value: "recarnation" },
|
||||
{ label: "Reincarnation", value: "reincarnation" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "School", value: "school" },
|
||||
{ label: "School Life", value: "school-life" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Shotacon", value: "shotacon" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Shoujo Ai", value: "shoujo-ai" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Shounen Ai", value: "shounen-ai" },
|
||||
{ label: "Slice of Life", value: "slice-of-life" },
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: "Sports", value: "sports" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Urban", value: "urban" },
|
||||
{ label: "Urban Life", value: "urban-life" },
|
||||
{ label: "ve names:N/A Genre:Romance", value: "ve-namesn-a-genreromance" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: "Yuri", value: "yuri" },
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Celebrity', value: 'celebrity' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'ction', value: 'ction' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Eastern', value: 'eastern' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Game', value: 'game' },
|
||||
{ label: 'Games', value: 'games' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Isekai', value: 'isekai' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Life', value: 'life' },
|
||||
{ label: 'LitRPG', value: 'litrpg' },
|
||||
{ label: 'Magical Realism', value: 'magical-realism' },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Psychologic', value: 'psychologic' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Recarnation', value: 'recarnation' },
|
||||
{ label: 'Reincarnation', value: 'reincarnation' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'School', value: 'school' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Shotacon', value: 'shotacon' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Shoujo Ai', value: 'shoujo-ai' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Shounen Ai', value: 'shounen-ai' },
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Urban', value: 'urban' },
|
||||
{ label: 'Urban Life', value: 'urban-life' },
|
||||
{
|
||||
label: 've names:N/A Genre:Romance',
|
||||
value: 've-namesn-a-genreromance',
|
||||
},
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
{ label: 'Yuri', value: 'yuri' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
|
||||
class SakuraNovel implements Plugin.PluginBase {
|
||||
id = "sakura.id";
|
||||
name = "SakuraNovel";
|
||||
icon = "src/id/sakuranovel/icon.png";
|
||||
site = "https://sakuranovel.id/";
|
||||
version = "1.0.1";
|
||||
id = 'sakura.id';
|
||||
name = 'SakuraNovel';
|
||||
icon = 'src/id/sakuranovel/icon.png';
|
||||
site = 'https://sakuranovel.id/';
|
||||
version = '1.0.1';
|
||||
|
||||
parseNovels(loadedCheerio:CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".flexbox2-item").each((i,el) => {
|
||||
loadedCheerio('.flexbox2-item').each((i, el) => {
|
||||
const novelName = loadedCheerio(el)
|
||||
.find(".flexbox2-title span")
|
||||
.find('.flexbox2-title span')
|
||||
.first()
|
||||
.text();
|
||||
const novelCover = loadedCheerio(el).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(el).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(el)
|
||||
.find(".flexbox2-content > a")
|
||||
.attr("href");
|
||||
.find('.flexbox2-content > a')
|
||||
.attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -37,17 +37,17 @@ class SakuraNovel implements Plugin.PluginBase {
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}advanced-search/page/${page}/?title&author&yearx`;
|
||||
link += `&status=${filters.status.value}`
|
||||
link += `&status=${filters.status.value}`;
|
||||
link += `&type=${filters.type.value}`;
|
||||
link += `&order=${filters.sort.value}`;
|
||||
|
||||
if (filters.lang.value.length)
|
||||
link += filters.lang.value.map((i) => `&country[]=${i}`).join("");
|
||||
link += filters.lang.value.map(i => `&country[]=${i}`).join('');
|
||||
if (filters.genre.value.length)
|
||||
link += filters.genre.value.map((i) => `&genre[]=${i}`).join("");
|
||||
link += filters.genre.value.map(i => `&genre[]=${i}`).join('');
|
||||
|
||||
const result = await fetchApi(link);
|
||||
const body = await result.text();
|
||||
@@ -61,53 +61,67 @@ class SakuraNovel implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
loadedCheerio(".series-synops div").remove();
|
||||
loadedCheerio('.series-synops div').remove();
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".series-title h2").text().trim() || "Untitled",
|
||||
cover: loadedCheerio(".series-thumb img").attr("src"),
|
||||
author: loadedCheerio(".series-infolist > li b:contains('Author') +").text().trim(),
|
||||
status: loadedCheerio(".status").text().trim(),
|
||||
summary: loadedCheerio(".series-synops").text().trim(),
|
||||
chapters: []
|
||||
name: loadedCheerio('.series-title h2').text().trim() || 'Untitled',
|
||||
cover: loadedCheerio('.series-thumb img').attr('src'),
|
||||
author: loadedCheerio(".series-infolist > li b:contains('Author') +")
|
||||
.text()
|
||||
.trim(),
|
||||
status: loadedCheerio('.status').text().trim(),
|
||||
summary: loadedCheerio('.series-synops').text().trim(),
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.genres = loadedCheerio(".series-genres")
|
||||
.children("a")
|
||||
novel.genres = loadedCheerio('.series-genres')
|
||||
.children('a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
const imageTitle = novel.cover?.split('/').pop()?.split('-').join(' ').split('.')[0];
|
||||
const chapterTitle = novel.name.replace(/\(LN\)|\(WN\)/, "").split(',')[0].trim();
|
||||
const imageTitle = novel.cover
|
||||
?.split('/')
|
||||
.pop()
|
||||
?.split('-')
|
||||
.join(' ')
|
||||
.split('.')[0];
|
||||
const chapterTitle = novel.name
|
||||
.replace(/\(LN\)|\(WN\)/, '')
|
||||
.split(',')[0]
|
||||
.trim();
|
||||
const chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".series-flexright li").each((i,el) => {
|
||||
loadedCheerio('.series-flexright li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el)
|
||||
.find("a span")
|
||||
.find('a span')
|
||||
.first()
|
||||
.text()
|
||||
.replace(chapterTitle, '')
|
||||
.replace(imageTitle!, '')
|
||||
.replace(/Bahasa Indonesia/, '')
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
const releaseDate = loadedCheerio(el)
|
||||
.find(".date")
|
||||
.find('.date')
|
||||
.text()
|
||||
.trim()
|
||||
.split('/')
|
||||
.map(x => Number(x));
|
||||
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
chapter.push({
|
||||
name: chapterName,
|
||||
releaseTime: new Date(releaseDate[2], releaseDate[1], releaseDate[0]).toISOString(),
|
||||
releaseTime: new Date(
|
||||
releaseDate[2],
|
||||
releaseDate[1],
|
||||
releaseDate[0],
|
||||
).toISOString(),
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
});
|
||||
});
|
||||
@@ -124,15 +138,19 @@ class SakuraNovel implements Plugin.PluginBase {
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const divi = loadedCheerio("div:contains('Daftar Isi') +")
|
||||
.find("div:first")
|
||||
.attr("class");
|
||||
loadedCheerio(`.${divi}`).remove()
|
||||
const chapterText = loadedCheerio("div:contains('Daftar Isi') +").html() || '';
|
||||
.find('div:first')
|
||||
.attr('class');
|
||||
loadedCheerio(`.${divi}`).remove();
|
||||
const chapterText =
|
||||
loadedCheerio("div:contains('Daftar Isi') +").html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}page/${page}/?s=${searchTerm}`;
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
@@ -147,83 +165,83 @@ class SakuraNovel implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
status: {
|
||||
value: "",
|
||||
label: "Status",
|
||||
value: '',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
value: "",
|
||||
label: "Type",
|
||||
value: '',
|
||||
label: 'Type',
|
||||
options: [
|
||||
{ label: "All", value: "" },
|
||||
{ label: "Web Novel", value: "Web+Novel" },
|
||||
{ label: "Light Novel", value: "Light+Novel" },
|
||||
{ label: 'All', value: '' },
|
||||
{ label: 'Web Novel', value: 'Web+Novel' },
|
||||
{ label: 'Light Novel', value: 'Light+Novel' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sort: {
|
||||
value: "rating",
|
||||
label: "Order By",
|
||||
value: 'rating',
|
||||
label: 'Order By',
|
||||
options: [
|
||||
{ label: "A-Z", value: "title" },
|
||||
{ label: "Z-A", value: "titlereverse" },
|
||||
{ label: "Latest Update", value: "update" },
|
||||
{ label: "Latest Added", value: "latest" },
|
||||
{ label: "Popular", value: "popular" },
|
||||
{ label: "Rating", value: "rating" },
|
||||
{ label: 'A-Z', value: 'title' },
|
||||
{ label: 'Z-A', value: 'titlereverse' },
|
||||
{ label: 'Latest Update', value: 'update' },
|
||||
{ label: 'Latest Added', value: 'latest' },
|
||||
{ label: 'Popular', value: 'popular' },
|
||||
{ label: 'Rating', value: 'rating' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
lang: {
|
||||
value: ['china','jepang','korea','unknown'],
|
||||
label: "Country",
|
||||
value: ['china', 'jepang', 'korea', 'unknown'],
|
||||
label: 'Country',
|
||||
options: [
|
||||
{ label: "China", value: "china" },
|
||||
{ label: "Jepang", value: "jepang" },
|
||||
{ label: "Korea", value: "korea" },
|
||||
{ label: "Unknown", value: "unknown" },
|
||||
{ label: 'China', value: 'china' },
|
||||
{ label: 'Jepang', value: 'jepang' },
|
||||
{ label: 'Korea', value: 'korea' },
|
||||
{ label: 'Unknown', value: 'unknown' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genre: {
|
||||
value: [],
|
||||
label: "Genres",
|
||||
label: 'Genres',
|
||||
options: [
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: "Adventure", value: "adventure" },
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Mystery", value: "mystery" },
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "School Life", value: "school-life" },
|
||||
{ label: "Sci-fi", value: "sci-fi" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Slice of Life", value: "slice-of-life" },
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" }
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+94
-102
@@ -1,34 +1,34 @@
|
||||
import { load, load as loadCheerio } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { NovelItem } from "../../test_web/static/js";
|
||||
import { load, load as loadCheerio } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { NovelItem } from '../../test_web/static/js';
|
||||
// const novelStatus = require('@libs/novelStatus');
|
||||
// const isUrlAbsolute = require('@libs/isAbsoluteUrl');
|
||||
// const parseDate = require('@libs/parseDate');
|
||||
|
||||
class Syosetu implements Plugin.PluginBase {
|
||||
id = "yomou.syosetu";
|
||||
name = "Syosetu";
|
||||
icon = "src/jp/syosetu/icon.png";
|
||||
site = "https://yomou.syosetu.com/";
|
||||
novelPrefix = "https://ncode.syosetu.com";
|
||||
version = "1.1.0";
|
||||
id = 'yomou.syosetu';
|
||||
name = 'Syosetu';
|
||||
icon = 'src/jp/syosetu/icon.png';
|
||||
site = 'https://yomou.syosetu.com/';
|
||||
novelPrefix = 'https://ncode.syosetu.com';
|
||||
version = '1.1.0';
|
||||
headers = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
'User-Agent':
|
||||
'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) => {
|
||||
return `https://yomou.syosetu.com/search.php?order=${order || "hyoka"}${
|
||||
return `https://yomou.syosetu.com/search.php?order=${order || 'hyoka'}${
|
||||
pagenum !== undefined
|
||||
? `&p=${pagenum <= 1 || pagenum > 100 ? "1" : pagenum}` // check if pagenum is between 1 and 100
|
||||
: "" // if isn't don't set ?p
|
||||
? `&p=${pagenum <= 1 || pagenum > 100 ? '1' : pagenum}` // check if pagenum is between 1 and 100
|
||||
: '' // if isn't don't set ?p
|
||||
}`;
|
||||
};
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const getNovelsFromPage = async (pagenumber: number) => {
|
||||
// load page
|
||||
@@ -38,13 +38,9 @@ class Syosetu implements Plugin.PluginBase {
|
||||
url += `rank/list/type/${filters.ranking.value}_${filters.modifier.value}/?p=${pagenumber}`;
|
||||
} else {
|
||||
url += `rank/${
|
||||
filters.genre.value.length === 1
|
||||
? "isekailist"
|
||||
: "genrelist"
|
||||
filters.genre.value.length === 1 ? 'isekailist' : 'genrelist'
|
||||
}/type/${filters.ranking.value}_${filters.genre.value}${
|
||||
filters.modifier.value === "total"
|
||||
? ""
|
||||
: `_${filters.modifier.value}`
|
||||
filters.modifier.value === 'total' ? '' : `_${filters.modifier.value}`
|
||||
}/?p=${pagenumber}`;
|
||||
}
|
||||
const html = await (await fetchApi(url)).text();
|
||||
@@ -53,18 +49,13 @@ class Syosetu implements Plugin.PluginBase {
|
||||
decodeEntities: false,
|
||||
});
|
||||
|
||||
if (
|
||||
parseInt(loadedCheerio(".is-current").html() || "1") !==
|
||||
pagenumber
|
||||
)
|
||||
if (parseInt(loadedCheerio('.is-current').html() || '1') !== pagenumber)
|
||||
return [];
|
||||
|
||||
const novels: NovelItem[] = [];
|
||||
loadedCheerio(".c-card").each((_, e) => {
|
||||
const anchor = loadedCheerio(e).find(
|
||||
".p-ranklist-item__title a"
|
||||
);
|
||||
const url = anchor.attr("href");
|
||||
loadedCheerio('.c-card').each((_, e) => {
|
||||
const anchor = loadedCheerio(e).find('.p-ranklist-item__title a');
|
||||
const url = anchor.attr('href');
|
||||
if (!url) return;
|
||||
const name = anchor.text();
|
||||
const novel: NovelItem = {
|
||||
@@ -81,40 +72,40 @@ class Syosetu implements Plugin.PluginBase {
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
let chapters: Plugin.ChapterItem[] = [];
|
||||
const result = await fetch(this.novelPrefix + novelPath, { headers: this.headers });
|
||||
const result = await fetch(this.novelPrefix + novelPath, {
|
||||
headers: this.headers,
|
||||
});
|
||||
const body = await result.text();
|
||||
const loadedCheerio = loadCheerio(body, { decodeEntities: false });
|
||||
|
||||
// create novel object
|
||||
let novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".novel_title").text(),
|
||||
author: loadedCheerio(".novel_writername")
|
||||
.text()
|
||||
.replace("作者:", ""),
|
||||
name: loadedCheerio('.novel_title').text(),
|
||||
author: loadedCheerio('.novel_writername').text().replace('作者:', ''),
|
||||
cover: defaultCover,
|
||||
};
|
||||
|
||||
// Get all the chapters
|
||||
const cqGetChapters = loadedCheerio(".novel_sublist2");
|
||||
const cqGetChapters = loadedCheerio('.novel_sublist2');
|
||||
if (cqGetChapters.length !== 0) {
|
||||
// has more than 1 chapter
|
||||
novel.summary = loadedCheerio("#novel_ex")
|
||||
novel.summary = loadedCheerio('#novel_ex')
|
||||
.text()
|
||||
.replace(/<\s*br.*?>/g, "\n");
|
||||
.replace(/<\s*br.*?>/g, '\n');
|
||||
cqGetChapters.each(function (i, e) {
|
||||
const chapterA = loadedCheerio(this).find("a");
|
||||
const chapterA = loadedCheerio(this).find('a');
|
||||
const [chapterName, releaseDate, chapterUrl] = [
|
||||
// set the variables
|
||||
chapterA.text(),
|
||||
loadedCheerio(this)
|
||||
.find("dt") // get title
|
||||
.find('dt') // get title
|
||||
.text() // get text
|
||||
.replace(/(.)/g, "") // remove "(edited)" mark
|
||||
.replace(/(.)/g, '') // remove "(edited)" mark
|
||||
.trim(), // trim spaces
|
||||
chapterA.attr("href"),
|
||||
chapterA.attr('href'),
|
||||
];
|
||||
if(chapterUrl) {
|
||||
if (chapterUrl) {
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
releaseTime: releaseDate,
|
||||
@@ -130,27 +121,26 @@ class Syosetu implements Plugin.PluginBase {
|
||||
*/
|
||||
// get summary for oneshot chapter
|
||||
|
||||
const nameResult = await fetch(
|
||||
this.searchUrl() + `&word=${novel.name}`,
|
||||
{ headers: this.headers }
|
||||
);
|
||||
const nameResult = await fetch(this.searchUrl() + `&word=${novel.name}`, {
|
||||
headers: this.headers,
|
||||
});
|
||||
const nameBody = await nameResult.text();
|
||||
const summaryQuery = loadCheerio(nameBody, {
|
||||
decodeEntities: false,
|
||||
});
|
||||
const foundText = summaryQuery(".searchkekka_box")
|
||||
const foundText = summaryQuery('.searchkekka_box')
|
||||
.first()
|
||||
.find(".ex")
|
||||
.find('.ex')
|
||||
.text()
|
||||
.replace(/\s{2,}/g, "\n");
|
||||
.replace(/\s{2,}/g, '\n');
|
||||
novel.summary = foundText;
|
||||
|
||||
// add single chapter
|
||||
chapters.push({
|
||||
name: "Oneshot",
|
||||
releaseTime: loadedCheerio("head")
|
||||
name: 'Oneshot',
|
||||
releaseTime: loadedCheerio('head')
|
||||
.find("meta[name='WWWC']")
|
||||
.attr("content"), // get date from metadata
|
||||
.attr('content'), // get date from metadata
|
||||
path: novelPath, // set chapterUrl to oneshot so that chapterScraper knows it's a one-shot,
|
||||
});
|
||||
}
|
||||
@@ -158,7 +148,9 @@ class Syosetu implements Plugin.PluginBase {
|
||||
return novel;
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetch(this.novelPrefix + chapterPath, { headers: this.headers });
|
||||
const result = await fetch(this.novelPrefix + chapterPath, {
|
||||
headers: this.headers,
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
// create cheerioQuery
|
||||
@@ -167,13 +159,13 @@ class Syosetu implements Plugin.PluginBase {
|
||||
});
|
||||
|
||||
let chapterText =
|
||||
cheerioQuery("#novel_honbun") // get chapter text
|
||||
.html() || "";
|
||||
cheerioQuery('#novel_honbun') // get chapter text
|
||||
.html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let novels = [];
|
||||
|
||||
@@ -188,14 +180,14 @@ class Syosetu implements Plugin.PluginBase {
|
||||
|
||||
let pageNovels: Plugin.NovelItem[] = [];
|
||||
// find class=searchkekka_box
|
||||
cheerioQuery(".searchkekka_box").each((i, e) => {
|
||||
cheerioQuery('.searchkekka_box').each((i, e) => {
|
||||
// get div with link and name
|
||||
const novelDIV = cheerioQuery(e).find(".novel_h");
|
||||
const novelDIV = cheerioQuery(e).find('.novel_h');
|
||||
// get link element
|
||||
const novelA = novelDIV.children()[0];
|
||||
// add new novel to array
|
||||
const novelPath = novelA.attribs.href.replace(this.novelPrefix, '');
|
||||
if(novelPath){
|
||||
if (novelPath) {
|
||||
pageNovels.push({
|
||||
name: novelDIV.text(), // get the name
|
||||
path: novelPath, // get last part of the link
|
||||
@@ -234,57 +226,57 @@ class Syosetu implements Plugin.PluginBase {
|
||||
filters = {
|
||||
ranking: {
|
||||
type: FilterTypes.Picker,
|
||||
label: "Ranked by",
|
||||
label: 'Ranked by',
|
||||
options: [
|
||||
{ label: "日間", value: "daily" },
|
||||
{ label: "週間", value: "weekly" },
|
||||
{ label: "月間", value: "monthly" },
|
||||
{ label: "四半期", value: "quarter" },
|
||||
{ label: "年間", value: "yearly" },
|
||||
{ label: "累計", value: "total" },
|
||||
{ label: '日間', value: 'daily' },
|
||||
{ label: '週間', value: 'weekly' },
|
||||
{ label: '月間', value: 'monthly' },
|
||||
{ label: '四半期', value: 'quarter' },
|
||||
{ label: '年間', value: 'yearly' },
|
||||
{ label: '累計', value: 'total' },
|
||||
],
|
||||
value: "total",
|
||||
value: 'total',
|
||||
},
|
||||
genre: {
|
||||
type: FilterTypes.Picker,
|
||||
label: "Ranking Genre",
|
||||
label: 'Ranking Genre',
|
||||
options: [
|
||||
{ label: "総ジャンル", value: "" },
|
||||
{ label: "異世界転生/転移〔恋愛〕〕", value: "1" },
|
||||
{ label: "異世界転生/転移〔ファンタジー〕", value: "2" },
|
||||
{ label: "異世界転生/転移〔文芸・SF・その他〕", value: "o" },
|
||||
{ label: "異世界〔恋愛〕", value: "101" },
|
||||
{ label: "現実世界〔恋愛〕", value: "102" },
|
||||
{ label: "ハイファンタジー〔ファンタジー〕", value: "201" },
|
||||
{ label: "ローファンタジー〔ファンタジー〕", value: "202" },
|
||||
{ label: "純文学〔文芸〕", value: "301" },
|
||||
{ label: "ヒューマンドラマ〔文芸〕", value: "302" },
|
||||
{ label: "歴史〔文芸〕", value: "303" },
|
||||
{ label: "推理〔文芸〕", value: "304" },
|
||||
{ label: "ホラー〔文芸〕", value: "305" },
|
||||
{ label: "アクション〔文芸〕", value: "306" },
|
||||
{ label: "コメディー〔文芸〕", value: "307" },
|
||||
{ label: "VRゲーム〔SF〕", value: "401" },
|
||||
{ label: "宇宙〔SF〕", value: "402" },
|
||||
{ label: "空想科学〔SF〕", value: "403" },
|
||||
{ label: "パニック〔SF〕", value: "404" },
|
||||
{ label: "童話〔その他〕", value: "9901" },
|
||||
{ label: "詩〔その他〕", value: "9902" },
|
||||
{ label: "エッセイ〔その他〕", value: "9903" },
|
||||
{ label: "その他〔その他〕", value: "9999" },
|
||||
{ label: '総ジャンル', value: '' },
|
||||
{ label: '異世界転生/転移〔恋愛〕〕', value: '1' },
|
||||
{ label: '異世界転生/転移〔ファンタジー〕', value: '2' },
|
||||
{ label: '異世界転生/転移〔文芸・SF・その他〕', value: 'o' },
|
||||
{ label: '異世界〔恋愛〕', value: '101' },
|
||||
{ label: '現実世界〔恋愛〕', value: '102' },
|
||||
{ label: 'ハイファンタジー〔ファンタジー〕', value: '201' },
|
||||
{ label: 'ローファンタジー〔ファンタジー〕', value: '202' },
|
||||
{ label: '純文学〔文芸〕', value: '301' },
|
||||
{ label: 'ヒューマンドラマ〔文芸〕', value: '302' },
|
||||
{ label: '歴史〔文芸〕', value: '303' },
|
||||
{ label: '推理〔文芸〕', value: '304' },
|
||||
{ label: 'ホラー〔文芸〕', value: '305' },
|
||||
{ label: 'アクション〔文芸〕', value: '306' },
|
||||
{ label: 'コメディー〔文芸〕', value: '307' },
|
||||
{ label: 'VRゲーム〔SF〕', value: '401' },
|
||||
{ label: '宇宙〔SF〕', value: '402' },
|
||||
{ label: '空想科学〔SF〕', value: '403' },
|
||||
{ label: 'パニック〔SF〕', value: '404' },
|
||||
{ label: '童話〔その他〕', value: '9901' },
|
||||
{ label: '詩〔その他〕', value: '9902' },
|
||||
{ label: 'エッセイ〔その他〕', value: '9903' },
|
||||
{ label: 'その他〔その他〕', value: '9999' },
|
||||
],
|
||||
value: "",
|
||||
value: '',
|
||||
},
|
||||
modifier: {
|
||||
type: FilterTypes.Picker,
|
||||
label: "Modifier",
|
||||
label: 'Modifier',
|
||||
options: [
|
||||
{ label: "すべて", value: "total" },
|
||||
{ label: "連載中", value: "r" },
|
||||
{ label: "完結済", value: "er" },
|
||||
{ label: "短編", value: "t" },
|
||||
{ label: 'すべて', value: 'total' },
|
||||
{ label: '連載中', value: 'r' },
|
||||
{ label: '完結済', value: 'er' },
|
||||
{ label: '短編', value: 't' },
|
||||
],
|
||||
value: "total",
|
||||
value: 'total',
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
+55
-52
@@ -1,35 +1,35 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import qs from "qs";
|
||||
import { fetchFile, fetchApi } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import qs from 'qs';
|
||||
|
||||
class Agitoon implements Plugin.PluginBase {
|
||||
id = "agit.xyz";
|
||||
name = "Agitoon";
|
||||
icon = "src/kr/agitoon/agit.png";
|
||||
site = "https://agit501.xyz";
|
||||
version = "1.0.0";
|
||||
id = 'agit.xyz';
|
||||
name = 'Agitoon';
|
||||
icon = 'src/kr/agitoon/agit.png';
|
||||
site = 'https://agit501.xyz';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const res = await fetchApi(this.site + "/novel/index.update.php", {
|
||||
const res = await fetchApi(this.site + '/novel/index.update.php', {
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
mode: "get_data_novel_list_p",
|
||||
novel_menu: showLatestNovels ? "1" : "3",
|
||||
mode: 'get_data_novel_list_p',
|
||||
novel_menu: showLatestNovels ? '1' : '3',
|
||||
np_day: new Date().getDay(),
|
||||
np_rank: "1",
|
||||
np_distributor: "0",
|
||||
np_genre: "00",
|
||||
np_order: "1",
|
||||
np_genre_ex_1: "00",
|
||||
np_genre_ex_2: "00",
|
||||
np_rank: '1',
|
||||
np_distributor: '0',
|
||||
np_genre: '00',
|
||||
np_order: '1',
|
||||
np_genre_ex_1: '00',
|
||||
np_genre_ex_2: '00',
|
||||
list_limit: 20 * (pageNo - 1),
|
||||
is_query_first: pageNo == 1,
|
||||
}),
|
||||
@@ -37,10 +37,10 @@ class Agitoon implements Plugin.PluginBase {
|
||||
const resJson = (await res.json()) as response;
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
resJson?.list?.forEach((novel) =>
|
||||
resJson?.list?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.wr_subject,
|
||||
cover: this.site + novel.np_dir + "/thumbnail/" + novel.np_thumbnail,
|
||||
cover: this.site + novel.np_dir + '/thumbnail/' + novel.np_thumbnail,
|
||||
path: novel.wr_id,
|
||||
}),
|
||||
);
|
||||
@@ -49,50 +49,51 @@ class Agitoon implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const result = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(result, { decodeEntities: false });
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h5.pt-2").text(),
|
||||
cover: this.site + loadedCheerio("div.col-5.pr-0.pl-0 img").attr("src"),
|
||||
summary: loadedCheerio(".pt-1.mt-1.pb-1.mb-1").text(),
|
||||
name: loadedCheerio('h5.pt-2').text(),
|
||||
cover: this.site + loadedCheerio('div.col-5.pr-0.pl-0 img').attr('src'),
|
||||
summary: loadedCheerio('.pt-1.mt-1.pb-1.mb-1').text(),
|
||||
};
|
||||
|
||||
novel.author = loadedCheerio(".post-item-list-cate-v")
|
||||
novel.author = loadedCheerio('.post-item-list-cate-v')
|
||||
.first()
|
||||
.text()
|
||||
.split(" : ")[1];
|
||||
.split(' : ')[1];
|
||||
|
||||
const genres = loadedCheerio(".col-7 > .post-item-list-cate > span")
|
||||
const genres = loadedCheerio('.col-7 > .post-item-list-cate > span')
|
||||
.map((index, element) => loadedCheerio(element).text().trim())
|
||||
.get();
|
||||
|
||||
if (genres.length) {
|
||||
novel.genres = genres.join(", ");
|
||||
novel.genres = genres.join(', ');
|
||||
}
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const res = await fetchApi(this.site + "/novel/list.update.php", {
|
||||
const res = await fetchApi(this.site + '/novel/list.update.php', {
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
mode: "get_data_novel_list_c",
|
||||
mode: 'get_data_novel_list_c',
|
||||
wr_id_p: novelPath,
|
||||
page_no: "1",
|
||||
cnt_list: "10000",
|
||||
order_type: "Asc",
|
||||
page_no: '1',
|
||||
cnt_list: '10000',
|
||||
order_type: 'Asc',
|
||||
}),
|
||||
});
|
||||
|
||||
const resJson = (await res.json()) as responseBook;
|
||||
resJson?.list?.forEach((chapter) =>
|
||||
resJson?.list?.forEach(chapter =>
|
||||
chapters.push({
|
||||
name: chapter.wr_subject,
|
||||
path: chapter.wr_id + "/2",
|
||||
path: chapter.wr_id + '/2',
|
||||
releaseTime: chapter.wr_datetime,
|
||||
}),
|
||||
);
|
||||
@@ -102,28 +103,29 @@ class Agitoon implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.resolveUrl(chapterPath))
|
||||
.then((res) => res.text());
|
||||
const result = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(result);
|
||||
let content = loadedCheerio("#id_wr_content").html() || "";
|
||||
let content = loadedCheerio('#id_wr_content').html() || '';
|
||||
|
||||
// gets rid of the popup thingy
|
||||
content = content
|
||||
.replace("팝업메뉴는 빈공간을 더치하거나 스크룰시 사라집니다", "")
|
||||
.replace('팝업메뉴는 빈공간을 더치하거나 스크룰시 사라집니다', '')
|
||||
.trim();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const rawResults = await fetchApi("https://agit501.xyz/novel/search.php", {
|
||||
const rawResults = await fetchApi('https://agit501.xyz/novel/search.php', {
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
mode: "get_data_novel_list_p_sch",
|
||||
mode: 'get_data_novel_list_p_sch',
|
||||
search_novel: searchTerm,
|
||||
list_limit: 0,
|
||||
}),
|
||||
@@ -131,10 +133,11 @@ class Agitoon implements Plugin.PluginBase {
|
||||
const resJson = (await rawResults.json()) as response;
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
resJson?.list?.forEach((novel) =>
|
||||
resJson?.list?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.wr_subject,
|
||||
cover: this.site + "/" + novel.np_dir + "/thumbnail/" + novel.np_thumbnail,
|
||||
cover:
|
||||
this.site + '/' + novel.np_dir + '/thumbnail/' + novel.np_thumbnail,
|
||||
path: novel.wr_id,
|
||||
}),
|
||||
);
|
||||
@@ -144,7 +147,7 @@ class Agitoon implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + (isNovel ? "/novel/list/" : "/novel/view/") + path;
|
||||
this.site + (isNovel ? '/novel/list/' : '/novel/view/') + path;
|
||||
}
|
||||
|
||||
export default new Agitoon();
|
||||
|
||||
+146
-122
@@ -1,75 +1,78 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class NovelkiPL implements Plugin.PluginBase {
|
||||
id = "novelki.pl";
|
||||
name = "Novelki";
|
||||
icon = "src/pl/novelki/icon.png";
|
||||
site = "https://novelki.pl";
|
||||
version = "1.0.3";
|
||||
id = 'novelki.pl';
|
||||
name = 'Novelki';
|
||||
icon = 'src/pl/novelki/icon.png';
|
||||
site = 'https://novelki.pl';
|
||||
version = '1.0.3';
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
//So far is working, If the author of the site changes it, it will have to be changed.
|
||||
let link = this.site + "/projekty?filter=t"
|
||||
let link = this.site + '/projekty?filter=t';
|
||||
link += '&genre=' + filters.genres.value;
|
||||
link += '&status=' + filters.status.value;
|
||||
link += '&type=' + filters.type.value;
|
||||
link += '&page=' + page;
|
||||
|
||||
|
||||
const body = await fetchApi(link).then((res) => {
|
||||
if(res.url == `${this.site}/guest`) throw new Error("Failed to load novels (open in web view and login)")
|
||||
return res.text()
|
||||
const body = await fetchApi(link).then(res => {
|
||||
if (res.url == `${this.site}/guest`)
|
||||
throw new Error('Failed to load novels (open in web view and login)');
|
||||
return res.text();
|
||||
});
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const load = loadedCheerio("#projects > div")
|
||||
const load = loadedCheerio('#projects > div');
|
||||
|
||||
//if(load.length == 0) throw new Error("Failed to load page (open in web view and login)");
|
||||
|
||||
const novels: Plugin.NovelItem[] = load
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find(".card-title").attr('title') as string,
|
||||
cover: this.site+loadedCheerio(element).find(".card-img-top").attr("src"),
|
||||
path: loadedCheerio(element).find("a").attr("href") || "",
|
||||
name: loadedCheerio(element)
|
||||
.find('.card-title')
|
||||
.attr('title') as string,
|
||||
cover:
|
||||
this.site + loadedCheerio(element).find('.card-img-top').attr('src'),
|
||||
path: loadedCheerio(element).find('a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
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 novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: ''
|
||||
name: '',
|
||||
};
|
||||
|
||||
loadedCheerio("p.h5").each((i, e) => {
|
||||
const text = loadedCheerio(e).text().trim()
|
||||
if(text.includes('Autor:')) novel.author = text.split(':')[1].trim()
|
||||
if(text.includes("Status projektu:")) {
|
||||
loadedCheerio('p.h5').each((i, e) => {
|
||||
const text = loadedCheerio(e).text().trim();
|
||||
if (text.includes('Autor:')) novel.author = text.split(':')[1].trim();
|
||||
if (text.includes('Status projektu:')) {
|
||||
switch (`${text.split(':')[1].trim()}`.toLowerCase()) {
|
||||
case "wstrzymany":
|
||||
case 'wstrzymany':
|
||||
novel.status = NovelStatus.OnHiatus;
|
||||
break;
|
||||
case "zakończony":
|
||||
case 'zakończony':
|
||||
novel.status = NovelStatus.Completed;
|
||||
break;
|
||||
case "aktywny":
|
||||
case 'aktywny':
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
break;
|
||||
case "porzucony":
|
||||
case 'porzucony':
|
||||
novel.status = NovelStatus.Cancelled;
|
||||
break;
|
||||
case "zlicencjonowany":
|
||||
case 'zlicencjonowany':
|
||||
novel.status = NovelStatus.Licensed;
|
||||
break;
|
||||
default:
|
||||
@@ -77,65 +80,88 @@ class NovelkiPL implements Plugin.PluginBase {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
novel.name = loadedCheerio('div.col-sm-12.col-md-6.col-lg-8.mb-5')
|
||||
.find('h3')
|
||||
.text()
|
||||
.trim();
|
||||
novel.cover = this.site + loadedCheerio('.img-fluid').attr('src'); // TODO: return not only undefined
|
||||
|
||||
})
|
||||
novel.name = loadedCheerio("div.col-sm-12.col-md-6.col-lg-8.mb-5").find("h3").text().trim();
|
||||
novel.cover = this.site+loadedCheerio(".img-fluid").attr("src"); // TODO: return not only undefined
|
||||
novel.genres = loadedCheerio('span.badge')
|
||||
.map((i, e) => loadedCheerio(e).text())
|
||||
.get()
|
||||
.join(', ');
|
||||
|
||||
novel.genres = loadedCheerio("span.badge").map((i, e) => loadedCheerio(e).text()).get().join(", ")
|
||||
|
||||
novel.summary = loadedCheerio('.h5:contains("Opis:")').next("p").next("p").text().trim()
|
||||
novel.summary = loadedCheerio('.h5:contains("Opis:")')
|
||||
.next('p')
|
||||
.next('p')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
//TODO: Dodać tłumacza danej serii :D
|
||||
|
||||
let chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
let chaptersList = loadedCheerio(".chapters > .col-md-3 > div").get()
|
||||
let chaptersList = loadedCheerio('.chapters > .col-md-3 > div').get();
|
||||
chaptersList.forEach((e, i) => {
|
||||
let urlChapters = loadedCheerio(e).find("a").attr("href") || "";
|
||||
let urlChapters = loadedCheerio(e).find('a').attr('href') || '';
|
||||
|
||||
const chapter: Plugin.ChapterItem = {
|
||||
name: loadedCheerio(e).find("a")?.text().trim(),
|
||||
name: loadedCheerio(e).find('a')?.text().trim(),
|
||||
path: urlChapters,
|
||||
releaseTime: loadedCheerio(e).find(".card-footer > span").text().trim().split('-').reverse().join('-'),
|
||||
chapterNumber: chaptersList.length-i,
|
||||
releaseTime: loadedCheerio(e)
|
||||
.find('.card-footer > span')
|
||||
.text()
|
||||
.trim()
|
||||
.split('-')
|
||||
.reverse()
|
||||
.join('-'),
|
||||
chapterNumber: chaptersList.length - i,
|
||||
};
|
||||
chapters.push(chapter);
|
||||
})
|
||||
});
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
var pattern = /\/projekty\/([^\/]+)\/([^\/]+)/;
|
||||
let codeChapter = pattern.exec(chapterPath) || "";
|
||||
const body = await fetchApi(`${this.site}/api/reader/chapters/${codeChapter[2]}`).then((res) => {
|
||||
if(res.url == `${this.site}/guest`) throw new Error("Failed to load chapter (open in web view and login)")
|
||||
return res.json()
|
||||
let codeChapter = pattern.exec(chapterPath) || '';
|
||||
const body = await fetchApi(
|
||||
`${this.site}/api/reader/chapters/${codeChapter[2]}`,
|
||||
).then(res => {
|
||||
if (res.url == `${this.site}/guest`)
|
||||
throw new Error('Failed to load chapter (open in web view and login)');
|
||||
return res.json();
|
||||
});
|
||||
|
||||
const chapterText = body.data.content
|
||||
const chapterText = body.data.content;
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(`${this.site}/projekty?filter=t&title=${searchTerm}+&page=${page}`).then((res) => {
|
||||
if(res.url == `${this.site}/guest`) throw new Error("Failed to search novels (open in web view and login)")
|
||||
return res.text()
|
||||
const body = await fetchApi(
|
||||
`${this.site}/projekty?filter=t&title=${searchTerm}+&page=${page}`,
|
||||
).then(res => {
|
||||
if (res.url == `${this.site}/guest`)
|
||||
throw new Error('Failed to search novels (open in web view and login)');
|
||||
return res.text();
|
||||
});
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
let novels: Plugin.NovelItem[] = loadedCheerio("#projects > div")
|
||||
let novels: Plugin.NovelItem[] = loadedCheerio('#projects > div')
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find(".card-title").attr('title') as string,
|
||||
cover: this.site+loadedCheerio(element).find(".card-img-top").attr("src"),
|
||||
path: loadedCheerio(element).find("a").attr("href") || "",
|
||||
name: loadedCheerio(element)
|
||||
.find('.card-title')
|
||||
.attr('title') as string,
|
||||
cover:
|
||||
this.site + loadedCheerio(element).find('.card-img-top').attr('src'),
|
||||
path: loadedCheerio(element).find('a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path)
|
||||
.filter(novel => novel.name && novel.path);
|
||||
|
||||
return novels;
|
||||
}
|
||||
@@ -146,80 +172,78 @@ class NovelkiPL implements Plugin.PluginBase {
|
||||
}
|
||||
filters = {
|
||||
genres: {
|
||||
value: "",
|
||||
label: "Genre",
|
||||
value: '',
|
||||
label: 'Genre',
|
||||
options: [
|
||||
{ label: "Wybierz gatunek", value: "" },
|
||||
{ label: "Adaptacja do anime", value: "35" },
|
||||
{ label: "Adaptacja do dramy", value: "40" },
|
||||
{ label: "Adaptacja do mangi", value: "36" },
|
||||
{ label: "Akcja", value: "1" },
|
||||
{ label: "Boys Love", value: "22" },
|
||||
{ label: "Bromans", value: "33" },
|
||||
{ label: "Dojrzały", value: "11" },
|
||||
{ label: "Dramat", value: "10" },
|
||||
{ label: "Ecchi", value: "29" },
|
||||
{ label: "Fantasy", value: "2" },
|
||||
{ label: "Gender Bender", value: "28" },
|
||||
{ label: "Girls Love", value: "23" },
|
||||
{ label: "Harem", value: "8" },
|
||||
{ label: "Hentai", value: "43" },
|
||||
{ label: "Historyczny", value: "30" },
|
||||
{ label: "Horror", value: "15" },
|
||||
{ label: "Isekai", value: "44" },
|
||||
{ label: "Josei", value: "27" },
|
||||
{ label: "Komedia", value: "3" },
|
||||
{ label: "Mecha", value: "16" },
|
||||
{ label: "Nadprzyrodzone", value: "9" },
|
||||
{ label: "Okruchy życia", value: "18" },
|
||||
{ label: "Oneshot", value: "41" },
|
||||
{ label: "Parodia", value: "19" },
|
||||
{ label: "Przygodowy", value: "4" },
|
||||
{ label: "Psychologiczny", value: "12" },
|
||||
{ label: "Reinkarnacja", value: "42" },
|
||||
{ label: "Romans", value: "7" },
|
||||
{ label: "RPG", value: "17" },
|
||||
{ label: "Sci-fi", value: "21" },
|
||||
{ label: "Seinen", value: "26" },
|
||||
{ label: "Shoujo", value: "25" },
|
||||
{ label: "Shounen", value: "24" },
|
||||
{ label: "Smut", value: "31" },
|
||||
{ label: "Sport", value: "32" },
|
||||
{ label: "Świat gry", value: "5" },
|
||||
{ label: "Szkolne życie", value: "20" },
|
||||
{ label: "Sztuki walki", value: "6" },
|
||||
{ label: "Tajemnica", value: "14" },
|
||||
{ label: "Tragedia", value: "13" },
|
||||
|
||||
{ label: 'Wybierz gatunek', value: '' },
|
||||
{ label: 'Adaptacja do anime', value: '35' },
|
||||
{ label: 'Adaptacja do dramy', value: '40' },
|
||||
{ label: 'Adaptacja do mangi', value: '36' },
|
||||
{ label: 'Akcja', value: '1' },
|
||||
{ label: 'Boys Love', value: '22' },
|
||||
{ label: 'Bromans', value: '33' },
|
||||
{ label: 'Dojrzały', value: '11' },
|
||||
{ label: 'Dramat', value: '10' },
|
||||
{ label: 'Ecchi', value: '29' },
|
||||
{ label: 'Fantasy', value: '2' },
|
||||
{ label: 'Gender Bender', value: '28' },
|
||||
{ label: 'Girls Love', value: '23' },
|
||||
{ label: 'Harem', value: '8' },
|
||||
{ label: 'Hentai', value: '43' },
|
||||
{ label: 'Historyczny', value: '30' },
|
||||
{ label: 'Horror', value: '15' },
|
||||
{ label: 'Isekai', value: '44' },
|
||||
{ label: 'Josei', value: '27' },
|
||||
{ label: 'Komedia', value: '3' },
|
||||
{ label: 'Mecha', value: '16' },
|
||||
{ label: 'Nadprzyrodzone', value: '9' },
|
||||
{ label: 'Okruchy życia', value: '18' },
|
||||
{ label: 'Oneshot', value: '41' },
|
||||
{ label: 'Parodia', value: '19' },
|
||||
{ label: 'Przygodowy', value: '4' },
|
||||
{ label: 'Psychologiczny', value: '12' },
|
||||
{ label: 'Reinkarnacja', value: '42' },
|
||||
{ label: 'Romans', value: '7' },
|
||||
{ label: 'RPG', value: '17' },
|
||||
{ label: 'Sci-fi', value: '21' },
|
||||
{ label: 'Seinen', value: '26' },
|
||||
{ label: 'Shoujo', value: '25' },
|
||||
{ label: 'Shounen', value: '24' },
|
||||
{ label: 'Smut', value: '31' },
|
||||
{ label: 'Sport', value: '32' },
|
||||
{ label: 'Świat gry', value: '5' },
|
||||
{ label: 'Szkolne życie', value: '20' },
|
||||
{ label: 'Sztuki walki', value: '6' },
|
||||
{ label: 'Tajemnica', value: '14' },
|
||||
{ label: 'Tragedia', value: '13' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
status: {
|
||||
label: "Status",
|
||||
value: "",
|
||||
label: 'Status',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Wybierz status", value: "" },
|
||||
{ label: "Zakończony", value: "0" },
|
||||
{ label: "Aktywny", value: "1" },
|
||||
{ label: "Porzucony", value: "2" },
|
||||
{ label: "Wstrzymany", value: "3" },
|
||||
{ label: "Zlicencjonowany", value: "4" },
|
||||
|
||||
{ label: 'Wybierz status', value: '' },
|
||||
{ label: 'Zakończony', value: '0' },
|
||||
{ label: 'Aktywny', value: '1' },
|
||||
{ label: 'Porzucony', value: '2' },
|
||||
{ label: 'Wstrzymany', value: '3' },
|
||||
{ label: 'Zlicencjonowany', value: '4' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
label: "Type",
|
||||
value: "",
|
||||
label: 'Type',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Wybierz typ", value: "" },
|
||||
{ label: "Nie zdefiniowano", value: "0" },
|
||||
{ label: "Light Novel", value: "1" },
|
||||
{ label: "Wuxia", value: "2" },
|
||||
{ label: "Xianxia", value: "3" },
|
||||
{ label: "Web Novel", value: "4" },
|
||||
{ label: "Autorska", value: "5" },
|
||||
{ label: "Inne", value: "6" },
|
||||
{ label: 'Wybierz typ', value: '' },
|
||||
{ label: 'Nie zdefiniowano', value: '0' },
|
||||
{ label: 'Light Novel', value: '1' },
|
||||
{ label: 'Wuxia', value: '2' },
|
||||
{ label: 'Xianxia', value: '3' },
|
||||
{ label: 'Web Novel', value: '4' },
|
||||
{ label: 'Autorska', value: '5' },
|
||||
{ label: 'Inne', value: '6' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
class LightNovelBrasil implements Plugin.PluginBase {
|
||||
id = "lightnovelbrasil";
|
||||
name = "Light Novel Brasil";
|
||||
icon = "multisrc/wpmangastream/icons/lightnovelbrasil.png";
|
||||
site = "https://lightnovelbrasil.com/";
|
||||
version = "1.0.0";
|
||||
id = 'lightnovelbrasil';
|
||||
name = 'Light Novel Brasil';
|
||||
icon = 'multisrc/wpmangastream/icons/lightnovelbrasil.png';
|
||||
site = 'https://lightnovelbrasil.com/';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}series/?page=${pageNo}`;
|
||||
|
||||
if (filters.genres.value.length)
|
||||
if (filters.genres.value.length)
|
||||
link += filters.genres.value.map(i => `&genre[]=${i}`).join('');
|
||||
|
||||
if (filters.type.value.length)
|
||||
if (filters.type.value.length)
|
||||
link += filters.type.value.map(i => `&lang[]=${i}`).join('');
|
||||
|
||||
|
||||
link += '&status=' + (filters?.status ? filters.status : '');
|
||||
|
||||
|
||||
link += '&order=' + (filters?.order ? filters.order : 'popular');
|
||||
|
||||
const headers = new Headers();
|
||||
const headers = new Headers();
|
||||
const body = await fetchApi(link, { headers }).then((result) =>
|
||||
const body = await fetchApi(link, { headers }).then(result =>
|
||||
result.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
loadedCheerio("article.bs").each(function () {
|
||||
const novelName = loadedCheerio(this).find(".ntitle").text().trim();
|
||||
let image = loadedCheerio(this).find("img");
|
||||
loadedCheerio('article.bs').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.ntitle').text().trim();
|
||||
let image = loadedCheerio(this).find('img');
|
||||
const novelCover = image.attr('data-src') || image.attr('src');
|
||||
|
||||
|
||||
const novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -69,60 +69,49 @@ class LightNovelBrasil implements Plugin.PluginBase {
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
|
||||
novel.name = loadedCheerio('.entry-title').text();
|
||||
|
||||
novel.cover =
|
||||
novel.cover =
|
||||
loadedCheerio("img.wp-post-image").attr("data-src") ||
|
||||
loadedCheerio('img.wp-post-image').attr('data-src') ||
|
||||
loadedCheerio('img.wp-post-image').attr('src');
|
||||
|
||||
|
||||
loadedCheerio("div.spe > span").each(function () {
|
||||
const detailName = loadedCheerio(this).find("b").text().trim();
|
||||
const detail = loadedCheerio(this)
|
||||
.find("b")
|
||||
.remove()
|
||||
.end()
|
||||
.text()
|
||||
loadedCheerio('div.spe > span').each(function () {
|
||||
const detailName = loadedCheerio(this).find('b').text().trim();
|
||||
const detail = loadedCheerio(this).find('b').remove().end().text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
switch (detailName) {
|
||||
case "Autor:":
|
||||
case 'Autor:':
|
||||
case 'Author:':
|
||||
novel.author = detail;
|
||||
break;
|
||||
break;
|
||||
case 'Status:':
|
||||
novel.status = detail;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
novel.genres = loadedCheerio(".genxed")
|
||||
.text()
|
||||
.trim()
|
||||
novel.genres = loadedCheerio('.genxed').text().trim().replace(/\s/g, ',');
|
||||
|
||||
loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove();
|
||||
novel.summary = loadedCheerio('div[itemprop="description"]')
|
||||
novel.summary = loadedCheerio('div[itemprop="description"]')
|
||||
.find("br")
|
||||
.find('br')
|
||||
.replaceWith('\n')
|
||||
.end()
|
||||
.text();
|
||||
|
||||
let chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
|
||||
loadedCheerio(".eplister")
|
||||
loadedCheerio('.eplister')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
const chapterName =
|
||||
const chapterName =
|
||||
loadedCheerio(this).find(".epl-num").text() +
|
||||
" - " +
|
||||
loadedCheerio(this).find('.epl-num').text() +
|
||||
' - ' +
|
||||
loadedCheerio(this).find('.epl-title').text();
|
||||
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find(".epl-date")
|
||||
.text()
|
||||
const releaseDate = loadedCheerio(this).find('.epl-date').text().trim();
|
||||
|
||||
|
||||
const chapterUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -144,14 +133,14 @@ class LightNovelBrasil implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
|
||||
let chapterText = loadedCheerio('.epcontent').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
searchTerm: string,
|
||||
pageNo?: number | undefined,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}?s=${searchTerm}`;
|
||||
const headers = new Headers();
|
||||
@@ -162,10 +151,10 @@ class LightNovelBrasil implements Plugin.PluginBase {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
|
||||
loadedCheerio("article.bs").each(function () {
|
||||
const novelName = loadedCheerio(this).find(".ntitle").text().trim();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
loadedCheerio('article.bs').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.ntitle').text().trim();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
|
||||
novels.push({
|
||||
@@ -183,227 +172,227 @@ class LightNovelBrasil implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
order: {
|
||||
label: "Sort By",
|
||||
label: 'Sort By',
|
||||
value: '',
|
||||
options: [
|
||||
options: [
|
||||
{ label: 'Default', value: '' },
|
||||
|
||||
|
||||
{ label: 'A-Z', value: 'title' },
|
||||
|
||||
|
||||
{ label: 'Z-A', value: 'titlereverse' },
|
||||
|
||||
|
||||
{ label: 'Latest Update', value: 'update' },
|
||||
|
||||
|
||||
{ label: 'Latest Added', value: 'latest' },
|
||||
|
||||
|
||||
{ label: 'Popular', value: 'popular' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
status: {
|
||||
status: {
|
||||
label: "Status",
|
||||
label: 'Status',
|
||||
value: '',
|
||||
options: [
|
||||
options: [
|
||||
{ label: 'All', value: '' },
|
||||
|
||||
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
|
||||
|
||||
{ label: 'Hiatus', value: 'hiatus' },
|
||||
|
||||
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
type: {
|
||||
label: 'Type',
|
||||
value: [],
|
||||
options: [
|
||||
options: [
|
||||
{ label: 'Chinese novel', value: 'chinese-novel' },
|
||||
|
||||
|
||||
{ label: 'habyeol', value: 'habyeol' },
|
||||
|
||||
|
||||
{ label: 'korean novel', value: 'korean-novel' },
|
||||
|
||||
|
||||
{ label: 'Web Novel', value: 'web-novel' },
|
||||
|
||||
|
||||
{ label: '삼심', value: '%ec%82%bc%ec%8b%ac' },
|
||||
|
||||
|
||||
{ label: '호곡', value: '%ed%98%b8%ea%b3%a1' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genres: {
|
||||
value: [],
|
||||
value: [],
|
||||
label: 'Genres',
|
||||
options: [
|
||||
options: [
|
||||
{ label: 'A.I', value: 'a.i' },
|
||||
|
||||
|
||||
{ label: 'Academy', value: 'academy' },
|
||||
|
||||
|
||||
{ label: 'Action', value: 'action' },
|
||||
|
||||
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
|
||||
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
|
||||
|
||||
{ label: 'Alternative History', value: 'alternative-history' },
|
||||
|
||||
|
||||
{ label: 'Another World', value: 'another-world' },
|
||||
|
||||
|
||||
{ label: 'Apocalypse', value: 'apocalypse' },
|
||||
|
||||
|
||||
{ label: 'Bromance', value: 'bromance' },
|
||||
|
||||
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
|
||||
|
||||
{ label: 'Dark fantasy', value: 'dark-fantasy' },
|
||||
|
||||
|
||||
{ label: 'Demons', value: 'demons' },
|
||||
|
||||
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
|
||||
|
||||
{ label: 'Dystopia', value: 'dystopia' },
|
||||
|
||||
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
|
||||
|
||||
{ label: 'Entertainment', value: 'entertainment' },
|
||||
|
||||
|
||||
{ label: 'Exhaustion', value: 'exhaustion' },
|
||||
|
||||
|
||||
{ label: 'Fanfiction', value: 'fanfiction' },
|
||||
|
||||
|
||||
{ label: 'fantasy', value: 'fantasy' },
|
||||
|
||||
|
||||
{ label: 'finance', value: 'finance' },
|
||||
|
||||
|
||||
{ label: 'Full color', value: 'full-color' },
|
||||
|
||||
|
||||
{ label: 'Game', value: 'game' },
|
||||
|
||||
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
|
||||
|
||||
{ label: 'Genius', value: 'genius' },
|
||||
|
||||
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
|
||||
|
||||
{ label: 'Hero', value: 'hero' },
|
||||
|
||||
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
|
||||
|
||||
{ label: 'Hunter', value: 'hunter' },
|
||||
|
||||
|
||||
{ label: 'korean novel', value: 'korean-novel' },
|
||||
|
||||
|
||||
{ label: 'Light Novel', value: 'light-novel' },
|
||||
|
||||
{
|
||||
{
|
||||
label: "List Adventure Manga Genres",
|
||||
label: 'List Adventure Manga Genres',
|
||||
value: 'list-adventure-manga-genres',
|
||||
},
|
||||
|
||||
|
||||
{ label: 'Long Strip', value: 'long-strip' },
|
||||
|
||||
|
||||
{ label: 'Love comedy', value: 'love-comedy' },
|
||||
|
||||
|
||||
{ label: 'magic', value: 'magic' },
|
||||
|
||||
|
||||
{ label: 'Manhua', value: 'manhua' },
|
||||
|
||||
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
|
||||
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
|
||||
|
||||
{ label: 'Medieval', value: 'medieval' },
|
||||
|
||||
|
||||
{ label: 'Misunderstanding', value: 'misunderstanding' },
|
||||
|
||||
|
||||
{ label: 'Modern', value: 'modern' },
|
||||
|
||||
|
||||
{ label: 'modern fantasy', value: 'modern-fantasy' },
|
||||
|
||||
|
||||
{ label: 'music', value: 'music' },
|
||||
|
||||
|
||||
{ label: 'Mystery', value: 'mystery' },
|
||||
|
||||
|
||||
{ label: 'Necromancy', value: 'necromancy' },
|
||||
|
||||
|
||||
{ label: 'No Romance', value: 'no-romance' },
|
||||
|
||||
|
||||
{ label: 'NTL', value: 'ntl' },
|
||||
|
||||
|
||||
{ label: 'o', value: 'o' },
|
||||
|
||||
|
||||
{ label: 'Obsession', value: 'obsession' },
|
||||
|
||||
|
||||
{ label: 'Politics', value: 'politics' },
|
||||
|
||||
|
||||
{ label: 'Possession', value: 'possession' },
|
||||
|
||||
|
||||
{ label: 'Programming', value: 'programming' },
|
||||
|
||||
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
|
||||
|
||||
{ label: 'Pure Love', value: 'pure-love' },
|
||||
|
||||
|
||||
{ label: 'Redemption', value: 'redemption' },
|
||||
|
||||
|
||||
{ label: 'Regression', value: 'regression' },
|
||||
|
||||
|
||||
{ label: 'Regret', value: 'regret' },
|
||||
|
||||
|
||||
{ label: 'Reincarnation', value: 'reincarnation' },
|
||||
|
||||
|
||||
{ label: 'Revenge', value: 'revenge' },
|
||||
|
||||
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
|
||||
|
||||
{ label: 'Romance Fanrasy', value: 'romance-fanrasy' },
|
||||
|
||||
|
||||
{ label: 'Salvation', value: 'salvation' },
|
||||
|
||||
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
|
||||
|
||||
{ label: 'Sci-fi', value: 'sci-fi' },
|
||||
|
||||
|
||||
{ label: 'Science fiction', value: 'science-fiction' },
|
||||
|
||||
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
|
||||
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
|
||||
|
||||
{ label: 'Slice of Life', value: 'slice-of-life' },
|
||||
|
||||
|
||||
{ label: 'Soft yandere', value: 'soft-yandere' },
|
||||
|
||||
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
|
||||
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
|
||||
|
||||
{ label: 'Survival', value: 'survival' },
|
||||
|
||||
|
||||
{ label: 'system', value: 'system' },
|
||||
|
||||
|
||||
{ label: 'Time limit', value: 'time-limit' },
|
||||
|
||||
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
|
||||
|
||||
{ label: 'Transmigration', value: 'transmigration' },
|
||||
|
||||
|
||||
{ label: 'TS', value: 'ts' },
|
||||
|
||||
|
||||
{ label: 'Tsundere', value: 'tsundere' },
|
||||
|
||||
|
||||
{ label: 'Unique', value: 'unique' },
|
||||
|
||||
|
||||
{ label: 'Wholesome', value: 'wholesome' },
|
||||
|
||||
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
|
||||
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
|
||||
|
||||
{ label: 'Yandere', value: 'yandere' },
|
||||
|
||||
|
||||
{ label: 'Yuri', value: 'yuri' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+157
-150
@@ -1,16 +1,16 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class freedlit implements Plugin.PluginBase {
|
||||
id = "freedlit.space";
|
||||
name = "LitSpace";
|
||||
site = "https://freedlit.space";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/freedlit/icon.png";
|
||||
id = 'freedlit.space';
|
||||
name = 'LitSpace';
|
||||
site = 'https://freedlit.space';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/freedlit/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
page: number,
|
||||
@@ -19,73 +19,75 @@ class freedlit implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/get-books/all/list/" + page + "?sort=";
|
||||
url += showLatestNovels ? "recent" : filters?.sort?.value || "popular";
|
||||
url += "&status=" + (filters?.status?.value || "all");
|
||||
url += "&access=" + (filters?.access?.value || "all");
|
||||
url += "&adult=" + (filters?.adult?.value || "hide");
|
||||
let url = this.site + '/get-books/all/list/' + page + '?sort=';
|
||||
url += showLatestNovels ? 'recent' : filters?.sort?.value || 'popular';
|
||||
url += '&status=' + (filters?.status?.value || 'all');
|
||||
url += '&access=' + (filters?.access?.value || 'all');
|
||||
url += '&adult=' + (filters?.adult?.value || 'hide');
|
||||
|
||||
if (filters?.genre?.value?.include?.length) {
|
||||
url += filters.genre.value.include
|
||||
.map((id) => "&genres_included[]=" + id)
|
||||
.join("");
|
||||
.map(id => '&genres_included[]=' + id)
|
||||
.join('');
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.exclude?.length) {
|
||||
url += filters.genre.value.exclude
|
||||
.map((id) => "&genres_excluded[]=" + id)
|
||||
.join("");
|
||||
.map(id => '&genres_excluded[]=' + id)
|
||||
.join('');
|
||||
}
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio("#bookListBlock > div > div").each((index, element) => {
|
||||
const name = loadedCheerio(element).find("div > h4 > a").text()?.trim();
|
||||
loadedCheerio('#bookListBlock > div > div').each((index, element) => {
|
||||
const name = loadedCheerio(element).find('div > h4 > a').text()?.trim();
|
||||
const cover = loadedCheerio(element)
|
||||
.find("div > a > img")
|
||||
.attr("src")
|
||||
.find('div > a > img')
|
||||
.attr('src')
|
||||
?.trim();
|
||||
const url = loadedCheerio(element)
|
||||
.find("div > h4 > a")
|
||||
.attr("href")
|
||||
.find('div > h4 > a')
|
||||
.attr('href')
|
||||
?.trim();
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".book-info h4").text(),
|
||||
cover: loadedCheerio(".book-cover > div > img").attr("src")?.trim(),
|
||||
summary: loadedCheerio("#nav-home").text()?.trim(),
|
||||
author: loadedCheerio(".book-info h5 > a").text(),
|
||||
genres: loadedCheerio(".genre-list > a")
|
||||
name: loadedCheerio('.book-info h4').text(),
|
||||
cover: loadedCheerio('.book-cover > div > img').attr('src')?.trim(),
|
||||
summary: loadedCheerio('#nav-home').text()?.trim(),
|
||||
author: loadedCheerio('.book-info h5 > a').text(),
|
||||
genres: loadedCheerio('.genre-list > a')
|
||||
.map((index, element) => loadedCheerio(element).text())
|
||||
.get()
|
||||
.join(","),
|
||||
.join(','),
|
||||
};
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("a.chapter-line").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("h6").text();
|
||||
const url = loadedCheerio(element).attr("href");
|
||||
loadedCheerio('a.chapter-line').each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find('h6').text();
|
||||
const url = loadedCheerio(element).attr('href');
|
||||
if (!name || !url) return;
|
||||
|
||||
const releaseDate = loadedCheerio(element).find('span[class="date"]').text();
|
||||
const releaseDate = loadedCheerio(element)
|
||||
.find('span[class="date"]')
|
||||
.text();
|
||||
chapters.push({
|
||||
name,
|
||||
path: url.replace(this.site, ""),
|
||||
path: url.replace(this.site, ''),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
@@ -96,35 +98,37 @@ class freedlit implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("div.mobile-block").remove();
|
||||
loadedCheerio("div.standart-block").remove();
|
||||
loadedCheerio('div.mobile-block').remove();
|
||||
loadedCheerio('div.standart-block').remove();
|
||||
|
||||
const chapterText = loadedCheerio('div[class="chapter"]').html() || "";
|
||||
const chapterText = loadedCheerio('div[class="chapter"]').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + "/search?query=" + searchTerm + "&type=all";
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const url = this.site + '/search?query=' + searchTerm + '&type=all';
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio("#bookGridBlock > div").each((index, element) => {
|
||||
const name = loadedCheerio(element).find("h4 > a").text()?.trim();
|
||||
const cover = loadedCheerio(element).find("a > img").attr("src")?.trim();
|
||||
const url = loadedCheerio(element).find("h4 > a").attr("href")?.trim();
|
||||
loadedCheerio('#bookGridBlock > div').each((index, element) => {
|
||||
const name = loadedCheerio(element).find('h4 > a').text()?.trim();
|
||||
const cover = loadedCheerio(element).find('a > img').attr('src')?.trim();
|
||||
const url = loadedCheerio(element).find('h4 > a').attr('href')?.trim();
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const months: { [key: string]: number } = {
|
||||
января: 1,
|
||||
февраля: 2,
|
||||
@@ -140,9 +144,9 @@ class freedlit implements Plugin.PluginBase {
|
||||
декабря: 12,
|
||||
};
|
||||
|
||||
const [day, month, year] = dateString.split(" ");
|
||||
const [day, month, year] = dateString.split(' ');
|
||||
if (day && months[month] && year) {
|
||||
return dayjs(year + "-" + months[month] + "-" + day).format("LL");
|
||||
return dayjs(year + '-' + months[month] + '-' + day).format('LL');
|
||||
}
|
||||
return dateString || null;
|
||||
};
|
||||
@@ -151,124 +155,127 @@ class freedlit implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка:",
|
||||
value: "popular",
|
||||
label: 'Сортировка:',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству комментариев", value: "comments" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
{ label: 'По популярности', value: 'popular' },
|
||||
{ label: 'По количеству комментариев', value: 'comments' },
|
||||
{ label: 'По количеству лайков', value: 'likes' },
|
||||
{ label: 'По новизне', value: 'recent' },
|
||||
{ label: 'По просмотрам', value: 'views' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
label: "Жанры:",
|
||||
label: 'Жанры:',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "Любой жанр", value: "all" },
|
||||
{ label: "Альтернативная история", value: "alternative-history" },
|
||||
{ label: "Антиутопия", value: "dystopia" },
|
||||
{ label: "Бизнес-литература", value: "business-literature" },
|
||||
{ label: "Боевая фантастика", value: "combat-fiction" },
|
||||
{ label: "Боевик", value: "action" },
|
||||
{ label: "Боевое фэнтези", value: "combat-fantasy" },
|
||||
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
||||
{ label: "Героическая фантастика", value: "heroic-fiction" },
|
||||
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
||||
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
||||
{ label: "Гримдарк", value: "grimdark" },
|
||||
{ label: "Детектив", value: "mystery" },
|
||||
{ label: "Детская литература", value: "kids-literature" },
|
||||
{ label: "Документальная проза", value: "biography" },
|
||||
{ label: "Историческая проза", value: "historical-fiction" },
|
||||
{ label: "Исторический детектив", value: "historical-mystery" },
|
||||
{ label: "Исторический любовный роман", value: "historical-romantic-novel" },
|
||||
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
||||
{ label: "Киберпанк", value: "cyberpunk" },
|
||||
{ label: "Космическая фантастика", value: "cosmic-fiction" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Лоу / Низкое фэнтези", value: "low-fantasy" },
|
||||
{ label: "Любовное фэнтези", value: "romantic-fantasy" },
|
||||
{ label: "Любовный роман", value: "romantic-novel" },
|
||||
{ label: "Мистика", value: "mystic" },
|
||||
{ label: "Мистический детектив", value: "mystic-detective" },
|
||||
{ label: "Научная фантастика", value: "science-fiction" },
|
||||
{ label: "Подростковая проза", value: "young-adult" },
|
||||
{ label: "Политический роман", value: "political-romance" },
|
||||
{ label: "Попаданцы", value: "accidental-travel" },
|
||||
{ label: "Попаданцы в магические миры", value: "magic-worlds-travel" },
|
||||
{ label: "Попаданцы во времени", value: "time-travel" },
|
||||
{ label: "Порнотика", value: "pornotica" },
|
||||
{ label: "Постапокалипсис", value: "post-apocalypse" },
|
||||
{ label: "Поэзия", value: "poetry" },
|
||||
{ label: "Приключения", value: "adventure" },
|
||||
{ label: "Публицистика", value: "journalism" },
|
||||
{ label: "Пьеса", value: "play" },
|
||||
{ label: "Развитие личности", value: "how-to-book" },
|
||||
{ label: "Разное", value: "other" },
|
||||
{ label: "Реализм", value: "Realism" },
|
||||
{ label: "РеалРПГ", value: "realrpg" },
|
||||
{ label: "Репликация", value: "replication" },
|
||||
{ label: "Романтическая эротика", value: "romantic-erotic-fiction" },
|
||||
{ label: "Сказка", value: "fairy-tale" },
|
||||
{ label: "Слэш", value: "slash" },
|
||||
{ label: "Современная проза", value: "modern-prose" },
|
||||
{ label: "Современный любовный роман", value: "modern-romantic-novel" },
|
||||
{ label: "Социальная фантастика", value: "social-fiction" },
|
||||
{ label: "Стимпанк", value: "steampunk" },
|
||||
{ label: "Сценарий", value: "scenario" },
|
||||
{ label: "Сюаньхуань", value: "xuanhuan" },
|
||||
{ label: "Сянься", value: "xianxia" },
|
||||
{ label: "Тёмное фэнтези", value: "dark-fantasy" },
|
||||
{ label: "Триллер", value: "thriller" },
|
||||
{ label: "Уся", value: "wuxia" },
|
||||
{ label: "Фантастика", value: "fiction" },
|
||||
{ label: "Фантастический детектив", value: "mystery-fiction" },
|
||||
{ label: "Фанфик", value: "fan-fiction" },
|
||||
{ label: "Фемслэш", value: "femslash" },
|
||||
{ label: "Фэнтези", value: "fantasy" },
|
||||
{ label: "Хоррор", value: "horror" },
|
||||
{ label: "Шпионский детектив", value: "spy-crime" },
|
||||
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
||||
{ label: "Эротика", value: "erotic-fiction" },
|
||||
{ label: "Эротическая фантастика", value: "erotic-fiction" },
|
||||
{ label: "Эротический фанфик", value: "erotic-fan-fiction" },
|
||||
{ label: "Эротическое фэнтези", value: "erotic-fantasy" },
|
||||
{ label: "Этническое фэнтези", value: "ethnic-fantasy" },
|
||||
{ label: "Юмор", value: "humor" },
|
||||
{ label: "Юмористическая фантастика", value: "humor-fiction" },
|
||||
{ label: "Юмористическое фэнтези", value: "humor-fantasy" },
|
||||
{ label: "RPS", value: "rps" },
|
||||
{ label: 'Любой жанр', value: 'all' },
|
||||
{ label: 'Альтернативная история', value: 'alternative-history' },
|
||||
{ label: 'Антиутопия', value: 'dystopia' },
|
||||
{ label: 'Бизнес-литература', value: 'business-literature' },
|
||||
{ label: 'Боевая фантастика', value: 'combat-fiction' },
|
||||
{ label: 'Боевик', value: 'action' },
|
||||
{ label: 'Боевое фэнтези', value: 'combat-fantasy' },
|
||||
{ label: 'Бояръ-Аниме', value: 'boyar-anime' },
|
||||
{ label: 'Героическая фантастика', value: 'heroic-fiction' },
|
||||
{ label: 'Героическое фэнтези', value: 'heroic-fantasy' },
|
||||
{ label: 'Городское фэнтези', value: 'urban-fantasy' },
|
||||
{ label: 'Гримдарк', value: 'grimdark' },
|
||||
{ label: 'Детектив', value: 'mystery' },
|
||||
{ label: 'Детская литература', value: 'kids-literature' },
|
||||
{ label: 'Документальная проза', value: 'biography' },
|
||||
{ label: 'Историческая проза', value: 'historical-fiction' },
|
||||
{ label: 'Исторический детектив', value: 'historical-mystery' },
|
||||
{
|
||||
label: 'Исторический любовный роман',
|
||||
value: 'historical-romantic-novel',
|
||||
},
|
||||
{ label: 'Историческое фэнтези', value: 'historical-fantasy' },
|
||||
{ label: 'Киберпанк', value: 'cyberpunk' },
|
||||
{ label: 'Космическая фантастика', value: 'cosmic-fiction' },
|
||||
{ label: 'ЛитРПГ', value: 'litrpg' },
|
||||
{ label: 'Лоу / Низкое фэнтези', value: 'low-fantasy' },
|
||||
{ label: 'Любовное фэнтези', value: 'romantic-fantasy' },
|
||||
{ label: 'Любовный роман', value: 'romantic-novel' },
|
||||
{ label: 'Мистика', value: 'mystic' },
|
||||
{ label: 'Мистический детектив', value: 'mystic-detective' },
|
||||
{ label: 'Научная фантастика', value: 'science-fiction' },
|
||||
{ label: 'Подростковая проза', value: 'young-adult' },
|
||||
{ label: 'Политический роман', value: 'political-romance' },
|
||||
{ label: 'Попаданцы', value: 'accidental-travel' },
|
||||
{ label: 'Попаданцы в магические миры', value: 'magic-worlds-travel' },
|
||||
{ label: 'Попаданцы во времени', value: 'time-travel' },
|
||||
{ label: 'Порнотика', value: 'pornotica' },
|
||||
{ label: 'Постапокалипсис', value: 'post-apocalypse' },
|
||||
{ label: 'Поэзия', value: 'poetry' },
|
||||
{ label: 'Приключения', value: 'adventure' },
|
||||
{ label: 'Публицистика', value: 'journalism' },
|
||||
{ label: 'Пьеса', value: 'play' },
|
||||
{ label: 'Развитие личности', value: 'how-to-book' },
|
||||
{ label: 'Разное', value: 'other' },
|
||||
{ label: 'Реализм', value: 'Realism' },
|
||||
{ label: 'РеалРПГ', value: 'realrpg' },
|
||||
{ label: 'Репликация', value: 'replication' },
|
||||
{ label: 'Романтическая эротика', value: 'romantic-erotic-fiction' },
|
||||
{ label: 'Сказка', value: 'fairy-tale' },
|
||||
{ label: 'Слэш', value: 'slash' },
|
||||
{ label: 'Современная проза', value: 'modern-prose' },
|
||||
{ label: 'Современный любовный роман', value: 'modern-romantic-novel' },
|
||||
{ label: 'Социальная фантастика', value: 'social-fiction' },
|
||||
{ label: 'Стимпанк', value: 'steampunk' },
|
||||
{ label: 'Сценарий', value: 'scenario' },
|
||||
{ label: 'Сюаньхуань', value: 'xuanhuan' },
|
||||
{ label: 'Сянься', value: 'xianxia' },
|
||||
{ label: 'Тёмное фэнтези', value: 'dark-fantasy' },
|
||||
{ label: 'Триллер', value: 'thriller' },
|
||||
{ label: 'Уся', value: 'wuxia' },
|
||||
{ label: 'Фантастика', value: 'fiction' },
|
||||
{ label: 'Фантастический детектив', value: 'mystery-fiction' },
|
||||
{ label: 'Фанфик', value: 'fan-fiction' },
|
||||
{ label: 'Фемслэш', value: 'femslash' },
|
||||
{ label: 'Фэнтези', value: 'fantasy' },
|
||||
{ label: 'Хоррор', value: 'horror' },
|
||||
{ label: 'Шпионский детектив', value: 'spy-crime' },
|
||||
{ label: 'Эпическое фэнтези', value: 'epic-fantasy' },
|
||||
{ label: 'Эротика', value: 'erotic-fiction' },
|
||||
{ label: 'Эротическая фантастика', value: 'erotic-fiction' },
|
||||
{ label: 'Эротический фанфик', value: 'erotic-fan-fiction' },
|
||||
{ label: 'Эротическое фэнтези', value: 'erotic-fantasy' },
|
||||
{ label: 'Этническое фэнтези', value: 'ethnic-fantasy' },
|
||||
{ label: 'Юмор', value: 'humor' },
|
||||
{ label: 'Юмористическая фантастика', value: 'humor-fiction' },
|
||||
{ label: 'Юмористическое фэнтези', value: 'humor-fantasy' },
|
||||
{ label: 'RPS', value: 'rps' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
status: {
|
||||
label: "Статус:",
|
||||
value: "all",
|
||||
label: 'Статус:',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "Любой статус", value: "all" },
|
||||
{ label: "В процессе", value: "in-process" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
{ label: 'Любой статус', value: 'all' },
|
||||
{ label: 'В процессе', value: 'in-process' },
|
||||
{ label: 'Завершено', value: 'finished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
access: {
|
||||
label: "Доступ:",
|
||||
value: "all",
|
||||
label: 'Доступ:',
|
||||
value: 'all',
|
||||
options: [
|
||||
{ label: "Любой доступ", value: "all" },
|
||||
{ label: "Бесплатные", value: "free" },
|
||||
{ label: "Платные", value: "paid" },
|
||||
{ label: 'Любой доступ', value: 'all' },
|
||||
{ label: 'Бесплатные', value: 'free' },
|
||||
{ label: 'Платные', value: 'paid' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
adult: {
|
||||
label: "Возрастные ограничения:",
|
||||
value: "hide",
|
||||
label: 'Возрастные ограничения:',
|
||||
value: 'hide',
|
||||
options: [
|
||||
{ label: "Скрыть 18+", value: "hide" },
|
||||
{ label: "Показать +18", value: "show" },
|
||||
{ label: 'Скрыть 18+', value: 'hide' },
|
||||
{ label: 'Показать +18', value: 'show' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+159
-153
@@ -1,39 +1,39 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const apiUrl = "https://api.author.today/";
|
||||
const token = "Bearer guest";
|
||||
const apiUrl = 'https://api.author.today/';
|
||||
const token = 'Bearer guest';
|
||||
|
||||
class AuthorToday implements Plugin.PluginBase {
|
||||
id = "AT";
|
||||
name = "Автор Тудей";
|
||||
icon = "src/ru/authortoday/icon.png";
|
||||
site = "https://author.today";
|
||||
version = "1.0.0";
|
||||
id = 'AT';
|
||||
name = 'Автор Тудей';
|
||||
icon = 'src/ru/authortoday/icon.png';
|
||||
site = 'https://author.today';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = apiUrl + "v1/catalog/search?page=" + pageNo;
|
||||
let url = apiUrl + 'v1/catalog/search?page=' + pageNo;
|
||||
if (filters?.genre?.value) {
|
||||
url += "&genre=" + filters.genre.value;
|
||||
url += '&genre=' + filters.genre.value;
|
||||
}
|
||||
|
||||
url +=
|
||||
"&sorting=" +
|
||||
(showLatestNovels ? "recent" : filters?.sort?.value || "popular");
|
||||
'&sorting=' +
|
||||
(showLatestNovels ? 'recent' : filters?.sort?.value || 'popular');
|
||||
|
||||
url += "&form=" + (filters?.form?.value || "any");
|
||||
url += "&state=" + (filters?.state?.value || "any");
|
||||
url += "&series=" + (filters?.series?.value || "any");
|
||||
url += "&access=" + (filters?.access?.value || "any");
|
||||
url += "&promo=" + (filters?.promo?.value || "hide");
|
||||
url += '&form=' + (filters?.form?.value || 'any');
|
||||
url += '&state=' + (filters?.state?.value || 'any');
|
||||
url += '&series=' + (filters?.series?.value || 'any');
|
||||
url += '&access=' + (filters?.access?.value || 'any');
|
||||
url += '&promo=' + (filters?.promo?.value || 'hide');
|
||||
|
||||
const result = await fetchApi(url, {
|
||||
headers: {
|
||||
@@ -43,15 +43,15 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const json = (await result.json()) as response;
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (json.code === "NotFound") {
|
||||
if (json.code === 'NotFound') {
|
||||
return novels;
|
||||
}
|
||||
|
||||
json?.searchResults?.forEach((novel) =>
|
||||
json?.searchResults?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel.coverUrl
|
||||
? "https://cm.author.today/content/" + novel.coverUrl
|
||||
? 'https://cm.author.today/content/' + novel.coverUrl
|
||||
: defaultCover,
|
||||
path: novel.id.toString(),
|
||||
}),
|
||||
@@ -71,24 +71,24 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: workID,
|
||||
name: book.title,
|
||||
cover: book.coverUrl ? book.coverUrl.split("?")[0] : defaultCover,
|
||||
genres: book.tags?.join(", "),
|
||||
summary: "",
|
||||
cover: book.coverUrl ? book.coverUrl.split('?')[0] : defaultCover,
|
||||
genres: book.tags?.join(', '),
|
||||
summary: '',
|
||||
author:
|
||||
book.originalAuthor ||
|
||||
book.authorFIO ||
|
||||
book.coAuthorFIO ||
|
||||
book.secondCoAuthorFIO ||
|
||||
book.translator ||
|
||||
"",
|
||||
'',
|
||||
status: book.isFinished ? NovelStatus.Completed : NovelStatus.Ongoing,
|
||||
};
|
||||
|
||||
if (book.annotation) {
|
||||
novel.summary += book.annotation + "\n";
|
||||
novel.summary += book.annotation + '\n';
|
||||
}
|
||||
if (book.authorNotes) {
|
||||
novel.summary += "Примечания автора:\n" + book.authorNotes;
|
||||
novel.summary += 'Примечания автора:\n' + book.authorNotes;
|
||||
}
|
||||
|
||||
const chaptersRaw = await fetchApi(`${apiUrl}v1/work/${workID}/content`, {
|
||||
@@ -103,11 +103,11 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
chaptersJSON.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.isAvailable && !chapter.isDraft) {
|
||||
chapters.push({
|
||||
name: chapter.title || "Глава " + (chapterIndex + 1),
|
||||
path: workID + "/" + chapter.id,
|
||||
name: chapter.title || 'Глава ' + (chapterIndex + 1),
|
||||
path: workID + '/' + chapter.id,
|
||||
releaseTime: dayjs(
|
||||
chapter.publishTime || chapter.lastModificationTime,
|
||||
).format("LLL"),
|
||||
).format('LLL'),
|
||||
chapterNumber: (chapter.sortOrder || chapterIndex) + 1,
|
||||
});
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const [workID, chapterID] = chapterPath.split("/");
|
||||
const [workID, chapterID] = chapterPath.split('/');
|
||||
const result = await fetchApi(
|
||||
apiUrl + `v1/work/${workID}/chapter/${chapterID}/text`,
|
||||
{
|
||||
@@ -130,11 +130,11 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
const json = (await result.json()) as encryptedСhapter;
|
||||
|
||||
if (json.code) {
|
||||
return json.code + "\n" + json?.message;
|
||||
return json.code + '\n' + json?.message;
|
||||
}
|
||||
|
||||
const key = json.key.split("").reverse().join("") + "@_@";
|
||||
let text = "";
|
||||
const key = json.key.split('').reverse().join('') + '@_@';
|
||||
let text = '';
|
||||
|
||||
for (let i = 0; i < json.text.length; i++) {
|
||||
text += String.fromCharCode(
|
||||
@@ -143,10 +143,10 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const loadedCheerio = parseHTML(text);
|
||||
loadedCheerio("img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio('img').each((index, element) => {
|
||||
if (!loadedCheerio(element).attr('src')?.startsWith('http')) {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
}
|
||||
});
|
||||
const chapterText = loadedCheerio.html();
|
||||
@@ -158,23 +158,23 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site + "/search?category=works&q=" + searchTerm + "&page=" + pageNo;
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
this.site + '/search?category=works&q=' + searchTerm + '&page=' + pageNo;
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("a.work-row").each((index, element) => {
|
||||
loadedCheerio('a.work-row').each((index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('h4[class="work-title"]')
|
||||
.text()
|
||||
.trim();
|
||||
let cover = loadedCheerio(element).find("img").attr("data-src");
|
||||
const path = loadedCheerio(element).attr("href");
|
||||
let cover = loadedCheerio(element).find('img').attr('data-src');
|
||||
const path = loadedCheerio(element).attr('href');
|
||||
|
||||
cover = cover ? cover.split("?")[0] : defaultCover;
|
||||
cover = cover ? cover.split('?')[0] : defaultCover;
|
||||
|
||||
if (!path) return;
|
||||
novels.push({ name, cover, path: path.replace("/work/", "") });
|
||||
novels.push({ name, cover, path: path.replace('/work/', '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
@@ -182,143 +182,149 @@ class AuthorToday implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
isNovel ? this.site + "/work/" + path : this.site + "/reader/" + path;
|
||||
isNovel ? this.site + '/work/' + path : this.site + '/reader/' + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "popular",
|
||||
label: 'Сортировка',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "По популярности", value: "popular" },
|
||||
{ label: "По количеству лайков", value: "likes" },
|
||||
{ label: "По комментариям", value: "comments" },
|
||||
{ label: "По новизне", value: "recent" },
|
||||
{ label: "По просмотрам", value: "views" },
|
||||
{ label: "Набирающие популярность", value: "trending" },
|
||||
{ label: 'По популярности', value: 'popular' },
|
||||
{ label: 'По количеству лайков', value: 'likes' },
|
||||
{ label: 'По комментариям', value: 'comments' },
|
||||
{ label: 'По новизне', value: 'recent' },
|
||||
{ label: 'По просмотрам', value: 'views' },
|
||||
{ label: 'Набирающие популярность', value: 'trending' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
label: "Жанры",
|
||||
value: "",
|
||||
label: 'Жанры',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Все", value: "" },
|
||||
{ label: "Альтернативная история", value: "sf-history" },
|
||||
{ label: "Антиутопия", value: "dystopia" },
|
||||
{ label: "Бизнес-литература", value: "biznes-literatura" },
|
||||
{ label: "Боевая фантастика", value: "sf-action" },
|
||||
{ label: "Боевик", value: "action" },
|
||||
{ label: "Боевое фэнтези", value: "fantasy-action" },
|
||||
{ label: "Бояръ-Аниме", value: "boyar-anime" },
|
||||
{ label: "Героическая фантастика", value: "sf-heroic" },
|
||||
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
|
||||
{ label: "Городское фэнтези", value: "urban-fantasy" },
|
||||
{ label: "Детектив", value: "detective" },
|
||||
{ label: "Детская литература", value: "detskaya-literatura" },
|
||||
{ label: "Документальная проза", value: "non-fiction" },
|
||||
{ label: "Историческая проза", value: "historical-fiction" },
|
||||
{ label: "Исторические приключения", value: "historical-adventure" },
|
||||
{ label: "Исторический детектив", value: "historical-mystery" },
|
||||
{ label: "Исторический любовный роман", value: "historical-romance" },
|
||||
{ label: "Историческое фэнтези", value: "historical-fantasy" },
|
||||
{ label: "Киберпанк", value: "cyberpunk" },
|
||||
{ label: "Короткий любовный роман", value: "short-romance" },
|
||||
{ label: "Космическая фантастика", value: "sf-space" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Любовное фэнтези", value: "love-fantasy" },
|
||||
{ label: "Любовные романы", value: "romance" },
|
||||
{ label: "Мистика", value: "paranormal" },
|
||||
{ label: "Назад в СССР", value: "back-to-ussr" },
|
||||
{ label: "Научная фантастика", value: "science-fiction" },
|
||||
{ label: "Подростковая проза", value: "teen-prose" },
|
||||
{ label: "Политический роман", value: "political-fiction" },
|
||||
{ label: "Попаданцы", value: "popadantsy" },
|
||||
{ label: "Попаданцы в космос", value: "popadantsy-v-kosmos" },
|
||||
{ label: "Попаданцы в магические миры", value: "popadantsy-v-magicheskie-miry" },
|
||||
{ label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" },
|
||||
{ label: "Постапокалипсис", value: "postapocalyptic" },
|
||||
{ label: "Поэзия", value: "poetry" },
|
||||
{ label: "Приключения", value: "adventure" },
|
||||
{ label: "Публицистика", value: "publicism" },
|
||||
{ label: "Развитие личности", value: "razvitie-lichnosti" },
|
||||
{ label: "Разное", value: "other" },
|
||||
{ label: "РеалРПГ", value: "realrpg" },
|
||||
{ label: "Романтическая эротика", value: "romantic-erotika" },
|
||||
{ label: "Сказка", value: "fairy-tale" },
|
||||
{ label: "Современная проза", value: "modern-prose" },
|
||||
{ label: "Современный любовный роман", value: "contemporary-romance" },
|
||||
{ label: "Социальная фантастика", value: "sf-social" },
|
||||
{ label: "Стимпанк", value: "steampunk" },
|
||||
{ label: "Темное фэнтези", value: "dark-fantasy" },
|
||||
{ label: "Триллер", value: "thriller" },
|
||||
{ label: "Ужасы", value: "horror" },
|
||||
{ label: "Фантастика", value: "sci-fi" },
|
||||
{ label: "Фантастический детектив", value: "detective-science-fiction" },
|
||||
{ label: "Фанфик", value: "fanfiction" },
|
||||
{ label: "Фэнтези", value: "fantasy" },
|
||||
{ label: "Шпионский детектив", value: "spy-mystery" },
|
||||
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
|
||||
{ label: "Эротика", value: "erotica" },
|
||||
{ label: "Эротическая фантастика", value: "sf-erotika" },
|
||||
{ label: "Эротический фанфик", value: "fanfiction-erotika" },
|
||||
{ label: "Эротическое фэнтези", value: "fantasy-erotika" },
|
||||
{ label: "Юмор", value: "humor" },
|
||||
{ label: "Юмористическая фантастика", value: "sf-humor" },
|
||||
{ label: "Юмористическое фэнтези", value: "ironical-fantasy" },
|
||||
{ label: 'Все', value: '' },
|
||||
{ label: 'Альтернативная история', value: 'sf-history' },
|
||||
{ label: 'Антиутопия', value: 'dystopia' },
|
||||
{ label: 'Бизнес-литература', value: 'biznes-literatura' },
|
||||
{ label: 'Боевая фантастика', value: 'sf-action' },
|
||||
{ label: 'Боевик', value: 'action' },
|
||||
{ label: 'Боевое фэнтези', value: 'fantasy-action' },
|
||||
{ label: 'Бояръ-Аниме', value: 'boyar-anime' },
|
||||
{ label: 'Героическая фантастика', value: 'sf-heroic' },
|
||||
{ label: 'Героическое фэнтези', value: 'heroic-fantasy' },
|
||||
{ label: 'Городское фэнтези', value: 'urban-fantasy' },
|
||||
{ label: 'Детектив', value: 'detective' },
|
||||
{ label: 'Детская литература', value: 'detskaya-literatura' },
|
||||
{ label: 'Документальная проза', value: 'non-fiction' },
|
||||
{ label: 'Историческая проза', value: 'historical-fiction' },
|
||||
{ label: 'Исторические приключения', value: 'historical-adventure' },
|
||||
{ label: 'Исторический детектив', value: 'historical-mystery' },
|
||||
{ label: 'Исторический любовный роман', value: 'historical-romance' },
|
||||
{ label: 'Историческое фэнтези', value: 'historical-fantasy' },
|
||||
{ label: 'Киберпанк', value: 'cyberpunk' },
|
||||
{ label: 'Короткий любовный роман', value: 'short-romance' },
|
||||
{ label: 'Космическая фантастика', value: 'sf-space' },
|
||||
{ label: 'ЛитРПГ', value: 'litrpg' },
|
||||
{ label: 'Любовное фэнтези', value: 'love-fantasy' },
|
||||
{ label: 'Любовные романы', value: 'romance' },
|
||||
{ label: 'Мистика', value: 'paranormal' },
|
||||
{ label: 'Назад в СССР', value: 'back-to-ussr' },
|
||||
{ label: 'Научная фантастика', value: 'science-fiction' },
|
||||
{ label: 'Подростковая проза', value: 'teen-prose' },
|
||||
{ label: 'Политический роман', value: 'political-fiction' },
|
||||
{ label: 'Попаданцы', value: 'popadantsy' },
|
||||
{ label: 'Попаданцы в космос', value: 'popadantsy-v-kosmos' },
|
||||
{
|
||||
label: 'Попаданцы в магические миры',
|
||||
value: 'popadantsy-v-magicheskie-miry',
|
||||
},
|
||||
{ label: 'Попаданцы во времени', value: 'popadantsy-vo-vremeni' },
|
||||
{ label: 'Постапокалипсис', value: 'postapocalyptic' },
|
||||
{ label: 'Поэзия', value: 'poetry' },
|
||||
{ label: 'Приключения', value: 'adventure' },
|
||||
{ label: 'Публицистика', value: 'publicism' },
|
||||
{ label: 'Развитие личности', value: 'razvitie-lichnosti' },
|
||||
{ label: 'Разное', value: 'other' },
|
||||
{ label: 'РеалРПГ', value: 'realrpg' },
|
||||
{ label: 'Романтическая эротика', value: 'romantic-erotika' },
|
||||
{ label: 'Сказка', value: 'fairy-tale' },
|
||||
{ label: 'Современная проза', value: 'modern-prose' },
|
||||
{ label: 'Современный любовный роман', value: 'contemporary-romance' },
|
||||
{ label: 'Социальная фантастика', value: 'sf-social' },
|
||||
{ label: 'Стимпанк', value: 'steampunk' },
|
||||
{ label: 'Темное фэнтези', value: 'dark-fantasy' },
|
||||
{ label: 'Триллер', value: 'thriller' },
|
||||
{ label: 'Ужасы', value: 'horror' },
|
||||
{ label: 'Фантастика', value: 'sci-fi' },
|
||||
{
|
||||
label: 'Фантастический детектив',
|
||||
value: 'detective-science-fiction',
|
||||
},
|
||||
{ label: 'Фанфик', value: 'fanfiction' },
|
||||
{ label: 'Фэнтези', value: 'fantasy' },
|
||||
{ label: 'Шпионский детектив', value: 'spy-mystery' },
|
||||
{ label: 'Эпическое фэнтези', value: 'epic-fantasy' },
|
||||
{ label: 'Эротика', value: 'erotica' },
|
||||
{ label: 'Эротическая фантастика', value: 'sf-erotika' },
|
||||
{ label: 'Эротический фанфик', value: 'fanfiction-erotika' },
|
||||
{ label: 'Эротическое фэнтези', value: 'fantasy-erotika' },
|
||||
{ label: 'Юмор', value: 'humor' },
|
||||
{ label: 'Юмористическая фантастика', value: 'sf-humor' },
|
||||
{ label: 'Юмористическое фэнтези', value: 'ironical-fantasy' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
form: {
|
||||
label: "Форма произведения",
|
||||
value: "any",
|
||||
label: 'Форма произведения',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Перевод", value: "translation" },
|
||||
{ label: "Повесть", value: "tale" },
|
||||
{ label: "Рассказ", value: "story" },
|
||||
{ label: "Роман", value: "novel" },
|
||||
{ label: "Сборник поэзии", value: "poetry" },
|
||||
{ label: "Сборник рассказов", value: "story-book" },
|
||||
{ label: 'Любой', value: 'any' },
|
||||
{ label: 'Перевод', value: 'translation' },
|
||||
{ label: 'Повесть', value: 'tale' },
|
||||
{ label: 'Рассказ', value: 'story' },
|
||||
{ label: 'Роман', value: 'novel' },
|
||||
{ label: 'Сборник поэзии', value: 'poetry' },
|
||||
{ label: 'Сборник рассказов', value: 'story-book' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
state: {
|
||||
label: "Статус произведения",
|
||||
value: "any",
|
||||
label: 'Статус произведения',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Любой статус", value: "any" },
|
||||
{ label: "В процессе", value: "in-progress" },
|
||||
{ label: "Завершено", value: "finished" },
|
||||
{ label: 'Любой статус', value: 'any' },
|
||||
{ label: 'В процессе', value: 'in-progress' },
|
||||
{ label: 'Завершено', value: 'finished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
series: {
|
||||
label: "Статус цикла",
|
||||
value: "any",
|
||||
label: 'Статус цикла',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Не важно", value: "any" },
|
||||
{ label: "Вне цикла", value: "out" },
|
||||
{ label: "Цикл завершен", value: "finished" },
|
||||
{ label: "Цикл не завершен", value: "unfinished" },
|
||||
{ label: 'Не важно', value: 'any' },
|
||||
{ label: 'Вне цикла', value: 'out' },
|
||||
{ label: 'Цикл завершен', value: 'finished' },
|
||||
{ label: 'Цикл не завершен', value: 'unfinished' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
access: {
|
||||
label: "Тип доступа",
|
||||
value: "any",
|
||||
label: 'Тип доступа',
|
||||
value: 'any',
|
||||
options: [
|
||||
{ label: "Любой", value: "any" },
|
||||
{ label: "Платный", value: "paid" },
|
||||
{ label: "Бесплатный", value: "free" },
|
||||
{ label: 'Любой', value: 'any' },
|
||||
{ label: 'Платный', value: 'paid' },
|
||||
{ label: 'Бесплатный', value: 'free' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
promo: {
|
||||
label: "Промо-фрагмент",
|
||||
value: "hide",
|
||||
label: 'Промо-фрагмент',
|
||||
value: 'hide',
|
||||
options: [
|
||||
{ label: "Скрывать", value: "hide" },
|
||||
{ label: "Показывать", value: "show" },
|
||||
{ label: 'Скрывать', value: 'hide' },
|
||||
{ label: 'Показывать', value: 'show' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+106
-87
@@ -1,16 +1,16 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class Bookriver implements Plugin.PluginBase {
|
||||
id = "bookriver";
|
||||
name = "Bookriver";
|
||||
site = "https://bookriver.ru";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/bookriver/icon.png";
|
||||
id = 'bookriver';
|
||||
name = 'Bookriver';
|
||||
site = 'https://bookriver.ru';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/bookriver/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -19,23 +19,23 @@ class Bookriver implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/genre?page=" + pageNo + "&perPage=24&sortingType=";
|
||||
let url = this.site + '/genre?page=' + pageNo + '&perPage=24&sortingType=';
|
||||
url += showLatestNovels
|
||||
? "last-update"
|
||||
: filters?.sort?.value || "bestseller";
|
||||
? 'last-update'
|
||||
: filters?.sort?.value || 'bestseller';
|
||||
|
||||
if (filters?.genres?.value?.length) {
|
||||
url += "&g=" + filters.genres.value.join(",");
|
||||
url += '&g=' + filters.genres.value.join(',');
|
||||
}
|
||||
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
if (jsonRaw) {
|
||||
const json: response = JSON.parse(jsonRaw);
|
||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach((novel) =>
|
||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
@@ -47,23 +47,24 @@ class Bookriver implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const result = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
const book = json.props.pageProps.state.book?.bookPage;
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: book?.name || "",
|
||||
name: book?.name || '',
|
||||
cover: book?.coverImages[0].url,
|
||||
summary: book?.annotation,
|
||||
author: book?.author?.name,
|
||||
genres: book?.tags?.map((tag) => tag.name).join(", "),
|
||||
genres: book?.tags?.map(tag => tag.name).join(', '),
|
||||
status:
|
||||
book?.statusComplete === "writing"
|
||||
book?.statusComplete === 'writing'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
@@ -73,10 +74,10 @@ class Bookriver implements Plugin.PluginBase {
|
||||
if (chapter.available) {
|
||||
chapters.push({
|
||||
name: chapter.name,
|
||||
path: book?.slug + "/" + chapter.chapterId,
|
||||
path: book?.slug + '/' + chapter.chapterId,
|
||||
releaseTime: dayjs(
|
||||
chapter.firstPublishedAt || chapter.createdAt || undefined,
|
||||
).format("LLL"),
|
||||
).format('LLL'),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
}
|
||||
@@ -86,13 +87,13 @@ class Bookriver implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const url = "https://api.bookriver.ru/api/v1/books/chapter/text/";
|
||||
const result = await fetchApi(url + chapterPath.split("/").pop());
|
||||
const url = 'https://api.bookriver.ru/api/v1/books/chapter/text/';
|
||||
const result = await fetchApi(url + chapterPath.split('/').pop());
|
||||
const json = (await result.json()) as responseChapter;
|
||||
|
||||
let chapterText = json.data.content || "Конец произведения";
|
||||
let chapterText = json.data.content || 'Конец произведения';
|
||||
if (json.data?.audio?.available) {
|
||||
chapterText += "\n" + json.data.audio.url;
|
||||
chapterText += '\n' + json.data.audio.url;
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
@@ -103,13 +104,16 @@ class Bookriver implements Plugin.PluginBase {
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
"https://api.bookriver.ru/api/v1/search/autocomplete?keyword=" + searchTerm +
|
||||
"&page=" + pageNo + "&perPage=10";
|
||||
'https://api.bookriver.ru/api/v1/search/autocomplete?keyword=' +
|
||||
searchTerm +
|
||||
'&page=' +
|
||||
pageNo +
|
||||
'&perPage=10';
|
||||
const result = await fetchApi(url);
|
||||
const json = (await result.json()) as responseSearch;
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
json.data?.books?.forEach((novel) =>
|
||||
json.data?.books?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: novel.coverImages[0].url,
|
||||
@@ -122,69 +126,84 @@ class Bookriver implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + (isNovel ? "/book/" : "/reader/") + path;
|
||||
this.site + (isNovel ? '/book/' : '/reader/') + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "bestseller",
|
||||
label: 'Сортировка',
|
||||
value: 'bestseller',
|
||||
options: [
|
||||
{ label: "Бестселлеры", value: "bestseller" },
|
||||
{ label: "Дате добавления", value: "newest" },
|
||||
{ label: "Дате обновления", value: "last-update" },
|
||||
{ label: 'Бестселлеры', value: 'bestseller' },
|
||||
{ label: 'Дате добавления', value: 'newest' },
|
||||
{ label: 'Дате обновления', value: 'last-update' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
label: "жанры",
|
||||
label: 'жанры',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Альтернативная история", value: "alternativnaya-istoriya" },
|
||||
{ label: "Боевая фантастика", value: "boevaya-fantastika" },
|
||||
{ label: "Боевое фэнтези", value: "boevoe-fentezi" },
|
||||
{ label: "Бытовое фэнтези", value: "bytovoe-fentezi" },
|
||||
{ label: "Героическая фантастика", value: "geroicheskaya-fantastika" },
|
||||
{ label: "Героическое фэнтези", value: "geroicheskoe-fentezi" },
|
||||
{ label: "Городское фэнтези", value: "gorodskoe-fentezi" },
|
||||
{ label: "Детектив", value: "detektiv" },
|
||||
{ label: "Детективная фантастика", value: "detektivnaya-fantastika" },
|
||||
{ label: "Жёсткая эротика", value: "zhyostkaya-erotika" },
|
||||
{ label: "Исторический детектив", value: "istoricheskii-detektiv" },
|
||||
{ label: "Исторический любовный роман", value: "istoricheskii-lyubovnyi-roman" },
|
||||
{ label: "Историческое фэнтези", value: "istoricheskoe-fentezi" },
|
||||
{ label: "Киберпанк", value: "kiberpank" },
|
||||
{ label: "Классический детектив", value: "klassicheskii-detektiv" },
|
||||
{ label: "Короткий любовный роман", value: "korotkii-lyubovnyi-roman" },
|
||||
{ label: "Космическая фантастика", value: "kosmicheskaya-fantastika" },
|
||||
{ label: "Криминальный детектив", value: "kriminalnyi-detektiv" },
|
||||
{ label: "ЛитРПГ", value: "litrpg" },
|
||||
{ label: "Любовная фантастика", value: "lyubovnaya-fantastika" },
|
||||
{ label: "Любовное фэнтези", value: "lyubovnoe-fentezi" },
|
||||
{ label: "Любовный роман", value: "lyubovnyi-roman" },
|
||||
{ label: "Мистика", value: "mistika" },
|
||||
{ label: "Молодежная проза", value: "molodezhnaya-proza" },
|
||||
{ label: "Научная фантастика", value: "nauchnaya-fantastika" },
|
||||
{ label: "Остросюжетный любовный роман", value: "ostrosyuzhetnyi-lyubovnyi-roman" },
|
||||
{ label: "Политический детектив", value: "politicheskii-detektiv" },
|
||||
{ label: "Попаданцы", value: "popadantsy" },
|
||||
{ label: "Постапокалипсис", value: "postapokalipsis" },
|
||||
{ label: "Приключенческое фэнтези", value: "priklyuchencheskoe-fentezi" },
|
||||
{ label: "Романтическая эротика", value: "romanticheskaya-erotika" },
|
||||
{ label: "С элементами эротики", value: "s-elementami-erotiki" },
|
||||
{ label: "Славянское фэнтези", value: "slavyanskoe-fentezi" },
|
||||
{ label: "Современный любовный роман", value: "sovremennyi-lyubovnyi-roman" },
|
||||
{ label: "Социальная фантастика", value: "sotsialnaya-fantastika" },
|
||||
{ label: "Тёмное фэнтези", value: "temnoe-fentezi" },
|
||||
{ label: "Фантастика", value: "fantastika" },
|
||||
{ label: "Фэнтези", value: "fentezi" },
|
||||
{ label: "Шпионский детектив", value: "shpionskii-detektiv" },
|
||||
{ label: "Эпическое фэнтези", value: "epicheskoe-fentezi" },
|
||||
{ label: "Эротика", value: "erotika" },
|
||||
{ label: "Эротическая фантастика", value: "eroticheskaya-fantastika" },
|
||||
{ label: "Эротический фанфик", value: "eroticheskii-fanfik" },
|
||||
{ label: "Эротическое фэнтези", value: "eroticheskoe-fentezi" },
|
||||
{ label: "Юмористический детектив", value: "yumoristicheskii-detektiv" },
|
||||
{ label: "Юмористическое фэнтези", value: "yumoristicheskoe-fentezi" },
|
||||
{ label: 'Альтернативная история', value: 'alternativnaya-istoriya' },
|
||||
{ label: 'Боевая фантастика', value: 'boevaya-fantastika' },
|
||||
{ label: 'Боевое фэнтези', value: 'boevoe-fentezi' },
|
||||
{ label: 'Бытовое фэнтези', value: 'bytovoe-fentezi' },
|
||||
{ label: 'Героическая фантастика', value: 'geroicheskaya-fantastika' },
|
||||
{ label: 'Героическое фэнтези', value: 'geroicheskoe-fentezi' },
|
||||
{ label: 'Городское фэнтези', value: 'gorodskoe-fentezi' },
|
||||
{ label: 'Детектив', value: 'detektiv' },
|
||||
{ label: 'Детективная фантастика', value: 'detektivnaya-fantastika' },
|
||||
{ label: 'Жёсткая эротика', value: 'zhyostkaya-erotika' },
|
||||
{ label: 'Исторический детектив', value: 'istoricheskii-detektiv' },
|
||||
{
|
||||
label: 'Исторический любовный роман',
|
||||
value: 'istoricheskii-lyubovnyi-roman',
|
||||
},
|
||||
{ label: 'Историческое фэнтези', value: 'istoricheskoe-fentezi' },
|
||||
{ label: 'Киберпанк', value: 'kiberpank' },
|
||||
{ label: 'Классический детектив', value: 'klassicheskii-detektiv' },
|
||||
{ label: 'Короткий любовный роман', value: 'korotkii-lyubovnyi-roman' },
|
||||
{ label: 'Космическая фантастика', value: 'kosmicheskaya-fantastika' },
|
||||
{ label: 'Криминальный детектив', value: 'kriminalnyi-detektiv' },
|
||||
{ label: 'ЛитРПГ', value: 'litrpg' },
|
||||
{ label: 'Любовная фантастика', value: 'lyubovnaya-fantastika' },
|
||||
{ label: 'Любовное фэнтези', value: 'lyubovnoe-fentezi' },
|
||||
{ label: 'Любовный роман', value: 'lyubovnyi-roman' },
|
||||
{ label: 'Мистика', value: 'mistika' },
|
||||
{ label: 'Молодежная проза', value: 'molodezhnaya-proza' },
|
||||
{ label: 'Научная фантастика', value: 'nauchnaya-fantastika' },
|
||||
{
|
||||
label: 'Остросюжетный любовный роман',
|
||||
value: 'ostrosyuzhetnyi-lyubovnyi-roman',
|
||||
},
|
||||
{ label: 'Политический детектив', value: 'politicheskii-detektiv' },
|
||||
{ label: 'Попаданцы', value: 'popadantsy' },
|
||||
{ label: 'Постапокалипсис', value: 'postapokalipsis' },
|
||||
{
|
||||
label: 'Приключенческое фэнтези',
|
||||
value: 'priklyuchencheskoe-fentezi',
|
||||
},
|
||||
{ label: 'Романтическая эротика', value: 'romanticheskaya-erotika' },
|
||||
{ label: 'С элементами эротики', value: 's-elementami-erotiki' },
|
||||
{ label: 'Славянское фэнтези', value: 'slavyanskoe-fentezi' },
|
||||
{
|
||||
label: 'Современный любовный роман',
|
||||
value: 'sovremennyi-lyubovnyi-roman',
|
||||
},
|
||||
{ label: 'Социальная фантастика', value: 'sotsialnaya-fantastika' },
|
||||
{ label: 'Тёмное фэнтези', value: 'temnoe-fentezi' },
|
||||
{ label: 'Фантастика', value: 'fantastika' },
|
||||
{ label: 'Фэнтези', value: 'fentezi' },
|
||||
{ label: 'Шпионский детектив', value: 'shpionskii-detektiv' },
|
||||
{ label: 'Эпическое фэнтези', value: 'epicheskoe-fentezi' },
|
||||
{ label: 'Эротика', value: 'erotika' },
|
||||
{ label: 'Эротическая фантастика', value: 'eroticheskaya-fantastika' },
|
||||
{ label: 'Эротический фанфик', value: 'eroticheskii-fanfik' },
|
||||
{ label: 'Эротическое фэнтези', value: 'eroticheskoe-fentezi' },
|
||||
{
|
||||
label: 'Юмористический детектив',
|
||||
value: 'yumoristicheskii-detektiv',
|
||||
},
|
||||
{ label: 'Юмористическое фэнтези', value: 'yumoristicheskoe-fentezi' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+2496
-2482
File diff suppressed because it is too large
Load Diff
+155
-142
@@ -1,16 +1,16 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class Jaomix implements Plugin.PluginBase {
|
||||
id = "jaomix.ru";
|
||||
name = "Jaomix";
|
||||
site = "https://jaomix.ru";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/jaomix/icon.png";
|
||||
id = 'jaomix.ru';
|
||||
name = 'Jaomix';
|
||||
site = 'https://jaomix.ru';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/jaomix/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -19,109 +19,116 @@ class Jaomix implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/?searchrn";
|
||||
let url = this.site + '/?searchrn';
|
||||
|
||||
if (filters?.lang?.value?.length) {
|
||||
url += filters.lang.value
|
||||
.map((lang, idx) => `&lang[${idx}]=${lang}`)
|
||||
.join("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.include?.length) {
|
||||
url += filters.genre.value.include
|
||||
.map((genre, idx) => `&genre[${idx}]=${genre}`)
|
||||
.join("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
if (filters?.genre?.value?.exclude?.length) {
|
||||
url += filters.genre.value.exclude
|
||||
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`)
|
||||
.join("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
url += "&sortcountchapt=" + (filters?.sortcountchapt?.value || "1");
|
||||
url += "&sortdaycreate=" + (filters?.sortdaycreate?.value || "1");
|
||||
url += "&sortby=" + (showLatestNovels ? "upd" : filters?.sortby?.value || "topweek");
|
||||
url += "&gpage=" + pageNo;
|
||||
url += '&sortcountchapt=' + (filters?.sortcountchapt?.value || '1');
|
||||
url += '&sortdaycreate=' + (filters?.sortdaycreate?.value || '1');
|
||||
url +=
|
||||
'&sortby=' +
|
||||
(showLatestNovels ? 'upd' : filters?.sortby?.value || 'topweek');
|
||||
url += '&gpage=' + pageNo;
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]')
|
||||
.each((index, element) => {
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||
(index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
.attr('title');
|
||||
const cover = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
.attr('src')
|
||||
?.replace('-150x150', '');
|
||||
const url = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
.attr('href');
|
||||
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
});
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
},
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio('div[class="desc-book"] > h1').text().trim(),
|
||||
cover: loadedCheerio('div[class="img-book"] > img').attr("src"),
|
||||
cover: loadedCheerio('div[class="img-book"] > img').attr('src'),
|
||||
summary: loadedCheerio('div[id="desc-tab"]').text().trim(),
|
||||
};
|
||||
|
||||
loadedCheerio("#info-book > p").each(function () {
|
||||
let text = loadedCheerio(this).text().replace(/,/g, "").split(" ");
|
||||
if (text[0] === "Автор:") {
|
||||
novel.author = text.splice(1).join(" ");
|
||||
} else if (text[0] === "Жанры:") {
|
||||
novel.genres = text.splice(1).join(",");
|
||||
} else if (text[0] === "Статус:") {
|
||||
novel.status = text.includes("продолжается")
|
||||
loadedCheerio('#info-book > p').each(function () {
|
||||
let text = loadedCheerio(this).text().replace(/,/g, '').split(' ');
|
||||
if (text[0] === 'Автор:') {
|
||||
novel.author = text.splice(1).join(' ');
|
||||
} else if (text[0] === 'Жанры:') {
|
||||
novel.genres = text.splice(1).join(',');
|
||||
} else if (text[0] === 'Статус:') {
|
||||
novel.status = text.includes('продолжается')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
}
|
||||
});
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const totalChapters = loadedCheerio(".download-chapter div.title").length;
|
||||
const totalChapters = loadedCheerio('.download-chapter div.title').length;
|
||||
|
||||
loadedCheerio(".download-chapter div.title").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("a").attr("title");
|
||||
const url = loadedCheerio(element).find("a").attr("href");
|
||||
loadedCheerio('.download-chapter div.title').each(
|
||||
(chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find('a').attr('title');
|
||||
const url = loadedCheerio(element).find('a').attr('href');
|
||||
if (!name || !url) return;
|
||||
|
||||
const releaseDate = loadedCheerio(element).find("time").text();
|
||||
const releaseDate = loadedCheerio(element).find('time').text();
|
||||
chapters.push({
|
||||
name,
|
||||
path: url.replace(this.site, ""),
|
||||
path: url.replace(this.site, ''),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
novel.chapters = chapters.reverse();
|
||||
return novel;
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio('div[class="adblock-service"]').remove();
|
||||
const chapterText = loadedCheerio('div[class="entry-content"]').html();
|
||||
|
||||
return chapterText || "";
|
||||
return chapterText || '';
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
@@ -129,34 +136,38 @@ class Jaomix implements Plugin.PluginBase {
|
||||
pageNo: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site + "/?searchrn=" + searchTerm +
|
||||
"&but=Поиск по названию&sortby=upd&gpage=" + pageNo;
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
this.site +
|
||||
'/?searchrn=' +
|
||||
searchTerm +
|
||||
'&but=Поиск по названию&sortby=upd&gpage=' +
|
||||
pageNo;
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]')
|
||||
.each((index, element) => {
|
||||
loadedCheerio('div[class="block-home"] > div[class="one"]').each(
|
||||
(index, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("title");
|
||||
.attr('title');
|
||||
const cover = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a > img')
|
||||
.attr("src")
|
||||
?.replace("-150x150", "");
|
||||
.attr('src')
|
||||
?.replace('-150x150', '');
|
||||
const url = loadedCheerio(element)
|
||||
.find('div[class="img-home"] > a')
|
||||
.attr("href");
|
||||
.attr('href');
|
||||
|
||||
if (!name || !url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
});
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
},
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const months: { [key: string]: number } = {
|
||||
Янв: 1,
|
||||
Фев: 2,
|
||||
@@ -172,9 +183,11 @@ class Jaomix implements Plugin.PluginBase {
|
||||
Дек: 12,
|
||||
};
|
||||
|
||||
const [time, day, month, year] = dateString.split(" ");
|
||||
const [time, day, month, year] = dateString.split(' ');
|
||||
if (time && day && months[month] && year) {
|
||||
return dayjs(year + "-" + months[month] + "-" + day + " " + time).format("LLL");
|
||||
return dayjs(year + '-' + months[month] + '-' + day + ' ' + time).format(
|
||||
'LLL',
|
||||
);
|
||||
}
|
||||
|
||||
return dateString || null;
|
||||
@@ -184,110 +197,110 @@ class Jaomix implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sortby: {
|
||||
label: "Сортировка:",
|
||||
value: "topweek",
|
||||
label: 'Сортировка:',
|
||||
value: 'topweek',
|
||||
options: [
|
||||
{ label: "Топ недели", value: "topweek" },
|
||||
{ label: "По алфавиту", value: "alphabet" },
|
||||
{ label: "По дате обновления", value: "upd" },
|
||||
{ label: "По дате создания", value: "new" },
|
||||
{ label: "По просмотрам", value: "count" },
|
||||
{ label: "Топ года", value: "topyear" },
|
||||
{ label: "Топ дня", value: "topday" },
|
||||
{ label: "Топ за все время", value: "alltime" },
|
||||
{ label: "Топ месяца", value: "topmonth" },
|
||||
{ label: 'Топ недели', value: 'topweek' },
|
||||
{ label: 'По алфавиту', value: 'alphabet' },
|
||||
{ label: 'По дате обновления', value: 'upd' },
|
||||
{ label: 'По дате создания', value: 'new' },
|
||||
{ label: 'По просмотрам', value: 'count' },
|
||||
{ label: 'Топ года', value: 'topyear' },
|
||||
{ label: 'Топ дня', value: 'topday' },
|
||||
{ label: 'Топ за все время', value: 'alltime' },
|
||||
{ label: 'Топ месяца', value: 'topmonth' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sortdaycreate: {
|
||||
label: "Дата добавления:",
|
||||
value: "1",
|
||||
label: 'Дата добавления:',
|
||||
value: '1',
|
||||
options: [
|
||||
{ label: "Любое", value: "1" },
|
||||
{ label: "От 120 до 180 дней", value: "1218" },
|
||||
{ label: "От 180 до 365 дней", value: "1836" },
|
||||
{ label: "От 30 до 60 дней", value: "3060" },
|
||||
{ label: "От 365 дней", value: "365" },
|
||||
{ label: "От 60 до 90 дней", value: "6090" },
|
||||
{ label: "От 90 до 120 дней", value: "9012" },
|
||||
{ label: "Послед. 30 дней", value: "30" },
|
||||
{ label: 'Любое', value: '1' },
|
||||
{ label: 'От 120 до 180 дней', value: '1218' },
|
||||
{ label: 'От 180 до 365 дней', value: '1836' },
|
||||
{ label: 'От 30 до 60 дней', value: '3060' },
|
||||
{ label: 'От 365 дней', value: '365' },
|
||||
{ label: 'От 60 до 90 дней', value: '6090' },
|
||||
{ label: 'От 90 до 120 дней', value: '9012' },
|
||||
{ label: 'Послед. 30 дней', value: '30' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
sortcountchapt: {
|
||||
label: "Количество глав:",
|
||||
value: "1",
|
||||
label: 'Количество глав:',
|
||||
value: '1',
|
||||
options: [
|
||||
{ label: "Любое кол-во глав", value: "1" },
|
||||
{ label: "До 500", value: "500" },
|
||||
{ label: "От 1000 до 2000", value: "1020" },
|
||||
{ label: "От 2000 до 3000", value: "2030" },
|
||||
{ label: "От 3000 до 4000", value: "3040" },
|
||||
{ label: "От 4000", value: "400" },
|
||||
{ label: "От 500 до 1000", value: "510" },
|
||||
{ label: 'Любое кол-во глав', value: '1' },
|
||||
{ label: 'До 500', value: '500' },
|
||||
{ label: 'От 1000 до 2000', value: '1020' },
|
||||
{ label: 'От 2000 до 3000', value: '2030' },
|
||||
{ label: 'От 3000 до 4000', value: '3040' },
|
||||
{ label: 'От 4000', value: '400' },
|
||||
{ label: 'От 500 до 1000', value: '510' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre: {
|
||||
label: "Жанры:",
|
||||
label: 'Жанры:',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "Боевые Искусства", value: "Боевые Искусства" },
|
||||
{ label: "Виртуальный Мир", value: "Виртуальный Мир" },
|
||||
{ label: "Гарем", value: "Гарем" },
|
||||
{ label: "Детектив", value: "Детектив" },
|
||||
{ label: "Драма", value: "Драма" },
|
||||
{ label: "Игра", value: "Игра" },
|
||||
{ label: "Истории из жизни", value: "Истории из жизни" },
|
||||
{ label: "Исторический", value: "Исторический" },
|
||||
{ label: "История", value: "История" },
|
||||
{ label: "Исэкай", value: "Исэкай" },
|
||||
{ label: "Комедия", value: "Комедия" },
|
||||
{ label: "Меха", value: "Меха" },
|
||||
{ label: "Мистика", value: "Мистика" },
|
||||
{ label: "Научная Фантастика", value: "Научная Фантастика" },
|
||||
{ label: "Повседневность", value: "Повседневность" },
|
||||
{ label: "Постапокалипсис", value: "Постапокалипсис" },
|
||||
{ label: "Приключения", value: "Приключения" },
|
||||
{ label: "Психология", value: "Психология" },
|
||||
{ label: "Романтика", value: "Романтика" },
|
||||
{ label: "Сверхъестественное", value: "Сверхъестественное" },
|
||||
{ label: "Сёнэн", value: "Сёнэн" },
|
||||
{ label: "Сёнэн-ай", value: "Сёнэн-ай" },
|
||||
{ label: "Спорт", value: "Спорт" },
|
||||
{ label: "Сэйнэн", value: "Сэйнэн" },
|
||||
{ label: "Сюаньхуа", value: "Сюаньхуа" },
|
||||
{ label: "Трагедия", value: "Трагедия" },
|
||||
{ label: "Триллер", value: "Триллер" },
|
||||
{ label: "Фантастика", value: "Фантастика" },
|
||||
{ label: "Фэнтези", value: "Фэнтези" },
|
||||
{ label: "Хоррор", value: "Хоррор" },
|
||||
{ label: "Школьная жизнь", value: "Школьная жизнь" },
|
||||
{ label: "Шоунен", value: "Шоунен" },
|
||||
{ label: "Экшн", value: "Экшн" },
|
||||
{ label: "Этти", value: "Этти" },
|
||||
{ label: "Юри", value: "Юри" },
|
||||
{ label: "Adult", value: "Adult" },
|
||||
{ label: "Ecchi", value: "Ecchi" },
|
||||
{ label: "Josei", value: "Josei" },
|
||||
{ label: "Lolicon", value: "Lolicon" },
|
||||
{ label: "Mature", value: "Mature" },
|
||||
{ label: "Shoujo", value: "Shoujo" },
|
||||
{ label: "Wuxia", value: "Wuxia" },
|
||||
{ label: "Xianxia", value: "Xianxia" },
|
||||
{ label: "Xuanhuan", value: "Xuanhuan" },
|
||||
{ label: "Yaoi", value: "Yaoi" },
|
||||
{ label: 'Боевые Искусства', value: 'Боевые Искусства' },
|
||||
{ label: 'Виртуальный Мир', value: 'Виртуальный Мир' },
|
||||
{ label: 'Гарем', value: 'Гарем' },
|
||||
{ label: 'Детектив', value: 'Детектив' },
|
||||
{ label: 'Драма', value: 'Драма' },
|
||||
{ label: 'Игра', value: 'Игра' },
|
||||
{ label: 'Истории из жизни', value: 'Истории из жизни' },
|
||||
{ label: 'Исторический', value: 'Исторический' },
|
||||
{ label: 'История', value: 'История' },
|
||||
{ label: 'Исэкай', value: 'Исэкай' },
|
||||
{ label: 'Комедия', value: 'Комедия' },
|
||||
{ label: 'Меха', value: 'Меха' },
|
||||
{ label: 'Мистика', value: 'Мистика' },
|
||||
{ label: 'Научная Фантастика', value: 'Научная Фантастика' },
|
||||
{ label: 'Повседневность', value: 'Повседневность' },
|
||||
{ label: 'Постапокалипсис', value: 'Постапокалипсис' },
|
||||
{ label: 'Приключения', value: 'Приключения' },
|
||||
{ label: 'Психология', value: 'Психология' },
|
||||
{ label: 'Романтика', value: 'Романтика' },
|
||||
{ label: 'Сверхъестественное', value: 'Сверхъестественное' },
|
||||
{ label: 'Сёнэн', value: 'Сёнэн' },
|
||||
{ label: 'Сёнэн-ай', value: 'Сёнэн-ай' },
|
||||
{ label: 'Спорт', value: 'Спорт' },
|
||||
{ label: 'Сэйнэн', value: 'Сэйнэн' },
|
||||
{ label: 'Сюаньхуа', value: 'Сюаньхуа' },
|
||||
{ label: 'Трагедия', value: 'Трагедия' },
|
||||
{ label: 'Триллер', value: 'Триллер' },
|
||||
{ label: 'Фантастика', value: 'Фантастика' },
|
||||
{ label: 'Фэнтези', value: 'Фэнтези' },
|
||||
{ label: 'Хоррор', value: 'Хоррор' },
|
||||
{ label: 'Школьная жизнь', value: 'Школьная жизнь' },
|
||||
{ label: 'Шоунен', value: 'Шоунен' },
|
||||
{ label: 'Экшн', value: 'Экшн' },
|
||||
{ label: 'Этти', value: 'Этти' },
|
||||
{ label: 'Юри', value: 'Юри' },
|
||||
{ label: 'Adult', value: 'Adult' },
|
||||
{ label: 'Ecchi', value: 'Ecchi' },
|
||||
{ label: 'Josei', value: 'Josei' },
|
||||
{ label: 'Lolicon', value: 'Lolicon' },
|
||||
{ label: 'Mature', value: 'Mature' },
|
||||
{ label: 'Shoujo', value: 'Shoujo' },
|
||||
{ label: 'Wuxia', value: 'Wuxia' },
|
||||
{ label: 'Xianxia', value: 'Xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'Xuanhuan' },
|
||||
{ label: 'Yaoi', value: 'Yaoi' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
lang: {
|
||||
label: "Выбрать языки:",
|
||||
label: 'Выбрать языки:',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Английский", value: "Английский" },
|
||||
{ label: "Китайский", value: "Китайский" },
|
||||
{ label: "Корейский", value: "Корейский" },
|
||||
{ label: "Японский", value: "Японский" },
|
||||
{ label: 'Английский', value: 'Английский' },
|
||||
{ label: 'Китайский', value: 'Китайский' },
|
||||
{ label: 'Корейский', value: 'Корейский' },
|
||||
{ label: 'Японский', value: 'Японский' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
+100
-97
@@ -1,61 +1,64 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
const statusKey: { [key: string]: string } = {
|
||||
"0": NovelStatus.Unknown,
|
||||
"1": NovelStatus.Ongoing,
|
||||
"2": NovelStatus.Completed,
|
||||
"3": NovelStatus.OnHiatus,
|
||||
"4": NovelStatus.Cancelled,
|
||||
'0': NovelStatus.Unknown,
|
||||
'1': NovelStatus.Ongoing,
|
||||
'2': NovelStatus.Completed,
|
||||
'3': NovelStatus.OnHiatus,
|
||||
'4': NovelStatus.Cancelled,
|
||||
};
|
||||
|
||||
class Neobook implements Plugin.PluginBase {
|
||||
id = "neobook";
|
||||
name = "Neobook";
|
||||
site = "https://neobook.org";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/neobook/icon.png";
|
||||
id = 'neobook';
|
||||
name = 'Neobook';
|
||||
site = 'https://neobook.org';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/neobook/icon.png';
|
||||
|
||||
async fetchNovels(
|
||||
page: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
searchTerm?: string,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const formData = new FormData();
|
||||
formData.append("version", "4.0");
|
||||
formData.append("uid", "0");
|
||||
formData.append("utoken", "");
|
||||
formData.append("resource", "general");
|
||||
formData.append("action", "get_bundle");
|
||||
formData.append("bundle", "bundle_books");
|
||||
formData.append("target", "feed");
|
||||
formData.append("page", page.toString());
|
||||
formData.append("filter_category_id", filters?.category?.value || "0");
|
||||
formData.append("filter_completed", "-1");
|
||||
formData.append("filter_search", searchTerm || "");
|
||||
formData.append("filter_tags", filters?.tags?.value || "");
|
||||
formData.append('version', '4.0');
|
||||
formData.append('uid', '0');
|
||||
formData.append('utoken', '');
|
||||
formData.append('resource', 'general');
|
||||
formData.append('action', 'get_bundle');
|
||||
formData.append('bundle', 'bundle_books');
|
||||
formData.append('target', 'feed');
|
||||
formData.append('page', page.toString());
|
||||
formData.append('filter_category_id', filters?.category?.value || '0');
|
||||
formData.append('filter_completed', '-1');
|
||||
formData.append('filter_search', searchTerm || '');
|
||||
formData.append('filter_tags', filters?.tags?.value || '');
|
||||
formData.append(
|
||||
"filter_sort",
|
||||
showLatestNovels ? "new" : filters?.sort?.value || "popular",
|
||||
'filter_sort',
|
||||
showLatestNovels ? 'new' : filters?.sort?.value || 'popular',
|
||||
);
|
||||
formData.append("filter_timeread", filters?.timeread?.value || "0-999999");
|
||||
formData.append('filter_timeread', filters?.timeread?.value || '0-999999');
|
||||
|
||||
const result = await fetchApi("https://api.neobook.org/", {
|
||||
method: "post",
|
||||
const result = await fetchApi('https://api.neobook.org/', {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
});
|
||||
const json = (await result.json()) as { bundle_books: BundleBooks };
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (json.bundle_books.feed?.length) {
|
||||
json.bundle_books.feed?.forEach((novel) =>
|
||||
json.bundle_books.feed?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.attachment?.image?.m || defaultCover,
|
||||
path: novel.token + "/",
|
||||
path: novel.token + '/',
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -77,12 +80,13 @@ class Neobook implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: "",
|
||||
name: '',
|
||||
cover: defaultCover,
|
||||
};
|
||||
|
||||
@@ -91,22 +95,22 @@ class Neobook implements Plugin.PluginBase {
|
||||
const book = JSON.parse(bookRaw[1]) as Novels;
|
||||
|
||||
novel.name = book.title;
|
||||
novel.summary = book.text?.replace?.(/<br>/g, "\n") || book.text_fix;
|
||||
novel.summary = book.text?.replace?.(/<br>/g, '\n') || book.text_fix;
|
||||
novel.author =
|
||||
book.user && book.user.firstname && book.user.lastname
|
||||
? book.user.firstname + " " + book.user.lastname
|
||||
: book.user?.initials || "";
|
||||
novel.status = statusKey[book.status || "0"] || NovelStatus.Unknown;
|
||||
? book.user.firstname + ' ' + book.user.lastname
|
||||
: book.user?.initials || '';
|
||||
novel.status = statusKey[book.status || '0'] || NovelStatus.Unknown;
|
||||
|
||||
if (book.attachment?.image?.m) novel.cover = book.attachment.image.m;
|
||||
if (book.tags?.length) novel.genres = book.tags.join(",");
|
||||
if (book.tags?.length) novel.genres = book.tags.join(',');
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
book.chapters?.forEach((chapter, chapterIndex) => {
|
||||
if (chapter.access == "1" && chapter.status == "1") {
|
||||
if (chapter.access == '1' && chapter.status == '1') {
|
||||
chapters.push({
|
||||
name: chapter.title || "Глава " + (chapterIndex + 1),
|
||||
name: chapter.title || 'Глава ' + (chapterIndex + 1),
|
||||
path: `?book=${book.token}&chapter=${chapter.token}`,
|
||||
releaseTime: null,
|
||||
chapterNumber: Number(chapter.sort) || chapterIndex + 1,
|
||||
@@ -120,19 +124,18 @@ class Neobook implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.resolveUrl(chapterPath))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const bookRaw = body.match(/var data = ({.*?});/);
|
||||
let chapterText = "";
|
||||
let chapterText = '';
|
||||
|
||||
if (bookRaw instanceof Array && bookRaw.length >= 2) {
|
||||
const book = JSON.parse(bookRaw[1]) as Novels;
|
||||
const token = chapterPath.split("=")[2];
|
||||
const chapter = book.chapters?.find?.(
|
||||
(chapter) => chapter.token == token,
|
||||
);
|
||||
chapterText = (chapter?.data?.html || "").replace(/<br>/g, "");
|
||||
const token = chapterPath.split('=')[2];
|
||||
const chapter = book.chapters?.find?.(chapter => chapter.token == token);
|
||||
chapterText = (chapter?.data?.html || '').replace(/<br>/g, '');
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
@@ -140,67 +143,67 @@ class Neobook implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + (isNovel ? "/book/" : "/reader/") + path;
|
||||
this.site + (isNovel ? '/book/' : '/reader/') + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка:",
|
||||
value: "popular",
|
||||
label: 'Сортировка:',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "Сначала популярные", value: "popular" },
|
||||
{ label: "Сначала новые", value: "new" },
|
||||
{ label: "В случайном порядке", value: "rand" },
|
||||
{ label: 'Сначала популярные', value: 'popular' },
|
||||
{ label: 'Сначала новые', value: 'new' },
|
||||
{ label: 'В случайном порядке', value: 'rand' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
timeread: {
|
||||
label: "Время прочтения:",
|
||||
value: "0-999999",
|
||||
label: 'Время прочтения:',
|
||||
value: '0-999999',
|
||||
options: [
|
||||
{ label: "Все", value: "0-999999" },
|
||||
{ label: "Менее 15 минут", value: "0-900" },
|
||||
{ label: "15-30 минут", value: "900-1800" },
|
||||
{ label: "30-60 минут", value: "1800-3600" },
|
||||
{ label: "1-2 часа", value: "3600-7200" },
|
||||
{ label: "Более 2 часов", value: "7200-999999" },
|
||||
{ label: 'Все', value: '0-999999' },
|
||||
{ label: 'Менее 15 минут', value: '0-900' },
|
||||
{ label: '15-30 минут', value: '900-1800' },
|
||||
{ label: '30-60 минут', value: '1800-3600' },
|
||||
{ label: '1-2 часа', value: '3600-7200' },
|
||||
{ label: 'Более 2 часов', value: '7200-999999' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
category: {
|
||||
label: "Жанр:",
|
||||
value: "",
|
||||
label: 'Жанр:',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Все", value: "" },
|
||||
{ label: "Антиутопия", value: "10" },
|
||||
{ label: "Детектив", value: "13" },
|
||||
{ label: "Детские книги", value: "14" },
|
||||
{ label: "Драма", value: "15" },
|
||||
{ label: "Другое", value: "16" },
|
||||
{ label: "История", value: "18" },
|
||||
{ label: "Мелодрама", value: "21" },
|
||||
{ label: "Мистика", value: "22" },
|
||||
{ label: "Научная фантастика", value: "23" },
|
||||
{ label: "Нон-фикшн", value: "35" },
|
||||
{ label: "Подростки и молодежь", value: "26" },
|
||||
{ label: "Постапокалипсис", value: "27" },
|
||||
{ label: "Поэзия", value: "28" },
|
||||
{ label: "Приключения", value: "29" },
|
||||
{ label: "Рассказ", value: "34" },
|
||||
{ label: "Роман", value: "36" },
|
||||
{ label: "Творчество", value: "40" },
|
||||
{ label: "Триллер", value: "91" },
|
||||
{ label: "Ужасы", value: "90" },
|
||||
{ label: "Фантастика", value: "41" },
|
||||
{ label: "Фанфик", value: "42" },
|
||||
{ label: "Фэнтези", value: "44" },
|
||||
{ label: "Эротика", value: "46" },
|
||||
{ label: "Юмор", value: "47" },
|
||||
{ label: 'Все', value: '' },
|
||||
{ label: 'Антиутопия', value: '10' },
|
||||
{ label: 'Детектив', value: '13' },
|
||||
{ label: 'Детские книги', value: '14' },
|
||||
{ label: 'Драма', value: '15' },
|
||||
{ label: 'Другое', value: '16' },
|
||||
{ label: 'История', value: '18' },
|
||||
{ label: 'Мелодрама', value: '21' },
|
||||
{ label: 'Мистика', value: '22' },
|
||||
{ label: 'Научная фантастика', value: '23' },
|
||||
{ label: 'Нон-фикшн', value: '35' },
|
||||
{ label: 'Подростки и молодежь', value: '26' },
|
||||
{ label: 'Постапокалипсис', value: '27' },
|
||||
{ label: 'Поэзия', value: '28' },
|
||||
{ label: 'Приключения', value: '29' },
|
||||
{ label: 'Рассказ', value: '34' },
|
||||
{ label: 'Роман', value: '36' },
|
||||
{ label: 'Творчество', value: '40' },
|
||||
{ label: 'Триллер', value: '91' },
|
||||
{ label: 'Ужасы', value: '90' },
|
||||
{ label: 'Фантастика', value: '41' },
|
||||
{ label: 'Фанфик', value: '42' },
|
||||
{ label: 'Фэнтези', value: '44' },
|
||||
{ label: 'Эротика', value: '46' },
|
||||
{ label: 'Юмор', value: '47' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
tags: {
|
||||
label: "Тэги:",
|
||||
value: "",
|
||||
label: 'Тэги:',
|
||||
value: '',
|
||||
type: FilterTypes.TextInput,
|
||||
},
|
||||
} satisfies Filters;
|
||||
@@ -401,7 +404,7 @@ interface Price {
|
||||
deadline: string;
|
||||
}
|
||||
|
||||
type Currency = "NEO";
|
||||
type Currency = 'NEO';
|
||||
|
||||
interface Rating {
|
||||
value: string;
|
||||
|
||||
+64
-53
@@ -1,29 +1,33 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class novelOvh implements Plugin.PluginBase {
|
||||
id = "novelovh";
|
||||
name = "НовелОВХ";
|
||||
site = "https://novel.ovh";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/novelovh/icon.png";
|
||||
id = 'novelovh';
|
||||
name = 'НовелОВХ';
|
||||
site = 'https://novel.ovh';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/novelovh/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/novel?page=" + (pageNo - 1);
|
||||
url += "&sort=" +
|
||||
(showLatestNovels ? "updatedAt" : filters?.sort?.value || "averageRating") + ",desc";
|
||||
let url = this.site + '/novel?page=' + (pageNo - 1);
|
||||
url +=
|
||||
'&sort=' +
|
||||
(showLatestNovels
|
||||
? 'updatedAt'
|
||||
: filters?.sort?.value || 'averageRating') +
|
||||
',desc';
|
||||
|
||||
const result = await fetchApi(url + "&_data=routes/reader/book/index");
|
||||
const result = await fetchApi(url + '&_data=routes/reader/book/index');
|
||||
const body = (await result.json()) as { books: BooksEntity[] };
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
body.books.forEach((novel) =>
|
||||
body.books.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name.ru,
|
||||
cover: novel.poster,
|
||||
@@ -36,7 +40,8 @@ class novelOvh implements Plugin.PluginBase {
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(
|
||||
this.resolveUrl(novelPath, true) + "?_data=routes/reader/book/$slug/index",
|
||||
this.resolveUrl(novelPath, true) +
|
||||
'?_data=routes/reader/book/$slug/index',
|
||||
);
|
||||
const json = (await result.json()) as responseNovel;
|
||||
|
||||
@@ -44,19 +49,19 @@ class novelOvh implements Plugin.PluginBase {
|
||||
path: novelPath,
|
||||
name: json.book.name.ru,
|
||||
cover: json.book.poster,
|
||||
genres: json.book.labels?.map?.((label) => label.name).join(","),
|
||||
genres: json.book.labels?.map?.(label => label.name).join(','),
|
||||
summary: json.book.description,
|
||||
status:
|
||||
json.book.status == "ONGOING"
|
||||
json.book.status == 'ONGOING'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
json.book.relations?.forEach((person) => {
|
||||
json.book.relations?.forEach(person => {
|
||||
switch (person.type) {
|
||||
case "AUTHOR":
|
||||
case 'AUTHOR':
|
||||
novel.author = person.publisher.name;
|
||||
case "ARTIST":
|
||||
case 'ARTIST':
|
||||
novel.artist = person.publisher.name;
|
||||
}
|
||||
});
|
||||
@@ -67,11 +72,14 @@ class novelOvh implements Plugin.PluginBase {
|
||||
chapters.push({
|
||||
name:
|
||||
chapter.title ||
|
||||
"Том " + (chapter.volume || 0) + " " +
|
||||
(chapter.name || "Глава " +
|
||||
'Том ' +
|
||||
(chapter.volume || 0) +
|
||||
' ' +
|
||||
(chapter.name ||
|
||||
'Глава ' +
|
||||
(chapter.number || json.chapters.length - chapterIndex)),
|
||||
path: novelPath + "/" + chapter.id + "/0",
|
||||
releaseTime: dayjs(chapter.createdAt).format("LLL"),
|
||||
path: novelPath + '/' + chapter.id + '/0',
|
||||
releaseTime: dayjs(chapter.createdAt).format('LLL'),
|
||||
chapterNumber: json.chapters.length - chapterIndex,
|
||||
}),
|
||||
);
|
||||
@@ -82,7 +90,7 @@ class novelOvh implements Plugin.PluginBase {
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(
|
||||
"https://api.novel.ovh/v2/chapters/" + chapterPath.split("/")[1],
|
||||
'https://api.novel.ovh/v2/chapters/' + chapterPath.split('/')[1],
|
||||
);
|
||||
const book = (await result.json()) as responseChapter;
|
||||
const image = Object.fromEntries(
|
||||
@@ -98,11 +106,11 @@ class novelOvh implements Plugin.PluginBase {
|
||||
page: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/novel?search=${searchTerm}&page=${page - 1}`;
|
||||
const result = await fetchApi(url + "&_data=routes/reader/book/index");
|
||||
const result = await fetchApi(url + '&_data=routes/reader/book/index');
|
||||
const body = (await result.json()) as { books: BooksEntity[] };
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
body.books.forEach((novel) =>
|
||||
body.books.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name.ru,
|
||||
cover: novel.poster,
|
||||
@@ -116,37 +124,39 @@ class novelOvh implements Plugin.PluginBase {
|
||||
jsonToHtml = (
|
||||
json: ContentEntity[],
|
||||
image: { [key: string]: string },
|
||||
html: string = "",
|
||||
html: string = '',
|
||||
) => {
|
||||
json.forEach((element) => {
|
||||
json.forEach(element => {
|
||||
switch (element.type) {
|
||||
case "image":
|
||||
case 'image':
|
||||
if (element.attrs?.pages?.[0]) {
|
||||
html += `<img src="${image[element.attrs.pages[0]]}"/>`;
|
||||
}
|
||||
break;
|
||||
case "hardBreak":
|
||||
html += "<br>";
|
||||
case 'hardBreak':
|
||||
html += '<br>';
|
||||
break;
|
||||
case "horizontalRule":
|
||||
case "delimiter":
|
||||
case 'horizontalRule':
|
||||
case 'delimiter':
|
||||
html += '<h2 style="text-align: center">***</h2>';
|
||||
break;
|
||||
case "paragraph":
|
||||
case 'paragraph':
|
||||
html +=
|
||||
"<p" +
|
||||
'<p' +
|
||||
(element?.attrs?.textAlign
|
||||
? ` style="text-align: ${element.attrs.textAlign}"`
|
||||
: "") +
|
||||
">" +
|
||||
(element.content ? this.jsonToHtml(element.content, image) : "<br>") +
|
||||
"</p>";
|
||||
: '') +
|
||||
'>' +
|
||||
(element.content
|
||||
? this.jsonToHtml(element.content, image)
|
||||
: '<br>') +
|
||||
'</p>';
|
||||
break;
|
||||
case "text":
|
||||
case 'text':
|
||||
html += element.text;
|
||||
break;
|
||||
default:
|
||||
html += JSON.stringify(element, null, "\t"); //maybe I missed something.
|
||||
html += JSON.stringify(element, null, '\t'); //maybe I missed something.
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -154,20 +164,21 @@ class novelOvh implements Plugin.PluginBase {
|
||||
};
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) => this.site + "/novel/" + path;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + '/novel/' + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "averageRating",
|
||||
label: 'Сортировка',
|
||||
value: 'averageRating',
|
||||
options: [
|
||||
{ label: "Кол-во просмотров", value: "viewsCount" },
|
||||
{ label: "Кол-во лайков", value: "likesCount" },
|
||||
{ label: "Кол-во глав", value: "chaptersCount" },
|
||||
{ label: "Кол-во закладок", value: "bookmarksCount" },
|
||||
{ label: "Рейтингу", value: "averageRating" },
|
||||
{ label: "Дате создания", value: "createdAt" },
|
||||
{ label: "Дате обновления", value: "updatedAt" },
|
||||
{ label: 'Кол-во просмотров', value: 'viewsCount' },
|
||||
{ label: 'Кол-во лайков', value: 'likesCount' },
|
||||
{ label: 'Кол-во глав', value: 'chaptersCount' },
|
||||
{ label: 'Кол-во закладок', value: 'bookmarksCount' },
|
||||
{ label: 'Рейтингу', value: 'averageRating' },
|
||||
{ label: 'Дате создания', value: 'createdAt' },
|
||||
{ label: 'Дате обновления', value: 'updatedAt' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+717
-715
File diff suppressed because it is too large
Load Diff
+1004
-977
File diff suppressed because it is too large
Load Diff
+257
-236
@@ -1,9 +1,9 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const statusKey: { [key: number]: string } = {
|
||||
1: NovelStatus.Ongoing,
|
||||
@@ -13,11 +13,11 @@ const statusKey: { [key: number]: string } = {
|
||||
};
|
||||
|
||||
class RLIB implements Plugin.PluginBase {
|
||||
id = "RLIB";
|
||||
name = "RanobeLib";
|
||||
site = "https://ranobelib.me";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ranobelib/icon.png";
|
||||
id = 'RLIB';
|
||||
name = 'RanobeLib';
|
||||
site = 'https://ranobelib.me';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/ranobelib/icon.png';
|
||||
ui: string | undefined = undefined;
|
||||
|
||||
async popularNovels(
|
||||
@@ -27,63 +27,74 @@ class RLIB implements Plugin.PluginBase {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/manga-list?sort=";
|
||||
url += showLatestNovels ? "last_chapter_at" : filters?.sort?.value || "rate";
|
||||
url += "&dir=" + (filters?.order?.value || "desc");
|
||||
url += "&chapters[min]=" + (filters?.require_chapters?.value ? "1" : "0");
|
||||
let url = this.site + '/manga-list?sort=';
|
||||
url += showLatestNovels
|
||||
? 'last_chapter_at'
|
||||
: filters?.sort?.value || 'rate';
|
||||
url += '&dir=' + (filters?.order?.value || 'desc');
|
||||
url += '&chapters[min]=' + (filters?.require_chapters?.value ? '1' : '0');
|
||||
|
||||
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||
url += '&' + type + '[]=' + value.join('&' + type + '[]=');
|
||||
}
|
||||
if (value?.include instanceof Array && value.include.length) {
|
||||
url += "&" + type + "[include][]=" +
|
||||
value.include.join("&" + type + "[include][]=");
|
||||
url +=
|
||||
'&' +
|
||||
type +
|
||||
'[include][]=' +
|
||||
value.include.join('&' + type + '[include][]=');
|
||||
}
|
||||
if (value?.exclude instanceof Array && value.exclude.length) {
|
||||
url += "&" + type + "[exclude][]=" +
|
||||
value.exclude.join("&" + type + "[exclude][]=");
|
||||
url +=
|
||||
'&' +
|
||||
type +
|
||||
'[exclude][]=' +
|
||||
value.exclude.join('&' + type + '[exclude][]=');
|
||||
}
|
||||
});
|
||||
|
||||
url += "&page=" + pageNo;
|
||||
url += '&page=' + pageNo;
|
||||
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
this.ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
this.ui = loadedCheerio('a.header-right-menu__item')
|
||||
.attr('href')
|
||||
?.replace?.(/[^0-9]/g, '');
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio(".media-card-wrap").each((index, element) => {
|
||||
const name = loadedCheerio(element).find(".media-card__title").text();
|
||||
const cover = loadedCheerio(element).find("a.media-card").attr("data-src");
|
||||
const url = loadedCheerio(element).find("a.media-card").attr("href");
|
||||
loadedCheerio('.media-card-wrap').each((index, element) => {
|
||||
const name = loadedCheerio(element).find('.media-card__title').text();
|
||||
const cover = loadedCheerio(element)
|
||||
.find('a.media-card')
|
||||
.attr('data-src');
|
||||
const url = loadedCheerio(element).find('a.media-card').attr('href');
|
||||
if (!url) return;
|
||||
novels.push({ name, cover, path: url.replace(this.site, "") });
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".media-name__main").text()?.trim?.() || "",
|
||||
name: loadedCheerio('.media-name__main').text()?.trim?.() || '',
|
||||
};
|
||||
novel.cover = loadedCheerio(".container_responsive img").attr("src");
|
||||
novel.cover = loadedCheerio('.container_responsive img').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio(".media-description__text").text().trim();
|
||||
novel.summary = loadedCheerio('.media-description__text').text().trim();
|
||||
|
||||
novel.genres = loadedCheerio('div[class="media-tags"]')
|
||||
.text()
|
||||
.trim()
|
||||
.replace(/[\n\r]+/g, ", ")
|
||||
.replace(/ /g, "");
|
||||
.replace(/[\n\r]+/g, ', ')
|
||||
.replace(/ /g, '');
|
||||
|
||||
loadedCheerio(
|
||||
'div[class="media-info-list paper"] > [class="media-info-list__item"]',
|
||||
@@ -92,12 +103,12 @@ class RLIB implements Plugin.PluginBase {
|
||||
.find('div[class="media-info-list__title"]')
|
||||
.text();
|
||||
|
||||
if (name === "Автор") {
|
||||
if (name === 'Автор') {
|
||||
novel.author = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__value"]')
|
||||
.text()
|
||||
.trim();
|
||||
} else if (name === "Художник") {
|
||||
} else if (name === 'Художник') {
|
||||
novel.artist = loadedCheerio(this)
|
||||
.find('div[class="media-info-list__value"]')
|
||||
.text()
|
||||
@@ -105,9 +116,9 @@ class RLIB implements Plugin.PluginBase {
|
||||
}
|
||||
});
|
||||
|
||||
this.ui = loadedCheerio("a.header-right-menu__item")
|
||||
.attr("href")
|
||||
?.replace?.(/[^0-9]/g, "");
|
||||
this.ui = loadedCheerio('a.header-right-menu__item')
|
||||
.attr('href')
|
||||
?.replace?.(/[^0-9]/g, '');
|
||||
|
||||
const chaptersRaw = body.match(/window\.__DATA__ = ({.*?});/);
|
||||
if (chaptersRaw instanceof Array && chaptersRaw.length >= 2) {
|
||||
@@ -119,7 +130,8 @@ class RLIB implements Plugin.PluginBase {
|
||||
chaptersJson.manga.engName ||
|
||||
chaptersJson.manga.name;
|
||||
}
|
||||
novel.status = statusKey[chaptersJson.manga.status] || NovelStatus.Unknown;
|
||||
novel.status =
|
||||
statusKey[chaptersJson.manga.status] || NovelStatus.Unknown;
|
||||
this.ui = chaptersJson?.user?.id;
|
||||
|
||||
if (!chaptersJson.chapters.list?.length) return novel;
|
||||
@@ -127,16 +139,19 @@ class RLIB implements Plugin.PluginBase {
|
||||
|
||||
const customPage: { [key: number]: string } = {};
|
||||
const customOrder: { [key: number]: number } = {};
|
||||
if (chaptersJson.chapters.branches?.length && chaptersJson.chapters.branches.length > 1) {
|
||||
if (
|
||||
chaptersJson.chapters.branches?.length &&
|
||||
chaptersJson.chapters.branches.length > 1
|
||||
) {
|
||||
//if the novel is being translated by more than one team
|
||||
chaptersJson.chapters.branches.forEach(({ teams, id }) => {
|
||||
if (teams?.length) {
|
||||
customPage[id || 0] =
|
||||
teams.find((team) => team.is_active)?.name || teams[0].name;
|
||||
teams.find(team => team.is_active)?.name || teams[0].name;
|
||||
}
|
||||
});
|
||||
//fixes the chapter's position.
|
||||
chaptersJson.chapters.list.forEach((chapter) => {
|
||||
chaptersJson.chapters.list.forEach(chapter => {
|
||||
chapter.index = customOrder[chapter.branch_id || 0] || 1;
|
||||
customOrder[chapter.branch_id || 0] =
|
||||
(customOrder[chapter.branch_id || 0] || 1) + 1;
|
||||
@@ -147,19 +162,24 @@ class RLIB implements Plugin.PluginBase {
|
||||
chaptersJson.chapters.list.forEach((chapter, chapterIndex) =>
|
||||
chapters.push({
|
||||
name:
|
||||
"Том " + chapter.chapter_volume +
|
||||
" Глава " + chapter.chapter_number +
|
||||
(chapter.chapter_name ? " " + chapter.chapter_name.trim() : ""),
|
||||
'Том ' +
|
||||
chapter.chapter_volume +
|
||||
' Глава ' +
|
||||
chapter.chapter_number +
|
||||
(chapter.chapter_name ? ' ' + chapter.chapter_name.trim() : ''),
|
||||
path:
|
||||
novelPath +
|
||||
"/v" + chapter.chapter_volume +
|
||||
"/c" + chapter.chapter_number +
|
||||
"?bid=" + (chapter.branch_id || ""),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format("LLL"),
|
||||
'/v' +
|
||||
chapter.chapter_volume +
|
||||
'/c' +
|
||||
chapter.chapter_number +
|
||||
'?bid=' +
|
||||
(chapter.branch_id || ''),
|
||||
releaseTime: dayjs(chapter.chapter_created_at).format('LLL'),
|
||||
chapterNumber:
|
||||
customOrder[chapter.branch_id || 0] - (chapter.index || 0) ||
|
||||
totalChapters - chapterIndex,
|
||||
page: customPage[chapter.branch_id || 0] || "Основной перевод",
|
||||
page: customPage[chapter.branch_id || 0] || 'Основной перевод',
|
||||
}),
|
||||
);
|
||||
novel.chapters =
|
||||
@@ -176,38 +196,39 @@ class RLIB implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.resolveUrl(chapterPath))
|
||||
.then((res) => res.text());
|
||||
const result = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(result);
|
||||
loadedCheerio(".reader-container img").each((index, element) => {
|
||||
loadedCheerio('.reader-container img').each((index, element) => {
|
||||
const src =
|
||||
loadedCheerio(element).attr("data-src") ||
|
||||
loadedCheerio(element).attr("src");
|
||||
if (!src?.startsWith("http")) {
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio(element).attr('data-src') ||
|
||||
loadedCheerio(element).attr('src');
|
||||
if (!src?.startsWith('http')) {
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
} else {
|
||||
loadedCheerio(element).attr("src", src);
|
||||
loadedCheerio(element).attr('src', src);
|
||||
}
|
||||
loadedCheerio(element).removeAttr("data-src");
|
||||
loadedCheerio(element).removeAttr('data-src');
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".reader-container").html();
|
||||
return chapterText || "";
|
||||
const chapterText = loadedCheerio('.reader-container').html();
|
||||
return chapterText || '';
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi(
|
||||
this.site + "/search?q=" + searchTerm + "&type=manga",
|
||||
this.site + '/search?q=' + searchTerm + '&type=manga',
|
||||
);
|
||||
const body = (await result.json()) as Manga[];
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.forEach((novel) =>
|
||||
body.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.rus_name || novel.name,
|
||||
cover: novel.coverImage,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -216,223 +237,223 @@ class RLIB implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + path + (this.ui ? (isNovel ? "?" : "&") + "ui=" + this.ui : "");
|
||||
this.site + path + (this.ui ? (isNovel ? '?' : '&') + 'ui=' + this.ui : '');
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "rate",
|
||||
label: 'Сортировка',
|
||||
value: 'rate',
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "rate" },
|
||||
{ label: "Имя", value: "name" },
|
||||
{ label: "Просмотры", value: "views" },
|
||||
{ label: "Дате добавления", value: "created_at" },
|
||||
{ label: "Дате обновления", value: "last_chapter_at" },
|
||||
{ label: "Количество глав", value: "chap_count" },
|
||||
{ label: 'Рейтинг', value: 'rate' },
|
||||
{ label: 'Имя', value: 'name' },
|
||||
{ label: 'Просмотры', value: 'views' },
|
||||
{ label: 'Дате добавления', value: 'created_at' },
|
||||
{ label: 'Дате обновления', value: 'last_chapter_at' },
|
||||
{ label: 'Количество глав', value: 'chap_count' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
order: {
|
||||
label: "Порядок",
|
||||
value: "desc",
|
||||
label: 'Порядок',
|
||||
value: 'desc',
|
||||
options: [
|
||||
{ label: "По убыванию", value: "desc" },
|
||||
{ label: "По возрастанию", value: "asc" },
|
||||
{ label: 'По убыванию', value: 'desc' },
|
||||
{ label: 'По возрастанию', value: 'asc' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
label: "Тип",
|
||||
label: 'Тип',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Авторский", value: "14" },
|
||||
{ label: "Английский", value: "13" },
|
||||
{ label: "Китай", value: "12" },
|
||||
{ label: "Корея", value: "11" },
|
||||
{ label: "Фанфик", value: "15" },
|
||||
{ label: "Япония", value: "10" },
|
||||
{ label: 'Авторский', value: '14' },
|
||||
{ label: 'Английский', value: '13' },
|
||||
{ label: 'Китай', value: '12' },
|
||||
{ label: 'Корея', value: '11' },
|
||||
{ label: 'Фанфик', value: '15' },
|
||||
{ label: 'Япония', value: '10' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
format: {
|
||||
label: "Формат выпуска",
|
||||
label: 'Формат выпуска',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "4-кома (Ёнкома)", value: "1" },
|
||||
{ label: "В цвете", value: "4" },
|
||||
{ label: "Веб", value: "6" },
|
||||
{ label: "Вебтун", value: "7" },
|
||||
{ label: "Додзинси", value: "3" },
|
||||
{ label: "Сборник", value: "2" },
|
||||
{ label: "Сингл", value: "5" },
|
||||
{ label: '4-кома (Ёнкома)', value: '1' },
|
||||
{ label: 'В цвете', value: '4' },
|
||||
{ label: 'Веб', value: '6' },
|
||||
{ label: 'Вебтун', value: '7' },
|
||||
{ label: 'Додзинси', value: '3' },
|
||||
{ label: 'Сборник', value: '2' },
|
||||
{ label: 'Сингл', value: '5' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
status: {
|
||||
label: "Статус перевода",
|
||||
label: 'Статус перевода',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Продолжается", value: "1" },
|
||||
{ label: "Завершен", value: "2" },
|
||||
{ label: "Заморожен", value: "3" },
|
||||
{ label: "Заброшен", value: "4" },
|
||||
{ label: 'Продолжается', value: '1' },
|
||||
{ label: 'Завершен', value: '2' },
|
||||
{ label: 'Заморожен', value: '3' },
|
||||
{ label: 'Заброшен', value: '4' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
manga_status: {
|
||||
label: "Статус тайтла",
|
||||
label: 'Статус тайтла',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: "Онгоинг", value: "1" },
|
||||
{ label: "Завершён", value: "2" },
|
||||
{ label: "Анонс", value: "3" },
|
||||
{ label: "Приостановлен", value: "4" },
|
||||
{ label: "Выпуск прекращён", value: "5" },
|
||||
{ label: 'Онгоинг', value: '1' },
|
||||
{ label: 'Завершён', value: '2' },
|
||||
{ label: 'Анонс', value: '3' },
|
||||
{ label: 'Приостановлен', value: '4' },
|
||||
{ label: 'Выпуск прекращён', value: '5' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genres: {
|
||||
label: "Жанры",
|
||||
label: 'Жанры',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "Арт", value: "32" },
|
||||
{ label: "Безумие", value: "91" },
|
||||
{ label: "Боевик", value: "34" },
|
||||
{ label: "Боевые искусства", value: "35" },
|
||||
{ label: "Вампиры", value: "36" },
|
||||
{ label: "Военное", value: "89" },
|
||||
{ label: "Гарем", value: "37" },
|
||||
{ label: "Гендерная интрига", value: "38" },
|
||||
{ label: "Героическое фэнтези", value: "39" },
|
||||
{ label: "Демоны", value: "81" },
|
||||
{ label: "Детектив", value: "40" },
|
||||
{ label: "Детское", value: "88" },
|
||||
{ label: "Дзёсэй", value: "41" },
|
||||
{ label: "Драма", value: "43" },
|
||||
{ label: "Игра", value: "44" },
|
||||
{ label: "Исекай", value: "79" },
|
||||
{ label: "История", value: "45" },
|
||||
{ label: "Киберпанк", value: "46" },
|
||||
{ label: "Кодомо", value: "76" },
|
||||
{ label: "Комедия", value: "47" },
|
||||
{ label: "Космос", value: "83" },
|
||||
{ label: "Магия", value: "85" },
|
||||
{ label: "Махо-сёдзё", value: "48" },
|
||||
{ label: "Машины", value: "90" },
|
||||
{ label: "Меха", value: "49" },
|
||||
{ label: "Мистика", value: "50" },
|
||||
{ label: "Музыка", value: "80" },
|
||||
{ label: "Научная фантастика", value: "51" },
|
||||
{ label: "Омегаверс", value: "77" },
|
||||
{ label: "Пародия", value: "86" },
|
||||
{ label: "Повседневность", value: "52" },
|
||||
{ label: "Полиция", value: "82" },
|
||||
{ label: "Постапокалиптика", value: "53" },
|
||||
{ label: "Приключения", value: "54" },
|
||||
{ label: "Психология", value: "55" },
|
||||
{ label: "Романтика", value: "56" },
|
||||
{ label: "Самурайский боевик", value: "57" },
|
||||
{ label: "Сверхъестественное", value: "58" },
|
||||
{ label: "Сёдзё", value: "59" },
|
||||
{ label: "Сёдзё-ай", value: "60" },
|
||||
{ label: "Сёнэн", value: "61" },
|
||||
{ label: "Сёнэн-ай", value: "62" },
|
||||
{ label: "Спорт", value: "63" },
|
||||
{ label: "Супер сила", value: "87" },
|
||||
{ label: "Сэйнэн", value: "64" },
|
||||
{ label: "Трагедия", value: "65" },
|
||||
{ label: "Триллер", value: "66" },
|
||||
{ label: "Ужасы", value: "67" },
|
||||
{ label: "Фантастика", value: "68" },
|
||||
{ label: "Фэнтези", value: "69" },
|
||||
{ label: "Школа", value: "70" },
|
||||
{ label: "Эротика", value: "71" },
|
||||
{ label: "Этти", value: "72" },
|
||||
{ label: "Юри", value: "73" },
|
||||
{ label: "Яой", value: "74" },
|
||||
{ label: 'Арт', value: '32' },
|
||||
{ label: 'Безумие', value: '91' },
|
||||
{ label: 'Боевик', value: '34' },
|
||||
{ label: 'Боевые искусства', value: '35' },
|
||||
{ label: 'Вампиры', value: '36' },
|
||||
{ label: 'Военное', value: '89' },
|
||||
{ label: 'Гарем', value: '37' },
|
||||
{ label: 'Гендерная интрига', value: '38' },
|
||||
{ label: 'Героическое фэнтези', value: '39' },
|
||||
{ label: 'Демоны', value: '81' },
|
||||
{ label: 'Детектив', value: '40' },
|
||||
{ label: 'Детское', value: '88' },
|
||||
{ label: 'Дзёсэй', value: '41' },
|
||||
{ label: 'Драма', value: '43' },
|
||||
{ label: 'Игра', value: '44' },
|
||||
{ label: 'Исекай', value: '79' },
|
||||
{ label: 'История', value: '45' },
|
||||
{ label: 'Киберпанк', value: '46' },
|
||||
{ label: 'Кодомо', value: '76' },
|
||||
{ label: 'Комедия', value: '47' },
|
||||
{ label: 'Космос', value: '83' },
|
||||
{ label: 'Магия', value: '85' },
|
||||
{ label: 'Махо-сёдзё', value: '48' },
|
||||
{ label: 'Машины', value: '90' },
|
||||
{ label: 'Меха', value: '49' },
|
||||
{ label: 'Мистика', value: '50' },
|
||||
{ label: 'Музыка', value: '80' },
|
||||
{ label: 'Научная фантастика', value: '51' },
|
||||
{ label: 'Омегаверс', value: '77' },
|
||||
{ label: 'Пародия', value: '86' },
|
||||
{ label: 'Повседневность', value: '52' },
|
||||
{ label: 'Полиция', value: '82' },
|
||||
{ label: 'Постапокалиптика', value: '53' },
|
||||
{ label: 'Приключения', value: '54' },
|
||||
{ label: 'Психология', value: '55' },
|
||||
{ label: 'Романтика', value: '56' },
|
||||
{ label: 'Самурайский боевик', value: '57' },
|
||||
{ label: 'Сверхъестественное', value: '58' },
|
||||
{ label: 'Сёдзё', value: '59' },
|
||||
{ label: 'Сёдзё-ай', value: '60' },
|
||||
{ label: 'Сёнэн', value: '61' },
|
||||
{ label: 'Сёнэн-ай', value: '62' },
|
||||
{ label: 'Спорт', value: '63' },
|
||||
{ label: 'Супер сила', value: '87' },
|
||||
{ label: 'Сэйнэн', value: '64' },
|
||||
{ label: 'Трагедия', value: '65' },
|
||||
{ label: 'Триллер', value: '66' },
|
||||
{ label: 'Ужасы', value: '67' },
|
||||
{ label: 'Фантастика', value: '68' },
|
||||
{ label: 'Фэнтези', value: '69' },
|
||||
{ label: 'Школа', value: '70' },
|
||||
{ label: 'Эротика', value: '71' },
|
||||
{ label: 'Этти', value: '72' },
|
||||
{ label: 'Юри', value: '73' },
|
||||
{ label: 'Яой', value: '74' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
tags: {
|
||||
label: "Теги",
|
||||
label: 'Теги',
|
||||
value: { include: [], exclude: [] },
|
||||
options: [
|
||||
{ label: "Авантюристы", value: "328" },
|
||||
{ label: "Антигерой", value: "176" },
|
||||
{ label: "Бессмертные", value: "333" },
|
||||
{ label: "Боги", value: "218" },
|
||||
{ label: "Борьба за власть", value: "309" },
|
||||
{ label: "Брат и сестра", value: "360" },
|
||||
{ label: "Ведьма", value: "339" },
|
||||
{ label: "Видеоигры", value: "204" },
|
||||
{ label: "Виртуальная реальность", value: "214" },
|
||||
{ label: "Владыка демонов", value: "349" },
|
||||
{ label: "Военные", value: "198" },
|
||||
{ label: "Воспоминания из другого мира", value: "310" },
|
||||
{ label: "Выживание", value: "212" },
|
||||
{ label: "ГГ женщина", value: "294" },
|
||||
{ label: "ГГ имба", value: "292" },
|
||||
{ label: "ГГ мужчина", value: "295" },
|
||||
{ label: "ГГ не ояш", value: "325" },
|
||||
{ label: "ГГ не человек", value: "331" },
|
||||
{ label: "ГГ ояш", value: "326" },
|
||||
{ label: "Главный герой бог", value: "324" },
|
||||
{ label: "Глупый ГГ", value: "298" },
|
||||
{ label: "Горничные", value: "171" },
|
||||
{ label: "Гуро", value: "306" },
|
||||
{ label: "Гяру", value: "197" },
|
||||
{ label: "Демоны", value: "157" },
|
||||
{ label: "Драконы", value: "313" },
|
||||
{ label: "Древний мир", value: "317" },
|
||||
{ label: "Зверолюди", value: "163" },
|
||||
{ label: "Зомби", value: "155" },
|
||||
{ label: "Исторические фигуры", value: "323" },
|
||||
{ label: "Кулинария", value: "158" },
|
||||
{ label: "Культивация", value: "161" },
|
||||
{ label: "ЛГБТ", value: "344" },
|
||||
{ label: "ЛитРПГ", value: "319" },
|
||||
{ label: "Лоли", value: "206" },
|
||||
{ label: "Магия", value: "170" },
|
||||
{ label: "Машинный перевод", value: "345" },
|
||||
{ label: "Медицина", value: "159" },
|
||||
{ label: "Межгалактическая война", value: "330" },
|
||||
{ label: "Монстр Девушки", value: "207" },
|
||||
{ label: "Монстры", value: "208" },
|
||||
{ label: "Мрачный мир", value: "316" },
|
||||
{ label: "Музыка", value: "358" },
|
||||
{ label: "Музыка", value: "209" },
|
||||
{ label: "Ниндзя", value: "199" },
|
||||
{ label: "Обратный Гарем", value: "210" },
|
||||
{ label: "Офисные Работники", value: "200" },
|
||||
{ label: "Пираты", value: "341" },
|
||||
{ label: "Подземелья", value: "314" },
|
||||
{ label: "Политика", value: "311" },
|
||||
{ label: "Полиция", value: "201" },
|
||||
{ label: "Преступники / Криминал", value: "205" },
|
||||
{ label: "Призраки / Духи", value: "196" },
|
||||
{ label: "Призыватели", value: "329" },
|
||||
{ label: "Прыжки между мирами", value: "321" },
|
||||
{ label: "Путешествие в другой мир", value: "318" },
|
||||
{ label: "Путешествие во времени", value: "213" },
|
||||
{ label: "Рабы", value: "355" },
|
||||
{ label: "Ранги силы", value: "312" },
|
||||
{ label: "Реинкарнация", value: "154" },
|
||||
{ label: "Самураи", value: "202" },
|
||||
{ label: "Скрытие личности", value: "315" },
|
||||
{ label: "Средневековье", value: "174" },
|
||||
{ label: "Традиционные игры", value: "203" },
|
||||
{ label: "Умный ГГ", value: "303" },
|
||||
{ label: "Характерный рост", value: "332" },
|
||||
{ label: "Хикикомори", value: "167" },
|
||||
{ label: "Эволюция", value: "322" },
|
||||
{ label: "Элементы РПГ", value: "327" },
|
||||
{ label: "Эльфы", value: "217" },
|
||||
{ label: "Якудза", value: "165" },
|
||||
{ label: 'Авантюристы', value: '328' },
|
||||
{ label: 'Антигерой', value: '176' },
|
||||
{ label: 'Бессмертные', value: '333' },
|
||||
{ label: 'Боги', value: '218' },
|
||||
{ label: 'Борьба за власть', value: '309' },
|
||||
{ label: 'Брат и сестра', value: '360' },
|
||||
{ label: 'Ведьма', value: '339' },
|
||||
{ label: 'Видеоигры', value: '204' },
|
||||
{ label: 'Виртуальная реальность', value: '214' },
|
||||
{ label: 'Владыка демонов', value: '349' },
|
||||
{ label: 'Военные', value: '198' },
|
||||
{ label: 'Воспоминания из другого мира', value: '310' },
|
||||
{ label: 'Выживание', value: '212' },
|
||||
{ label: 'ГГ женщина', value: '294' },
|
||||
{ label: 'ГГ имба', value: '292' },
|
||||
{ label: 'ГГ мужчина', value: '295' },
|
||||
{ label: 'ГГ не ояш', value: '325' },
|
||||
{ label: 'ГГ не человек', value: '331' },
|
||||
{ label: 'ГГ ояш', value: '326' },
|
||||
{ label: 'Главный герой бог', value: '324' },
|
||||
{ label: 'Глупый ГГ', value: '298' },
|
||||
{ label: 'Горничные', value: '171' },
|
||||
{ label: 'Гуро', value: '306' },
|
||||
{ label: 'Гяру', value: '197' },
|
||||
{ label: 'Демоны', value: '157' },
|
||||
{ label: 'Драконы', value: '313' },
|
||||
{ label: 'Древний мир', value: '317' },
|
||||
{ label: 'Зверолюди', value: '163' },
|
||||
{ label: 'Зомби', value: '155' },
|
||||
{ label: 'Исторические фигуры', value: '323' },
|
||||
{ label: 'Кулинария', value: '158' },
|
||||
{ label: 'Культивация', value: '161' },
|
||||
{ label: 'ЛГБТ', value: '344' },
|
||||
{ label: 'ЛитРПГ', value: '319' },
|
||||
{ label: 'Лоли', value: '206' },
|
||||
{ label: 'Магия', value: '170' },
|
||||
{ label: 'Машинный перевод', value: '345' },
|
||||
{ label: 'Медицина', value: '159' },
|
||||
{ label: 'Межгалактическая война', value: '330' },
|
||||
{ label: 'Монстр Девушки', value: '207' },
|
||||
{ label: 'Монстры', value: '208' },
|
||||
{ label: 'Мрачный мир', value: '316' },
|
||||
{ label: 'Музыка', value: '358' },
|
||||
{ label: 'Музыка', value: '209' },
|
||||
{ label: 'Ниндзя', value: '199' },
|
||||
{ label: 'Обратный Гарем', value: '210' },
|
||||
{ label: 'Офисные Работники', value: '200' },
|
||||
{ label: 'Пираты', value: '341' },
|
||||
{ label: 'Подземелья', value: '314' },
|
||||
{ label: 'Политика', value: '311' },
|
||||
{ label: 'Полиция', value: '201' },
|
||||
{ label: 'Преступники / Криминал', value: '205' },
|
||||
{ label: 'Призраки / Духи', value: '196' },
|
||||
{ label: 'Призыватели', value: '329' },
|
||||
{ label: 'Прыжки между мирами', value: '321' },
|
||||
{ label: 'Путешествие в другой мир', value: '318' },
|
||||
{ label: 'Путешествие во времени', value: '213' },
|
||||
{ label: 'Рабы', value: '355' },
|
||||
{ label: 'Ранги силы', value: '312' },
|
||||
{ label: 'Реинкарнация', value: '154' },
|
||||
{ label: 'Самураи', value: '202' },
|
||||
{ label: 'Скрытие личности', value: '315' },
|
||||
{ label: 'Средневековье', value: '174' },
|
||||
{ label: 'Традиционные игры', value: '203' },
|
||||
{ label: 'Умный ГГ', value: '303' },
|
||||
{ label: 'Характерный рост', value: '332' },
|
||||
{ label: 'Хикикомори', value: '167' },
|
||||
{ label: 'Эволюция', value: '322' },
|
||||
{ label: 'Элементы РПГ', value: '327' },
|
||||
{ label: 'Эльфы', value: '217' },
|
||||
{ label: 'Якудза', value: '165' },
|
||||
],
|
||||
type: FilterTypes.ExcludableCheckboxGroup,
|
||||
},
|
||||
require_chapters: {
|
||||
label: "Только проекты с главами",
|
||||
label: 'Только проекты с главами',
|
||||
value: true,
|
||||
type: FilterTypes.Switch,
|
||||
},
|
||||
|
||||
+43
-43
@@ -1,43 +1,43 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class RNRF implements Plugin.PluginBase {
|
||||
id = "RNRF";
|
||||
name = "РанобэРФ";
|
||||
site = "https://ранобэ.рф";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/ranoberf/icon.png";
|
||||
id = 'RNRF';
|
||||
name = 'РанобэРФ';
|
||||
site = 'https://ранобэ.рф';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/ranoberf/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/books?order=";
|
||||
let url = this.site + '/books?order=';
|
||||
url += showLatestNovels
|
||||
? "lastPublishedChapter"
|
||||
: filters?.sort?.value || "popular";
|
||||
url += "&page=" + pageNo;
|
||||
? 'lastPublishedChapter'
|
||||
: filters?.sort?.value || 'popular';
|
||||
url += '&page=' + pageNo;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
json.props.pageProps?.totalData?.items?.forEach((novel) =>
|
||||
json.props.pageProps?.totalData?.items?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? this.site + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -49,19 +49,19 @@ class RNRF implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const book = (JSON.parse(jsonRaw || "{}") as response).props.pageProps.book;
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const book = (JSON.parse(jsonRaw || '{}') as response).props.pageProps.book;
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: book?.title || "",
|
||||
name: book?.title || '',
|
||||
cover: book?.verticalImage?.url
|
||||
? this.site + book.verticalImage.url
|
||||
: defaultCover,
|
||||
summary: book?.description,
|
||||
author: book?.author || "",
|
||||
genres: book?.genres.map((item) => item.title).join(", "),
|
||||
status: book?.additionalInfo.includes("Активен")
|
||||
author: book?.author || '',
|
||||
genres: book?.genres.map(item => item.title).join(', '),
|
||||
status: book?.additionalInfo.includes('Активен')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
@@ -73,7 +73,7 @@ class RNRF implements Plugin.PluginBase {
|
||||
chapters.push({
|
||||
name: chapter.title,
|
||||
path: chapter.url,
|
||||
releaseTime: dayjs(chapter.publishedAt).format("LLL"),
|
||||
releaseTime: dayjs(chapter.publishedAt).format('LLL'),
|
||||
chapterNumber: book.chapters.length - chapterIndex,
|
||||
});
|
||||
}
|
||||
@@ -84,22 +84,22 @@ class RNRF implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath).then((res) =>
|
||||
const result = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
let loadedCheerio = parseHTML(result);
|
||||
const jsonRaw = loadedCheerio("#__NEXT_DATA__").html();
|
||||
const json: response = JSON.parse(jsonRaw || "{}");
|
||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
||||
const json: response = JSON.parse(jsonRaw || '{}');
|
||||
|
||||
loadedCheerio = parseHTML(
|
||||
json.props.pageProps?.chapter?.content?.text || "",
|
||||
json.props.pageProps?.chapter?.content?.text || '',
|
||||
);
|
||||
|
||||
loadedCheerio("img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio('img').each((index, element) => {
|
||||
if (!loadedCheerio(element).attr('src')?.startsWith('http')) {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -113,13 +113,13 @@ class RNRF implements Plugin.PluginBase {
|
||||
const body = (await result.json()) as { items: Item[] };
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.items.forEach((novel) =>
|
||||
body.items.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel?.verticalImage?.url
|
||||
? this.site + novel.verticalImage.url
|
||||
: defaultCover,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -130,13 +130,13 @@ class RNRF implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "popular",
|
||||
label: 'Сортировка',
|
||||
value: 'popular',
|
||||
options: [
|
||||
{ label: "Рейтинг", value: "popular" },
|
||||
{ label: "Дате добавления", value: "new" },
|
||||
{ label: "Дате обновления", value: "lastPublishedChapter" },
|
||||
{ label: "Законченные", value: "completed" },
|
||||
{ label: 'Рейтинг', value: 'popular' },
|
||||
{ label: 'Дате добавления', value: 'new' },
|
||||
{ label: 'Дате обновления', value: 'lastPublishedChapter' },
|
||||
{ label: 'Законченные', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
+799
-791
File diff suppressed because it is too large
Load Diff
+244
-241
@@ -1,17 +1,17 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import qs from "qs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
import qs from 'qs';
|
||||
|
||||
class Zelluloza implements Plugin.PluginBase {
|
||||
id = "zelluloza";
|
||||
name = "Целлюлоза";
|
||||
site = "https://zelluloza.ru";
|
||||
version = "1.0.0";
|
||||
icon = "src/ru/zelluloza/icon.png";
|
||||
id = 'zelluloza';
|
||||
name = 'Целлюлоза';
|
||||
site = 'https://zelluloza.ru';
|
||||
version = '1.0.0';
|
||||
icon = 'src/ru/zelluloza/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -20,36 +20,36 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const sort = showLatestNovels ? "0.0" : filters?.sort?.value || "6.0";
|
||||
const genres = filters?.genres?.value || "";
|
||||
const sort = showLatestNovels ? '0.0' : filters?.sort?.value || '6.0';
|
||||
const genres = filters?.genres?.value || '';
|
||||
|
||||
const body = await fetchApi(this.site + "/ajaxcall/", {
|
||||
const body = await fetchApi(this.site + '/ajaxcall/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site + "/top/freebooks/",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.site + '/top/freebooks/',
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
op: "morebooks",
|
||||
par1: "",
|
||||
op: 'morebooks',
|
||||
par1: '',
|
||||
par2: `125:0:${genres}:0.${sort}.0.0.0.0.0.0.0.0.0.1.s.1..:${pageNo}`,
|
||||
par4: "",
|
||||
par4: '',
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('div[style="display: flex"]').each((index, element) => {
|
||||
novels.push({
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || "",
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || '',
|
||||
cover:
|
||||
this.site +
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("src"),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('src'),
|
||||
path: (
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("onclick") ||
|
||||
""
|
||||
).replace(/\D/g, ""),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('onclick') ||
|
||||
''
|
||||
).replace(/\D/g, ''),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,20 +57,21 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true))
|
||||
.then((res) => res.text());
|
||||
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio('span[itemprop="name"]').text(),
|
||||
cover: this.site + loadedCheerio('img[class="shadow"]').attr("src"),
|
||||
genres: loadedCheerio(".gnres").text()?.split?.(": ")?.[1],
|
||||
cover: this.site + loadedCheerio('img[class="shadow"]').attr('src'),
|
||||
genres: loadedCheerio('.gnres').text()?.split?.(': ')?.[1],
|
||||
summary:
|
||||
loadedCheerio("#bann_full").text() ||
|
||||
loadedCheerio("#bann_short").text(),
|
||||
author: loadedCheerio(".author_link").text(),
|
||||
status: loadedCheerio(".tech_decription").text().includes("Пишется")
|
||||
loadedCheerio('#bann_full').text() ||
|
||||
loadedCheerio('#bann_short').text(),
|
||||
author: loadedCheerio('.author_link').text(),
|
||||
status: loadedCheerio('.tech_decription').text().includes('Пишется')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
@@ -78,7 +79,9 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('ul[class="g0"] div[class="w800_m"]').each(
|
||||
(chapterIndex, element) => {
|
||||
const isFree = loadedCheerio(element).find('div[class="chaptfree"]').length;
|
||||
const isFree = loadedCheerio(element).find(
|
||||
'div[class="chaptfree"]',
|
||||
).length;
|
||||
if (isFree) {
|
||||
const chapter = loadedCheerio(element).find('a[class="chptitle"]');
|
||||
const releaseDate = loadedCheerio(element)
|
||||
@@ -86,7 +89,7 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
.text();
|
||||
chapters.push({
|
||||
name: chapter.text().trim(),
|
||||
path: (chapter.attr("href") || "").split("/").slice(2, 4).join("/"),
|
||||
path: (chapter.attr('href') || '').split('/').slice(2, 4).join('/'),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: chapterIndex + 1,
|
||||
});
|
||||
@@ -99,33 +102,33 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const [bookID, chapterID] = chapterPath.split("/");
|
||||
const body = await fetchApi(this.site + "/ajaxcall/", {
|
||||
const [bookID, chapterID] = chapterPath.split('/');
|
||||
const body = await fetchApi(this.site + '/ajaxcall/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.resolveUrl(chapterPath),
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
op: "getbook",
|
||||
op: 'getbook',
|
||||
par1: bookID,
|
||||
par2: chapterID,
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
|
||||
const encrypted = body.split("<END>")[0].split("\n");
|
||||
const encrypted = body.split('<END>')[0].split('\n');
|
||||
const decrypted = encrypted
|
||||
.map((str) => decrypt(str))
|
||||
.join("")
|
||||
.replace(/\r/g, "")
|
||||
.map(str => decrypt(str))
|
||||
.join('')
|
||||
.replace(/\r/g, '')
|
||||
.trim();
|
||||
|
||||
const chapterText = decrypted //cosmetic filters
|
||||
.replace(/\[\*]([\s\S]*?)\[\/]/g, "<b>$1</b>")
|
||||
.replace(/\[_]([\s\S]*?)\[\/]/g, "<u>$1</u>")
|
||||
.replace(/\[-]([\s\S]*?)\[\/]/g, "<s>$1</s>")
|
||||
.replace(/\[~]([\s\S]*?)\[\/]/g, "<i>$1</i>"); /*
|
||||
.replace(/\[\*]([\s\S]*?)\[\/]/g, '<b>$1</b>')
|
||||
.replace(/\[_]([\s\S]*?)\[\/]/g, '<u>$1</u>')
|
||||
.replace(/\[-]([\s\S]*?)\[\/]/g, '<s>$1</s>')
|
||||
.replace(/\[~]([\s\S]*?)\[\/]/g, '<i>$1</i>'); /*
|
||||
.replace(/\[!]([\s\S]*?)\[\/]/g, '<span style="font-family: Times New Roman">$1</span>')
|
||||
.replace(/\[ctr]([\s\S]*?)\[\/]/g, "<center>$1</center>")
|
||||
.replace(/\[rht]([\s\S]*?)\[\/]/g, '<span style="text-align: right">$1</span>')
|
||||
@@ -143,47 +146,47 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const body = await fetchApi(this.site + "/ajaxcall/", {
|
||||
const body = await fetchApi(this.site + '/ajaxcall/', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site + "/search/done/#result",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.site + '/search/done/#result',
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
op: "morebooks",
|
||||
op: 'morebooks',
|
||||
par1: searchTerm,
|
||||
par2: "206:0:0:0.0.0.0.0.0.0.10.0.0.0.0.0..0..:" + pageNo,
|
||||
par4: "",
|
||||
par2: '206:0:0:0.0.0.0.0.0.0.10.0.0.0.0.0..0..:' + pageNo,
|
||||
par4: '',
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio('div[style="display: flex"]').each((index, element) => {
|
||||
novels.push({
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || "",
|
||||
name: loadedCheerio(element).find('span[itemprop="name"]').text() || '',
|
||||
cover:
|
||||
this.site +
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("src"),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('src'),
|
||||
path: (
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr("onclick") ||
|
||||
""
|
||||
).replace(/\D/g, ""),
|
||||
loadedCheerio(element).find('img[class="shadow"]').attr('onclick') ||
|
||||
''
|
||||
).replace(/\D/g, ''),
|
||||
});
|
||||
});
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const [, day, month, year, hour, minute] =
|
||||
dateString.match(/(\d{2})\.(\d{2})\.(\d{4})г (\d{2}):(\d{2})/) || [];
|
||||
|
||||
if (day && month && year && hour && minute) {
|
||||
return dayjs(
|
||||
year + "-" + month + "-" + day + " " + hour + ":" + minute,
|
||||
).format("LLL");
|
||||
year + '-' + month + '-' + day + ' ' + hour + ':' + minute,
|
||||
).format('LLL');
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -191,167 +194,167 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
|
||||
fetchImage = fetchFile;
|
||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||
this.site + "/books/" + path;
|
||||
this.site + '/books/' + path;
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: "Сортировка",
|
||||
value: "6.0",
|
||||
label: 'Сортировка',
|
||||
value: '6.0',
|
||||
options: [
|
||||
{ label: "Топ-недели", value: "6.0" },
|
||||
{ label: "Бестселеры", value: "5.0" },
|
||||
{ label: "Популярные у читателей", value: "2.0" },
|
||||
{ label: "Книжные новинки", value: "0.0" },
|
||||
{ label: "Законченные книги", value: "0.1" },
|
||||
{ label: 'Топ-недели', value: '6.0' },
|
||||
{ label: 'Бестселеры', value: '5.0' },
|
||||
{ label: 'Популярные у читателей', value: '2.0' },
|
||||
{ label: 'Книжные новинки', value: '0.0' },
|
||||
{ label: 'Законченные книги', value: '0.1' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
label: "Жанры",
|
||||
value: "",
|
||||
label: 'Жанры',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Все", value: "" },
|
||||
{ label: "16+", value: "204" },
|
||||
{ label: "18+", value: "100" },
|
||||
{ label: "Авантюра", value: "347" },
|
||||
{ label: "Альтернативная история", value: "55" },
|
||||
{ label: "Ангст", value: "463" },
|
||||
{ label: "Аниме", value: "405" },
|
||||
{ label: "Антиутопия", value: "21" },
|
||||
{ label: "Апокалипсис", value: "368" },
|
||||
{ label: "Аудиокнига", value: "391" },
|
||||
{ label: "Байки", value: "435" },
|
||||
{ label: "Бизнес-книги", value: "522" },
|
||||
{ label: "Биография", value: "52" },
|
||||
{ label: "Биопанк", value: "379" },
|
||||
{ label: "Боевая фантастика", value: "512" },
|
||||
{ label: "Боевик", value: "61" },
|
||||
{ label: "Боевые искусства", value: "142" },
|
||||
{ label: "Бояръ-аниме", value: "433" },
|
||||
{ label: "Вбоквелл", value: "372" },
|
||||
{ label: "Вестерн", value: "96" },
|
||||
{ label: "Война миров", value: "137" },
|
||||
{ label: "Восточное фэнтези", value: "505" },
|
||||
{ label: "Городское фентези", value: "63" },
|
||||
{ label: "Городское фэнтези", value: "520" },
|
||||
{ label: "Готика", value: "83" },
|
||||
{ label: "Детективы", value: "4" },
|
||||
{ label: "Детские книги", value: "8" },
|
||||
{ label: "Документальные книги", value: "351" },
|
||||
{ label: "Дом и дача", value: "534" },
|
||||
{ label: "Дорожные истории", value: "41" },
|
||||
{ label: "Драма", value: "47" },
|
||||
{ label: "Древняя литература", value: "529" },
|
||||
{ label: "Женский психологический роман", value: "186" },
|
||||
{ label: "Женский роман", value: "141" },
|
||||
{ label: "Зарубежная классика", value: "524" },
|
||||
{ label: "Здоровье и красота", value: "533" },
|
||||
{ label: "Изучение языков", value: "537" },
|
||||
{ label: "Иронический детектив", value: "85" },
|
||||
{ label: "Исследования", value: "156" },
|
||||
{ label: "Историческая литература", value: "3" },
|
||||
{ label: "Исторический любовный роман", value: "518" },
|
||||
{ label: "Историческое фэнтези", value: "90" },
|
||||
{ label: "Киберпанк", value: "7" },
|
||||
{ label: "Классическая литература", value: "9" },
|
||||
{ label: "Книга-игра", value: "371" },
|
||||
{ label: "Книги для родителей", value: "535" },
|
||||
{ label: "Комедия", value: "80" },
|
||||
{ label: "Конкурсные сборники", value: "349" },
|
||||
{ label: "Короткий любовный роман", value: "517" },
|
||||
{ label: "Космическая фантастика", value: "513" },
|
||||
{ label: "Космоопера", value: "58" },
|
||||
{ label: "Криминальная проза", value: "35" },
|
||||
{ label: "Криптоистория", value: "400" },
|
||||
{ label: "Ксенофантастика", value: "102" },
|
||||
{ label: "Культура и искусство", value: "536" },
|
||||
{ label: "Легенды", value: "161" },
|
||||
{ label: "Лирика", value: "49" },
|
||||
{ label: "ЛитРПГ", value: "62" },
|
||||
{ label: "Любовная фантастика", value: "516" },
|
||||
{ label: "Любовное фэнтези", value: "515" },
|
||||
{ label: "Любовный роман", value: "46" },
|
||||
{ label: "Магический реализм", value: "42" },
|
||||
{ label: "Матриархат", value: "385" },
|
||||
{ label: "Медицина", value: "57" },
|
||||
{ label: "Мелодрама", value: "45" },
|
||||
{ label: "Мемуары", value: "532" },
|
||||
{ label: "Метафизика", value: "430" },
|
||||
{ label: "Милитари", value: "126" },
|
||||
{ label: "Мистика", value: "22" },
|
||||
{ label: "Морские приключения", value: "211" },
|
||||
{ label: "Мотивация", value: "135" },
|
||||
{ label: "Научная фантастика", value: "122" },
|
||||
{ label: "Научно-популярная", value: "43" },
|
||||
{ label: "Ненаучная фантастика", value: "387" },
|
||||
{ label: "Новелла", value: "498" },
|
||||
{ label: "Нон-фикшн", value: "507" },
|
||||
{ label: "Нуар", value: "121" },
|
||||
{ label: "О войне", value: "136" },
|
||||
{ label: "Оккультизм", value: "424" },
|
||||
{ label: "Параллельные миры", value: "438" },
|
||||
{ label: "Пародия", value: "108" },
|
||||
{ label: "Подростковая фантастика", value: "27" },
|
||||
{ label: "Подростковый роман", value: "388" },
|
||||
{ label: "Подростковый ужастик", value: "110" },
|
||||
{ label: "Постапокалипсис", value: "82" },
|
||||
{ label: "Постапокалиптика", value: "67" },
|
||||
{ label: "Поэзия", value: "44" },
|
||||
{ label: "Превращение", value: "170" },
|
||||
{ label: "Приключение", value: "497" },
|
||||
{ label: "Приключения", value: "5" },
|
||||
{ label: "Природа и животные", value: "538" },
|
||||
{ label: "Проза", value: "95" },
|
||||
{ label: "Производственный роман", value: "148" },
|
||||
{ label: "Психология", value: "31" },
|
||||
{ label: "Путешествия", value: "383" },
|
||||
{ label: "Рассказы", value: "101" },
|
||||
{ label: "Реализм", value: "422" },
|
||||
{ label: "РеалРПГ", value: "442" },
|
||||
{ label: "Религия", value: "188" },
|
||||
{ label: "Робинзонада", value: "434" },
|
||||
{ label: "Романтика", value: "130" },
|
||||
{ label: "Романтическая фантастика", value: "501" },
|
||||
{ label: "Русская классика", value: "523" },
|
||||
{ label: "Рыцарский роман", value: "112" },
|
||||
{ label: "Символизм", value: "439" },
|
||||
{ label: "Сказки", value: "93" },
|
||||
{ label: "Славянское фэнтези", value: "508" },
|
||||
{ label: "Сновидения", value: "428" },
|
||||
{ label: "Современный любовный роман", value: "519" },
|
||||
{ label: "Социально-психологическая фантастика", value: "98" },
|
||||
{ label: "Спорт", value: "147" },
|
||||
{ label: "Справочники", value: "528" },
|
||||
{ label: "Стимпанк", value: "56" },
|
||||
{ label: "Субкультура", value: "209" },
|
||||
{ label: "Сюрреализм", value: "26" },
|
||||
{ label: "Тёмное фэнтези", value: "437" },
|
||||
{ label: "Техномагия", value: "159" },
|
||||
{ label: "Триллер", value: "6" },
|
||||
{ label: "Трэш", value: "162" },
|
||||
{ label: "Ужасы", value: "531" },
|
||||
{ label: "Утопия", value: "191" },
|
||||
{ label: "Учебник", value: "411" },
|
||||
{ label: "Фантастика", value: "1" },
|
||||
{ label: "Фантастический боевик", value: "38" },
|
||||
{ label: "Фанфик", value: "111" },
|
||||
{ label: "Философия", value: "461" },
|
||||
{ label: "Фурри", value: "394" },
|
||||
{ label: "Фэнтези", value: "2" },
|
||||
{ label: "Хентай", value: "441" },
|
||||
{ label: "Хобби и досуг", value: "530" },
|
||||
{ label: "Хоррор", value: "86" },
|
||||
{ label: "Чёрный юмор", value: "73" },
|
||||
{ label: "Эзотерика", value: "30" },
|
||||
{ label: "Экшн", value: "94" },
|
||||
{ label: "Эпическое фэнтези", value: "499" },
|
||||
{ label: "Эротика", value: "19" },
|
||||
{ label: "Эссе", value: "353" },
|
||||
{ label: "ЭТТИ", value: "502" },
|
||||
{ label: "Юмористическая литература", value: "20" },
|
||||
{ label: "Юмористическая фантастика", value: "514" },
|
||||
{ label: "Юмористическое фэнтези", value: "521" },
|
||||
{ label: "EVE - Миры Содружества", value: "99" },
|
||||
{ label: 'Все', value: '' },
|
||||
{ label: '16+', value: '204' },
|
||||
{ label: '18+', value: '100' },
|
||||
{ label: 'Авантюра', value: '347' },
|
||||
{ label: 'Альтернативная история', value: '55' },
|
||||
{ label: 'Ангст', value: '463' },
|
||||
{ label: 'Аниме', value: '405' },
|
||||
{ label: 'Антиутопия', value: '21' },
|
||||
{ label: 'Апокалипсис', value: '368' },
|
||||
{ label: 'Аудиокнига', value: '391' },
|
||||
{ label: 'Байки', value: '435' },
|
||||
{ label: 'Бизнес-книги', value: '522' },
|
||||
{ label: 'Биография', value: '52' },
|
||||
{ label: 'Биопанк', value: '379' },
|
||||
{ label: 'Боевая фантастика', value: '512' },
|
||||
{ label: 'Боевик', value: '61' },
|
||||
{ label: 'Боевые искусства', value: '142' },
|
||||
{ label: 'Бояръ-аниме', value: '433' },
|
||||
{ label: 'Вбоквелл', value: '372' },
|
||||
{ label: 'Вестерн', value: '96' },
|
||||
{ label: 'Война миров', value: '137' },
|
||||
{ label: 'Восточное фэнтези', value: '505' },
|
||||
{ label: 'Городское фентези', value: '63' },
|
||||
{ label: 'Городское фэнтези', value: '520' },
|
||||
{ label: 'Готика', value: '83' },
|
||||
{ label: 'Детективы', value: '4' },
|
||||
{ label: 'Детские книги', value: '8' },
|
||||
{ label: 'Документальные книги', value: '351' },
|
||||
{ label: 'Дом и дача', value: '534' },
|
||||
{ label: 'Дорожные истории', value: '41' },
|
||||
{ label: 'Драма', value: '47' },
|
||||
{ label: 'Древняя литература', value: '529' },
|
||||
{ label: 'Женский психологический роман', value: '186' },
|
||||
{ label: 'Женский роман', value: '141' },
|
||||
{ label: 'Зарубежная классика', value: '524' },
|
||||
{ label: 'Здоровье и красота', value: '533' },
|
||||
{ label: 'Изучение языков', value: '537' },
|
||||
{ label: 'Иронический детектив', value: '85' },
|
||||
{ label: 'Исследования', value: '156' },
|
||||
{ label: 'Историческая литература', value: '3' },
|
||||
{ label: 'Исторический любовный роман', value: '518' },
|
||||
{ label: 'Историческое фэнтези', value: '90' },
|
||||
{ label: 'Киберпанк', value: '7' },
|
||||
{ label: 'Классическая литература', value: '9' },
|
||||
{ label: 'Книга-игра', value: '371' },
|
||||
{ label: 'Книги для родителей', value: '535' },
|
||||
{ label: 'Комедия', value: '80' },
|
||||
{ label: 'Конкурсные сборники', value: '349' },
|
||||
{ label: 'Короткий любовный роман', value: '517' },
|
||||
{ label: 'Космическая фантастика', value: '513' },
|
||||
{ label: 'Космоопера', value: '58' },
|
||||
{ label: 'Криминальная проза', value: '35' },
|
||||
{ label: 'Криптоистория', value: '400' },
|
||||
{ label: 'Ксенофантастика', value: '102' },
|
||||
{ label: 'Культура и искусство', value: '536' },
|
||||
{ label: 'Легенды', value: '161' },
|
||||
{ label: 'Лирика', value: '49' },
|
||||
{ label: 'ЛитРПГ', value: '62' },
|
||||
{ label: 'Любовная фантастика', value: '516' },
|
||||
{ label: 'Любовное фэнтези', value: '515' },
|
||||
{ label: 'Любовный роман', value: '46' },
|
||||
{ label: 'Магический реализм', value: '42' },
|
||||
{ label: 'Матриархат', value: '385' },
|
||||
{ label: 'Медицина', value: '57' },
|
||||
{ label: 'Мелодрама', value: '45' },
|
||||
{ label: 'Мемуары', value: '532' },
|
||||
{ label: 'Метафизика', value: '430' },
|
||||
{ label: 'Милитари', value: '126' },
|
||||
{ label: 'Мистика', value: '22' },
|
||||
{ label: 'Морские приключения', value: '211' },
|
||||
{ label: 'Мотивация', value: '135' },
|
||||
{ label: 'Научная фантастика', value: '122' },
|
||||
{ label: 'Научно-популярная', value: '43' },
|
||||
{ label: 'Ненаучная фантастика', value: '387' },
|
||||
{ label: 'Новелла', value: '498' },
|
||||
{ label: 'Нон-фикшн', value: '507' },
|
||||
{ label: 'Нуар', value: '121' },
|
||||
{ label: 'О войне', value: '136' },
|
||||
{ label: 'Оккультизм', value: '424' },
|
||||
{ label: 'Параллельные миры', value: '438' },
|
||||
{ label: 'Пародия', value: '108' },
|
||||
{ label: 'Подростковая фантастика', value: '27' },
|
||||
{ label: 'Подростковый роман', value: '388' },
|
||||
{ label: 'Подростковый ужастик', value: '110' },
|
||||
{ label: 'Постапокалипсис', value: '82' },
|
||||
{ label: 'Постапокалиптика', value: '67' },
|
||||
{ label: 'Поэзия', value: '44' },
|
||||
{ label: 'Превращение', value: '170' },
|
||||
{ label: 'Приключение', value: '497' },
|
||||
{ label: 'Приключения', value: '5' },
|
||||
{ label: 'Природа и животные', value: '538' },
|
||||
{ label: 'Проза', value: '95' },
|
||||
{ label: 'Производственный роман', value: '148' },
|
||||
{ label: 'Психология', value: '31' },
|
||||
{ label: 'Путешествия', value: '383' },
|
||||
{ label: 'Рассказы', value: '101' },
|
||||
{ label: 'Реализм', value: '422' },
|
||||
{ label: 'РеалРПГ', value: '442' },
|
||||
{ label: 'Религия', value: '188' },
|
||||
{ label: 'Робинзонада', value: '434' },
|
||||
{ label: 'Романтика', value: '130' },
|
||||
{ label: 'Романтическая фантастика', value: '501' },
|
||||
{ label: 'Русская классика', value: '523' },
|
||||
{ label: 'Рыцарский роман', value: '112' },
|
||||
{ label: 'Символизм', value: '439' },
|
||||
{ label: 'Сказки', value: '93' },
|
||||
{ label: 'Славянское фэнтези', value: '508' },
|
||||
{ label: 'Сновидения', value: '428' },
|
||||
{ label: 'Современный любовный роман', value: '519' },
|
||||
{ label: 'Социально-психологическая фантастика', value: '98' },
|
||||
{ label: 'Спорт', value: '147' },
|
||||
{ label: 'Справочники', value: '528' },
|
||||
{ label: 'Стимпанк', value: '56' },
|
||||
{ label: 'Субкультура', value: '209' },
|
||||
{ label: 'Сюрреализм', value: '26' },
|
||||
{ label: 'Тёмное фэнтези', value: '437' },
|
||||
{ label: 'Техномагия', value: '159' },
|
||||
{ label: 'Триллер', value: '6' },
|
||||
{ label: 'Трэш', value: '162' },
|
||||
{ label: 'Ужасы', value: '531' },
|
||||
{ label: 'Утопия', value: '191' },
|
||||
{ label: 'Учебник', value: '411' },
|
||||
{ label: 'Фантастика', value: '1' },
|
||||
{ label: 'Фантастический боевик', value: '38' },
|
||||
{ label: 'Фанфик', value: '111' },
|
||||
{ label: 'Философия', value: '461' },
|
||||
{ label: 'Фурри', value: '394' },
|
||||
{ label: 'Фэнтези', value: '2' },
|
||||
{ label: 'Хентай', value: '441' },
|
||||
{ label: 'Хобби и досуг', value: '530' },
|
||||
{ label: 'Хоррор', value: '86' },
|
||||
{ label: 'Чёрный юмор', value: '73' },
|
||||
{ label: 'Эзотерика', value: '30' },
|
||||
{ label: 'Экшн', value: '94' },
|
||||
{ label: 'Эпическое фэнтези', value: '499' },
|
||||
{ label: 'Эротика', value: '19' },
|
||||
{ label: 'Эссе', value: '353' },
|
||||
{ label: 'ЭТТИ', value: '502' },
|
||||
{ label: 'Юмористическая литература', value: '20' },
|
||||
{ label: 'Юмористическая фантастика', value: '514' },
|
||||
{ label: 'Юмористическое фэнтези', value: '521' },
|
||||
{ label: 'EVE - Миры Содружества', value: '99' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
@@ -359,26 +362,26 @@ class Zelluloza implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
const alphabet: { [key: string]: string } = {
|
||||
"~": "0",
|
||||
"H": "1",
|
||||
"^": "2",
|
||||
"@": "3",
|
||||
"f": "4",
|
||||
"0": "5",
|
||||
"5": "6",
|
||||
"n": "7",
|
||||
"r": "8",
|
||||
"=": "9",
|
||||
"W": "a",
|
||||
"L": "b",
|
||||
"7": "c",
|
||||
" ": "d",
|
||||
"u": "e",
|
||||
"c": "f",
|
||||
'~': '0',
|
||||
'H': '1',
|
||||
'^': '2',
|
||||
'@': '3',
|
||||
'f': '4',
|
||||
'0': '5',
|
||||
'5': '6',
|
||||
'n': '7',
|
||||
'r': '8',
|
||||
'=': '9',
|
||||
'W': 'a',
|
||||
'L': 'b',
|
||||
'7': 'c',
|
||||
' ': 'd',
|
||||
'u': 'e',
|
||||
'c': 'f',
|
||||
};
|
||||
|
||||
function decrypt(encrypt: string) {
|
||||
if (!encrypt) return "";
|
||||
if (!encrypt) return '';
|
||||
const hexArray = [];
|
||||
|
||||
for (let j = 0; j < encrypt.length; j += 2) {
|
||||
@@ -387,12 +390,12 @@ function decrypt(encrypt: string) {
|
||||
hexArray.push(alphabet[firstChar] + alphabet[secondChar]);
|
||||
}
|
||||
|
||||
return "<p>" + hexToUtf8(hexArray) + "</p>";
|
||||
return '<p>' + hexToUtf8(hexArray) + '</p>';
|
||||
}
|
||||
|
||||
function hexToUtf8(hexArray: string[]) {
|
||||
let index = 0;
|
||||
let result = "";
|
||||
let result = '';
|
||||
|
||||
while (index < hexArray.length) {
|
||||
const currentHex = parseInt(hexArray[index], 16) & 0xff;
|
||||
@@ -401,13 +404,13 @@ function hexToUtf8(hexArray: string[]) {
|
||||
if (currentHex < 16) {
|
||||
switch (currentHex) {
|
||||
case 9:
|
||||
result += " ";
|
||||
result += ' ';
|
||||
break;
|
||||
case 13:
|
||||
result += "\r";
|
||||
result += '\r';
|
||||
break;
|
||||
case 10:
|
||||
result += "\n";
|
||||
result += '\n';
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
class HasulTL implements Plugin.PluginBase {
|
||||
id = "HasuTL";
|
||||
name = "Hasu Translations";
|
||||
icon = "src/es/hasutl/icon.png";
|
||||
site = "https://hasutl.wordpress.com/";
|
||||
id = 'HasuTL';
|
||||
name = 'Hasu Translations';
|
||||
icon = 'src/es/hasutl/icon.png';
|
||||
site = 'https://hasutl.wordpress.com/';
|
||||
filters?: Filters | undefined;
|
||||
version = "1.0.0";
|
||||
baseUrl = this.site
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl + "light-novels-activas/";
|
||||
version = '1.0.0';
|
||||
baseUrl = this.site;
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl + 'light-novels-activas/';
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -21,13 +24,13 @@ class HasulTL implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("div.wp-block-columns").each(function () {
|
||||
const novelName = loadedCheerio(this).find(".wp-block-button").text();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
loadedCheerio('div.wp-block-columns').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.wp-block-button').text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this)
|
||||
.find(".wp-block-button > a")
|
||||
.attr("href");
|
||||
.find('.wp-block-button > a')
|
||||
.attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -56,23 +59,23 @@ class HasulTL implements Plugin.PluginBase {
|
||||
|
||||
novel.url = novelUrl;
|
||||
|
||||
novel.name = loadedCheerio(".post-header").text();
|
||||
novel.name = loadedCheerio('.post-header').text();
|
||||
|
||||
novel.cover = loadedCheerio(".featured-media > img").attr("src");
|
||||
novel.cover = loadedCheerio('.featured-media > img').attr('src');
|
||||
|
||||
let novelSummary = loadedCheerio(".post-content").find("p").html()!;
|
||||
let novelSummary = loadedCheerio('.post-content').find('p').html()!;
|
||||
novel.summary = novelSummary;
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".wp-block-media-text__content")
|
||||
.find("a")
|
||||
loadedCheerio('.wp-block-media-text__content')
|
||||
.find('a')
|
||||
.each(function () {
|
||||
const chapterName = loadedCheerio(this).text().trim();
|
||||
|
||||
const releaseDate = null;
|
||||
|
||||
let chapterUrl = loadedCheerio(this).attr("href");
|
||||
let chapterUrl = loadedCheerio(this).attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -97,11 +100,14 @@ class HasulTL implements Plugin.PluginBase {
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio(".post-content").html() || '';
|
||||
let chapterText = loadedCheerio('.post-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.baseUrl}?s=${searchTerm}&post_type=wp-manga`;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -111,16 +117,16 @@ class HasulTL implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".post-container").each(function () {
|
||||
const novelName = loadedCheerio(this).find(".post-header").text();
|
||||
loadedCheerio('.post-container').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.post-header').text();
|
||||
if (
|
||||
!novelName.includes("Cap") &&
|
||||
!novelName.includes("Vol") &&
|
||||
!novelName.includes("Light Novels")
|
||||
!novelName.includes('Cap') &&
|
||||
!novelName.includes('Vol') &&
|
||||
!novelName.includes('Light Novels')
|
||||
) {
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -139,7 +145,6 @@ class HasulTL implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new HasulTL();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
class Ligera implements Plugin.PluginBase {
|
||||
id = "ligera.com";
|
||||
name = "Novelas Ligera";
|
||||
icon = "src/es/novelasligera/icon.png";
|
||||
site = "https://novelasligera.com/";
|
||||
id = 'ligera.com';
|
||||
name = 'Novelas Ligera';
|
||||
icon = 'src/es/novelasligera/icon.png';
|
||||
site = 'https://novelasligera.com/';
|
||||
filters?: Filters | undefined;
|
||||
version = "1.0.0";
|
||||
version = '1.0.0';
|
||||
baseUrl = this.site;
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -21,16 +24,16 @@ class Ligera implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".elementor-column").each(function () {
|
||||
loadedCheerio('.elementor-column').each(function () {
|
||||
const novelName = loadedCheerio(this)
|
||||
.find(".widget-image-caption.wp-caption-text")
|
||||
.find('.widget-image-caption.wp-caption-text')
|
||||
.text();
|
||||
if (novelName) {
|
||||
const novelCover = loadedCheerio(this)
|
||||
.find("a > img")
|
||||
.attr("data-lazy-src");
|
||||
.find('a > img')
|
||||
.attr('data-lazy-src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
@@ -45,7 +48,7 @@ class Ligera implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const url = this.baseUrl + "novela/" + novelUrl;
|
||||
const url = this.baseUrl + 'novela/' + novelUrl;
|
||||
|
||||
// console.log(url);
|
||||
|
||||
@@ -56,50 +59,48 @@ class Ligera implements Plugin.PluginBase {
|
||||
|
||||
let novel: Plugin.SourceNovel = { url };
|
||||
|
||||
novel.name = loadedCheerio("h1").text();
|
||||
novel.name = loadedCheerio('h1').text();
|
||||
|
||||
novel.cover = loadedCheerio(".elementor-widget-container")
|
||||
.find("img")
|
||||
.attr("src");
|
||||
novel.cover = loadedCheerio('.elementor-widget-container')
|
||||
.find('img')
|
||||
.attr('src');
|
||||
|
||||
loadedCheerio(".elementor-row")
|
||||
.find("p")
|
||||
loadedCheerio('.elementor-row')
|
||||
.find('p')
|
||||
.each(function () {
|
||||
if (loadedCheerio(this).text().includes("Autor:")) {
|
||||
if (loadedCheerio(this).text().includes('Autor:')) {
|
||||
novel.author = loadedCheerio(this)
|
||||
.text()
|
||||
.replace("Autor:", "")
|
||||
.replace('Autor:', '')
|
||||
.trim();
|
||||
}
|
||||
if (loadedCheerio(this).text().includes("Estado:")) {
|
||||
if (loadedCheerio(this).text().includes('Estado:')) {
|
||||
novel.status = loadedCheerio(this)
|
||||
.text()
|
||||
.replace("Estado: ", "")
|
||||
.replace('Estado: ', '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
if (loadedCheerio(this).text().includes("Género:")) {
|
||||
loadedCheerio(this).find("span").remove();
|
||||
novel.genres = loadedCheerio(this)
|
||||
.text()
|
||||
.replace(/,\s/g, ",");
|
||||
if (loadedCheerio(this).text().includes('Género:')) {
|
||||
loadedCheerio(this).find('span').remove();
|
||||
novel.genres = loadedCheerio(this).text().replace(/,\s/g, ',');
|
||||
}
|
||||
});
|
||||
|
||||
novel.summary = loadedCheerio(
|
||||
".elementor-text-editor.elementor-clearfix"
|
||||
'.elementor-text-editor.elementor-clearfix',
|
||||
).text();
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".elementor-accordion-item").remove();
|
||||
loadedCheerio('.elementor-accordion-item').remove();
|
||||
|
||||
loadedCheerio(".elementor-tab-content")
|
||||
.find("li")
|
||||
loadedCheerio('.elementor-tab-content')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
const chapterName = loadedCheerio(this).text();
|
||||
const releaseDate = null;
|
||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||
const chapterUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!chapterUrl) return;
|
||||
const chapter = {
|
||||
name: chapterName,
|
||||
@@ -122,17 +123,20 @@ class Ligera implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
loadedCheerio(".osny-nightmode.osny-nightmode--left").remove();
|
||||
loadedCheerio(".code-block.code-block-1").remove();
|
||||
loadedCheerio(".adsb30").remove();
|
||||
loadedCheerio(".saboxplugin-wrap").remove();
|
||||
loadedCheerio(".wp-post-navigation").remove();
|
||||
loadedCheerio('.osny-nightmode.osny-nightmode--left').remove();
|
||||
loadedCheerio('.code-block.code-block-1').remove();
|
||||
loadedCheerio('.adsb30').remove();
|
||||
loadedCheerio('.saboxplugin-wrap').remove();
|
||||
loadedCheerio('.wp-post-navigation').remove();
|
||||
|
||||
let chapterText = loadedCheerio(".entry-content").html() || '';
|
||||
let chapterText = loadedCheerio('.entry-content').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.baseUrl + "?s=" + searchTerm + "&post_type=wp-manga";
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.baseUrl + '?s=' + searchTerm + '&post_type=wp-manga';
|
||||
// console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -142,19 +146,19 @@ class Ligera implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".inside-article").each(function () {
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
loadedCheerio('.inside-article').each(function () {
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
let novelName;
|
||||
|
||||
if (novelUrl) {
|
||||
novelName = novelUrl.replace(/-/g, " ").replace(/^./, function (x) {
|
||||
novelName = novelUrl.replace(/-/g, ' ').replace(/^./, function (x) {
|
||||
return x.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
novelUrl += "/";
|
||||
novelUrl += '/';
|
||||
|
||||
if (!novelName || !novelUrl) return;
|
||||
|
||||
@@ -174,7 +178,6 @@ class Ligera implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Ligera();
|
||||
@@ -1,25 +1,28 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
class ReinoWuxia implements Plugin.PluginBase {
|
||||
id = "awuxia.com";
|
||||
name = "ReinoWuxia";
|
||||
icon = "src/es/novelawuxia/icon.png";
|
||||
id = 'awuxia.com';
|
||||
name = 'ReinoWuxia';
|
||||
icon = 'src/es/novelawuxia/icon.png';
|
||||
filters?: Filters | undefined;
|
||||
version = "1.0.0";
|
||||
site = "http://www.reinowuxia.com/"
|
||||
version = '1.0.0';
|
||||
site = 'http://www.reinowuxia.com/';
|
||||
baseUrl = this.site;
|
||||
getNovelName(y: string | undefined) {
|
||||
return y?.replace(/-/g, " ").replace(/(?:^|\s)\S/g, (a) => a.toUpperCase());
|
||||
return y?.replace(/-/g, ' ').replace(/(?:^|\s)\S/g, a => a.toUpperCase());
|
||||
}
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl + "p/todas-las-novelas.html";
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl + 'p/todas-las-novelas.html';
|
||||
|
||||
const result = await fetchApi(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
@@ -27,18 +30,18 @@ class ReinoWuxia implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".post-body.entry-content")
|
||||
.find("a")
|
||||
.each( (idx, ele) => {
|
||||
loadedCheerio('.post-body.entry-content')
|
||||
.find('a')
|
||||
.each((idx, ele) => {
|
||||
let novelName = loadedCheerio(ele)
|
||||
.attr("href")
|
||||
?.split("/")
|
||||
.attr('href')
|
||||
?.split('/')
|
||||
.pop()
|
||||
?.replace(".html", "");
|
||||
?.replace('.html', '');
|
||||
novelName = this.getNovelName(novelName);
|
||||
const novelCover = loadedCheerio(ele).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(ele).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(ele).attr("href");
|
||||
let novelUrl = loadedCheerio(ele).attr('href');
|
||||
|
||||
if (!novelName || !novelUrl) return;
|
||||
|
||||
@@ -63,68 +66,60 @@ class ReinoWuxia implements Plugin.PluginBase {
|
||||
|
||||
let novel: Plugin.SourceNovel = { url };
|
||||
|
||||
novel.name = loadedCheerio("h1.post-title").text().trim();
|
||||
novel.name = loadedCheerio('h1.post-title').text().trim();
|
||||
|
||||
novel.cover = loadedCheerio("div.separator").find("a").attr("href");
|
||||
novel.cover = loadedCheerio('div.separator').find('a').attr('href');
|
||||
|
||||
novel.status = "";
|
||||
novel.status = '';
|
||||
|
||||
loadedCheerio("div > b").each(function () {
|
||||
loadedCheerio('div > b').each(function () {
|
||||
const detailName = loadedCheerio(this).text();
|
||||
let detail = loadedCheerio(this)[0].nextSibling;
|
||||
|
||||
if (detailName && detail) {
|
||||
const text = loadedCheerio(detail).text();
|
||||
|
||||
if (detailName.includes("Autor")) {
|
||||
novel.author = text.replace("Autor:", "");
|
||||
if (detailName.includes('Autor')) {
|
||||
novel.author = text.replace('Autor:', '');
|
||||
}
|
||||
|
||||
if (detailName.includes("Estatus")) {
|
||||
novel.status = text.replace("Estatus: ", "");
|
||||
if (detailName.includes('Estatus')) {
|
||||
novel.status = text.replace('Estatus: ', '');
|
||||
}
|
||||
if (detailName.includes("Géneros:")) {
|
||||
novel.genres = text
|
||||
.replace("Géneros: ", "")
|
||||
.replace(/,\s/g, ",");
|
||||
if (detailName.includes('Géneros:')) {
|
||||
novel.genres = text.replace('Géneros: ', '').replace(/,\s/g, ',');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("div").each(function () {
|
||||
loadedCheerio('div').each(function () {
|
||||
const detailName = loadedCheerio(this).text();
|
||||
if (detailName.includes("Sinopsis")) {
|
||||
if (detailName.includes('Sinopsis')) {
|
||||
novel.summary =
|
||||
loadedCheerio(this).next().text() !== ""
|
||||
? loadedCheerio(this)
|
||||
.next()
|
||||
.text()
|
||||
.replace("Sinopsis", "")
|
||||
.trim()
|
||||
loadedCheerio(this).next().text() !== ''
|
||||
? loadedCheerio(this).next().text().replace('Sinopsis', '').trim()
|
||||
: loadedCheerio(this)
|
||||
.next()
|
||||
.next()
|
||||
.text()
|
||||
.replace("Sinopsis", "")
|
||||
.replace('Sinopsis', '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
if (detailName.includes("Lista de Capítulos")) {
|
||||
if (detailName.includes('Lista de Capítulos')) {
|
||||
loadedCheerio(this)
|
||||
.find("a")
|
||||
.find('a')
|
||||
.each(function (res) {
|
||||
const chapterName = loadedCheerio(this).text();
|
||||
let chapterUrl = loadedCheerio(this).attr("href");
|
||||
let chapterUrl = loadedCheerio(this).attr('href');
|
||||
const releaseDate = null;
|
||||
|
||||
if (
|
||||
chapterName &&
|
||||
chapterUrl &&
|
||||
!novelChapters.some(
|
||||
(chap) => chap.name === chapterName
|
||||
)
|
||||
!novelChapters.some(chap => chap.name === chapterName)
|
||||
) {
|
||||
const chapter = {
|
||||
name: chapterName,
|
||||
@@ -150,11 +145,14 @@ class ReinoWuxia implements Plugin.PluginBase {
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio(".post-body.entry-content").html() || '';
|
||||
let chapterText = loadedCheerio('.post-body.entry-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.baseUrl}search?q=${searchTerm}`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
@@ -164,19 +162,19 @@ class ReinoWuxia implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".date-outer").each((idx, ele) => {
|
||||
loadedCheerio('.date-outer').each((idx, ele) => {
|
||||
let novelName = loadedCheerio(ele)
|
||||
.find("a")
|
||||
.attr("href")
|
||||
?.split("/")
|
||||
.find('a')
|
||||
.attr('href')
|
||||
?.split('/')
|
||||
.pop()
|
||||
?.replace(/-capitulo(.*?).html/, "");
|
||||
?.replace(/-capitulo(.*?).html/, '');
|
||||
|
||||
const novelUrl = novelName + ".html/";
|
||||
const novelUrl = novelName + '.html/';
|
||||
|
||||
novelName = this.getNovelName(novelName);
|
||||
|
||||
const exists = novels.some((novel) => novel.name === novelName);
|
||||
const exists = novels.some(novel => novel.name === novelName);
|
||||
|
||||
if (!exists) {
|
||||
const novelCover = defaultCover;
|
||||
@@ -197,7 +195,6 @@ class ReinoWuxia implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ReinoWuxia();
|
||||
@@ -1,24 +1,27 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile, fetchApi } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
|
||||
export const id = "oasisTL.wp";
|
||||
export const name = "Oasis Translations";
|
||||
export const site = "https://oasistranslations.wordpress.com/";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/es/oasistranslations/icon.png";
|
||||
export const id = 'oasisTL.wp';
|
||||
export const name = 'Oasis Translations';
|
||||
export const site = 'https://oasistranslations.wordpress.com/';
|
||||
export const version = '1.0.0';
|
||||
export const icon = 'src/es/oasistranslations/icon.png';
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
class Oasis implements Plugin.PluginBase {
|
||||
id = "oasisTL.wp";
|
||||
name = "Oasis Translations";
|
||||
site = "https://oasistranslations.wordpress.com/";
|
||||
version = "1.0.0";
|
||||
id = 'oasisTL.wp';
|
||||
name = 'Oasis Translations';
|
||||
site = 'https://oasistranslations.wordpress.com/';
|
||||
version = '1.0.0';
|
||||
filters?: Filters | undefined;
|
||||
icon = "src/es/oasistranslations/icon.png";
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
icon = 'src/es/oasistranslations/icon.png';
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = baseUrl;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
@@ -28,14 +31,14 @@ class Oasis implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".menu-item-1819")
|
||||
.find(".sub-menu > li")
|
||||
loadedCheerio('.menu-item-1819')
|
||||
.find('.sub-menu > li')
|
||||
.each(function () {
|
||||
const novelName = loadedCheerio(this).text();
|
||||
if (!novelName.match(/Activas|Finalizadas|Dropeadas/)) {
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -61,36 +64,38 @@ class Oasis implements Plugin.PluginBase {
|
||||
|
||||
let novel: Plugin.SourceNovel = { url };
|
||||
|
||||
novel.name = loadedCheerio("h1.entry-title")
|
||||
novel.name = loadedCheerio('h1.entry-title')
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "")
|
||||
.replace(/[\t\n]/g, '')
|
||||
.trim();
|
||||
|
||||
novel.cover = loadedCheerio('img[loading="lazy"]').attr("src");
|
||||
novel.cover = loadedCheerio('img[loading="lazy"]').attr('src');
|
||||
|
||||
loadedCheerio(".entry-content > p").each(function (res) {
|
||||
if (loadedCheerio(this).text().includes("Autor")) {
|
||||
const details = loadedCheerio(this).html()?.match(/<\/strong>(.|\n)*?<br>/g)?.
|
||||
map((detail) => detail.replace(/<strong>|<\/strong>|<br>|:\s/g, ""));
|
||||
loadedCheerio('.entry-content > p').each(function (res) {
|
||||
if (loadedCheerio(this).text().includes('Autor')) {
|
||||
const details = loadedCheerio(this)
|
||||
.html()
|
||||
?.match(/<\/strong>(.|\n)*?<br>/g)
|
||||
?.map(detail => detail.replace(/<strong>|<\/strong>|<br>|:\s/g, ''));
|
||||
|
||||
novel.genres = "";
|
||||
if (details){
|
||||
novel.genres = '';
|
||||
if (details) {
|
||||
novel.author = details[2];
|
||||
novel.genres = details[4].replace(/\s| /g, "");
|
||||
novel.genres = details[4].replace(/\s| /g, '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// let novelSummary = $(this).next().html();
|
||||
novel.summary = "";
|
||||
novel.summary = '';
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
// if ($(".entry-content").find("li").length) {
|
||||
loadedCheerio(".entry-content")
|
||||
.find("a")
|
||||
loadedCheerio('.entry-content')
|
||||
.find('a')
|
||||
.each(function () {
|
||||
let chapterUrl = loadedCheerio(this).attr("href");
|
||||
let chapterUrl = loadedCheerio(this).attr('href');
|
||||
|
||||
if (chapterUrl && chapterUrl.includes(baseUrl)) {
|
||||
const chapterName = loadedCheerio(this).text();
|
||||
@@ -118,13 +123,16 @@ class Oasis implements Plugin.PluginBase {
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio("div#jp-post-flair").remove();
|
||||
loadedCheerio('div#jp-post-flair').remove();
|
||||
|
||||
let chapterText = loadedCheerio(".entry-content").html() || '';
|
||||
let chapterText = loadedCheerio('.entry-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
|
||||
let url = baseUrl;
|
||||
@@ -135,14 +143,14 @@ class Oasis implements Plugin.PluginBase {
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio(".menu-item-1819")
|
||||
.find(".sub-menu > li")
|
||||
loadedCheerio('.menu-item-1819')
|
||||
.find('.sub-menu > li')
|
||||
.each(function () {
|
||||
const novelName = loadedCheerio(this).text();
|
||||
if (!novelName.match(/Activas|Finalizadas|Dropeadas/)) {
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -156,8 +164,8 @@ class Oasis implements Plugin.PluginBase {
|
||||
}
|
||||
});
|
||||
|
||||
novels = novels.filter((novel) =>
|
||||
novel.name.toLowerCase().includes(searchTerm)
|
||||
novels = novels.filter(novel =>
|
||||
novel.name.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
|
||||
return novels;
|
||||
@@ -165,7 +173,6 @@ class Oasis implements Plugin.PluginBase {
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Oasis();
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
class SkyNovels implements Plugin.PluginBase {
|
||||
id = "skynovels.net";
|
||||
name = "SkyNovels";
|
||||
site = "https://www.skynovels.net/";
|
||||
version = "1.0.0";
|
||||
icon = "src/es/skynovels/icon.png";
|
||||
id = 'skynovels.net';
|
||||
name = 'SkyNovels';
|
||||
site = 'https://www.skynovels.net/';
|
||||
version = '1.0.0';
|
||||
icon = 'src/es/skynovels/icon.png';
|
||||
baseUrl = this.site;
|
||||
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
const url = "https://api.skynovels.net/api/novels?&q";
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = 'https://api.skynovels.net/api/novels?&q';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as response;
|
||||
const body = (await result.json()) as response;
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
body.novels?.forEach((res) => {
|
||||
body.novels?.forEach(res => {
|
||||
const novelName = res.nvl_title;
|
||||
const novelCover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
'https://api.skynovels.net/api/get-image/' +
|
||||
res.image +
|
||||
"/novels/false";
|
||||
'/novels/false';
|
||||
const novelUrl =
|
||||
this.baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/";
|
||||
this.baseUrl + 'novelas/' + res.id + '/' + res.nvl_name + '/';
|
||||
|
||||
const novel = { name: novelName, url: novelUrl, cover: novelCover };
|
||||
|
||||
@@ -35,12 +38,12 @@ class SkyNovels implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
async parseNovelAndChapters(novelUrl: string): Promise<Plugin.SourceNovel> {
|
||||
const novelId = novelUrl.split("/")[4];
|
||||
const novelId = novelUrl.split('/')[4];
|
||||
const url =
|
||||
"https://api.skynovels.net/api/novel/" + novelId + "/reading?&q";
|
||||
'https://api.skynovels.net/api/novel/' + novelId + '/reading?&q';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as responseBook;
|
||||
const body = (await result.json()) as responseBook;
|
||||
|
||||
const item = body?.novel?.[0];
|
||||
|
||||
@@ -49,24 +52,24 @@ class SkyNovels implements Plugin.PluginBase {
|
||||
novel.name = item?.nvl_title;
|
||||
|
||||
novel.cover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
'https://api.skynovels.net/api/get-image/' +
|
||||
item?.image +
|
||||
"/novels/false";
|
||||
'/novels/false';
|
||||
|
||||
let genres: string[] = [];
|
||||
item?.genres?.forEach((genre) => genres.push(genre.genre_name));
|
||||
novel.genres = genres.join(",");
|
||||
item?.genres?.forEach(genre => genres.push(genre.genre_name));
|
||||
novel.genres = genres.join(',');
|
||||
novel.author = item?.nvl_writer;
|
||||
novel.summary = item?.nvl_content;
|
||||
novel.status = item?.nvl_status;
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
item?.volumes?.forEach((volume) => {
|
||||
volume?.chapters?.forEach((chapter) => {
|
||||
item?.volumes?.forEach(volume => {
|
||||
volume?.chapters?.forEach(chapter => {
|
||||
const chapterName = chapter.chp_index_title;
|
||||
const releaseDate = new Date(chapter.createdAt).toDateString();
|
||||
const chapterUrl = novelUrl + chapter.id + "/" + chapter.chp_name;
|
||||
const chapterUrl = novelUrl + chapter.id + '/' + chapter.chp_name;
|
||||
|
||||
const chap = {
|
||||
name: chapterName,
|
||||
@@ -83,11 +86,11 @@ class SkyNovels implements Plugin.PluginBase {
|
||||
return novel;
|
||||
}
|
||||
async parseChapter(chapterUrl: string): Promise<string> {
|
||||
let chapterId: string = chapterUrl.split("/")[6];
|
||||
let chapterId: string = chapterUrl.split('/')[6];
|
||||
const url = `https://api.skynovels.net/api/novel-chapter/${chapterId}`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as responseChapter;
|
||||
const body = (await result.json()) as responseChapter;
|
||||
|
||||
const item = body?.chapter?.[0];
|
||||
|
||||
@@ -95,27 +98,30 @@ class SkyNovels implements Plugin.PluginBase {
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
const url = "https://api.skynovels.net/api/novels?&q";
|
||||
const url = 'https://api.skynovels.net/api/novels?&q';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const body = await result.json() as response;;
|
||||
const body = (await result.json()) as response;
|
||||
|
||||
let results = body?.novels?.filter((novel) =>
|
||||
novel.nvl_title.toLowerCase().includes(searchTerm)
|
||||
let results = body?.novels?.filter(novel =>
|
||||
novel.nvl_title.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
results?.forEach((res) => {
|
||||
results?.forEach(res => {
|
||||
const novelName = res.nvl_title;
|
||||
const novelCover =
|
||||
"https://api.skynovels.net/api/get-image/" +
|
||||
'https://api.skynovels.net/api/get-image/' +
|
||||
res.image +
|
||||
"/novels/false";
|
||||
'/novels/false';
|
||||
const novelUrl =
|
||||
this.baseUrl + "novelas/" + res.id + "/" + res.nvl_name + "/";
|
||||
this.baseUrl + 'novelas/' + res.id + '/' + res.nvl_name + '/';
|
||||
|
||||
const novel = { name: novelName, url: novelUrl, cover: novelCover };
|
||||
|
||||
@@ -132,7 +138,7 @@ class SkyNovels implements Plugin.PluginBase {
|
||||
export default new SkyNovels();
|
||||
|
||||
interface response {
|
||||
novels?: (NovelsEntity)[] | null;
|
||||
novels?: NovelsEntity[] | null;
|
||||
}
|
||||
interface NovelsEntity {
|
||||
id: number;
|
||||
@@ -154,16 +160,15 @@ interface NovelsEntity {
|
||||
nvl_last_update: string;
|
||||
nvl_rating?: number | null;
|
||||
nvl_ratings_count: number;
|
||||
genres?: (GenresEntity)[] | null;
|
||||
genres?: GenresEntity[] | null;
|
||||
}
|
||||
interface GenresEntity {
|
||||
id: number;
|
||||
genre_name: string;
|
||||
}
|
||||
|
||||
|
||||
interface responseBook {
|
||||
novel?: (NovelEntity)[] | null;
|
||||
novel?: NovelEntity[] | null;
|
||||
}
|
||||
interface NovelEntity {
|
||||
id: number;
|
||||
@@ -184,11 +189,11 @@ interface NovelEntity {
|
||||
nvl_chapters: number;
|
||||
nvl_last_update: string;
|
||||
nvl_rating: number;
|
||||
bookmarks?: (BookmarksEntity)[] | null;
|
||||
volumes?: (VolumesEntity)[] | null;
|
||||
novel_ratings?: (NovelRatingsEntity)[] | null;
|
||||
collaborators?: (CollaboratorsEntity)[] | null;
|
||||
genres?: (GenresEntity)[] | null;
|
||||
bookmarks?: BookmarksEntity[] | null;
|
||||
volumes?: VolumesEntity[] | null;
|
||||
novel_ratings?: NovelRatingsEntity[] | null;
|
||||
collaborators?: CollaboratorsEntity[] | null;
|
||||
genres?: GenresEntity[] | null;
|
||||
}
|
||||
interface BookmarksEntity {
|
||||
id: number;
|
||||
@@ -201,7 +206,7 @@ interface VolumesEntity {
|
||||
id: number;
|
||||
nvl_id: number;
|
||||
user_id?: number | null;
|
||||
chapters?: (ChaptersEntity)[] | null;
|
||||
chapters?: ChaptersEntity[] | null;
|
||||
}
|
||||
interface ChaptersEntity {
|
||||
id: number;
|
||||
@@ -233,9 +238,8 @@ interface CollaboratorsEntity {
|
||||
user_login: string;
|
||||
}
|
||||
|
||||
|
||||
interface responseChapter {
|
||||
chapter?: (ChapterEntity)[] | null;
|
||||
chapter?: ChapterEntity[] | null;
|
||||
}
|
||||
interface ChapterEntity {
|
||||
id: number;
|
||||
@@ -256,9 +260,9 @@ interface ChapterEntity {
|
||||
nvl_name: string;
|
||||
user_login: string;
|
||||
reactions_count: number;
|
||||
comments?: (null)[] | null;
|
||||
reactions?: (null)[] | null;
|
||||
total_reactions?: (TotalReactionsEntity)[] | null;
|
||||
comments?: null[] | null;
|
||||
reactions?: null[] | null;
|
||||
total_reactions?: TotalReactionsEntity[] | null;
|
||||
}
|
||||
interface TotalReactionsEntity {
|
||||
reaction_id: number;
|
||||
|
||||
+126
-119
@@ -1,19 +1,19 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
id = "tunovelaligera";
|
||||
name = "TuNovelaLigera";
|
||||
icon = "src/es/tunovelaligera/icon.png";
|
||||
site = "https://tunovelaligera.com";
|
||||
version = "1.0.0";
|
||||
id = 'tunovelaligera';
|
||||
name = 'TuNovelaLigera';
|
||||
icon = 'src/es/tunovelaligera/icon.png';
|
||||
site = 'https://tunovelaligera.com';
|
||||
version = '1.0.0';
|
||||
|
||||
async sleep (ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
async sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
@@ -22,21 +22,23 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site
|
||||
let link = this.site;
|
||||
|
||||
link += filters?.genres?.value ? "/genero" + filters.genres.value : "/novelas";
|
||||
link += filters?.genres?.value
|
||||
? '/genero' + filters.genres.value
|
||||
: '/novelas';
|
||||
link += `/page/${pageNo}?m_orderby=`;
|
||||
link += (showLatestNovels ? "latest" : filters?.order?.value || "rating");
|
||||
link += showLatestNovels ? 'latest' : filters?.order?.value || 'rating';
|
||||
|
||||
const result = await fetchApi(link).then((res) => res.text());
|
||||
const result = await fetchApi(link).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".page-item-detail").each((i, el) => {
|
||||
const name = loadedCheerio(el).find(".h5 > a").text();
|
||||
const cover = loadedCheerio(el).find("img").attr("src");
|
||||
const url = loadedCheerio(el).find(".h5 > a").attr("href");
|
||||
loadedCheerio('.page-item-detail').each((i, el) => {
|
||||
const name = loadedCheerio(el).find('.h5 > a').text();
|
||||
const cover = loadedCheerio(el).find('img').attr('src');
|
||||
const url = loadedCheerio(el).find('.h5 > a').attr('href');
|
||||
if (!url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
@@ -47,15 +49,15 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
|
||||
parseChapters(loadedCheerio: CheerioAPI): Plugin.ChapterItem[] {
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio("#lcp_instance_0 li").each((i, el) => {
|
||||
loadedCheerio('#lcp_instance_0 li').each((i, el) => {
|
||||
const chapterName = loadedCheerio(el)
|
||||
.find("a")
|
||||
.find('a')
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "")
|
||||
.replace(/[\t\n]/g, '')
|
||||
.trim();
|
||||
|
||||
const chapterUrl = loadedCheerio(el).find("a").attr("href");
|
||||
if(!chapterUrl) return;
|
||||
const chapterUrl = loadedCheerio(el).find('a').attr('href');
|
||||
if (!chapterUrl) return;
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
@@ -64,58 +66,60 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
return chapters;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number; }> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const novelUrl = this.site + novelPath;
|
||||
const result = await fetchApi(novelUrl);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
let lastPage = 1;
|
||||
loadedCheerio('ul.lcp_paginator > li > a').each(function() {
|
||||
loadedCheerio('ul.lcp_paginator > li > a').each(function () {
|
||||
const page = Number(this.attribs['title']);
|
||||
if(page && page > lastPage) lastPage = page;
|
||||
if (page && page > lastPage) lastPage = page;
|
||||
});
|
||||
const novel: Plugin.SourceNovel & { totalPages: number; } = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
chapters: [],
|
||||
totalPages: lastPage,
|
||||
name: loadedCheerio(".post-title > h1").text().trim()
|
||||
name: loadedCheerio('.post-title > h1').text().trim(),
|
||||
};
|
||||
|
||||
loadedCheerio(".manga-title-badges").remove();
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
let novelCover = loadedCheerio(".summary_image > a > img");
|
||||
let novelCover = loadedCheerio('.summary_image > a > img');
|
||||
|
||||
novel.cover =
|
||||
novelCover.attr("data-src") ||
|
||||
novelCover.attr("src") ||
|
||||
novelCover.attr("data-cfsrc") ||
|
||||
novelCover.attr('data-src') ||
|
||||
novelCover.attr('src') ||
|
||||
novelCover.attr('data-cfsrc') ||
|
||||
defaultCover;
|
||||
|
||||
loadedCheerio(".post-content_item").each(function () {
|
||||
loadedCheerio('.post-content_item').each(function () {
|
||||
const detailName = loadedCheerio(this)
|
||||
.find(".summary-heading > h5")
|
||||
.find('.summary-heading > h5')
|
||||
.text()
|
||||
.trim();
|
||||
const detail = loadedCheerio(this).find(".summary-content").text().trim();
|
||||
const detail = loadedCheerio(this).find('.summary-content').text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Género(s)":
|
||||
novel.genres = detail.replace(/, /g, ",");
|
||||
case 'Género(s)':
|
||||
novel.genres = detail.replace(/, /g, ',');
|
||||
break;
|
||||
case "Autor(es)":
|
||||
case 'Autor(es)':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Estado":
|
||||
case 'Estado':
|
||||
novel.status =
|
||||
detail.includes("OnGoing") || detail.includes("Updating")
|
||||
detail.includes('OnGoing') || detail.includes('Updating')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
novel.summary = loadedCheerio("div.summary__content > p").text().trim();
|
||||
novel.summary = loadedCheerio('div.summary__content > p').text().trim();
|
||||
|
||||
novel.chapters = this.parseChapters(loadedCheerio);
|
||||
|
||||
@@ -130,22 +134,27 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
const latestChapterEle = loadedCheerio('#lcp_instance_0 li').first();
|
||||
const latestChapterUrl = loadedCheerio(latestChapterEle).find("a").attr("href");
|
||||
const latestChapterName = loadedCheerio(latestChapterEle).find("a")
|
||||
const latestChapterUrl = loadedCheerio(latestChapterEle)
|
||||
.find('a')
|
||||
.attr('href');
|
||||
const latestChapterName = loadedCheerio(latestChapterEle)
|
||||
.find('a')
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "")
|
||||
.replace(/[\t\n]/g, '')
|
||||
.trim();
|
||||
const latestChapter: Plugin.ChapterItem | undefined = latestChapterUrl ? {
|
||||
const latestChapter: Plugin.ChapterItem | undefined = latestChapterUrl
|
||||
? {
|
||||
path: latestChapterUrl.replace(this.site, ''),
|
||||
name: latestChapterName
|
||||
} : undefined
|
||||
name: latestChapterName,
|
||||
}
|
||||
: undefined;
|
||||
await this.sleep(1000);
|
||||
const pageText = await fetchApi(pageUrl).then(res => res.text());
|
||||
const chapters = this.parseChapters(parseHTML(pageText));
|
||||
return {
|
||||
chapters,
|
||||
latestChapter
|
||||
}
|
||||
latestChapter,
|
||||
};
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetchApi(this.site + chapterPath);
|
||||
@@ -153,14 +162,12 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio(".c-blog-post.post").html() || "";
|
||||
const chapterText = loadedCheerio('.c-blog-post.post').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/?s=${searchTerm}&post_type=wp-manga`;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
@@ -170,10 +177,10 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".c-tabs-item__content").each((i, el) => {
|
||||
const name = loadedCheerio(el).find(".h4 > a").text();
|
||||
const cover = loadedCheerio(el).find("img").attr("src");
|
||||
const url = loadedCheerio(el).find(".h4 > a").attr("href");
|
||||
loadedCheerio('.c-tabs-item__content').each((i, el) => {
|
||||
const name = loadedCheerio(el).find('.h4 > a').text();
|
||||
const cover = loadedCheerio(el).find('img').attr('src');
|
||||
const url = loadedCheerio(el).find('.h4 > a').attr('href');
|
||||
if (!url) return;
|
||||
|
||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
||||
@@ -188,70 +195,70 @@ class TuNovelaLigera implements Plugin.PagePlugin {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "rating",
|
||||
label: "Ordenado por",
|
||||
value: 'rating',
|
||||
label: 'Ordenado por',
|
||||
options: [
|
||||
{ label: "Lo mas reciente", value: "latest" },
|
||||
{ label: "A-Z", value: "alphabet" },
|
||||
{ label: "Clasificación", value: "rating" },
|
||||
{ label: "Trending", value: "trending" },
|
||||
{ label: "Mas visto", value: "views" },
|
||||
{ label: "Nuevo", value: "new-manga" },
|
||||
{ label: 'Lo mas reciente', value: 'latest' },
|
||||
{ label: 'A-Z', value: 'alphabet' },
|
||||
{ label: 'Clasificación', value: 'rating' },
|
||||
{ label: 'Trending', value: 'trending' },
|
||||
{ label: 'Mas visto', value: 'views' },
|
||||
{ label: 'Nuevo', value: 'new-manga' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
value: "",
|
||||
label: "Generos",
|
||||
value: '',
|
||||
label: 'Generos',
|
||||
options: [
|
||||
{ label: "None", value: "" },
|
||||
{ label: "Acción", value: "accion" },
|
||||
{ label: "Adulto", value: "adulto" },
|
||||
{ label: "Artes Marciales", value: "artes-marciales" },
|
||||
{ label: "Aventura", value: "aventura" },
|
||||
{ label: "Ciencia Ficción", value: "ciencia-ficcion" },
|
||||
{ label: "Comedia", value: "comedia" },
|
||||
{ label: "Deportes", value: "deportes" },
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: "Eastern Fantasy", value: "eastern-fantasy" },
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: "FanFiction", value: "fan-fiction" },
|
||||
{ label: "Fantasía", value: "fantasia" },
|
||||
{ label: "Fantasía oriental", value: "fantasia-oriental" },
|
||||
{ label: "Ficción Romántica", value: "ficcion-romantica" },
|
||||
{ label: "Gender Bender", value: "gender-bender" },
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: "Histórico", value: "historico" },
|
||||
{ label: "Horror", value: "horror" },
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: "Maduro", value: "maduro" },
|
||||
{ label: "Mecha", value: "mecha" },
|
||||
{ label: "Misterio", value: "misterio" },
|
||||
{ label: "Novela China", value: "novela-china" },
|
||||
{ label: "Novela FanFiction", value: "novela-fanfiction" },
|
||||
{ label: "Novela Japonesa", value: "novela-japonesa" },
|
||||
{ label: "Novela Koreana", value: "novela-koreana" },
|
||||
{ label: "Novela ligera", value: "novela-ligera" },
|
||||
{ label: "Novela original", value: "novela-original" },
|
||||
{ label: "Novela Web", value: "web-novel" },
|
||||
{ label: "Psicológico", value: "psicologico" },
|
||||
{ label: "Realismo Mágico", value: "realismo-magico" },
|
||||
{ label: "Recuento de vida", value: "recuento-de-vida" },
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: "Romance contemporáneo", value: "romance-contemporaneo" },
|
||||
{ label: "Romance Moderno", value: "romance-moderno" },
|
||||
{ label: "Seinen", value: "seinen" },
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: "Shounen", value: "shounen" },
|
||||
{ label: "Sobrenatural", value: "sobrenatural" },
|
||||
{ label: "Tragedia", value: "tragedia" },
|
||||
{ label: "Vampiros", value: "vampiros" },
|
||||
{ label: "Vida Escolar", value: "vida-escolar" },
|
||||
{ label: "Western Fantasy", value: "western-fantasy" },
|
||||
{ label: "Wuxia", value: "wuxia" },
|
||||
{ label: "Xianxia", value: "xianxia" },
|
||||
{ label: "Xuanhuan", value: "xuanhuan" },
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: 'None', value: '' },
|
||||
{ label: 'Acción', value: 'accion' },
|
||||
{ label: 'Adulto', value: 'adulto' },
|
||||
{ label: 'Artes Marciales', value: 'artes-marciales' },
|
||||
{ label: 'Aventura', value: 'aventura' },
|
||||
{ label: 'Ciencia Ficción', value: 'ciencia-ficcion' },
|
||||
{ label: 'Comedia', value: 'comedia' },
|
||||
{ label: 'Deportes', value: 'deportes' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'FanFiction', value: 'fan-fiction' },
|
||||
{ label: 'Fantasía', value: 'fantasia' },
|
||||
{ label: 'Fantasía oriental', value: 'fantasia-oriental' },
|
||||
{ label: 'Ficción Romántica', value: 'ficcion-romantica' },
|
||||
{ label: 'Gender Bender', value: 'gender-bender' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Histórico', value: 'historico' },
|
||||
{ label: 'Horror', value: 'horror' },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
{ label: 'Maduro', value: 'maduro' },
|
||||
{ label: 'Mecha', value: 'mecha' },
|
||||
{ label: 'Misterio', value: 'misterio' },
|
||||
{ label: 'Novela China', value: 'novela-china' },
|
||||
{ label: 'Novela FanFiction', value: 'novela-fanfiction' },
|
||||
{ label: 'Novela Japonesa', value: 'novela-japonesa' },
|
||||
{ label: 'Novela Koreana', value: 'novela-koreana' },
|
||||
{ label: 'Novela ligera', value: 'novela-ligera' },
|
||||
{ label: 'Novela original', value: 'novela-original' },
|
||||
{ label: 'Novela Web', value: 'web-novel' },
|
||||
{ label: 'Psicológico', value: 'psicologico' },
|
||||
{ label: 'Realismo Mágico', value: 'realismo-magico' },
|
||||
{ label: 'Recuento de vida', value: 'recuento-de-vida' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'Romance contemporáneo', value: 'romance-contemporaneo' },
|
||||
{ label: 'Romance Moderno', value: 'romance-moderno' },
|
||||
{ label: 'Seinen', value: 'seinen' },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
{ label: 'Shounen', value: 'shounen' },
|
||||
{ label: 'Sobrenatural', value: 'sobrenatural' },
|
||||
{ label: 'Tragedia', value: 'tragedia' },
|
||||
{ label: 'Vampiros', value: 'vampiros' },
|
||||
{ label: 'Vida Escolar', value: 'vida-escolar' },
|
||||
{ label: 'Western Fantasy', value: 'western-fantasy' },
|
||||
{ label: 'Wuxia', value: 'wuxia' },
|
||||
{ label: 'Xianxia', value: 'xianxia' },
|
||||
{ label: 'Xuanhuan', value: 'xuanhuan' },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
|
||||
export const id = "yuukitls.com";
|
||||
export const name = "Yuuki Tls";
|
||||
export const site = "https://yuukitls.com/";
|
||||
export const version = "1.0.0";
|
||||
export const icon = "src/es/yuukitls/icon.png";
|
||||
export const id = 'yuukitls.com';
|
||||
export const name = 'Yuuki Tls';
|
||||
export const site = 'https://yuukitls.com/';
|
||||
export const version = '1.0.0';
|
||||
export const icon = 'src/es/yuukitls/icon.png';
|
||||
|
||||
const baseUrl = site;
|
||||
|
||||
class Yuuki implements Plugin.PluginBase {
|
||||
id = "yuukitls.com";
|
||||
name = "Yuuki Tls";
|
||||
icon = "src/es/yuukitls/icon.png";
|
||||
site = "https://yuukitls.com/";
|
||||
id = 'yuukitls.com';
|
||||
name = 'Yuuki Tls';
|
||||
icon = 'src/es/yuukitls/icon.png';
|
||||
site = 'https://yuukitls.com/';
|
||||
filters?: Filters | undefined;
|
||||
version = "1.0.0";
|
||||
version = '1.0.0';
|
||||
baseUrl = this.site;
|
||||
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = baseUrl;
|
||||
|
||||
const result = await fetchApi(url);
|
||||
@@ -30,15 +33,15 @@ class Yuuki implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".quadmenu-navbar-collapse ul li:nth-child(2)")
|
||||
.find("li")
|
||||
loadedCheerio('.quadmenu-navbar-collapse ul li:nth-child(2)')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
const novelName = loadedCheerio(this)
|
||||
.text()
|
||||
.replace(/[\s\n]+/g, " ");
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
.replace(/[\s\n]+/g, ' ');
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
const novel = {
|
||||
name: novelName,
|
||||
@@ -63,40 +66,40 @@ class Yuuki implements Plugin.PluginBase {
|
||||
url,
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio("h1.entry-title")
|
||||
novel.name = loadedCheerio('h1.entry-title')
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "")
|
||||
.replace(/[\t\n]/g, '')
|
||||
.trim();
|
||||
|
||||
novel.cover = loadedCheerio('img[loading="lazy"]').attr("src");
|
||||
novel.cover = loadedCheerio('img[loading="lazy"]').attr('src');
|
||||
|
||||
loadedCheerio(".entry-content")
|
||||
.find("div")
|
||||
loadedCheerio('.entry-content')
|
||||
.find('div')
|
||||
.each(function () {
|
||||
if (loadedCheerio(this).text().includes("Escritor:")) {
|
||||
if (loadedCheerio(this).text().includes('Escritor:')) {
|
||||
novel.author = loadedCheerio(this)
|
||||
.text()
|
||||
.replace("Escritor: ", "")
|
||||
.replace('Escritor: ', '')
|
||||
.trim();
|
||||
}
|
||||
if (loadedCheerio(this).text().includes("Género:")) {
|
||||
if (loadedCheerio(this).text().includes('Género:')) {
|
||||
novel.genres = loadedCheerio(this)
|
||||
.text()
|
||||
.replace(/Género: |\s/g, "");
|
||||
.replace(/Género: |\s/g, '');
|
||||
}
|
||||
|
||||
if (loadedCheerio(this).text().includes("Sinopsis:")) {
|
||||
if (loadedCheerio(this).text().includes('Sinopsis:')) {
|
||||
novel.summary = loadedCheerio(this).next().text();
|
||||
}
|
||||
});
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
if (loadedCheerio(".entry-content").find("li").length) {
|
||||
loadedCheerio(".entry-content")
|
||||
.find("li")
|
||||
if (loadedCheerio('.entry-content').find('li').length) {
|
||||
loadedCheerio('.entry-content')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
let chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let chapterUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (chapterUrl && chapterUrl.includes(baseUrl)) {
|
||||
const chapterName = loadedCheerio(this).text();
|
||||
@@ -112,10 +115,10 @@ class Yuuki implements Plugin.PluginBase {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loadedCheerio(".entry-content")
|
||||
.find("p")
|
||||
loadedCheerio('.entry-content')
|
||||
.find('p')
|
||||
.each(function () {
|
||||
let chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let chapterUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (chapterUrl && chapterUrl.includes(baseUrl)) {
|
||||
const chapterName = loadedCheerio(this).text();
|
||||
@@ -143,11 +146,14 @@ class Yuuki implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
let chapterText = loadedCheerio(".entry-content").html() || '';
|
||||
let chapterText = loadedCheerio('.entry-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
|
||||
let url = baseUrl;
|
||||
@@ -159,13 +165,13 @@ class Yuuki implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".menu-item-2869")
|
||||
.find(".menu-item.menu-item-type-post_type.menu-item-object-post")
|
||||
loadedCheerio('.menu-item-2869')
|
||||
.find('.menu-item.menu-item-type-post_type.menu-item-object-post')
|
||||
.each(function () {
|
||||
const novelName = loadedCheerio(this).text();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
|
||||
const novel = {
|
||||
@@ -177,8 +183,8 @@ class Yuuki implements Plugin.PluginBase {
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
novels = novels.filter((novel) =>
|
||||
novel.name.toLowerCase().includes(searchTerm)
|
||||
novels = novels.filter(novel =>
|
||||
novel.name.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
|
||||
return novels;
|
||||
@@ -186,7 +192,6 @@ class Yuuki implements Plugin.PluginBase {
|
||||
fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Yuuki();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
class EpikNovel implements Plugin.PluginBase {
|
||||
id = "epiknovel.com";
|
||||
name = "EpikNovel";
|
||||
icon = "src/tr/epiknovel/icon.png";
|
||||
site = "https://www.epiknovel.com/";
|
||||
version = "1.0.0";
|
||||
id = 'epiknovel.com';
|
||||
name = 'EpikNovel';
|
||||
icon = 'src/tr/epiknovel/icon.png';
|
||||
site = 'https://www.epiknovel.com/';
|
||||
version = '1.0.0';
|
||||
baseUrl = this.site;
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl + "seri-listesi?Sayfa=" + pageNo;
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.baseUrl + 'seri-listesi?Sayfa=' + pageNo;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -20,10 +23,10 @@ class EpikNovel implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("div.col-lg-12.col-md-12").each(function () {
|
||||
const novelName = loadedCheerio(this).find("h3").text();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("data-src");
|
||||
const novelUrl = loadedCheerio(this).find("h3 > a").attr("href");
|
||||
loadedCheerio('div.col-lg-12.col-md-12').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h3').text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelUrl = loadedCheerio(this).find('h3 > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -51,52 +54,47 @@ class EpikNovel implements Plugin.PluginBase {
|
||||
|
||||
let novel: Plugin.SourceNovel = { url };
|
||||
|
||||
novel.name = loadedCheerio("h1#tables").text().trim();
|
||||
novel.name = loadedCheerio('h1#tables').text().trim();
|
||||
|
||||
novel.cover = loadedCheerio("img.manga-cover").attr("src");
|
||||
novel.cover = loadedCheerio('img.manga-cover').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio(
|
||||
"#wrapper > div.row > div.col-md-9 > div:nth-child(6) > p:nth-child(3)"
|
||||
'#wrapper > div.row > div.col-md-9 > div:nth-child(6) > p:nth-child(3)',
|
||||
)
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
novel.status = loadedCheerio(
|
||||
"#wrapper > div.row > div.col-md-9 > div.row > div.col-md-9 > h4:nth-child(3) > a"
|
||||
'#wrapper > div.row > div.col-md-9 > div.row > div.col-md-9 > h4:nth-child(3) > a',
|
||||
)
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
novel.author = loadedCheerio("#NovelInfo > p:nth-child(4)")
|
||||
novel.author = loadedCheerio('#NovelInfo > p:nth-child(4)')
|
||||
.text()
|
||||
.replace(/Publisher:|\s/g, "")
|
||||
.replace(/Publisher:|\s/g, '')
|
||||
.trim();
|
||||
|
||||
let novelChapters: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio("table").find("tr").first().remove();
|
||||
loadedCheerio('table').find('tr').first().remove();
|
||||
|
||||
loadedCheerio("table")
|
||||
.find("tr")
|
||||
loadedCheerio('table')
|
||||
.find('tr')
|
||||
.each(function () {
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find("td:nth-child(3)")
|
||||
.text();
|
||||
const releaseDate = loadedCheerio(this).find('td:nth-child(3)').text();
|
||||
|
||||
let chapterName = loadedCheerio(this)
|
||||
.find("td:nth-child(1) > a")
|
||||
.find('td:nth-child(1) > a')
|
||||
.text();
|
||||
|
||||
if (
|
||||
loadedCheerio(this).find("td:nth-child(1) > span").length >
|
||||
0
|
||||
) {
|
||||
chapterName = "🔒 " + chapterName;
|
||||
if (loadedCheerio(this).find('td:nth-child(1) > span').length > 0) {
|
||||
chapterName = '🔒 ' + chapterName;
|
||||
}
|
||||
|
||||
const chapterUrl = loadedCheerio(this)
|
||||
.find(" td:nth-child(1) > a")
|
||||
.attr("href");
|
||||
.find(' td:nth-child(1) > a')
|
||||
.attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -124,16 +122,20 @@ class EpikNovel implements Plugin.PluginBase {
|
||||
|
||||
let chapterText = '';
|
||||
|
||||
if (result.url === "https://www.epiknovel.com/login") {
|
||||
chapterText = "Premium Chapter";
|
||||
if (result.url === 'https://www.epiknovel.com/login') {
|
||||
chapterText = 'Premium Chapter';
|
||||
} else {
|
||||
chapterText = loadedCheerio("div#icerik").html() || '';
|
||||
chapterText = loadedCheerio('div#icerik').html() || '';
|
||||
}
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.baseUrl + "seri-listesi?q=" + searchTerm + '&Sayfa=' + pageNo;
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.baseUrl + 'seri-listesi?q=' + searchTerm + '&Sayfa=' + pageNo;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -142,10 +144,10 @@ class EpikNovel implements Plugin.PluginBase {
|
||||
|
||||
let novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("div.col-lg-12.col-md-12").each(function () {
|
||||
const novelName = loadedCheerio(this).find("h3").text();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("data-src");
|
||||
const novelUrl = loadedCheerio(this).find("h3 > a").attr("href");
|
||||
loadedCheerio('div.col-lg-12.col-md-12').each(function () {
|
||||
const novelName = loadedCheerio(this).find('h3').text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('data-src');
|
||||
const novelUrl = loadedCheerio(this).find('h3 > a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -163,7 +165,6 @@ class EpikNovel implements Plugin.PluginBase {
|
||||
fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new EpikNovel();
|
||||
+100
-111
@@ -1,47 +1,47 @@
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
|
||||
class LSHNovel implements Plugin.PluginBase {
|
||||
id = "lshnovel";
|
||||
name = "Liebe Schnee Hiver Novel";
|
||||
icon = "multisrc/wpmangastream/icons/lshnovel.png";
|
||||
site = "https://lshnovel.com/";
|
||||
version = "1.0.0";
|
||||
id = 'lshnovel';
|
||||
name = 'Liebe Schnee Hiver Novel';
|
||||
icon = 'multisrc/wpmangastream/icons/lshnovel.png';
|
||||
site = 'https://lshnovel.com/';
|
||||
version = '1.0.0';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = `${this.site}series/?page=${pageNo}`;
|
||||
|
||||
if (filters.genres.value.length) {
|
||||
link += filters.genres.value.map((i) => `&genre[]=${i}`).join("");
|
||||
link += filters.genres.value.map(i => `&genre[]=${i}`).join('');
|
||||
}
|
||||
|
||||
if (filters.type.value.length)
|
||||
link += filters.type.value.map((i) => `&lang[]=${i}`).join("");
|
||||
link += filters.type.value.map(i => `&lang[]=${i}`).join('');
|
||||
|
||||
link += "&status=" + filters.status;
|
||||
link += '&status=' + filters.status;
|
||||
|
||||
link += "&order=" + filters.order;
|
||||
link += '&order=' + filters.order;
|
||||
|
||||
const headers = new Headers();
|
||||
const body = await fetchApi(link, { headers }).then((result) =>
|
||||
result.text()
|
||||
const body = await fetchApi(link, { headers }).then(result =>
|
||||
result.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("article.bs").each(function () {
|
||||
const novelName = loadedCheerio(this).find(".ntitle").text().trim();
|
||||
let image = loadedCheerio(this).find("img");
|
||||
const novelCover = image.attr("data-src") || image.attr("src");
|
||||
loadedCheerio('article.bs').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.ntitle').text().trim();
|
||||
let image = loadedCheerio(this).find('img');
|
||||
const novelCover = image.attr('data-src') || image.attr('src');
|
||||
|
||||
const novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
const novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!novelUrl) return;
|
||||
|
||||
@@ -70,59 +70,48 @@ class LSHNovel implements Plugin.PluginBase {
|
||||
chapters: [],
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio(".entry-title").text();
|
||||
novel.name = loadedCheerio('.entry-title').text();
|
||||
|
||||
novel.cover =
|
||||
loadedCheerio("img.wp-post-image").attr("data-src") ||
|
||||
loadedCheerio("img.wp-post-image").attr("src");
|
||||
loadedCheerio('img.wp-post-image').attr('data-src') ||
|
||||
loadedCheerio('img.wp-post-image').attr('src');
|
||||
|
||||
loadedCheerio("div.spe > span").each(function () {
|
||||
const detailName = loadedCheerio(this).find("b").text().trim();
|
||||
const detail = loadedCheerio(this)
|
||||
.find("b")
|
||||
.remove()
|
||||
.end()
|
||||
.text()
|
||||
.trim();
|
||||
loadedCheerio('div.spe > span').each(function () {
|
||||
const detailName = loadedCheerio(this).find('b').text().trim();
|
||||
const detail = loadedCheerio(this).find('b').remove().end().text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Yazar:":
|
||||
case 'Yazar:':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Seviye:":
|
||||
case 'Seviye:':
|
||||
novel.status = detail;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
novel.genres = loadedCheerio(".genxed")
|
||||
.text()
|
||||
.trim()
|
||||
.replace(/\s/g, ",");
|
||||
novel.genres = loadedCheerio('.genxed').text().trim().replace(/\s/g, ',');
|
||||
|
||||
loadedCheerio('div[itemprop="description"] h3,p.a,strong').remove();
|
||||
novel.summary = loadedCheerio('div[itemprop="description"]')
|
||||
.find("br")
|
||||
.replaceWith("\n")
|
||||
.find('br')
|
||||
.replaceWith('\n')
|
||||
.end()
|
||||
.text();
|
||||
|
||||
let chapter: Plugin.ChapterItem[] = [];
|
||||
|
||||
loadedCheerio(".eplister")
|
||||
.find("li")
|
||||
loadedCheerio('.eplister')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
const chapterName =
|
||||
loadedCheerio(this).find(".epl-num").text() +
|
||||
" - " +
|
||||
loadedCheerio(this).find(".epl-title").text();
|
||||
loadedCheerio(this).find('.epl-num').text() +
|
||||
' - ' +
|
||||
loadedCheerio(this).find('.epl-title').text();
|
||||
|
||||
const releaseDate = loadedCheerio(this)
|
||||
.find(".epl-date")
|
||||
.text()
|
||||
.trim();
|
||||
const releaseDate = loadedCheerio(this).find('.epl-date').text().trim();
|
||||
|
||||
const chapterUrl = loadedCheerio(this).find("a").attr("href");
|
||||
const chapterUrl = loadedCheerio(this).find('a').attr('href');
|
||||
|
||||
if (!chapterUrl) return;
|
||||
|
||||
@@ -144,14 +133,14 @@ class LSHNovel implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio(".epcontent").html() || "";
|
||||
let chapterText = loadedCheerio('.epcontent').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo?: number | undefined
|
||||
pageNo?: number | undefined,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}?s=${searchTerm}`;
|
||||
const headers = new Headers();
|
||||
@@ -162,10 +151,10 @@ class LSHNovel implements Plugin.PluginBase {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio("article.bs").each(function () {
|
||||
const novelName = loadedCheerio(this).find(".ntitle").text().trim();
|
||||
const novelCover = loadedCheerio(this).find("img").attr("src");
|
||||
const novelUrl = loadedCheerio(this).find("a").attr("href");
|
||||
loadedCheerio('article.bs').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.ntitle').text().trim();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
if (!novelUrl) return;
|
||||
|
||||
novels.push({
|
||||
@@ -183,124 +172,124 @@ class LSHNovel implements Plugin.PluginBase {
|
||||
|
||||
filters = {
|
||||
order: {
|
||||
value: "popular",
|
||||
label: "Önerilen",
|
||||
value: 'popular',
|
||||
label: 'Önerilen',
|
||||
options: [
|
||||
{ label: "Varsayılan", value: "" },
|
||||
{ label: 'Varsayılan', value: '' },
|
||||
|
||||
{ label: "A-Z", value: "title" },
|
||||
{ label: 'A-Z', value: 'title' },
|
||||
|
||||
{ label: "Z-A", value: "titlereverse" },
|
||||
{ label: 'Z-A', value: 'titlereverse' },
|
||||
|
||||
{ label: "Son Yüklemeler", value: "update" },
|
||||
{ label: 'Son Yüklemeler', value: 'update' },
|
||||
|
||||
{ label: "Son Eklenenler", value: "latest" },
|
||||
{ label: 'Son Eklenenler', value: 'latest' },
|
||||
|
||||
{ label: "Bestimiz", value: "popular" },
|
||||
{ label: 'Bestimiz', value: 'popular' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
status: {
|
||||
label: "Statü",
|
||||
value: "",
|
||||
label: 'Statü',
|
||||
value: '',
|
||||
options: [
|
||||
{ label: "Tümü", value: "" },
|
||||
{ label: 'Tümü', value: '' },
|
||||
|
||||
{ label: "Ongoing", value: "ongoing" },
|
||||
{ label: 'Ongoing', value: 'ongoing' },
|
||||
|
||||
{ label: "Hiatus", value: "hiatus" },
|
||||
{ label: 'Hiatus', value: 'hiatus' },
|
||||
|
||||
{ label: "Completed", value: "completed" },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
type: {
|
||||
value: [],
|
||||
label: "Tür",
|
||||
label: 'Tür',
|
||||
options: [
|
||||
{ label: "Çeviri Novel", value: "ceviri-novel" },
|
||||
{ label: 'Çeviri Novel', value: 'ceviri-novel' },
|
||||
|
||||
{ label: "Liz-Chan", value: "liz-chan" },
|
||||
{ label: 'Liz-Chan', value: 'liz-chan' },
|
||||
|
||||
{ label: "Manhwa", value: "manhwa" },
|
||||
{ label: 'Manhwa', value: 'manhwa' },
|
||||
|
||||
{ label: "Orijinal Novel", value: "orijinal-novel" },
|
||||
{ label: 'Orijinal Novel', value: 'orijinal-novel' },
|
||||
|
||||
{ label: "Web Novel", value: "web-novel" },
|
||||
{ label: 'Web Novel', value: 'web-novel' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
genres: {
|
||||
value: [],
|
||||
label: "Kategori",
|
||||
label: 'Kategori',
|
||||
options: [
|
||||
{ label: "+18", value: "18" },
|
||||
{ label: '+18', value: '18' },
|
||||
|
||||
{ label: "Action", value: "action" },
|
||||
{ label: 'Action', value: 'action' },
|
||||
|
||||
{ label: "Adult", value: "adult" },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
|
||||
{ label: "Aksiyon", value: "aksiyon" },
|
||||
{ label: 'Aksiyon', value: 'aksiyon' },
|
||||
|
||||
{ label: "BL", value: "bl" },
|
||||
{ label: 'BL', value: 'bl' },
|
||||
|
||||
{ label: "Comedy", value: "comedy" },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
|
||||
{ label: "Doğaüstü", value: "dogaustu" },
|
||||
{ label: 'Doğaüstü', value: 'dogaustu' },
|
||||
|
||||
{ label: "Dram", value: "dram" },
|
||||
{ label: 'Dram', value: 'dram' },
|
||||
|
||||
{ label: "Drama", value: "drama" },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
|
||||
{ label: "Ecchi", value: "ecchi" },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
|
||||
{ label: "Fantastik", value: "fantastik" },
|
||||
{ label: 'Fantastik', value: 'fantastik' },
|
||||
|
||||
{ label: "Fantasy", value: "fantasy" },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
|
||||
{ label: "Gizem", value: "gizem" },
|
||||
{ label: 'Gizem', value: 'gizem' },
|
||||
|
||||
{ label: "Harem", value: "harem" },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
|
||||
{ label: "Historical", value: "historical" },
|
||||
{ label: 'Historical', value: 'historical' },
|
||||
|
||||
{ label: "Josei", value: "josei" },
|
||||
{ label: 'Josei', value: 'josei' },
|
||||
|
||||
{ label: "Macera", value: "macera" },
|
||||
{ label: 'Macera', value: 'macera' },
|
||||
|
||||
{ label: "Manhwa", value: "manhwa" },
|
||||
{ label: 'Manhwa', value: 'manhwa' },
|
||||
|
||||
{ label: "Martial Arts", value: "martial-arts" },
|
||||
{ label: 'Martial Arts', value: 'martial-arts' },
|
||||
|
||||
{ label: "Mature", value: "mature" },
|
||||
{ label: 'Mature', value: 'mature' },
|
||||
|
||||
{ label: "Novel", value: "novel" },
|
||||
{ label: 'Novel', value: 'novel' },
|
||||
|
||||
{ label: "Okul", value: "okul" },
|
||||
{ label: 'Okul', value: 'okul' },
|
||||
|
||||
{ label: "Psikolojik", value: "psikolojik" },
|
||||
{ label: 'Psikolojik', value: 'psikolojik' },
|
||||
|
||||
{ label: "Psychological", value: "psychological" },
|
||||
{ label: 'Psychological', value: 'psychological' },
|
||||
|
||||
{ label: "Reverse Harem", value: "reverse-harem" },
|
||||
{ label: 'Reverse Harem', value: 'reverse-harem' },
|
||||
|
||||
{ label: "Romance", value: "romance" },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
|
||||
{ label: "Romantik", value: "romantik" },
|
||||
{ label: 'Romantik', value: 'romantik' },
|
||||
|
||||
{ label: "Shoujo", value: "shoujo" },
|
||||
{ label: 'Shoujo', value: 'shoujo' },
|
||||
|
||||
{ label: "Slice Of Life", value: "slice-of-life" },
|
||||
{ label: 'Slice Of Life', value: 'slice-of-life' },
|
||||
|
||||
{ label: "Smut", value: "smut" },
|
||||
{ label: 'Smut', value: 'smut' },
|
||||
|
||||
{ label: "Supernatural", value: "supernatural" },
|
||||
{ label: 'Supernatural', value: 'supernatural' },
|
||||
|
||||
{ label: "Tarihi", value: "tarihi" },
|
||||
{ label: 'Tarihi', value: 'tarihi' },
|
||||
|
||||
{ label: "Tragedy", value: "tragedy" },
|
||||
{ label: 'Tragedy', value: 'tragedy' },
|
||||
|
||||
{ label: "Yaoi", value: "yaoi" },
|
||||
{ label: 'Yaoi', value: 'yaoi' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import dayjs from "dayjs";
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
class Smakolykytl implements Plugin.PluginBase {
|
||||
id = "smakolykytl";
|
||||
name = "Смаколики";
|
||||
site = "https://smakolykytl.site/";
|
||||
version = "1.0.0";
|
||||
icon = "src/uk/smakolykytl/icon.png";
|
||||
id = 'smakolykytl';
|
||||
name = 'Смаколики';
|
||||
site = 'https://smakolykytl.site/';
|
||||
version = '1.0.0';
|
||||
icon = 'src/uk/smakolykytl/icon.png';
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ showLatestNovels, filters }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = showLatestNovels
|
||||
? "https://api.smakolykytl.site/api/user/updates"
|
||||
: "https://api.smakolykytl.site/api/user/projects";
|
||||
? 'https://api.smakolykytl.site/api/user/updates'
|
||||
: 'https://api.smakolykytl.site/api/user/projects';
|
||||
|
||||
const result = await fetchApi(url);
|
||||
const json = (await result.json()) as response;
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
(json?.projects || json?.updates)?.forEach((novel) =>
|
||||
(json?.projects || json?.updates)?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel.image.url,
|
||||
path: "titles/" + novel.id,
|
||||
path: 'titles/' + novel.id,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -34,42 +34,42 @@ class Smakolykytl implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const id = novelPath.split("/").pop();
|
||||
const id = novelPath.split('/').pop();
|
||||
const result = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/projects/" + id,
|
||||
'https://api.smakolykytl.site/api/user/projects/' + id,
|
||||
);
|
||||
const book = (await result.json()) as response;
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: book?.project?.title || "",
|
||||
name: book?.project?.title || '',
|
||||
cover: book?.project?.image?.url,
|
||||
summary: book?.project?.description,
|
||||
author: book?.project?.author,
|
||||
status: book?.project?.status_translate.includes("Триває")
|
||||
status: book?.project?.status_translate.includes('Триває')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
const tags = [book?.project?.genres, book?.project?.tags]
|
||||
.flat()
|
||||
.map((tags) => tags?.title)
|
||||
.filter((tags) => tags);
|
||||
.map(tags => tags?.title)
|
||||
.filter(tags => tags);
|
||||
|
||||
if (tags.length) {
|
||||
novel.genres = tags.join(", ");
|
||||
novel.genres = tags.join(', ');
|
||||
}
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const res = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/projects/" + id + "/books",
|
||||
'https://api.smakolykytl.site/api/user/projects/' + id + '/books',
|
||||
);
|
||||
const data = (await res.json()) as response;
|
||||
data?.books?.forEach((volume) =>
|
||||
volume?.chapters?.forEach((chapter) =>
|
||||
data?.books?.forEach(volume =>
|
||||
volume?.chapters?.forEach(chapter =>
|
||||
chapters.push({
|
||||
name: volume.title + " " + chapter.title,
|
||||
path: "read/" + chapter.id,
|
||||
releaseTime: dayjs(chapter.modifiedAt).format("LLL"),
|
||||
name: volume.title + ' ' + chapter.title,
|
||||
path: 'read/' + chapter.id,
|
||||
releaseTime: dayjs(chapter.modifiedAt).format('LLL'),
|
||||
chapterNumber: chapters.length + 1,
|
||||
}),
|
||||
),
|
||||
@@ -80,19 +80,21 @@ class Smakolykytl implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const id = chapterPath.split("/").pop();
|
||||
const id = chapterPath.split('/').pop();
|
||||
const result = await fetchApi(
|
||||
"https://api.smakolykytl.site/api/user/chapters/" + id,
|
||||
'https://api.smakolykytl.site/api/user/chapters/' + id,
|
||||
);
|
||||
const json = (await result.json()) as response;
|
||||
const chapterRaw: HTML[] = JSON.parse(json?.chapter?.content || "[]");
|
||||
const chapterRaw: HTML[] = JSON.parse(json?.chapter?.content || '[]');
|
||||
|
||||
const chapterText = jsonToHtml(chapterRaw);
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi("https://api.smakolykytl.site/api/user/projects");
|
||||
const result = await fetchApi(
|
||||
'https://api.smakolykytl.site/api/user/projects',
|
||||
);
|
||||
const json = (await result.json()) as response;
|
||||
const searchTitle = searchTerm.toLowerCase();
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
@@ -102,11 +104,11 @@ class Smakolykytl implements Plugin.PluginBase {
|
||||
({ title, id }) =>
|
||||
title.toLowerCase().includes(searchTitle) || String(id) == searchTerm,
|
||||
)
|
||||
?.forEach((novel) =>
|
||||
?.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.title,
|
||||
cover: novel.image.url,
|
||||
path: "titles/" + novel.id,
|
||||
path: 'titles/' + novel.id,
|
||||
}),
|
||||
);
|
||||
return novels;
|
||||
@@ -117,34 +119,34 @@ class Smakolykytl implements Plugin.PluginBase {
|
||||
|
||||
export default new Smakolykytl();
|
||||
|
||||
function jsonToHtml(json: HTML[], html: string = "") {
|
||||
json.forEach((element) => {
|
||||
function jsonToHtml(json: HTML[], html: string = '') {
|
||||
json.forEach(element => {
|
||||
switch (element.type) {
|
||||
case "hardBreak":
|
||||
html += "<br>";
|
||||
case 'hardBreak':
|
||||
html += '<br>';
|
||||
break;
|
||||
case "horizontalRule":
|
||||
html += "<hr>";
|
||||
case 'horizontalRule':
|
||||
html += '<hr>';
|
||||
break;
|
||||
case "image":
|
||||
case 'image':
|
||||
if (element.attrs) {
|
||||
const attrs = Object.entries(element.attrs)
|
||||
.filter((attr) => attr?.[1])
|
||||
.map((attr) => `${attr[0]}="${attr[1]}"`);
|
||||
html += "<img " + attrs.join("; ") + ">";
|
||||
.filter(attr => attr?.[1])
|
||||
.map(attr => `${attr[0]}="${attr[1]}"`);
|
||||
html += '<img ' + attrs.join('; ') + '>';
|
||||
}
|
||||
break;
|
||||
case "paragraph":
|
||||
case 'paragraph':
|
||||
html +=
|
||||
"<p>" +
|
||||
(element.content ? jsonToHtml(element.content) : "<br>") +
|
||||
"</p>";
|
||||
'<p>' +
|
||||
(element.content ? jsonToHtml(element.content) : '<br>') +
|
||||
'</p>';
|
||||
break;
|
||||
case "text":
|
||||
case 'text':
|
||||
html += element.text;
|
||||
break;
|
||||
default:
|
||||
html += JSON.stringify(element, null, "\t"); //maybe I missed something.
|
||||
html += JSON.stringify(element, null, '\t'); //maybe I missed something.
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
+124
-114
@@ -1,31 +1,26 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { isUrlAbsolute } from "@libs/isAbsoluteUrl";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { isUrlAbsolute } from '@libs/isAbsoluteUrl';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
|
||||
class HakoPlugin implements Plugin.PluginBase {
|
||||
id = "ln.hako";
|
||||
name = "Hako";
|
||||
icon = "src/vi/hakolightnovel/icon.png";
|
||||
site = "https://ln.hako.vn";
|
||||
version = "1.0.1";
|
||||
id = 'ln.hako';
|
||||
name = 'Hako';
|
||||
icon = 'src/vi/hakolightnovel/icon.png';
|
||||
site = 'https://ln.hako.vn';
|
||||
version = '1.0.1';
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio("#mainpart .row .thumb-item-flow").each((index, ele) => {
|
||||
loadedCheerio('#mainpart .row .thumb-item-flow').each((index, ele) => {
|
||||
let url = loadedCheerio(ele)
|
||||
.find("div.thumb_attr.series-title > a")
|
||||
.attr("href");
|
||||
.find('div.thumb_attr.series-title > a')
|
||||
.attr('href');
|
||||
|
||||
if (url) {
|
||||
const name = loadedCheerio(ele)
|
||||
.find(".series-title")
|
||||
.text()
|
||||
.trim();
|
||||
let cover = loadedCheerio(ele)
|
||||
.find(".img-in-ratio")
|
||||
.attr("data-bg");
|
||||
const name = loadedCheerio(ele).find('.series-title').text().trim();
|
||||
let cover = loadedCheerio(ele).find('.img-in-ratio').attr('data-bg');
|
||||
|
||||
if (cover && !isUrlAbsolute(cover)) {
|
||||
cover = this.site + cover;
|
||||
@@ -40,21 +35,24 @@ class HakoPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{showLatestNovels, filters}: Plugin.PopularNovelsOptions<typeof this.filters>
|
||||
{
|
||||
showLatestNovels,
|
||||
filters,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site + "/danh-sach"
|
||||
if(filters.alphabet.value){
|
||||
link += "/" + filters.alphabet.value;
|
||||
let link = this.site + '/danh-sach';
|
||||
if (filters.alphabet.value) {
|
||||
link += '/' + filters.alphabet.value;
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
for(const novelType of filters.type.value){
|
||||
params.append(novelType, "1");
|
||||
for (const novelType of filters.type.value) {
|
||||
params.append(novelType, '1');
|
||||
}
|
||||
for(const status of filters.status.value){
|
||||
params.append(status, "1");
|
||||
for (const status of filters.status.value) {
|
||||
params.append(status, '1');
|
||||
}
|
||||
params.append("sapxep", filters.sort.value);
|
||||
link += '?' + params.toString() + '&page=' + pageNo
|
||||
params.append('sapxep', filters.sort.value);
|
||||
link += '?' + params.toString() + '&page=' + pageNo;
|
||||
const result = await fetch(link);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
@@ -70,37 +68,43 @@ class HakoPlugin implements Plugin.PluginBase {
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
novel.name = loadedCheerio(".series-name").text().trim();
|
||||
novel.name = loadedCheerio('.series-name').text().trim();
|
||||
|
||||
const background =
|
||||
loadedCheerio(".series-cover > .a6-ratio > div").attr("style") ||
|
||||
"";
|
||||
loadedCheerio('.series-cover > .a6-ratio > div').attr('style') || '';
|
||||
const novelCover = background.substring(
|
||||
background.indexOf("http"),
|
||||
background.length - 2
|
||||
background.indexOf('http'),
|
||||
background.length - 2,
|
||||
);
|
||||
|
||||
novel.cover = novelCover
|
||||
? isUrlAbsolute(novelCover)
|
||||
? novelCover
|
||||
: this.site + novelCover
|
||||
: "";
|
||||
: '';
|
||||
|
||||
novel.summary = loadedCheerio(".summary-content").text().trim();
|
||||
novel.summary = loadedCheerio('.summary-content').text().trim();
|
||||
|
||||
novel.genres = loadedCheerio(".series-information > div:nth-child(1)")
|
||||
novel.genres = loadedCheerio('.series-information > div:nth-child(1)')
|
||||
.text()
|
||||
.trim()
|
||||
.split(/\n[\s\n]*/).join(',');
|
||||
.split(/\n[\s\n]*/)
|
||||
.join(',');
|
||||
|
||||
novel.author = loadedCheerio('.series-information > div:nth-child(2) > .info-value')
|
||||
novel.author = loadedCheerio(
|
||||
'.series-information > div:nth-child(2) > .info-value',
|
||||
)
|
||||
.text()
|
||||
.trim();
|
||||
novel.artist = loadedCheerio('.series-information > div:nth-child(3) > .info-value')
|
||||
novel.artist = loadedCheerio(
|
||||
'.series-information > div:nth-child(3) > .info-value',
|
||||
)
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
novel.status = loadedCheerio('.series-information > div:nth-child(4) > .info-value')
|
||||
novel.status = loadedCheerio(
|
||||
'.series-information > div:nth-child(4) > .info-value',
|
||||
)
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
@@ -121,36 +125,41 @@ class HakoPlugin implements Plugin.PluginBase {
|
||||
novel.chapters = [];
|
||||
loadedCheerio('.volume-list').each((idx, ele) => {
|
||||
const customPage = loadedCheerio(ele).find('.sect-title').text().trim();
|
||||
let num = 0, part = 1;
|
||||
loadedCheerio(ele).find('.list-chapters > li').each((idx, chapterEle) => {
|
||||
const path = loadedCheerio(chapterEle).find("a").attr("href");
|
||||
let num = 0,
|
||||
part = 1;
|
||||
loadedCheerio(ele)
|
||||
.find('.list-chapters > li')
|
||||
.each((idx, chapterEle) => {
|
||||
const path = loadedCheerio(chapterEle).find('a').attr('href');
|
||||
const chapterName = loadedCheerio(chapterEle)
|
||||
.find(".chapter-name")
|
||||
.find('.chapter-name')
|
||||
.text()
|
||||
.trim();
|
||||
let chapterNumber = Number(chapterName.match(/Chương\s*(\d+)/i)?.[1]);
|
||||
if(chapterNumber){
|
||||
if (chapterNumber) {
|
||||
num = chapterNumber;
|
||||
part = 1;
|
||||
}else{
|
||||
} else {
|
||||
chapterNumber = num + part / 10;
|
||||
part ++;
|
||||
part++;
|
||||
}
|
||||
const chapterTime = loadedCheerio(chapterEle)
|
||||
.find(".chapter-time")
|
||||
.find('.chapter-time')
|
||||
.text()
|
||||
.split('/')
|
||||
.map(x => Number(x));
|
||||
if(path){
|
||||
novel.chapters?.push(
|
||||
{
|
||||
if (path) {
|
||||
novel.chapters?.push({
|
||||
page: customPage,
|
||||
path,
|
||||
name: chapterName,
|
||||
releaseTime: new Date(chapterTime[2], chapterTime[1], chapterTime[0]).toISOString(),
|
||||
releaseTime: new Date(
|
||||
chapterTime[2],
|
||||
chapterTime[1],
|
||||
chapterTime[0],
|
||||
).toISOString(),
|
||||
chapterNumber: chapterNumber,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -162,15 +171,16 @@ class HakoPlugin implements Plugin.PluginBase {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio("#chapter-content").html() || "";
|
||||
const chapterText = loadedCheerio('#chapter-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + "/tim-kiem?keywords=" + searchTerm + '&page=' + pageNo;
|
||||
const url =
|
||||
this.site + '/tim-kiem?keywords=' + searchTerm + '&page=' + pageNo;
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
@@ -178,81 +188,81 @@ class HakoPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
const headers = {
|
||||
Referer: "https://ln.hako.vn",
|
||||
Referer: 'https://ln.hako.vn',
|
||||
};
|
||||
return await fetchFile(url, { headers: headers });
|
||||
}
|
||||
filters = {
|
||||
alphabet: {
|
||||
type: FilterTypes.Picker,
|
||||
value: "",
|
||||
label: "Chữ cái",
|
||||
value: '',
|
||||
label: 'Chữ cái',
|
||||
options: [
|
||||
{label: 'Tất cả', value: ''},
|
||||
{label: 'Khác', value: 'khac'},
|
||||
{label: 'A', value: 'a'},
|
||||
{label: 'B', value: 'b'},
|
||||
{label: 'C', value: 'c'},
|
||||
{label: 'D', value: 'd'},
|
||||
{label: 'E', value: 'e'},
|
||||
{label: 'F', value: 'f'},
|
||||
{label: 'G', value: 'g'},
|
||||
{label: 'H', value: 'h'},
|
||||
{label: 'I', value: 'i'},
|
||||
{label: 'J', value: 'j'},
|
||||
{label: 'K', value: 'k'},
|
||||
{label: 'L', value: 'l'},
|
||||
{label: 'M', value: 'm'},
|
||||
{label: 'N', value: 'n'},
|
||||
{label: 'O', value: 'o'},
|
||||
{label: 'P', value: 'p'},
|
||||
{label: 'Q', value: 'q'},
|
||||
{label: 'R', value: 'r'},
|
||||
{label: 'S', value: 's'},
|
||||
{label: 'T', value: 't'},
|
||||
{label: 'U', value: 'u'},
|
||||
{label: 'V', value: 'v'},
|
||||
{label: 'W', value: 'w'},
|
||||
{label: 'X', value: 'x'},
|
||||
{label: 'Y', value: 'y'},
|
||||
{label: 'Z', value: 'z'},
|
||||
]
|
||||
{ label: 'Tất cả', value: '' },
|
||||
{ label: 'Khác', value: 'khac' },
|
||||
{ label: 'A', value: 'a' },
|
||||
{ label: 'B', value: 'b' },
|
||||
{ label: 'C', value: 'c' },
|
||||
{ label: 'D', value: 'd' },
|
||||
{ label: 'E', value: 'e' },
|
||||
{ label: 'F', value: 'f' },
|
||||
{ label: 'G', value: 'g' },
|
||||
{ label: 'H', value: 'h' },
|
||||
{ label: 'I', value: 'i' },
|
||||
{ label: 'J', value: 'j' },
|
||||
{ label: 'K', value: 'k' },
|
||||
{ label: 'L', value: 'l' },
|
||||
{ label: 'M', value: 'm' },
|
||||
{ label: 'N', value: 'n' },
|
||||
{ label: 'O', value: 'o' },
|
||||
{ label: 'P', value: 'p' },
|
||||
{ label: 'Q', value: 'q' },
|
||||
{ label: 'R', value: 'r' },
|
||||
{ label: 'S', value: 's' },
|
||||
{ label: 'T', value: 't' },
|
||||
{ label: 'U', value: 'u' },
|
||||
{ label: 'V', value: 'v' },
|
||||
{ label: 'W', value: 'w' },
|
||||
{ label: 'X', value: 'x' },
|
||||
{ label: 'Y', value: 'y' },
|
||||
{ label: 'Z', value: 'z' },
|
||||
],
|
||||
},
|
||||
type: {
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
label: "Phân loại",
|
||||
label: 'Phân loại',
|
||||
value: [],
|
||||
options: [
|
||||
{label: "Truyện dịch", value: "truyendich"},
|
||||
{label: "Truyện sáng tác", value: "sangtac"},
|
||||
{label: "Convert", value: "convert"}
|
||||
]
|
||||
{ label: 'Truyện dịch', value: 'truyendich' },
|
||||
{ label: 'Truyện sáng tác', value: 'sangtac' },
|
||||
{ label: 'Convert', value: 'convert' },
|
||||
],
|
||||
},
|
||||
status: {
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
label: "Tình trạng",
|
||||
label: 'Tình trạng',
|
||||
value: [],
|
||||
options: [
|
||||
{label: "Đang tiến hành", value: "dangtienhanh"},
|
||||
{label: "Tạm ngưng", value: "tamngung"},
|
||||
{label: "Đã hoàn thành", value: "hoanthanh"}
|
||||
]
|
||||
{ label: 'Đang tiến hành', value: 'dangtienhanh' },
|
||||
{ label: 'Tạm ngưng', value: 'tamngung' },
|
||||
{ label: 'Đã hoàn thành', value: 'hoanthanh' },
|
||||
],
|
||||
},
|
||||
sort: {
|
||||
type: FilterTypes.Picker,
|
||||
label: "Sắp xếp",
|
||||
value: "top",
|
||||
label: 'Sắp xếp',
|
||||
value: 'top',
|
||||
options: [
|
||||
{label: "A-Z", value: "tentruyen"},
|
||||
{label: "Z-A", value: "tentruyenza"},
|
||||
{label: "Mới cập nhật", value: "capnhat"},
|
||||
{label: "Truyện mới", value: "truyenmoi"},
|
||||
{label: "Theo dõi", value: "theodoi"},
|
||||
{label: "Top toàn thời gian", value: "top"},
|
||||
{label: "Top tháng", value: "topthang"},
|
||||
{label: "Số từ", value: "sotu"},
|
||||
]
|
||||
}
|
||||
{ label: 'A-Z', value: 'tentruyen' },
|
||||
{ label: 'Z-A', value: 'tentruyenza' },
|
||||
{ label: 'Mới cập nhật', value: 'capnhat' },
|
||||
{ label: 'Truyện mới', value: 'truyenmoi' },
|
||||
{ label: 'Theo dõi', value: 'theodoi' },
|
||||
{ label: 'Top toàn thời gian', value: 'top' },
|
||||
{ label: 'Top tháng', value: 'topthang' },
|
||||
{ label: 'Số từ', value: 'sotu' },
|
||||
],
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
|
||||
+135
-123
@@ -1,14 +1,13 @@
|
||||
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { FilterTypes, Filters } from "@libs/filterInputs";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
interface VolumeData {
|
||||
story: string;
|
||||
navigation: string;
|
||||
value: string
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ChapterSelect {
|
||||
@@ -17,44 +16,52 @@ interface ChapterSelect {
|
||||
}
|
||||
|
||||
interface VolumeList {
|
||||
chapters: Plugin.ChapterItem[],
|
||||
volumes?: VolumeData[]
|
||||
chapters: Plugin.ChapterItem[];
|
||||
volumes?: VolumeData[];
|
||||
}
|
||||
|
||||
class TruyenConect implements Plugin.PagePlugin {
|
||||
id = "truyenconect";
|
||||
name = "Truyen Conect";
|
||||
icon = "src/vi/truyenconect/icon.png";
|
||||
site = "https://truyenconect.com";
|
||||
version = "1.0.0";
|
||||
async sleep (ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
id = 'truyenconect';
|
||||
name = 'Truyen Conect';
|
||||
icon = 'src/vi/truyenconect/icon.png';
|
||||
site = 'https://truyenconect.com';
|
||||
version = '1.0.0';
|
||||
async sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
parseNovels(loadedCheerio: CheerioAPI, selector: string){
|
||||
parseNovels(loadedCheerio: CheerioAPI, selector: string) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio(selector).each((idx, ele) => {
|
||||
const url = loadedCheerio(ele).find('a').attr('href');
|
||||
if(url){
|
||||
if (url) {
|
||||
novels.push({
|
||||
name: loadedCheerio(ele).find('img').attr('alt') || '',
|
||||
path: url.replace(this.site, ''),
|
||||
cover: loadedCheerio(ele).find('img').attr('data-src'),
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
async popularNovels(pageNo: number, {filters, showLatestNovels}: Plugin.PopularNovelsOptions<typeof this.filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let link = this.site;
|
||||
let selector = '.c-page__content > .grid9.block .item-thumb.c-image-hover';
|
||||
|
||||
if(showLatestNovels){
|
||||
selector = '.c-page__content .page-content-listing.item-big_thumbnail .item-thumb.c-image-hover';
|
||||
}else if(filters.category.value){
|
||||
if (showLatestNovels) {
|
||||
selector =
|
||||
'.c-page__content .page-content-listing.item-big_thumbnail .item-thumb.c-image-hover';
|
||||
} else if (filters.category.value) {
|
||||
link += '/' + filters.category.value;
|
||||
selector = 'table.manga-shortcodes.manga-chapters-listing td[width="10%"]';
|
||||
if(filters.category.value === 'the-loai'){
|
||||
selector = '.item-thumb.hover-details.c-image-hover'
|
||||
selector =
|
||||
'table.manga-shortcodes.manga-chapters-listing td[width="10%"]';
|
||||
if (filters.category.value === 'the-loai') {
|
||||
selector = '.item-thumb.hover-details.c-image-hover';
|
||||
link += '/' + filters.genre.value;
|
||||
}
|
||||
link += '?page=' + pageNo;
|
||||
@@ -68,18 +75,18 @@ class TruyenConect implements Plugin.PagePlugin {
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('option').each((idx, ele) => {
|
||||
let url = ele.attribs['value'];
|
||||
if(!url) return;
|
||||
if (!url) return;
|
||||
const chapterId = url.match(/\/(\d+)-/)?.[1];
|
||||
if(chapterId){
|
||||
url = url.replace(chapterId+'-', '') + '-' + chapterId;
|
||||
if (chapterId) {
|
||||
url = url.replace(chapterId + '-', '') + '-' + chapterId;
|
||||
}
|
||||
let num = url.match(/chuong-(\d+)/)?.[1];
|
||||
chapters.push({
|
||||
path: url.replace(this.site, ''),
|
||||
name: loadedCheerio(ele).text().trim(),
|
||||
chapterNumber: Number(num) || undefined,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
return chapters.reverse();
|
||||
}
|
||||
parseVolumes(loadedCheerio: CheerioAPI) {
|
||||
@@ -89,91 +96,90 @@ class TruyenConect implements Plugin.PagePlugin {
|
||||
story: ele.attribs['data-story'],
|
||||
navigation: ele.attribs['data-navigation'],
|
||||
value: ele.attribs['value'],
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
return volumes;
|
||||
}
|
||||
async getVolumes (firstChapterUrl: string) {
|
||||
async getVolumes(firstChapterUrl: string) {
|
||||
const chapterId = firstChapterUrl.match(/-(\d+)$/)?.[1];
|
||||
if(!chapterId) throw new Error('Không tìm thấy chương');
|
||||
if (!chapterId) throw new Error('Không tìm thấy chương');
|
||||
const url = this.site + '/truyen/get-chap-selector?chap=' + chapterId;
|
||||
const data: ChapterSelect = await fetch(url).then(r => r.json());
|
||||
const volumeList: VolumeList = {
|
||||
chapters: this.parseChapters(parseHTML(data.chap_selector))
|
||||
chapters: this.parseChapters(parseHTML(data.chap_selector)),
|
||||
};
|
||||
if (data.eps_selector) {
|
||||
volumeList.volumes = this.parseVolumes(
|
||||
parseHTML(data.eps_selector as string),
|
||||
);
|
||||
}
|
||||
if(data.eps_selector){
|
||||
volumeList.volumes = this.parseVolumes(parseHTML(data.eps_selector as string))
|
||||
return volumeList;
|
||||
}
|
||||
return volumeList
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number; }> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const url = this.site + novelPath;
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel & { totalPages: number; }= {
|
||||
name: loadedCheerio(".post-title > h1").text().trim(),
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
name: loadedCheerio('.post-title > h1').text().trim(),
|
||||
path: novelPath,
|
||||
chapters: [],
|
||||
totalPages: 1
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
novel.cover = loadedCheerio(".summary_image > a > img").attr(
|
||||
"data-src"
|
||||
);
|
||||
novel.cover = loadedCheerio('.summary_image > a > img').attr('data-src');
|
||||
|
||||
loadedCheerio(".post-content_item").each(function () {
|
||||
loadedCheerio('.post-content_item').each(function () {
|
||||
const detailName = loadedCheerio(this)
|
||||
.find(".summary-heading > h5")
|
||||
.find('.summary-heading > h5')
|
||||
.text()
|
||||
.trim();
|
||||
const detail = loadedCheerio(this).find(".summary-content").html();
|
||||
const detail = loadedCheerio(this).find('.summary-content').html();
|
||||
|
||||
if (!detail) return;
|
||||
|
||||
switch (detailName) {
|
||||
case "Thể loại":
|
||||
case 'Thể loại':
|
||||
novel.genres = loadedCheerio(detail)
|
||||
.children("a")
|
||||
.children('a')
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
break;
|
||||
case "Tác giả":
|
||||
case 'Tác giả':
|
||||
novel.author = loadedCheerio(detail).text().trim();
|
||||
break;
|
||||
case "Hoạ sĩ":
|
||||
case 'Hoạ sĩ':
|
||||
novel.artist = loadedCheerio(detail).text().trim();
|
||||
break;
|
||||
case "Trạng thái":
|
||||
case 'Trạng thái':
|
||||
const status = detail.trim();
|
||||
if(status === 'Full') novel.status = NovelStatus.Completed;
|
||||
else if(status === 'Tạm ngưng') novel.status = NovelStatus.OnHiatus;
|
||||
else if(status === 'Đang tiến hành') novel.status = NovelStatus.Ongoing
|
||||
if (status === 'Full') novel.status = NovelStatus.Completed;
|
||||
else if (status === 'Tạm ngưng') novel.status = NovelStatus.OnHiatus;
|
||||
else if (status === 'Đang tiến hành')
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
else novel.status = NovelStatus.Unknown;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
loadedCheerio(
|
||||
".description-summary > div.summary__content > div"
|
||||
).remove();
|
||||
loadedCheerio('.description-summary > div.summary__content > div').remove();
|
||||
|
||||
novel.summary = loadedCheerio(
|
||||
".description-summary > div.summary__content"
|
||||
)
|
||||
novel.summary = loadedCheerio('.description-summary > div.summary__content')
|
||||
.text()
|
||||
.trim();
|
||||
const firstChapLink = loadedCheerio('#init-links > a').first().attr('href');
|
||||
if(!firstChapLink) throw new Error('Không tìm thấy truyện');
|
||||
if (!firstChapLink) throw new Error('Không tìm thấy truyện');
|
||||
await this.sleep(1000);
|
||||
const volumeList = await this.getVolumes(firstChapLink);
|
||||
if(volumeList.volumes) {
|
||||
if (volumeList.volumes) {
|
||||
novel.totalPages = volumeList.volumes.length;
|
||||
novel.chapters = volumeList.chapters
|
||||
}else{
|
||||
novel.chapters = volumeList.chapters;
|
||||
} else {
|
||||
novel.totalPages = 1;
|
||||
novel.chapters = volumeList.chapters;
|
||||
}
|
||||
@@ -185,32 +191,38 @@ class TruyenConect implements Plugin.PagePlugin {
|
||||
const body = await result.text();
|
||||
let loadedCheerio = parseHTML(body);
|
||||
const firstChapLink = loadedCheerio('#init-links > a').first().attr('href');
|
||||
if(!firstChapLink) throw new Error('Không tìm thấy truyện');
|
||||
if (!firstChapLink) throw new Error('Không tìm thấy truyện');
|
||||
await this.sleep(1000);
|
||||
const volumeList = await this.getVolumes(firstChapLink);
|
||||
const volumeIndex = Number(page) - 1;
|
||||
if(!volumeList.volumes) throw new Error("Không tìm thấy truyện");
|
||||
if(volumeIndex >= volumeList.volumes.length) throw new Error("Không tìm thấy volume");
|
||||
if (!volumeList.volumes) throw new Error('Không tìm thấy truyện');
|
||||
if (volumeIndex >= volumeList.volumes.length)
|
||||
throw new Error('Không tìm thấy volume');
|
||||
const volume = volumeList.volumes[volumeIndex];
|
||||
const query = `dataEpisodes=${volume.value}&datastory=${volume.story}&dataNavigation=${encodeURIComponent(volume.navigation)}`;
|
||||
const chaptersUrl = `${this.site}/truyen/getreadingchapAction?${query}`;
|
||||
await this.sleep(1000);
|
||||
const res: {err: number, html: string} = await fetch(chaptersUrl).then(r => r.json());
|
||||
if(res.err) throw new Error(res.html);
|
||||
const res: { err: number; html: string } = await fetch(chaptersUrl).then(
|
||||
r => r.json(),
|
||||
);
|
||||
if (res.err) throw new Error(res.html);
|
||||
return {
|
||||
chapters: this.parseChapters(parseHTML(res.html))
|
||||
}
|
||||
chapters: this.parseChapters(parseHTML(res.html)),
|
||||
};
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetch(this.site + chapterPath);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
let chapterText = loadedCheerio(".reading-content").html() || '';
|
||||
let chapterText = loadedCheerio('.reading-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}?key=${searchTerm}&page=${pageNo}`;
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -219,7 +231,7 @@ class TruyenConect implements Plugin.PagePlugin {
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".tab-thumb.c-image-hover > a").each((idx, ele) => {
|
||||
loadedCheerio('.tab-thumb.c-image-hover > a').each((idx, ele) => {
|
||||
const novelName = ele.attribs['title'];
|
||||
const novelCover = loadedCheerio(ele).find('img').first().attr('src');
|
||||
const novelUrl = ele.attribs['href'];
|
||||
@@ -240,59 +252,59 @@ class TruyenConect implements Plugin.PagePlugin {
|
||||
}
|
||||
filters = {
|
||||
category: {
|
||||
label: "Lọc theo",
|
||||
label: 'Lọc theo',
|
||||
value: '',
|
||||
type: FilterTypes.Picker,
|
||||
options: [
|
||||
{label: '', value: ''},
|
||||
{label: 'Thể loại', value: 'the-loai'},
|
||||
{label: 'Truyện dịch', value: 'truyen-dich'},
|
||||
{label: 'Truyện convert', value: 'convert'}
|
||||
]
|
||||
{ label: '', value: '' },
|
||||
{ label: 'Thể loại', value: 'the-loai' },
|
||||
{ label: 'Truyện dịch', value: 'truyen-dich' },
|
||||
{ label: 'Truyện convert', value: 'convert' },
|
||||
],
|
||||
},
|
||||
genre: {
|
||||
label: 'Thể loại',
|
||||
value: 'action',
|
||||
type: FilterTypes.Picker,
|
||||
options: [
|
||||
{label: 'Action', value: 'action'},
|
||||
{label: 'Adult', value: 'adult'},
|
||||
{label: 'Adventure', value: 'adventure'},
|
||||
{label: 'Chinese novel', value: 'chinese-novel'},
|
||||
{label: 'Chuyển Sinh', value: 'chuyen-sinh'},
|
||||
{label: 'English Novel', value: 'english-novel'},
|
||||
{label: 'Harem', value: 'harem'},
|
||||
{label: 'Ecchi', value: 'ecchi'},
|
||||
{label: 'Fantasy', value: 'fantasy'},
|
||||
{label: 'Drama', value: 'drama'},
|
||||
{label: 'Game', value: 'game'},
|
||||
{label: 'Tiên hiệp', value: 'tien-hiep'},
|
||||
{label: 'Kiếm Hiệp', value: 'kiem-hiep'},
|
||||
{label: 'Ngôn Tình', value: 'ngon-tinh'},
|
||||
{label: 'Isekai', value: 'isekai'},
|
||||
{label: 'Lịch Sử', value: 'lich-su'},
|
||||
{label: 'Web Novel', value: 'web-novel'},
|
||||
{label: 'Xuyên không', value: 'xuyen-khong'},
|
||||
{label: 'Trọng sinh', value: 'trong-sinh'},
|
||||
{label: 'Trinh thám', value: 'trinh-tham'},
|
||||
{label: 'Dị giới', value: 'di-gioi'},
|
||||
{label: 'Huyền ảo', value: 'huyen-ao'},
|
||||
{label: 'Sắc Hiệp', value: 'sac-hiep'},
|
||||
{label: 'Dị năng', value: 'di-nang'},
|
||||
{label: 'Linh dị', value: 'linh-di'},
|
||||
{label: 'Đô thị', value: 'do-thi'},
|
||||
{label: 'Comedy', value: 'comedy'},
|
||||
{label: 'School Life', value: 'school-life'},
|
||||
{label: 'Romance', value: 'romance'},
|
||||
{label: 'Martial-arts', value: 'martial-arts'},
|
||||
{label: 'Light Novel', value: 'light-novel'},
|
||||
{label: 'Huyền huyễn', value: 'huyen-huyen'},
|
||||
{label: 'Kỳ Huyễn', value: 'ky-huyen'},
|
||||
{label: 'Khoa Huyễn', value: 'khoa-huyen'},
|
||||
{label: 'Võng Du', value: 'vong-du'},
|
||||
{label: 'Đồng Nhân', value: 'dong-nhan'},
|
||||
]
|
||||
}
|
||||
{ label: 'Action', value: 'action' },
|
||||
{ label: 'Adult', value: 'adult' },
|
||||
{ label: 'Adventure', value: 'adventure' },
|
||||
{ label: 'Chinese novel', value: 'chinese-novel' },
|
||||
{ label: 'Chuyển Sinh', value: 'chuyen-sinh' },
|
||||
{ label: 'English Novel', value: 'english-novel' },
|
||||
{ label: 'Harem', value: 'harem' },
|
||||
{ label: 'Ecchi', value: 'ecchi' },
|
||||
{ label: 'Fantasy', value: 'fantasy' },
|
||||
{ label: 'Drama', value: 'drama' },
|
||||
{ label: 'Game', value: 'game' },
|
||||
{ label: 'Tiên hiệp', value: 'tien-hiep' },
|
||||
{ label: 'Kiếm Hiệp', value: 'kiem-hiep' },
|
||||
{ label: 'Ngôn Tình', value: 'ngon-tinh' },
|
||||
{ label: 'Isekai', value: 'isekai' },
|
||||
{ label: 'Lịch Sử', value: 'lich-su' },
|
||||
{ label: 'Web Novel', value: 'web-novel' },
|
||||
{ label: 'Xuyên không', value: 'xuyen-khong' },
|
||||
{ label: 'Trọng sinh', value: 'trong-sinh' },
|
||||
{ label: 'Trinh thám', value: 'trinh-tham' },
|
||||
{ label: 'Dị giới', value: 'di-gioi' },
|
||||
{ label: 'Huyền ảo', value: 'huyen-ao' },
|
||||
{ label: 'Sắc Hiệp', value: 'sac-hiep' },
|
||||
{ label: 'Dị năng', value: 'di-nang' },
|
||||
{ label: 'Linh dị', value: 'linh-di' },
|
||||
{ label: 'Đô thị', value: 'do-thi' },
|
||||
{ label: 'Comedy', value: 'comedy' },
|
||||
{ label: 'School Life', value: 'school-life' },
|
||||
{ label: 'Romance', value: 'romance' },
|
||||
{ label: 'Martial-arts', value: 'martial-arts' },
|
||||
{ label: 'Light Novel', value: 'light-novel' },
|
||||
{ label: 'Huyền huyễn', value: 'huyen-huyen' },
|
||||
{ label: 'Kỳ Huyễn', value: 'ky-huyen' },
|
||||
{ label: 'Khoa Huyễn', value: 'khoa-huyen' },
|
||||
{ label: 'Võng Du', value: 'vong-du' },
|
||||
{ label: 'Đồng Nhân', value: 'dong-nhan' },
|
||||
],
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { NovelItem } from "../../test_web/static/js";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { NovelItem } from '../../test_web/static/js';
|
||||
|
||||
interface SearchedNovel {
|
||||
name: string;
|
||||
@@ -11,90 +11,104 @@ interface SearchedNovel {
|
||||
coverUrl: string;
|
||||
}
|
||||
interface SearchedResult {
|
||||
data?: SearchedNovel[]
|
||||
data?: SearchedNovel[];
|
||||
}
|
||||
|
||||
class LightNovelVN implements Plugin.PagePlugin {
|
||||
id = "lightnovel.vn";
|
||||
name = "Light Novel VN";
|
||||
version = "1.0.0";
|
||||
icon = "src/vi/lightnovelvn/icon.png";
|
||||
id = 'lightnovel.vn';
|
||||
name = 'Light Novel VN';
|
||||
version = '1.0.0';
|
||||
icon = 'src/vi/lightnovelvn/icon.png';
|
||||
filters?: Filters | undefined;
|
||||
site = "https://lightnovel.vn";
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
site = 'https://lightnovel.vn';
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/truyen-hot-ds?page=${pageNo}`;
|
||||
const body = await fetch(url).then((r) => r.text());
|
||||
const body = await fetch(url).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".flex.flex-col[itemtype='https://schema.org/Book']").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('h3[itemprop="name"] > a').text().trim();
|
||||
loadedCheerio(".flex.flex-col[itemtype='https://schema.org/Book']").each(
|
||||
(idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find('h3[itemprop="name"] > a')
|
||||
.text()
|
||||
.trim();
|
||||
const img = loadedCheerio(ele).find('noscript').html();
|
||||
const novelCover = img?.match(/srcSet="([^\s]+)/)?.[1];
|
||||
const novelUrl = loadedCheerio(ele).find('h3[itemprop="name"] > a').attr('href');
|
||||
if(novelUrl)
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find('h3[itemprop="name"] > a')
|
||||
.attr('href');
|
||||
if (novelUrl)
|
||||
novels.push({
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
parseChapters(loadedCheerio: CheerioAPI){
|
||||
parseChapters(loadedCheerio: CheerioAPI) {
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('ul.chapter-list > li').each((idx, ele) => {
|
||||
const chNum = Number(loadedCheerio(ele).find('div').first().text());
|
||||
const chapterUrl = loadedCheerio(ele).find('a').attr('href');
|
||||
const name = loadedCheerio(ele).find('a').text().trim();
|
||||
if(chapterUrl){
|
||||
if (chapterUrl) {
|
||||
chapters.push({
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
name,
|
||||
chapterNumber: chNum
|
||||
})
|
||||
chapterNumber: chNum,
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
return chapters;
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number; }> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const url = this.site + novelPath;
|
||||
const body = await fetch(url).then((r) => r.text());
|
||||
const body = await fetch(url).then(r => r.text());
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel & { totalPages: number; } = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
chapters: [],
|
||||
name: 'Không có tiêu đề',
|
||||
totalPages: 1
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
novel.name = loadedCheerio('h1[itemprop="name"]').text().trim();
|
||||
|
||||
novel.cover = loadedCheerio('header div:nth-child(2) img').attr('srcset')?.split(/\s+/)[0]
|
||||
novel.cover = loadedCheerio('header div:nth-child(2) img')
|
||||
.attr('srcset')
|
||||
?.split(/\s+/)[0];
|
||||
|
||||
const genres: string[] = [];
|
||||
loadedCheerio('a[itemprop="genre"]').each(function() {
|
||||
loadedCheerio('a[itemprop="genre"]').each(function () {
|
||||
genres.push(loadedCheerio(this).text());
|
||||
})
|
||||
});
|
||||
novel.genres = genres.join(',');
|
||||
|
||||
novel.status = loadedCheerio('span.font-bold.text-size22:last').text();
|
||||
if(novel.status === 'Đang ra'){
|
||||
if (novel.status === 'Đang ra') {
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
}else if(novel.status === 'Hoàn thành'){
|
||||
} else if (novel.status === 'Hoàn thành') {
|
||||
novel.status = NovelStatus.Completed;
|
||||
}else {
|
||||
} else {
|
||||
novel.status = NovelStatus.Unknown;
|
||||
}
|
||||
novel.author = loadedCheerio('a[itemprop="author"] > span').text();
|
||||
|
||||
novel.summary = loadedCheerio('#bookIntro').text().replace(/\s+/g, ' ');
|
||||
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
||||
await delay(1000);
|
||||
const chapterListUrl = url + '/danh-sach-chuong';
|
||||
|
||||
@@ -103,13 +117,13 @@ class LightNovelVN implements Plugin.PagePlugin {
|
||||
novel.chapters = this.parseChapters(loadedChapterList);
|
||||
loadedChapterList('nav[aria-label="Pagination"] a').each((index, ele) => {
|
||||
const href = ele.attribs['href'];
|
||||
if(href){
|
||||
if (href) {
|
||||
const page = Number(href.match(/\?page=(\d+)/)?.[1]);
|
||||
if(page && page > novel.totalPages){
|
||||
if (page && page > novel.totalPages) {
|
||||
novel.totalPages = page;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
return novel;
|
||||
}
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
@@ -117,45 +131,48 @@ class LightNovelVN implements Plugin.PagePlugin {
|
||||
const chapterListBody = await fetchApi(url).then(r => r.text());
|
||||
const chapters = this.parseChapters(parseHTML(chapterListBody));
|
||||
return {
|
||||
chapters
|
||||
}
|
||||
chapters,
|
||||
};
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((r) => r.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(r => r.text());
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio("div.chapter-content").html() || '';
|
||||
let chapterText = loadedCheerio('div.chapter-content').html() || '';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
if(pageNo > 1) return [];
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
if (pageNo > 1) return [];
|
||||
const url = `${this.site}/api/book-search`;
|
||||
let formData = new FormData();
|
||||
formData.append("keyword", searchTerm);
|
||||
formData.append('keyword', searchTerm);
|
||||
|
||||
const result: SearchedResult = await fetchApi(
|
||||
url,
|
||||
{
|
||||
method: "POST",
|
||||
const result: SearchedResult = await fetchApi(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
},
|
||||
).then((r) => r.json());
|
||||
}).then(r => r.json());
|
||||
|
||||
const novels: NovelItem[] = result.data?.map(item => {
|
||||
const novels: NovelItem[] =
|
||||
result.data?.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
path: '/truyen/' + item.slug,
|
||||
cover: (this.site + item.coverUrl).replace('default.jpg', '150.jpg?w=256&q=')
|
||||
}
|
||||
cover: (this.site + item.coverUrl).replace(
|
||||
'default.jpg',
|
||||
'150.jpg?w=256&q=',
|
||||
),
|
||||
};
|
||||
}) || [];
|
||||
return novels;
|
||||
}
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new LightNovelVN();
|
||||
@@ -1,8 +1,8 @@
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
|
||||
class Nettruyen implements Plugin.PagePlugin {
|
||||
id = 'nettruyen';
|
||||
@@ -10,87 +10,102 @@ class Nettruyen implements Plugin.PagePlugin {
|
||||
icon = 'src/vi/nettruyen/icon.png';
|
||||
site = 'https://nettruyen.com.vn';
|
||||
version = '1.0.0';
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio('#listchuong > ul > li > a.thumb').each((idx, ele) => {
|
||||
const path = ele.attribs['href'];
|
||||
if(!path) return;
|
||||
if (!path) return;
|
||||
const name = ele.attribs['title'];
|
||||
const cover = this.site + '/' + loadedCheerio(ele).find('img').first().attr('src');
|
||||
const cover =
|
||||
this.site + '/' + loadedCheerio(ele).find('img').first().attr('src');
|
||||
novels.push({
|
||||
path: '/' + path,
|
||||
name,
|
||||
cover
|
||||
})
|
||||
})
|
||||
cover,
|
||||
});
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/xem-nhieu/trang-${pageNo}.html`;
|
||||
const body = await fetch(url).then(r => r.text());
|
||||
return this.parseNovels(parseHTML(body));
|
||||
}
|
||||
parseChapters(loadedCheerio: CheerioAPI){
|
||||
parseChapters(loadedCheerio: CheerioAPI) {
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio('ul.list-unstyled > li > a').each((idx, ele) => {
|
||||
const path = ele.attribs['href'];
|
||||
if(!path) return;
|
||||
if (!path) return;
|
||||
const name = ele.attribs['title'];
|
||||
chapters.push({
|
||||
path: '/' + path,
|
||||
name,
|
||||
chapterNumber: Number(name.match(/Chương (\d+)/)?.[1])
|
||||
})
|
||||
})
|
||||
chapterNumber: Number(name.match(/Chương (\d+)/)?.[1]),
|
||||
});
|
||||
});
|
||||
return chapters;
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & { totalPages: number; }> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const url = this.site + novelPath;
|
||||
const body = await fetch(url).then(r => r.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const novel: Plugin.SourceNovel & { totalPages: number; } = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio('.gioithieutruyen.profile-info > h1').text().trim(),
|
||||
chapters: [],
|
||||
totalPages: Number(loadedCheerio('.phantrang > span:nth-child(2)').text()) || 1,
|
||||
}
|
||||
novel.cover = this.site + '/' + loadedCheerio('.img-thumb > img').attr('src');
|
||||
totalPages:
|
||||
Number(loadedCheerio('.phantrang > span:nth-child(2)').text()) || 1,
|
||||
};
|
||||
novel.cover =
|
||||
this.site + '/' + loadedCheerio('.img-thumb > img').attr('src');
|
||||
loadedCheerio('.thongtintruyen.note.note-info').each((idx, ele) => {
|
||||
const strong = loadedCheerio(ele).find('strong').text().trim();
|
||||
switch(strong){
|
||||
switch (strong) {
|
||||
case 'Tác giả':
|
||||
novel.author = loadedCheerio(ele).text().replace(/Tác giả\s+\n?:/, '')
|
||||
novel.author = loadedCheerio(ele)
|
||||
.text()
|
||||
.replace(/Tác giả\s+\n?:/, '');
|
||||
break;
|
||||
case 'Trạng thái':
|
||||
const text = loadedCheerio(ele).text();
|
||||
if(text.includes('Đang ra')){
|
||||
if (text.includes('Đang ra')) {
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
}else if(text.includes('Hoàn thành')){
|
||||
} else if (text.includes('Hoàn thành')) {
|
||||
novel.status = NovelStatus.Completed;
|
||||
}else {
|
||||
} else {
|
||||
novel.status = NovelStatus.Unknown;
|
||||
}
|
||||
break;
|
||||
case 'Thể loại':
|
||||
novel.genres = loadedCheerio('a > span').toArray().map((gEle) => loadedCheerio(gEle).text()).join(',');
|
||||
novel.genres = loadedCheerio('a > span')
|
||||
.toArray()
|
||||
.map(gEle => loadedCheerio(gEle).text())
|
||||
.join(',');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
novel.summary = loadedCheerio('.gioithieutruyen > .gioithieutruyen').text().trim()
|
||||
novel.summary = loadedCheerio('.gioithieutruyen > .gioithieutruyen')
|
||||
.text()
|
||||
.trim();
|
||||
novel.chapters = this.parseChapters(loadedCheerio);
|
||||
return novel;
|
||||
}
|
||||
async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
|
||||
const id = novelPath.match(/-(\d+)\//)?.[1];
|
||||
if(!id) throw new Error("Cant parse page");
|
||||
const url = `${this.site}/ajax.chuong.php?id=${id}&page=${page}&url=${novelPath.replace(/\//g,'')}&loai=truyendich`;
|
||||
if (!id) throw new Error('Cant parse page');
|
||||
const url = `${this.site}/ajax.chuong.php?id=${id}&page=${page}&url=${novelPath.replace(/\//g, '')}&loai=truyendich`;
|
||||
const body = await fetch(url).then(r => r.text());
|
||||
const chapters = this.parseChapters(parseHTML(body));
|
||||
return {
|
||||
chapters
|
||||
}
|
||||
chapters,
|
||||
};
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const url = this.site + chapterPath;
|
||||
@@ -98,8 +113,11 @@ class Nettruyen implements Plugin.PagePlugin {
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return loadedCheerio('#noidungchap').html() || '';
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
|
||||
@@ -1,58 +1,64 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class TruyenFull implements Plugin.PagePlugin {
|
||||
id = "truyenchu";
|
||||
name = "Truyện Chữ";
|
||||
icon = "src/vi/truyenchu/icon.png";
|
||||
site = "https://truyenchu.vn";
|
||||
version = "1.0.0";
|
||||
id = 'truyenchu';
|
||||
name = 'Truyện Chữ';
|
||||
icon = 'src/vi/truyenchu/icon.png';
|
||||
site = 'https://truyenchu.vn';
|
||||
version = '1.0.0';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio(".list-truyen .row").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find("h3.truyen-title > a")
|
||||
.text();
|
||||
loadedCheerio('.list-truyen .row').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('h3.truyen-title > a').text();
|
||||
|
||||
const novelCover = this.site + loadedCheerio(ele).find("div[data-classname='cover']").attr("data-image");
|
||||
const novelCover =
|
||||
this.site +
|
||||
loadedCheerio(ele)
|
||||
.find("div[data-classname='cover']")
|
||||
.attr('data-image');
|
||||
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find("h3.truyen-title > a")
|
||||
.attr("href");
|
||||
if(novelUrl){
|
||||
.find('h3.truyen-title > a')
|
||||
.attr('href');
|
||||
if (novelUrl) {
|
||||
novels.push({
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, '')
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
});
|
||||
}
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
parseChapters(html: string){
|
||||
const listChapterHTML =
|
||||
html.match(/("list_chapter":\s?\\{0}"(.+)\\{0}"),/)?.[1];
|
||||
if(!listChapterHTML) throw new Error("Không tải được chương");
|
||||
parseChapters(html: string) {
|
||||
const listChapterHTML = html.match(
|
||||
/("list_chapter":\s?\\{0}"(.+)\\{0}"),/,
|
||||
)?.[1];
|
||||
if (!listChapterHTML) throw new Error('Không tải được chương');
|
||||
const listChapter = JSON.parse(`{${listChapterHTML}}`);
|
||||
const loadedChapterList = parseHTML(listChapter.list_chapter);
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedChapterList('ul > li > a').each((idx, ele) => {
|
||||
const path = ele.attribs['href'].replace(this.site, '');
|
||||
if(path){
|
||||
if (path) {
|
||||
chapters.push({
|
||||
name: ele.attribs['title'],
|
||||
path,
|
||||
chapterNumber: Number(path.match(/\/chuong-(\d+)/)?.[1])
|
||||
})
|
||||
chapterNumber: Number(path.match(/\/chuong-(\d+)/)?.[1]),
|
||||
});
|
||||
}
|
||||
});
|
||||
return chapters;
|
||||
}
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/danh-sach/truyen-hot?page=${pageNo}`;
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -60,7 +66,9 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & {totalPages: number}> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const url = this.site + novelPath;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -71,39 +79,39 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
const regex = new RegExp(`${novelPath}\\?page=\\d+`, 'g');
|
||||
body.match(regex)?.forEach(v => {
|
||||
const page = Number(v.match(/\?page=(\d+)/)?.[1]);
|
||||
if(page && page > lastPage){
|
||||
if (page && page > lastPage) {
|
||||
lastPage = page;
|
||||
}
|
||||
});
|
||||
const novel: Plugin.SourceNovel & {totalPages: number} = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("div.book > img").attr("alt") || 'Không có tiêu đề',
|
||||
name: loadedCheerio('div.book > img').attr('alt') || 'Không có tiêu đề',
|
||||
chapters: [],
|
||||
totalPages: lastPage,
|
||||
};
|
||||
|
||||
novel.cover = this.site + loadedCheerio("div.book > img").attr("src");
|
||||
novel.cover = this.site + loadedCheerio('div.book > img').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio("div.desc-text").text().trim();
|
||||
novel.summary = loadedCheerio('div.desc-text').text().trim();
|
||||
|
||||
novel.author = loadedCheerio('h3:contains("Tác giả:")')
|
||||
.parent()
|
||||
.contents()
|
||||
.text()
|
||||
.replace("Tác giả:", "");
|
||||
.replace('Tác giả:', '');
|
||||
|
||||
novel.genres = loadedCheerio('h3:contains("Thể loại")')
|
||||
.siblings()
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
novel.status = loadedCheerio('h3:contains("Trạng thái")').next().text();
|
||||
if(novel.status === "Full"){
|
||||
if (novel.status === 'Full') {
|
||||
novel.status = NovelStatus.Completed;
|
||||
}else if(novel.status === 'Đang ra'){
|
||||
} else if (novel.status === 'Đang ra') {
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
}else{
|
||||
} else {
|
||||
novel.status = NovelStatus.Unknown;
|
||||
}
|
||||
novel.chapters = this.parseChapters(body);
|
||||
@@ -116,8 +124,8 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
|
||||
const chapters = this.parseChapters(body);
|
||||
return {
|
||||
chapters
|
||||
}
|
||||
chapters,
|
||||
};
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetch(this.site + chapterPath);
|
||||
@@ -125,23 +133,27 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = (loadedCheerio(".chapter-title").html() || '') + (loadedCheerio('#chapter-c').html() || '');
|
||||
let chapterText =
|
||||
(loadedCheerio('.chapter-title').html() || '') +
|
||||
(loadedCheerio('#chapter-c').html() || '');
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = `${this.site}/tim-kiem?tukhoa=${searchTerm}&page=${pageNo}`;
|
||||
|
||||
const result = await fetch(searchUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio)
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new TruyenFull();
|
||||
|
||||
@@ -1,49 +1,54 @@
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
|
||||
class TruyenFull implements Plugin.PagePlugin {
|
||||
id = "truyenfull";
|
||||
name = "Truyện Full";
|
||||
icon = "src/vi/truyenfull/icon.png";
|
||||
site = "https://truyenfull.vn";
|
||||
version = "1.0.0";
|
||||
id = 'truyenfull';
|
||||
name = 'Truyện Full';
|
||||
icon = 'src/vi/truyenfull/icon.png';
|
||||
site = 'https://truyenfull.vn';
|
||||
version = '1.0.0';
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI){
|
||||
parseNovels(loadedCheerio: CheerioAPI) {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
loadedCheerio(".list-truyen .row").each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele)
|
||||
.find("h3.truyen-title > a")
|
||||
.text();
|
||||
loadedCheerio('.list-truyen .row').each((idx, ele) => {
|
||||
const novelName = loadedCheerio(ele).find('h3.truyen-title > a').text();
|
||||
|
||||
const novelCover = loadedCheerio(ele).find("div[data-classname='cover']").attr("data-image");
|
||||
const novelCover = loadedCheerio(ele)
|
||||
.find("div[data-classname='cover']")
|
||||
.attr('data-image');
|
||||
|
||||
const novelUrl = loadedCheerio(ele)
|
||||
.find("h3.truyen-title > a")
|
||||
.attr("href");
|
||||
if(novelUrl){
|
||||
.find('h3.truyen-title > a')
|
||||
.attr('href');
|
||||
if (novelUrl) {
|
||||
novels.push({
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, '')
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
});
|
||||
}
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
parseChapters(loadedCheerio: CheerioAPI): Plugin.ChapterItem[]{
|
||||
return loadedCheerio('ul.list-chapter > li > a').toArray().map((ele) => {
|
||||
parseChapters(loadedCheerio: CheerioAPI): Plugin.ChapterItem[] {
|
||||
return loadedCheerio('ul.list-chapter > li > a')
|
||||
.toArray()
|
||||
.map(ele => {
|
||||
const path = ele.attribs['href'].replace(this.site, '');
|
||||
return {
|
||||
name: loadedCheerio(ele).text().trim(),
|
||||
path,
|
||||
chapterNumber: Number(path.match(/\/chuong-(\d+)\//)?.[1])
|
||||
chapterNumber: Number(path.match(/\/chuong-(\d+)\//)?.[1]),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
async popularNovels(pageNo: number, options: Plugin.PopularNovelsOptions<Filters>): Promise<Plugin.NovelItem[]> {
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = `${this.site}/danh-sach/truyen-hot/trang-${pageNo}/`;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -53,7 +58,9 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel & {totalPages: number}> {
|
||||
async parseNovel(
|
||||
novelPath: string,
|
||||
): Promise<Plugin.SourceNovel & { totalPages: number }> {
|
||||
const url = this.site + novelPath;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -61,40 +68,40 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
|
||||
let loadedCheerio = parseHTML(body);
|
||||
let lastPage = 1;
|
||||
loadedCheerio('ul.pagination.pagination-sm > li > a').each(function() {
|
||||
loadedCheerio('ul.pagination.pagination-sm > li > a').each(function () {
|
||||
const page = Number(this.attribs['href'].match(/\/trang-(\d+)\//)?.[1]);
|
||||
if(page && page > lastPage) lastPage = page;
|
||||
if (page && page > lastPage) lastPage = page;
|
||||
});
|
||||
|
||||
const novel: Plugin.SourceNovel & {totalPages: number} = {
|
||||
const novel: Plugin.SourceNovel & { totalPages: number } = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("div.book > img").attr("alt") || 'Không có tiêu đề',
|
||||
name: loadedCheerio('div.book > img').attr('alt') || 'Không có tiêu đề',
|
||||
chapters: [],
|
||||
totalPages: lastPage,
|
||||
};
|
||||
|
||||
novel.cover = loadedCheerio("div.book > img").attr("src");
|
||||
novel.cover = loadedCheerio('div.book > img').attr('src');
|
||||
|
||||
novel.summary = loadedCheerio("div.desc-text").text().trim();
|
||||
novel.summary = loadedCheerio('div.desc-text').text().trim();
|
||||
|
||||
novel.author = loadedCheerio('h3:contains("Tác giả:")')
|
||||
.parent()
|
||||
.contents()
|
||||
.text()
|
||||
.replace("Tác giả:", "");
|
||||
.replace('Tác giả:', '');
|
||||
|
||||
novel.genres = loadedCheerio('h3:contains("Thể loại")')
|
||||
.siblings()
|
||||
.map((i, el) => loadedCheerio(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
novel.status = loadedCheerio('h3:contains("Trạng thái")').next().text();
|
||||
if(novel.status === "Full"){
|
||||
if (novel.status === 'Full') {
|
||||
novel.status = NovelStatus.Completed;
|
||||
}else if(novel.status === 'Đang ra'){
|
||||
} else if (novel.status === 'Đang ra') {
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
}else{
|
||||
} else {
|
||||
novel.status = NovelStatus.Unknown;
|
||||
}
|
||||
novel.chapters = this.parseChapters(loadedCheerio);
|
||||
@@ -108,8 +115,8 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapters = this.parseChapters(loadedCheerio);
|
||||
return {
|
||||
chapters
|
||||
}
|
||||
chapters,
|
||||
};
|
||||
}
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const result = await fetch(this.site + chapterPath);
|
||||
@@ -117,23 +124,27 @@ class TruyenFull implements Plugin.PagePlugin {
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = (loadedCheerio(".chapter-title").html() || '') + (loadedCheerio('#chapter-c').html() || '');
|
||||
let chapterText =
|
||||
(loadedCheerio('.chapter-title').html() || '') +
|
||||
(loadedCheerio('#chapter-c').html() || '');
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
async searchNovels(searchTerm: string, pageNo: number): Promise<Plugin.NovelItem[]> {
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const searchUrl = `${this.site}/tim-kiem?tukhoa=${searchTerm}&page=${pageNo}`;
|
||||
|
||||
const result = await fetch(searchUrl);
|
||||
const body = await result.text();
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
return this.parseNovels(loadedCheerio)
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
async fetchImage(url: string): Promise<string | undefined> {
|
||||
return fetchFile(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new TruyenFull();
|
||||
|
||||
+36
-36
@@ -1,27 +1,27 @@
|
||||
require("module-alias/register");
|
||||
require('module-alias/register');
|
||||
|
||||
import * as fs from "fs";
|
||||
import { languages } from "@libs/languages";
|
||||
import * as path from "path";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { minify } from "./terser";
|
||||
import * as fs from 'fs';
|
||||
import { languages } from '@libs/languages';
|
||||
import * as path from 'path';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { minify } from './terser';
|
||||
|
||||
const root = path.dirname(__dirname);
|
||||
const outRoot = path.join(root, "..");
|
||||
const { execSync } = require("child_process");
|
||||
const REMOTE = execSync("git remote get-url origin")
|
||||
const outRoot = path.join(root, '..');
|
||||
const { execSync } = require('child_process');
|
||||
const REMOTE = execSync('git remote get-url origin')
|
||||
.toString()
|
||||
.replace(/[\s\n]/g, "");
|
||||
let CURRENT_BRANCH = "dist";
|
||||
.replace(/[\s\n]/g, '');
|
||||
let CURRENT_BRANCH = 'dist';
|
||||
try {
|
||||
CURRENT_BRANCH = execSync("git rev-parse --abbrev-ref HEAD")
|
||||
CURRENT_BRANCH = execSync('git rev-parse --abbrev-ref HEAD')
|
||||
.toString()
|
||||
.replace(/[\s\n]/g, "");
|
||||
.replace(/[\s\n]/g, '');
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
const matched = REMOTE.match(/([^:/]+?)\/([^/.]+)(\.git)?$/);
|
||||
if (!matched) throw Error("Cant parse git url");
|
||||
if (!matched) throw Error('Cant parse git url');
|
||||
const USERNAME = matched[1];
|
||||
const REPO = matched[2];
|
||||
const USER_CONTENT_LINK = `https://raw.githubusercontent.com/${USERNAME}/${REPO}/${CURRENT_BRANCH}`;
|
||||
@@ -40,29 +40,29 @@ const json: {
|
||||
iconUrl: string;
|
||||
}[];
|
||||
} = {};
|
||||
if (!fs.existsSync(path.join(outRoot, ".dist"))) {
|
||||
fs.mkdirSync(path.join(outRoot, ".dist"));
|
||||
if (!fs.existsSync(path.join(outRoot, '.dist'))) {
|
||||
fs.mkdirSync(path.join(outRoot, '.dist'));
|
||||
}
|
||||
const jsonPath = path.join(outRoot, ".dist", "plugins.json");
|
||||
const jsonMinPath = path.join(outRoot, ".dist", "plugins.min.json");
|
||||
const jsonPath = path.join(outRoot, '.dist', 'plugins.json');
|
||||
const jsonMinPath = path.join(outRoot, '.dist', 'plugins.min.json');
|
||||
const pluginSet = new Set();
|
||||
let totalPlugins = 0;
|
||||
|
||||
for (let language in languages) {
|
||||
// language with English name
|
||||
const langPath = path.join(root, "plugins", language.toLowerCase());
|
||||
const langPath = path.join(root, 'plugins', language.toLowerCase());
|
||||
if (!fs.existsSync(langPath)) continue;
|
||||
const plugins = fs.readdirSync(langPath);
|
||||
json[language] = [];
|
||||
plugins.forEach((plugin) => {
|
||||
if (plugin.startsWith(".")) return;
|
||||
plugins.forEach(plugin => {
|
||||
if (plugin.startsWith('.')) return;
|
||||
minify(path.join(langPath, plugin));
|
||||
const instance: Plugin.PluginBase = require(
|
||||
`../plugins/${language.toLowerCase()}/${plugin.split(".")[0]}`,
|
||||
`../plugins/${language.toLowerCase()}/${plugin.split('.')[0]}`,
|
||||
).default;
|
||||
|
||||
const { id, name, site, version, icon } = instance;
|
||||
const normalisedName = name.replace(/\[.*\]/, "");
|
||||
const normalisedName = name.replace(/\[.*\]/, '');
|
||||
|
||||
const info = {
|
||||
id,
|
||||
@@ -76,44 +76,44 @@ for (let language in languages) {
|
||||
|
||||
if (pluginSet.has(id)) {
|
||||
console.log("There's already a plugin with id:", id);
|
||||
throw new Error("2 or more plugins have the same id");
|
||||
throw new Error('2 or more plugins have the same id');
|
||||
} else {
|
||||
pluginSet.add(id);
|
||||
}
|
||||
json[language].push(info);
|
||||
totalPlugins += 1;
|
||||
console.log(name, "✅");
|
||||
console.log(name, '✅');
|
||||
});
|
||||
}
|
||||
|
||||
for (let lang in json) json[lang].sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
fs.writeFileSync(jsonMinPath, JSON.stringify(json));
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(json, null, "\t"));
|
||||
if(CURRENT_BRANCH === 'dist'){
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(json, null, '\t'));
|
||||
if (CURRENT_BRANCH === 'dist') {
|
||||
fetch(`https://img.shields.io/badge/Plugins-${totalPlugins}-blue`)
|
||||
.then(res => res.text())
|
||||
.then(data => {
|
||||
fs.writeFileSync('total.svg', data, {encoding: 'utf-8'});
|
||||
})
|
||||
fs.writeFileSync('total.svg', data, { encoding: 'utf-8' });
|
||||
});
|
||||
}
|
||||
|
||||
// check for broken plugins
|
||||
for (let language in languages) {
|
||||
const tsFiles = fs.readdirSync(
|
||||
path.join(root, "..", "plugins", language.toLocaleLowerCase())
|
||||
path.join(root, '..', 'plugins', language.toLocaleLowerCase()),
|
||||
);
|
||||
tsFiles
|
||||
.filter((f) => f.endsWith(".broken.ts"))
|
||||
.forEach((fn) => {
|
||||
.filter(f => f.endsWith('.broken.ts'))
|
||||
.forEach(fn => {
|
||||
console.error(
|
||||
language.toLocaleLowerCase() +
|
||||
"/" +
|
||||
fn.replace(".broken.ts", "") +
|
||||
" ❌"
|
||||
'/' +
|
||||
fn.replace('.broken.ts', '') +
|
||||
' ❌',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(jsonPath);
|
||||
console.log("Done ✅");
|
||||
console.log('Done ✅');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
type GeneratedScript = {
|
||||
lang: string;
|
||||
@@ -10,34 +10,32 @@ type GeneratedScript = {
|
||||
export type ScrpitGeneratorFunction = (name: string) => GeneratedScript[];
|
||||
|
||||
const isScriptGenerator = (s: unknown): s is ScrpitGeneratorFunction => {
|
||||
return !!s && typeof s === "function";
|
||||
return !!s && typeof s === 'function';
|
||||
};
|
||||
|
||||
const generate = async (name: string): Promise<boolean> => {
|
||||
try {
|
||||
const generateAll: ScrpitGeneratorFunction | unknown =
|
||||
require(`./${name}/generator`).generateAll;
|
||||
const generateAll: ScrpitGeneratorFunction | unknown = require(
|
||||
`./${name}/generator`,
|
||||
).generateAll;
|
||||
if (!isScriptGenerator(generateAll)) return false;
|
||||
const sources = generateAll(name);
|
||||
for (let source of sources) {
|
||||
const { lang, filename, pluginScript } = source;
|
||||
if (!lang || !filename || !pluginScript) {
|
||||
console.warn(
|
||||
name,
|
||||
": lang, filename, pluginScript are required!"
|
||||
);
|
||||
console.warn(name, ': lang, filename, pluginScript are required!');
|
||||
continue;
|
||||
}
|
||||
const pluginsDir = path.join(
|
||||
path.dirname(path.dirname(__dirname)),
|
||||
"plugins"
|
||||
'plugins',
|
||||
);
|
||||
const filePath = path.join(
|
||||
pluginsDir,
|
||||
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' });
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -49,8 +47,10 @@ const generate = async (name: string): Promise<boolean> => {
|
||||
const run = async () => {
|
||||
const sources = fs
|
||||
.readdirSync(__dirname)
|
||||
.filter((name) =>
|
||||
fs.lstatSync(path.join(__dirname, name)).isDirectory() && !name.endsWith('.broken')
|
||||
.filter(
|
||||
name =>
|
||||
fs.lstatSync(path.join(__dirname, name)).isDirectory() &&
|
||||
!name.endsWith('.broken'),
|
||||
);
|
||||
|
||||
for (let name of sources) {
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import * as fs from "fs";
|
||||
import * as cheerio from "cheerio";
|
||||
import * as path from "path";
|
||||
import list from "./sources.json";
|
||||
import { HotNovelPubMetadata } from "./template";
|
||||
import * as fs from 'fs';
|
||||
import * as cheerio from 'cheerio';
|
||||
import * as path from 'path';
|
||||
import list from './sources.json';
|
||||
import { HotNovelPubMetadata } from './template';
|
||||
|
||||
async function getFilters(sources: HotNovelPubMetadata) {
|
||||
const filters: any = {
|
||||
sort: {
|
||||
type: "Picker",
|
||||
label: "Order",
|
||||
value: "hot",
|
||||
type: 'Picker',
|
||||
label: 'Order',
|
||||
value: 'hot',
|
||||
options: [],
|
||||
},
|
||||
category: {
|
||||
type: "Picker",
|
||||
label: "category",
|
||||
value: "",
|
||||
options: [{ label: "NONE", value: "" }],
|
||||
type: 'Picker',
|
||||
label: 'category',
|
||||
value: '',
|
||||
options: [{ label: 'NONE', value: '' }],
|
||||
},
|
||||
};
|
||||
const body = await fetch(sources.sourceSite).then((res) => res.text());
|
||||
const body = await fetch(sources.sourceSite).then(res => res.text());
|
||||
const $: cheerio.CheerioAPI = cheerio.load(body);
|
||||
$(".new-update").remove();
|
||||
$('.new-update').remove();
|
||||
|
||||
$("section > div").each((i, el) => {
|
||||
const id = $(el).find('a[class="see-all"]').attr("href");
|
||||
$('section > div').each((i, el) => {
|
||||
const id = $(el).find('a[class="see-all"]').attr('href');
|
||||
if (id) {
|
||||
filters.sort.options.push({
|
||||
label: $(el).find('[class="section-title"]').text().trim(),
|
||||
value: id.split("/").pop(),
|
||||
value: id.split('/').pop(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
filters.category.label = $(".category-title").text().trim();
|
||||
const apiSite = sources.sourceSite.replace("://", "://api.");
|
||||
const jsonRaw = await fetch(apiSite + "/categories", {
|
||||
filters.category.label = $('.category-title').text().trim();
|
||||
const apiSite = sources.sourceSite.replace('://', '://api.');
|
||||
const jsonRaw = await fetch(apiSite + '/categories', {
|
||||
headers: {
|
||||
lang: sources.options?.lang || "en",
|
||||
lang: sources.options?.lang || 'en',
|
||||
},
|
||||
});
|
||||
const json = (await jsonRaw.json()) as response;
|
||||
@@ -45,7 +45,7 @@ async function getFilters(sources: HotNovelPubMetadata) {
|
||||
if (json.data?.length) {
|
||||
json.data
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.forEach((category) =>
|
||||
.forEach(category =>
|
||||
filters.category.options.push({
|
||||
label: category.name,
|
||||
value: category.slug,
|
||||
@@ -69,13 +69,13 @@ interface DataEntity {
|
||||
async function start() {
|
||||
const result = [];
|
||||
for (const sources of list) {
|
||||
console.log("updating the filters in", sources.sourceName);
|
||||
console.log('updating the filters in', sources.sourceName);
|
||||
const NewFilters = await getFilters(sources as any);
|
||||
sources.filters = NewFilters;
|
||||
result.push(sources);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, "sources.json"),
|
||||
path.join(__dirname, 'sources.json'),
|
||||
JSON.stringify(result, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { FilterTypes, Filters } from "../../../libs/filterInputs";
|
||||
import { ScrpitGeneratorFunction } from "../generate";
|
||||
import list from "./sources.json";
|
||||
import { HotNovelPubMetadata } from "./template";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { FilterTypes, Filters } from '../../../libs/filterInputs';
|
||||
import { ScrpitGeneratorFunction } from '../generate';
|
||||
import list from './sources.json';
|
||||
import { HotNovelPubMetadata } from './template';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const lang: { [key: string]: string } = {
|
||||
en: "english",
|
||||
ru: "russian",
|
||||
es: "spanish",
|
||||
pt: "portuguese",
|
||||
th: "turkish",
|
||||
en: 'english',
|
||||
ru: 'russian',
|
||||
es: 'spanish',
|
||||
pt: 'portuguese',
|
||||
th: 'turkish',
|
||||
};
|
||||
|
||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
return list
|
||||
.map<HotNovelPubMetadata>((p) => {
|
||||
.map<HotNovelPubMetadata>(p => {
|
||||
const filters: Filters = {};
|
||||
for (const k in p.filters) {
|
||||
const f = p.filters[k as keyof typeof p.filters];
|
||||
@@ -36,8 +36,9 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
|
||||
const generator = function generator(metadata: HotNovelPubMetadata) {
|
||||
const HotNovelPubTemplate = readFileSync(
|
||||
path.join(__dirname, "template.ts"), {
|
||||
encoding: "utf-8",
|
||||
path.join(__dirname, 'template.ts'),
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -51,7 +52,7 @@ export default plugin;
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
lang: lang[metadata?.options?.lang || "en"] || "english",
|
||||
lang: lang[metadata?.options?.lang || 'en'] || 'english',
|
||||
filename: metadata.sourceName,
|
||||
pluginScript,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchFile, fetchApi } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface HotNovelPubMetadata {
|
||||
id: string;
|
||||
@@ -32,23 +32,23 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/hotnovelpub/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.apiSite = metadata.sourceSite.replace("://", "://api.");
|
||||
this.version = "1.0.0";
|
||||
this.apiSite = metadata.sourceSite.replace('://', '://api.');
|
||||
this.version = '1.0.0';
|
||||
this.filters = metadata.filters;
|
||||
this.lang = metadata.options?.lang || "en";
|
||||
this.lang = metadata.options?.lang || 'en';
|
||||
}
|
||||
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.apiSite + "/books/";
|
||||
url += showLatestNovels ? "new" : filters?.sort?.value || "hot";
|
||||
let url = this.apiSite + '/books/';
|
||||
url += showLatestNovels ? 'new' : filters?.sort?.value || 'hot';
|
||||
if (filters?.category?.value) {
|
||||
url = this.apiSite + "/category/" + filters.category.value;
|
||||
url = this.apiSite + '/category/' + filters.category.value;
|
||||
}
|
||||
|
||||
url += "/?page=" + (pageNo - 1) + "&limit=20";
|
||||
url += '/?page=' + (pageNo - 1) + '&limit=20';
|
||||
|
||||
const result = await fetchApi(url, {
|
||||
headers: {
|
||||
@@ -59,11 +59,11 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (json.status && json.data.books.data?.length) {
|
||||
json.data.books.data.forEach((novel) =>
|
||||
json.data.books.data.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: this.site + novel.image,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const result = await fetchApi(this.apiSite + "/book" + novelPath, {
|
||||
const result = await fetchApi(this.apiSite + '/book' + novelPath, {
|
||||
headers: {
|
||||
lang: this.lang,
|
||||
},
|
||||
@@ -85,13 +85,13 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
summary: json.data.book.authorize.description,
|
||||
author: json.data.book.authorize.name,
|
||||
status:
|
||||
json.data.book.status === "updating"
|
||||
json.data.book.status === 'updating'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed,
|
||||
};
|
||||
|
||||
if (json.data.tags.tags_name?.length) {
|
||||
novel.genres = json.data.tags.tags_name.join(",");
|
||||
novel.genres = json.data.tags.tags_name.join(',');
|
||||
}
|
||||
|
||||
if (json.data.chapters?.length) {
|
||||
@@ -99,7 +99,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
json.data.chapters.forEach((chapter, chapterIndex) =>
|
||||
chapters.push({
|
||||
name: chapter.title,
|
||||
path: "/" + chapter.slug,
|
||||
path: '/' + chapter.slug,
|
||||
releaseTime: undefined,
|
||||
chapterNumber: (chapter.index || chapterIndex) + 1,
|
||||
}),
|
||||
@@ -111,50 +111,52 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
let chapterText = loadedCheerio("#content-item").html() || "";
|
||||
let chapterText = loadedCheerio('#content-item').html() || '';
|
||||
if (chapterText) {
|
||||
const result = await fetchApi(
|
||||
this.site + "/server/getContent?slug=" + chapterPath,
|
||||
this.site + '/server/getContent?slug=' + chapterPath,
|
||||
);
|
||||
const json = (await result.json()) as ChapterType;
|
||||
|
||||
if (json.data) {
|
||||
chapterText += json.data
|
||||
.map((item) => "<p>" + item + "</p>")
|
||||
.join("")
|
||||
.replace(/\n/g, "</p><p>")
|
||||
.replace(/\s/g, " ");
|
||||
.map(item => '<p>' + item + '</p>')
|
||||
.join('')
|
||||
.replace(/\n/g, '</p><p>')
|
||||
.replace(/\s/g, ' ');
|
||||
}
|
||||
}
|
||||
return chapterText.replace(/\.copy right hot novel pub/g, "");
|
||||
return chapterText.replace(/\.copy right hot novel pub/g, '');
|
||||
}
|
||||
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi(this.apiSite + "/search", {
|
||||
const result = await fetchApi(this.apiSite + '/search', {
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
Referer: this.site,
|
||||
Origin: this.site,
|
||||
lang: this.lang,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ key_search: searchTerm }),
|
||||
});
|
||||
const json = (await result.json()) as responseSearch;
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
if (json.status && json.data.books?.length) {
|
||||
json.data.books.forEach((novel) =>
|
||||
json.data.books.forEach(novel =>
|
||||
novels.push({
|
||||
name: novel.name,
|
||||
cover: undefined,
|
||||
path: "/" + novel.slug,
|
||||
path: '/' + novel.slug,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Filters } from "../../../libs/filterInputs";
|
||||
import { ScrpitGeneratorFunction } from "../generate";
|
||||
import list from "./sources.json";
|
||||
import defaultSettings from "./settings.json";
|
||||
import { IfreedomMetadata } from "./template";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { Filters } from '../../../libs/filterInputs';
|
||||
import { ScrpitGeneratorFunction } from '../generate';
|
||||
import list from './sources.json';
|
||||
import defaultSettings from './settings.json';
|
||||
import { IfreedomMetadata } from './template';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
return list.map((metadata: IfreedomMetadata) => {
|
||||
@@ -15,8 +15,8 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
};
|
||||
|
||||
const generator = function generator(metadata: IfreedomMetadata) {
|
||||
const IfreedomTemplate = readFileSync(path.join(__dirname, "template.ts"), {
|
||||
encoding: "utf-8",
|
||||
const IfreedomTemplate = readFileSync(path.join(__dirname, 'template.ts'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
const pluginScript = `
|
||||
@@ -29,7 +29,7 @@ export default plugin;
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
lang: "russian",
|
||||
lang: 'russian',
|
||||
filename: metadata.sourceName,
|
||||
pluginScript,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchFile, fetchApi } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface IfreedomMetadata {
|
||||
id: string;
|
||||
@@ -25,7 +25,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/ifreedom/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
this.version = '1.0.0';
|
||||
this.filters = metadata.filters;
|
||||
}
|
||||
|
||||
@@ -36,87 +36,95 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/vse-knigi/?sort=" +
|
||||
(showLatestNovels ? "По дате обновления" : filters?.sort?.value || "По рейтингу");
|
||||
let url =
|
||||
this.site +
|
||||
'/vse-knigi/?sort=' +
|
||||
(showLatestNovels
|
||||
? 'По дате обновления'
|
||||
: filters?.sort?.value || 'По рейтингу');
|
||||
|
||||
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + type + "[]=" + value.join("&" + type + "[]=");
|
||||
url += '&' + type + '[]=' + value.join('&' + type + '[]=');
|
||||
}
|
||||
});
|
||||
|
||||
url += "&bpage=" + page;
|
||||
url += '&bpage=' + page;
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio(
|
||||
"div.one-book-home > div.img-home a",
|
||||
'div.one-book-home > div.img-home a',
|
||||
)
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).attr("title") || "",
|
||||
cover: loadedCheerio(element).find("img").attr("src"),
|
||||
name: loadedCheerio(element).attr('title') || '',
|
||||
cover: loadedCheerio(element).find('img').attr('src'),
|
||||
path:
|
||||
loadedCheerio(element).attr("href")?.replace?.(this.site, "") || "",
|
||||
loadedCheerio(element).attr('href')?.replace?.(this.site, '') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio(".entry-title").text(),
|
||||
cover: loadedCheerio(".img-ranobe > img").attr("src"),
|
||||
summary: loadedCheerio('meta[name="description"]').attr("content"),
|
||||
name: loadedCheerio('.entry-title').text(),
|
||||
cover: loadedCheerio('.img-ranobe > img').attr('src'),
|
||||
summary: loadedCheerio('meta[name="description"]').attr('content'),
|
||||
};
|
||||
|
||||
loadedCheerio("div.data-ranobe").each(function () {
|
||||
switch (loadedCheerio(this).find("b").text()) {
|
||||
case "Автор":
|
||||
loadedCheerio('div.data-ranobe').each(function () {
|
||||
switch (loadedCheerio(this).find('b').text()) {
|
||||
case 'Автор':
|
||||
novel.author = loadedCheerio(this)
|
||||
.find("div.data-value")
|
||||
.find('div.data-value')
|
||||
.text()
|
||||
.trim();
|
||||
break;
|
||||
case "Жанры":
|
||||
novel.genres = loadedCheerio("div.data-value > a")
|
||||
case 'Жанры':
|
||||
novel.genres = loadedCheerio('div.data-value > a')
|
||||
.map((index, element) => loadedCheerio(element).text()?.trim())
|
||||
.get()
|
||||
.join(",");
|
||||
.join(',');
|
||||
break;
|
||||
case "Статус книги":
|
||||
novel.status = loadedCheerio("div.data-value")
|
||||
case 'Статус книги':
|
||||
novel.status = loadedCheerio('div.data-value')
|
||||
.text()
|
||||
.includes("активен")
|
||||
.includes('активен')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (novel.author == "Не указан") delete novel.author;
|
||||
if (novel.author == 'Не указан') delete novel.author;
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
const totalChapters = loadedCheerio("div.li-ranobe").length;
|
||||
const totalChapters = loadedCheerio('div.li-ranobe').length;
|
||||
|
||||
loadedCheerio("div.li-ranobe").each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find("a").text();
|
||||
const url = loadedCheerio(element).find("a").attr("href");
|
||||
if (!loadedCheerio(element).find("label.buy-ranobe").length && name && url) {
|
||||
loadedCheerio('div.li-ranobe').each((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element).find('a').text();
|
||||
const url = loadedCheerio(element).find('a').attr('href');
|
||||
if (
|
||||
!loadedCheerio(element).find('label.buy-ranobe').length &&
|
||||
name &&
|
||||
url
|
||||
) {
|
||||
const releaseDate = loadedCheerio(element)
|
||||
.find("div.li-col2-ranobe")
|
||||
.find('div.li-col2-ranobe')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
chapters.push({
|
||||
name,
|
||||
path: url.replace(this.site, ""),
|
||||
path: url.replace(this.site, ''),
|
||||
releaseTime: this.parseDate(releaseDate),
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
});
|
||||
@@ -128,21 +136,23 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".entry-content img").each((index, element) => {
|
||||
const srcset = loadedCheerio(element).attr("srcset")?.split?.(" ");
|
||||
loadedCheerio('.entry-content img').each((index, element) => {
|
||||
const srcset = loadedCheerio(element).attr('srcset')?.split?.(' ');
|
||||
if (srcset?.length) {
|
||||
loadedCheerio(element).removeAttr("srcset");
|
||||
const bestlink: string[] = srcset.filter((url) => url.startsWith("http"));
|
||||
loadedCheerio(element).removeAttr('srcset');
|
||||
const bestlink: string[] = srcset.filter(url => url.startsWith('http'));
|
||||
if (bestlink[bestlink.length - 1]) {
|
||||
loadedCheerio(element).attr("src", bestlink[bestlink.length - 1]);
|
||||
loadedCheerio(element).attr('src', bestlink[bestlink.length - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".entry-content").html() || "";
|
||||
const chapterText = loadedCheerio('.entry-content').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
@@ -151,25 +161,26 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
page: number | undefined = 1,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site + "/vse-knigi/?searchname=" + searchTerm + "&bpage=" + page;
|
||||
const result = await fetchApi(url).then((res) => res.text());
|
||||
this.site + '/vse-knigi/?searchname=' + searchTerm + '&bpage=' + page;
|
||||
const result = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio(
|
||||
"div.one-book-home > div.img-home a",
|
||||
'div.one-book-home > div.img-home a',
|
||||
)
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).attr("title") || "",
|
||||
cover: loadedCheerio(element).find("img").attr("src"),
|
||||
path: loadedCheerio(element).attr("href")?.replace?.(this.site, "") || "",
|
||||
name: loadedCheerio(element).attr('title') || '',
|
||||
cover: loadedCheerio(element).find('img').attr('src'),
|
||||
path:
|
||||
loadedCheerio(element).attr('href')?.replace?.(this.site, '') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const months: { [key: string]: number } = {
|
||||
января: 1,
|
||||
февраля: 2,
|
||||
@@ -185,16 +196,16 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
||||
декабря: 12,
|
||||
};
|
||||
|
||||
if (dateString.includes(".")) {
|
||||
const [day, month, year] = dateString.split(".");
|
||||
if (dateString.includes('.')) {
|
||||
const [day, month, year] = dateString.split('.');
|
||||
if (day && month && year) {
|
||||
return dayjs(year + "-" + month + "-" + day).format("LL");
|
||||
return dayjs(year + '-' + month + '-' + day).format('LL');
|
||||
}
|
||||
} else if (dateString.includes(" ")) {
|
||||
const [day, month] = dateString.split(" ");
|
||||
} else if (dateString.includes(' ')) {
|
||||
const [day, month] = dateString.split(' ');
|
||||
if (day && months[month]) {
|
||||
const year = new Date().getFullYear();
|
||||
return dayjs(year + "-" + months[month] + "-" + day).format("LL");
|
||||
return dayjs(year + '-' + months[month] + '-' + day).format('LL');
|
||||
}
|
||||
}
|
||||
return dateString || null;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ScrpitGeneratorFunction } from "../generate";
|
||||
import { LightNovelWPMetadata } from "./template";
|
||||
import list from "./sources.json";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { ScrpitGeneratorFunction } from '../generate';
|
||||
import { LightNovelWPMetadata } from './template';
|
||||
import list from './sources.json';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
return list.map((source: LightNovelWPMetadata) => {
|
||||
@@ -11,15 +11,20 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
filters = require(`./filters/${source.id}`);
|
||||
source.filters = filters.filters;
|
||||
} catch (e) {}
|
||||
console.log(`[${name}] Generating: ${source.id}${" ".repeat(20 - source.id.length)} ${source.filters ? "🔎with filters🔍" : "🚫no filters🚫"}`);
|
||||
console.log(
|
||||
`[${name}] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
|
||||
);
|
||||
return generator(source);
|
||||
});
|
||||
};
|
||||
|
||||
const generator = function generator(source: LightNovelWPMetadata) {
|
||||
const LightNovelWPTemplate = readFileSync(path.join(__dirname, "template.ts"),{
|
||||
encoding: "utf-8",
|
||||
});
|
||||
const LightNovelWPTemplate = readFileSync(
|
||||
path.join(__dirname, 'template.ts'),
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
);
|
||||
|
||||
const pluginScript = `
|
||||
${LightNovelWPTemplate}
|
||||
@@ -28,7 +33,7 @@ export default plugin;
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
lang: source.options?.lang || "English",
|
||||
lang: source.options?.lang || 'English',
|
||||
filename: source.sourceName,
|
||||
pluginScript,
|
||||
};
|
||||
|
||||
@@ -3,99 +3,123 @@ import * as cheerio from 'cheerio';
|
||||
import * as path from 'path';
|
||||
|
||||
function getFilters(name: string, html: string) {
|
||||
const $:cheerio.CheerioAPI = cheerio.load(html);
|
||||
const filters: any = {filters: {
|
||||
"genre[]": {
|
||||
type: "Checkbox",
|
||||
label: "Genre",
|
||||
const $: cheerio.CheerioAPI = cheerio.load(html);
|
||||
const filters: any = {
|
||||
filters: {
|
||||
'genre[]': {
|
||||
type: 'Checkbox',
|
||||
label: 'Genre',
|
||||
value: [],
|
||||
options: []
|
||||
options: [],
|
||||
},
|
||||
"type[]": {
|
||||
type: "Checkbox",
|
||||
label: "Type",
|
||||
'type[]': {
|
||||
type: 'Checkbox',
|
||||
label: 'Type',
|
||||
value: [],
|
||||
options: []
|
||||
options: [],
|
||||
},
|
||||
"status": {
|
||||
type: "Picker",
|
||||
label: "Status",
|
||||
value: "",
|
||||
options: []
|
||||
'status': {
|
||||
type: 'Picker',
|
||||
label: 'Status',
|
||||
value: '',
|
||||
options: [],
|
||||
},
|
||||
"order": {
|
||||
type: "Picker",
|
||||
label: "Order",
|
||||
value: "",
|
||||
options: []
|
||||
'order': {
|
||||
type: 'Picker',
|
||||
label: 'Order',
|
||||
value: '',
|
||||
options: [],
|
||||
},
|
||||
}};
|
||||
},
|
||||
};
|
||||
|
||||
const filtersContainer = $('div.quickfilter').find("ul");
|
||||
const filtersContainer = $('div.quickfilter').find('ul');
|
||||
filtersContainer.each((i, el) => {
|
||||
const filterName = Object.keys(filters.filters)[i];
|
||||
filters.filters[filterName].label = $(el).prev().contents().first().text().trim();
|
||||
$(el).find("li").each((j, li) => {
|
||||
filters.filters[filterName].label = $(el)
|
||||
.prev()
|
||||
.contents()
|
||||
.first()
|
||||
.text()
|
||||
.trim();
|
||||
$(el)
|
||||
.find('li')
|
||||
.each((j, li) => {
|
||||
filters.filters[filterName].options.push({
|
||||
label: $(li).text().trim(),
|
||||
value: decodeURI($(li).find("input").attr("value") || "")
|
||||
value: decodeURI($(li).find('input').attr('value') || ''),
|
||||
});
|
||||
});
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, 'filters', name + ".json"),
|
||||
JSON.stringify(filters, null, 2)
|
||||
path.join(__dirname, 'filters', name + '.json'),
|
||||
JSON.stringify(filters, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
async function getFiltersFromURL(name: string, url: string) {
|
||||
const response = await fetch(url + "/series/");
|
||||
const response = await fetch(url + '/series/');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}, while fetching ${response.url}`);
|
||||
throw new Error(
|
||||
`HTTP error! status: ${response.status}, while fetching ${response.url}`,
|
||||
);
|
||||
}
|
||||
const html = await response.text();
|
||||
try {
|
||||
getFilters(name, html);
|
||||
} catch (e) {
|
||||
console.error("Error while getting filters from", url);
|
||||
console.error('Error while getting filters from', url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function askGetFilter(){
|
||||
async function askGetFilter() {
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const EREASE_PREV_LINE = '\x1b[1A\r\x1b[2K';
|
||||
await readline.question('Enter the id of the site (same one as in sources.json): ', async (name: string) => {
|
||||
await readline.question(EREASE_PREV_LINE + 'Do you want to get the filters from a URL or the html text? (if url dosen\'t work try html) (url/html): ', async (method: string) => {
|
||||
if (method.toLowerCase() === "url") {
|
||||
await readline.question(EREASE_PREV_LINE + 'Enter the URL (same one as in sources.json): ', async (url: string) => {
|
||||
await readline.question(
|
||||
'Enter the id of the site (same one as in sources.json): ',
|
||||
async (name: string) => {
|
||||
await readline.question(
|
||||
EREASE_PREV_LINE +
|
||||
"Do you want to get the filters from a URL or the html text? (if url dosen't work try html) (url/html): ",
|
||||
async (method: string) => {
|
||||
if (method.toLowerCase() === 'url') {
|
||||
await readline.question(
|
||||
EREASE_PREV_LINE +
|
||||
'Enter the URL (same one as in sources.json): ',
|
||||
async (url: string) => {
|
||||
try {
|
||||
await getFiltersFromURL(name, url);
|
||||
} catch (e: any) {
|
||||
console.error("Error while getting filters from", url);
|
||||
console.error('Error while getting filters from', url);
|
||||
console.log(e.message || e);
|
||||
}
|
||||
readline.close();
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
process.stdout.write(EREASE_PREV_LINE + `Enter the html text from the page at {sourceSite}/series (at the end press ENTER then press CTRL+C)
|
||||
(to make it faster you can run \`$(\"div.quickfilter\").parent().html()\` in the console to get only the important html part): `)
|
||||
let html = "";
|
||||
process.stdout.write(
|
||||
EREASE_PREV_LINE +
|
||||
`Enter the html text from the page at {sourceSite}/series (at the end press ENTER then press CTRL+C)
|
||||
(to make it faster you can run \`$(\"div.quickfilter\").parent().html()\` in the console to get only the important html part): `,
|
||||
);
|
||||
let html = '';
|
||||
readline.on('SIGINT', () => {
|
||||
console.log("Creating filters file");
|
||||
console.log('Creating filters file');
|
||||
getFilters(name, html);
|
||||
readline.close();
|
||||
});
|
||||
readline.on('line', (line: string) => {
|
||||
html += line + "\n";
|
||||
html += line + '\n';
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
askGetFilter();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CheerioAPI, load } from "cheerio";
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
import { CheerioAPI, load } from 'cheerio';
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
|
||||
interface LightNovelWPOptions {
|
||||
reverseChapters?: boolean;
|
||||
@@ -40,50 +40,64 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
getHostname(url: string): string {
|
||||
return url.split("/")[2];
|
||||
return url.split('/')[2];
|
||||
}
|
||||
|
||||
async getCheerio(url: string, search: boolean): Promise<CheerioAPI> {
|
||||
const r = await fetchApi(url);
|
||||
if (!r.ok && search != true) throw new Error("Could not reach site (" + r.status + ") try to open in webview.");
|
||||
if (!r.ok && search != true)
|
||||
throw new Error(
|
||||
'Could not reach site (' + r.status + ') try to open in webview.',
|
||||
);
|
||||
const $ = load(await r.text());
|
||||
const title = $("title").text().trim();
|
||||
if (this.getHostname(url) != this.getHostname(r.url) ||
|
||||
title == "Bot Verification" || title == "You are being redirected..." || title == "Un instant..." || title == "Just a moment..." || title == "Redirecting...")
|
||||
throw new Error("Captcha error, please open in webview");
|
||||
const title = $('title').text().trim();
|
||||
if (
|
||||
this.getHostname(url) != this.getHostname(r.url) ||
|
||||
title == 'Bot Verification' ||
|
||||
title == 'You are being redirected...' ||
|
||||
title == 'Un instant...' ||
|
||||
title == 'Just a moment...' ||
|
||||
title == 'Redirecting...'
|
||||
)
|
||||
throw new Error('Captcha error, please open in webview');
|
||||
|
||||
return ($);
|
||||
return $;
|
||||
}
|
||||
|
||||
parseNovels($: CheerioAPI): Plugin.NovelItem[] {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
$("div.listupd > article").each((i, elem) => {
|
||||
const novelName = $(elem).find("h2").text();
|
||||
const image = $(elem).find("img");
|
||||
const novelUrl = $(elem).find("a").attr("href");
|
||||
$('div.listupd > article').each((i, elem) => {
|
||||
const novelName = $(elem).find('h2').text();
|
||||
const image = $(elem).find('img');
|
||||
const novelUrl = $(elem).find('a').attr('href');
|
||||
|
||||
if (novelUrl) {
|
||||
novels.push({
|
||||
name: novelName,
|
||||
cover: image.attr("data-src") || image.attr("src") || defaultCover,
|
||||
path: novelUrl.replace(this.site, ""),
|
||||
cover: image.attr('data-src') || image.attr('src') || defaultCover,
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
});
|
||||
}
|
||||
});
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(pageNo: number, { filters, showLatestNovels }: Plugin.PopularNovelsOptions <typeof this.filters>): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/series/?page=" + pageNo;
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + '/series/?page=' + pageNo;
|
||||
if (!filters) filters = {};
|
||||
if (showLatestNovels) url += "&order=latest";
|
||||
if (showLatestNovels) url += '&order=latest';
|
||||
for (const key in filters) {
|
||||
if (typeof filters[key].value === "object")
|
||||
if (typeof filters[key].value === 'object')
|
||||
for (const value of filters[key].value as string[])
|
||||
url += `&${key}=${value}`;
|
||||
else if (filters[key].value)
|
||||
url += `&${key}=${filters[key].value}`;
|
||||
else if (filters[key].value) url += `&${key}=${filters[key].value}`;
|
||||
}
|
||||
const $ = await this.getCheerio(url, false);
|
||||
return this.parseNovels($);
|
||||
@@ -92,19 +106,22 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const $ = await this.getCheerio(this.site + novelPath, false);
|
||||
|
||||
const novel: Plugin.SourceNovel = { path: novelPath.replace(this.site, ""), name: "Untitled" };
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath.replace(this.site, ''),
|
||||
name: 'Untitled',
|
||||
};
|
||||
|
||||
novel.name = $("h1.entry-title").text().trim();
|
||||
const image = $("img.wp-post-image");
|
||||
novel.cover = image.attr("data-src") || image.attr("src") || defaultCover;
|
||||
switch ($("div.sertostat > span").attr("class")?.toLowerCase() || "") {
|
||||
case "completed":
|
||||
novel.name = $('h1.entry-title').text().trim();
|
||||
const image = $('img.wp-post-image');
|
||||
novel.cover = image.attr('data-src') || image.attr('src') || defaultCover;
|
||||
switch ($('div.sertostat > span').attr('class')?.toLowerCase() || '') {
|
||||
case 'completed':
|
||||
novel.status = NovelStatus.Completed;
|
||||
break;
|
||||
case "ongoing":
|
||||
case 'ongoing':
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
break;
|
||||
case "hiatus":
|
||||
case 'hiatus':
|
||||
novel.status = NovelStatus.OnHiatus;
|
||||
break;
|
||||
default:
|
||||
@@ -112,49 +129,55 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
break;
|
||||
}
|
||||
|
||||
let details = $("div.serl");
|
||||
if (!details.length) details = $("div.spe > span");
|
||||
let details = $('div.serl');
|
||||
if (!details.length) details = $('div.spe > span');
|
||||
details.each(function () {
|
||||
const detailName = $(this).contents().first().text().replace(":", "").trim().toLowerCase();
|
||||
const detailName = $(this)
|
||||
.contents()
|
||||
.first()
|
||||
.text()
|
||||
.replace(':', '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const detail = $(this).contents().last().text().trim().toLowerCase();
|
||||
|
||||
switch (detailName) {
|
||||
case "الكاتب":
|
||||
case "author":
|
||||
case "auteur":
|
||||
case "autor":
|
||||
case "yazar":
|
||||
case 'الكاتب':
|
||||
case 'author':
|
||||
case 'auteur':
|
||||
case 'autor':
|
||||
case 'yazar':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "الحالة":
|
||||
case "status":
|
||||
case "statut":
|
||||
case "estado":
|
||||
case "durum":
|
||||
case 'الحالة':
|
||||
case 'status':
|
||||
case 'statut':
|
||||
case 'estado':
|
||||
case 'durum':
|
||||
switch (detail) {
|
||||
case "مكتملة":
|
||||
case "completed":
|
||||
case "complété":
|
||||
case "completo":
|
||||
case "completado":
|
||||
case "tamamlandı":
|
||||
case 'مكتملة':
|
||||
case 'completed':
|
||||
case 'complété':
|
||||
case 'completo':
|
||||
case 'completado':
|
||||
case 'tamamlandı':
|
||||
novel.status = NovelStatus.Completed;
|
||||
break;
|
||||
case "مستمرة":
|
||||
case "ongoing":
|
||||
case "en cours":
|
||||
case "em andamento":
|
||||
case "en progreso":
|
||||
case "devam ediyor":
|
||||
case 'مستمرة':
|
||||
case 'ongoing':
|
||||
case 'en cours':
|
||||
case 'em andamento':
|
||||
case 'en progreso':
|
||||
case 'devam ediyor':
|
||||
novel.status = NovelStatus.Ongoing;
|
||||
break;
|
||||
case "متوقفة":
|
||||
case "hiatus":
|
||||
case "en pause":
|
||||
case "hiato":
|
||||
case "pausa":
|
||||
case "pausado":
|
||||
case "duraklatıldı":
|
||||
case 'متوقفة':
|
||||
case 'hiatus':
|
||||
case 'en pause':
|
||||
case 'hiato':
|
||||
case 'pausa':
|
||||
case 'pausado':
|
||||
case 'duraklatıldı':
|
||||
novel.status = NovelStatus.OnHiatus;
|
||||
break;
|
||||
default:
|
||||
@@ -162,40 +185,48 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "الفنان":
|
||||
case "artist":
|
||||
case "artiste":
|
||||
case "artista":
|
||||
case "çizer":
|
||||
case 'الفنان':
|
||||
case 'artist':
|
||||
case 'artiste':
|
||||
case 'artista':
|
||||
case 'çizer':
|
||||
novel.artist = detail;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let genres = $(".sertogenre")
|
||||
if (!genres.length) genres = $(".genxed")
|
||||
let genres = $('.sertogenre');
|
||||
if (!genres.length) genres = $('.genxed');
|
||||
novel.genres = genres
|
||||
.children("a")
|
||||
.children('a')
|
||||
.map((i, el) => $(el).text())
|
||||
.toArray()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
let summary = $(".sersys > p").map((i, el) => $(el).text().trim()).toArray();
|
||||
if (!summary.length) summary = $(".entry-content > p").map((i, el) => $(el).text().trim()).toArray();
|
||||
novel.summary = summary.join("\n");
|
||||
let summary = $('.sersys > p')
|
||||
.map((i, el) => $(el).text().trim())
|
||||
.toArray();
|
||||
if (!summary.length)
|
||||
summary = $('.entry-content > p')
|
||||
.map((i, el) => $(el).text().trim())
|
||||
.toArray();
|
||||
novel.summary = summary.join('\n');
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
$(".eplister li").each((i, elem) => {
|
||||
const chapterName = $(elem).find(".epl-num").text() + " " + $(elem).find(".epl-title").text();
|
||||
const chapterUrl = $(elem).find("a").attr("href") || "";
|
||||
const releaseTime = $(elem).find(".epl-date").text().trim();
|
||||
$('.eplister li').each((i, elem) => {
|
||||
const chapterName =
|
||||
$(elem).find('.epl-num').text() +
|
||||
' ' +
|
||||
$(elem).find('.epl-title').text();
|
||||
const chapterUrl = $(elem).find('a').attr('href') || '';
|
||||
const releaseTime = $(elem).find('.epl-date').text().trim();
|
||||
let isFreeChapter: boolean;
|
||||
switch ($(elem).find(".epl-price").text().trim().toLowerCase()) {
|
||||
case "free":
|
||||
case "gratuit":
|
||||
case "مجاني":
|
||||
case "livre":
|
||||
case "":
|
||||
switch ($(elem).find('.epl-price').text().trim().toLowerCase()) {
|
||||
case 'free':
|
||||
case 'gratuit':
|
||||
case 'مجاني':
|
||||
case 'livre':
|
||||
case '':
|
||||
isFreeChapter = true;
|
||||
break;
|
||||
default:
|
||||
@@ -205,7 +236,7 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
if (isFreeChapter)
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl.replace(this.site, ""),
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
releaseTime: releaseTime,
|
||||
});
|
||||
});
|
||||
@@ -218,16 +249,24 @@ class LightNovelWPPlugin implements Plugin.PluginBase {
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const $ = await this.getCheerio(this.site + chapterPath, false);
|
||||
if (this.id == "kolnovel") {
|
||||
let ignore = $("article > style").text().trim().split(",");
|
||||
if (this.id == 'kolnovel') {
|
||||
let ignore = $('article > style').text().trim().split(',');
|
||||
ignore.push(...(ignore.pop()?.match(/^\.\w+/) || []));
|
||||
ignore.map((tag) => $(`p${tag}`).remove());
|
||||
ignore.map(tag => $(`p${tag}`).remove());
|
||||
}
|
||||
return $(".epcontent p").map((i, el) => $(el)).toArray().join("\n") || "";
|
||||
return (
|
||||
$('.epcontent p')
|
||||
.map((i, el) => $(el))
|
||||
.toArray()
|
||||
.join('\n') || ''
|
||||
);
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, page: number): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + "page/" + page + "/?s=" + searchTerm;
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
page: number,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + 'page/' + page + '/?s=' + searchTerm;
|
||||
const $ = await this.getCheerio(url, true);
|
||||
return this.parseNovels($);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FilterTypes, Filters } from "../../../libs/filterInputs";
|
||||
import { ScrpitGeneratorFunction } from "../generate";
|
||||
import list from "./sources.json";
|
||||
import { MadaraMetadata } from "./template";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { FilterTypes, Filters } from '../../../libs/filterInputs';
|
||||
import { ScrpitGeneratorFunction } from '../generate';
|
||||
import list from './sources.json';
|
||||
import { MadaraMetadata } from './template';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
return list.map((metadata: MadaraMetadata) => {
|
||||
@@ -11,16 +11,17 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
try {
|
||||
filters = require(`./filters/${metadata.id}`);
|
||||
metadata.filters = filters.filters;
|
||||
}
|
||||
catch (e) {}
|
||||
console.log(`[${name}] Generating: ${metadata.id}${" ".repeat(20 - metadata.id.length)} ${metadata.filters ? "🔎with filters🔍" : "🚫no filters🚫"}`);
|
||||
} catch (e) {}
|
||||
console.log(
|
||||
`[${name}] Generating: ${metadata.id}${' '.repeat(20 - metadata.id.length)} ${metadata.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
|
||||
);
|
||||
return generator(metadata);
|
||||
});
|
||||
};
|
||||
|
||||
const generator = function generator(metadata: MadaraMetadata) {
|
||||
const madaraTemplate = readFileSync(path.join(__dirname, "template.ts"), {
|
||||
encoding: "utf-8",
|
||||
const madaraTemplate = readFileSync(path.join(__dirname, 'template.ts'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
const pluginScript = `
|
||||
@@ -29,7 +30,7 @@ const plugin = new MadaraPlugin(${JSON.stringify(metadata)});
|
||||
export default plugin;
|
||||
`.trim();
|
||||
return {
|
||||
lang: metadata.options?.lang || "English",
|
||||
lang: metadata.options?.lang || 'English',
|
||||
filename: metadata.sourceName,
|
||||
pluginScript,
|
||||
};
|
||||
|
||||
@@ -3,177 +3,208 @@ import * as cheerio from 'cheerio';
|
||||
import * as path from 'path';
|
||||
|
||||
function getFilters(name: string, html: string) {
|
||||
const $:cheerio.CheerioAPI = cheerio.load(html);
|
||||
const filters: any = {filters: {
|
||||
"genre[]": {
|
||||
type: "Checkbox",
|
||||
label: "Genre",
|
||||
const $: cheerio.CheerioAPI = cheerio.load(html);
|
||||
const filters: any = {
|
||||
filters: {
|
||||
'genre[]': {
|
||||
type: 'Checkbox',
|
||||
label: 'Genre',
|
||||
value: [],
|
||||
options: []
|
||||
},
|
||||
"op": {
|
||||
type: "Switch",
|
||||
label: "Having ALL selected genres",
|
||||
value: false
|
||||
},
|
||||
"author": {
|
||||
type: "Text",
|
||||
label: "Author",
|
||||
value: ""
|
||||
},
|
||||
"artist": {
|
||||
type: "Text",
|
||||
label: "Artist",
|
||||
value: ""
|
||||
},
|
||||
"release": {
|
||||
type: "Text",
|
||||
label: "Year of Released",
|
||||
value: ""
|
||||
},
|
||||
"adult": {
|
||||
type: "Picker",
|
||||
label: "Adult content",
|
||||
value: "",
|
||||
options: [],
|
||||
},
|
||||
"status[]": {
|
||||
type: "Checkbox",
|
||||
label: "Status",
|
||||
'op': {
|
||||
type: 'Switch',
|
||||
label: 'Having ALL selected genres',
|
||||
value: false,
|
||||
},
|
||||
'author': {
|
||||
type: 'Text',
|
||||
label: 'Author',
|
||||
value: '',
|
||||
},
|
||||
'artist': {
|
||||
type: 'Text',
|
||||
label: 'Artist',
|
||||
value: '',
|
||||
},
|
||||
'release': {
|
||||
type: 'Text',
|
||||
label: 'Year of Released',
|
||||
value: '',
|
||||
},
|
||||
'adult': {
|
||||
type: 'Picker',
|
||||
label: 'Adult content',
|
||||
value: '',
|
||||
options: [],
|
||||
},
|
||||
'status[]': {
|
||||
type: 'Checkbox',
|
||||
label: 'Status',
|
||||
value: [],
|
||||
options: []
|
||||
options: [],
|
||||
},
|
||||
"m_orderby": {
|
||||
type: "Picker",
|
||||
label: "Order by",
|
||||
value: "",
|
||||
options: []
|
||||
'm_orderby': {
|
||||
type: 'Picker',
|
||||
label: 'Order by',
|
||||
value: '',
|
||||
options: [],
|
||||
},
|
||||
}};
|
||||
},
|
||||
};
|
||||
|
||||
const divs = $("form.search-advanced-form > div");
|
||||
const divs = $('form.search-advanced-form > div');
|
||||
|
||||
// ==================== Genre ====================
|
||||
const genreDiv = divs.eq(0);
|
||||
genreDiv.find("label").each((i, el) => {
|
||||
genreDiv.find('label').each((i, el) => {
|
||||
const option = {
|
||||
label: $(el).text().trim(),
|
||||
value: decodeURI($(el).attr("for") || "")
|
||||
value: decodeURI($(el).attr('for') || ''),
|
||||
};
|
||||
filters.filters["genre[]"].options.push(option);
|
||||
filters.filters['genre[]'].options.push(option);
|
||||
});
|
||||
|
||||
// ===================== Op ======================
|
||||
const statusDiv = divs.eq(1);
|
||||
filters.filters["op"].label = statusDiv.find("option").eq(1)
|
||||
.text().replace("AND", "").replace("(", "").replace(")", "").trim();
|
||||
filters.filters['op'].label = statusDiv
|
||||
.find('option')
|
||||
.eq(1)
|
||||
.text()
|
||||
.replace('AND', '')
|
||||
.replace('(', '')
|
||||
.replace(')', '')
|
||||
.trim();
|
||||
|
||||
// ==================== Author ====================
|
||||
const authorDiv = divs.eq(2);
|
||||
filters.filters["author"].label = authorDiv.find("span").text().trim();
|
||||
filters.filters['author'].label = authorDiv.find('span').text().trim();
|
||||
|
||||
// ==================== Artist ====================
|
||||
const artistDiv = divs.eq(3);
|
||||
filters.filters["artist"].label = artistDiv.find("span").text().trim();
|
||||
filters.filters['artist'].label = artistDiv.find('span').text().trim();
|
||||
|
||||
// ==================== Release ====================
|
||||
const releaseDiv = divs.eq(4);
|
||||
filters.filters["release"].label = releaseDiv.find("span").text().trim();
|
||||
filters.filters['release'].label = releaseDiv.find('span').text().trim();
|
||||
|
||||
// ==================== Adult ====================
|
||||
const adultDiv = divs.eq(5);
|
||||
filters.filters["adult"].label = adultDiv.find("span").text().trim();
|
||||
adultDiv.find("option").each((i, el) => {
|
||||
filters.filters['adult'].label = adultDiv.find('span').text().trim();
|
||||
adultDiv.find('option').each((i, el) => {
|
||||
const option = {
|
||||
label: $(el).text().trim(),
|
||||
value: decodeURI($(el).attr("value") || "")
|
||||
value: decodeURI($(el).attr('value') || ''),
|
||||
};
|
||||
filters.filters["adult"].options.push(option);
|
||||
filters.filters['adult'].options.push(option);
|
||||
});
|
||||
|
||||
// ==================== Status ====================
|
||||
const statusDiv2 = divs.eq(6);
|
||||
filters.filters["status[]"].label = statusDiv2.find("span").text().trim();
|
||||
statusDiv2.find("label").each((i, el) => {
|
||||
filters.filters['status[]'].label = statusDiv2.find('span').text().trim();
|
||||
statusDiv2.find('label').each((i, el) => {
|
||||
const option = {
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr("for")
|
||||
value: $(el).attr('for'),
|
||||
};
|
||||
filters.filters["status[]"].options.push(option);
|
||||
filters.filters['status[]'].options.push(option);
|
||||
});
|
||||
|
||||
// ==================== Order by ====================
|
||||
const orderByDiv = $("div.c-nav-tabs").eq(0);
|
||||
filters.filters["m_orderby"].label = orderByDiv.find("span").text().trim();
|
||||
orderByDiv.find("a").each((i, el) => {
|
||||
const orderByDiv = $('div.c-nav-tabs').eq(0);
|
||||
filters.filters['m_orderby'].label = orderByDiv.find('span').text().trim();
|
||||
orderByDiv.find('a').each((i, el) => {
|
||||
const option = {
|
||||
label: $(el).text().trim(),
|
||||
value: decodeURI(($(el).attr("href")?.split("=").length == 3 ? $(el).attr("href")?.split("=")[2] : "") || "")
|
||||
value: decodeURI(
|
||||
($(el).attr('href')?.split('=').length == 3
|
||||
? $(el).attr('href')?.split('=')[2]
|
||||
: '') || '',
|
||||
),
|
||||
};
|
||||
filters.filters["m_orderby"].options.push(option);
|
||||
filters.filters['m_orderby'].options.push(option);
|
||||
});
|
||||
|
||||
if (filters.filters["m_orderby"].options.length == 0 ||
|
||||
filters.filters["status[]"].options.length == 0 ||
|
||||
filters.filters["adult"].options.length == 0 ||
|
||||
filters.filters["genre[]"].options.length == 0) {
|
||||
console.error(`🚨Error in filters for ${name} please fix manually (${path.join(__dirname, 'filters', name + ".json")})🚨`);
|
||||
if (
|
||||
filters.filters['m_orderby'].options.length == 0 ||
|
||||
filters.filters['status[]'].options.length == 0 ||
|
||||
filters.filters['adult'].options.length == 0 ||
|
||||
filters.filters['genre[]'].options.length == 0
|
||||
) {
|
||||
console.error(
|
||||
`🚨Error in filters for ${name} please fix manually (${path.join(__dirname, 'filters', name + '.json')})🚨`,
|
||||
);
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, 'filters', name + ".json"),
|
||||
JSON.stringify(filters, null, 2)
|
||||
path.join(__dirname, 'filters', name + '.json'),
|
||||
JSON.stringify(filters, null, 2),
|
||||
);
|
||||
console.log(`✅Filters created successfully for ${name}✅`);
|
||||
}
|
||||
|
||||
async function getFiltersFromURL(name: string, url: string) {
|
||||
const response = await fetch(url + "/?s=&post_type=wp-manga");
|
||||
const response = await fetch(url + '/?s=&post_type=wp-manga');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}, while fetching ${response.url}`);
|
||||
throw new Error(
|
||||
`HTTP error! status: ${response.status}, while fetching ${response.url}`,
|
||||
);
|
||||
}
|
||||
const html = await response.text();
|
||||
try {
|
||||
getFilters(name, html);
|
||||
} catch (e) {
|
||||
console.error("Error while getting filters from", url);
|
||||
console.error('Error while getting filters from', url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function askGetFilter(){
|
||||
async function askGetFilter() {
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const EREASE_PREV_LINE = '\x1b[1A\r\x1b[2K';
|
||||
await readline.question('Enter the id of the site (same one as in sources.json): ', async (name: string) => {
|
||||
await readline.question(EREASE_PREV_LINE + 'Do you want to get the filters from a URL or the html text? (if url dosen\'t work try html) (url/html): ', async (method: string) => {
|
||||
if (method.toLowerCase() === "url") {
|
||||
await readline.question(EREASE_PREV_LINE + 'Enter the URL (same one as in sources.json): ', async (url: string) => {
|
||||
await readline.question(
|
||||
'Enter the id of the site (same one as in sources.json): ',
|
||||
async (name: string) => {
|
||||
await readline.question(
|
||||
EREASE_PREV_LINE +
|
||||
"Do you want to get the filters from a URL or the html text? (if url dosen't work try html) (url/html): ",
|
||||
async (method: string) => {
|
||||
if (method.toLowerCase() === 'url') {
|
||||
await readline.question(
|
||||
EREASE_PREV_LINE +
|
||||
'Enter the URL (same one as in sources.json): ',
|
||||
async (url: string) => {
|
||||
try {
|
||||
await getFiltersFromURL(name, url);
|
||||
} catch (e: any) {
|
||||
console.error("Error while getting filters from", url);
|
||||
console.error('Error while getting filters from', url);
|
||||
console.log(e.message || e);
|
||||
}
|
||||
readline.close();
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
process.stdout.write(EREASE_PREV_LINE + `Enter the html text from the page at {sourceSite}/?s=&post_type=wp-manga (at the end press ENTER then press CTRL+C): `)
|
||||
let html = "";
|
||||
process.stdout.write(
|
||||
EREASE_PREV_LINE +
|
||||
`Enter the html text from the page at {sourceSite}/?s=&post_type=wp-manga (at the end press ENTER then press CTRL+C): `,
|
||||
);
|
||||
let html = '';
|
||||
readline.on('SIGINT', () => {
|
||||
console.log("Stopeed reading input, creating filters file");
|
||||
console.log('Stopeed reading input, creating filters file');
|
||||
getFilters(name, html);
|
||||
readline.close();
|
||||
});
|
||||
readline.on('line', (line: string) => {
|
||||
html += line + "\n";
|
||||
html += line + '\n';
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
askGetFilter();
|
||||
|
||||
+154
-122
@@ -1,13 +1,13 @@
|
||||
import { fetchApi, fetchFile } from "@libs/fetch";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { CheerioAPI, load as parseHTML } from "cheerio";
|
||||
import { defaultCover } from "@libs/defaultCover";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { CheerioAPI, load as parseHTML } from 'cheerio';
|
||||
import { defaultCover } from '@libs/defaultCover';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const includesAny = (str: string, keywords: string[]) =>
|
||||
new RegExp(keywords.join("|")).test(str);
|
||||
new RegExp(keywords.join('|')).test(str);
|
||||
|
||||
interface MadaraOptions {
|
||||
useNewChapterEndpoint?: boolean;
|
||||
@@ -45,157 +45,178 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
getHostname(url: string): string {
|
||||
return url.split("/")[2];
|
||||
return url.split('/')[2];
|
||||
}
|
||||
|
||||
async getCheerio(url: string): Promise<CheerioAPI> {
|
||||
const r = await fetchApi(url);
|
||||
if (!r.ok) throw new Error("Could not reach site (" + r.status + ") try to open in webview.");
|
||||
if (!r.ok)
|
||||
throw new Error(
|
||||
'Could not reach site (' + r.status + ') try to open in webview.',
|
||||
);
|
||||
const $ = parseHTML(await r.text());
|
||||
const title = $("title").text().trim();
|
||||
if (this.getHostname(url) != this.getHostname(r.url) ||
|
||||
title == "Bot Verification" || title == "You are being redirected..." || title == "Un instant..." || title == "Just a moment..." || title == "Redirecting...")
|
||||
throw new Error("Captcha error, please open in webview");
|
||||
return ($);
|
||||
const title = $('title').text().trim();
|
||||
if (
|
||||
this.getHostname(url) != this.getHostname(r.url) ||
|
||||
title == 'Bot Verification' ||
|
||||
title == 'You are being redirected...' ||
|
||||
title == 'Un instant...' ||
|
||||
title == 'Just a moment...' ||
|
||||
title == 'Redirecting...'
|
||||
)
|
||||
throw new Error('Captcha error, please open in webview');
|
||||
return $;
|
||||
}
|
||||
|
||||
parseNovels(loadedCheerio: CheerioAPI): Plugin.NovelItem[] {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
|
||||
loadedCheerio(".manga-title-badges").remove();
|
||||
loadedCheerio('.manga-title-badges').remove();
|
||||
|
||||
loadedCheerio(".page-item-detail, .c-tabs-item__content").each((index, element) => {
|
||||
const novelName = loadedCheerio(element).find(".post-title").text().trim();
|
||||
const novelUrl = loadedCheerio(element).find(".post-title").find("a").attr("href") || "";
|
||||
loadedCheerio('.page-item-detail, .c-tabs-item__content').each(
|
||||
(index, element) => {
|
||||
const novelName = loadedCheerio(element)
|
||||
.find('.post-title')
|
||||
.text()
|
||||
.trim();
|
||||
const novelUrl =
|
||||
loadedCheerio(element).find('.post-title').find('a').attr('href') ||
|
||||
'';
|
||||
if (!novelName || !novelUrl) return;
|
||||
const image = loadedCheerio(element).find("img");
|
||||
const novelCover = image.attr("data-src") || image.attr("src");
|
||||
const image = loadedCheerio(element).find('img');
|
||||
const novelCover = image.attr('data-src') || image.attr('src');
|
||||
const novel: Plugin.NovelItem = {
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
path: novelUrl.replace(this.site, ""),
|
||||
path: novelUrl.replace(this.site, ''),
|
||||
};
|
||||
novels.push(novel);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async popularNovels(pageNo: number,{ filters, showLatestNovels }: Plugin.PopularNovelsOptions<typeof this.filters>): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/page/" + pageNo + "/?s=&post_type=wp-manga";
|
||||
async popularNovels(
|
||||
pageNo: number,
|
||||
{
|
||||
filters,
|
||||
showLatestNovels,
|
||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + '/page/' + pageNo + '/?s=&post_type=wp-manga';
|
||||
if (!filters) filters = {};
|
||||
if (showLatestNovels) url += "&m_orderby=latest";
|
||||
if (showLatestNovels) url += '&m_orderby=latest';
|
||||
for (const key in filters) {
|
||||
if (typeof filters[key].value === "object")
|
||||
if (typeof filters[key].value === 'object')
|
||||
for (const value of filters[key].value as string[])
|
||||
url += `&${key}=${value}`;
|
||||
else if (filters[key].value)
|
||||
url += `&${key}=${filters[key].value}`;
|
||||
else if (filters[key].value) url += `&${key}=${filters[key].value}`;
|
||||
}
|
||||
const loadedCheerio = await this.getCheerio(url);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
let loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".manga-title-badges, #manga-title span").remove();
|
||||
loadedCheerio('.manga-title-badges, #manga-title span').remove();
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name:
|
||||
loadedCheerio(".post-title h1").text().trim() ||
|
||||
loadedCheerio("#manga-title h1").text(),
|
||||
loadedCheerio('.post-title h1').text().trim() ||
|
||||
loadedCheerio('#manga-title h1').text(),
|
||||
};
|
||||
|
||||
novel.cover =
|
||||
loadedCheerio(".summary_image > a > img").attr("data-lazy-src") ||
|
||||
loadedCheerio(".summary_image > a > img").attr("data-src") ||
|
||||
loadedCheerio(".summary_image > a > img").attr("src") ||
|
||||
loadedCheerio(".summary_image > a > img").attr("src") ||
|
||||
loadedCheerio('.summary_image > a > img').attr('data-lazy-src') ||
|
||||
loadedCheerio('.summary_image > a > img').attr('data-src') ||
|
||||
loadedCheerio('.summary_image > a > img').attr('src') ||
|
||||
loadedCheerio('.summary_image > a > img').attr('src') ||
|
||||
defaultCover;
|
||||
|
||||
loadedCheerio(".post-content_item, .post-content").each(function () {
|
||||
const detailName = loadedCheerio(this).find("h5").text().trim();
|
||||
const detail = loadedCheerio(this).find(".summary-content").text().trim();
|
||||
loadedCheerio('.post-content_item, .post-content').each(function () {
|
||||
const detailName = loadedCheerio(this).find('h5').text().trim();
|
||||
const detail = loadedCheerio(this).find('.summary-content').text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Genre(s)":
|
||||
case "Género(s)":
|
||||
case "التصنيفات":
|
||||
case 'Genre(s)':
|
||||
case 'Género(s)':
|
||||
case 'التصنيفات':
|
||||
novel.genres = detail;
|
||||
break;
|
||||
case "Author(s)":
|
||||
case "Autor(es)":
|
||||
case "المؤلف":
|
||||
case "المؤلف (ين)":
|
||||
case 'Author(s)':
|
||||
case 'Autor(es)':
|
||||
case 'المؤلف':
|
||||
case 'المؤلف (ين)':
|
||||
novel.author = detail;
|
||||
break;
|
||||
case "Status":
|
||||
case "Estado":
|
||||
case 'Status':
|
||||
case 'Estado':
|
||||
novel.status =
|
||||
detail.includes("OnGoing") || detail.includes("مستمرة")
|
||||
detail.includes('OnGoing') || detail.includes('مستمرة')
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
loadedCheerio("div.summary__content .code-block,script").remove();
|
||||
loadedCheerio('div.summary__content .code-block,script').remove();
|
||||
novel.summary =
|
||||
loadedCheerio("div.summary__content").text().trim() ||
|
||||
loadedCheerio("#tab-manga-about").text().trim() ||
|
||||
loadedCheerio('div.summary__content').text().trim() ||
|
||||
loadedCheerio('#tab-manga-about').text().trim() ||
|
||||
loadedCheerio('.post-content_item h5:contains("Summary")')
|
||||
.next()
|
||||
.text()
|
||||
.trim();
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
let html = "";
|
||||
let html = '';
|
||||
|
||||
if (this.options?.useNewChapterEndpoint) {
|
||||
html = await fetchApi(this.site + novelPath + "ajax/chapters/", {
|
||||
method: "POST",
|
||||
}).then((res) => res.text());
|
||||
html = await fetchApi(this.site + novelPath + 'ajax/chapters/', {
|
||||
method: 'POST',
|
||||
}).then(res => res.text());
|
||||
} else {
|
||||
const novelId =
|
||||
loadedCheerio(".rating-post-id").attr("value") ||
|
||||
loadedCheerio("#manga-chapters-holder").attr("data-id") ||
|
||||
"";
|
||||
loadedCheerio('.rating-post-id').attr('value') ||
|
||||
loadedCheerio('#manga-chapters-holder').attr('data-id') ||
|
||||
'';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("action", "manga_get_chapters");
|
||||
formData.append("manga", novelId);
|
||||
formData.append('action', 'manga_get_chapters');
|
||||
formData.append('manga', novelId);
|
||||
|
||||
html = await fetchApi(this.site + "wp-admin/admin-ajax.php", {
|
||||
method: "POST",
|
||||
html = await fetchApi(this.site + 'wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
}
|
||||
|
||||
if (html !== "0") {
|
||||
if (html !== '0') {
|
||||
loadedCheerio = parseHTML(html);
|
||||
}
|
||||
|
||||
const totalChapters = loadedCheerio(".wp-manga-chapter").length;
|
||||
loadedCheerio(".wp-manga-chapter").each((chapterIndex, element) => {
|
||||
const chapterName = loadedCheerio(element).find("a").text().trim();
|
||||
const totalChapters = loadedCheerio('.wp-manga-chapter').length;
|
||||
loadedCheerio('.wp-manga-chapter').each((chapterIndex, element) => {
|
||||
const chapterName = loadedCheerio(element).find('a').text().trim();
|
||||
|
||||
let releaseDate = loadedCheerio(element)
|
||||
.find("span.chapter-release-date")
|
||||
.find('span.chapter-release-date')
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
if (releaseDate) {
|
||||
releaseDate = this.parseData(releaseDate);
|
||||
} else {
|
||||
releaseDate = dayjs().format("LL");
|
||||
releaseDate = dayjs().format('LL');
|
||||
}
|
||||
|
||||
let chapterUrl = loadedCheerio(element).find("a").attr("href") || "";
|
||||
let chapterUrl = loadedCheerio(element).find('a').attr('href') || '';
|
||||
|
||||
chapters.push({
|
||||
name: chapterName,
|
||||
path: chapterUrl.replace(this.site, ""),
|
||||
path: chapterUrl.replace(this.site, ''),
|
||||
releaseTime: releaseDate || null,
|
||||
chapterNumber: totalChapters - chapterIndex,
|
||||
});
|
||||
@@ -206,21 +227,32 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
|
||||
const loadedCheerio = parseHTML(body);
|
||||
const chapterText =
|
||||
loadedCheerio(".text-left").html() ||
|
||||
loadedCheerio(".text-right").html() ||
|
||||
loadedCheerio(".entry-content").html() ||
|
||||
loadedCheerio(".c-blog-post > div > div:nth-child(2)").html() ||
|
||||
"";
|
||||
loadedCheerio('.text-left').html() ||
|
||||
loadedCheerio('.text-right').html() ||
|
||||
loadedCheerio('.entry-content').html() ||
|
||||
loadedCheerio('.c-blog-post > div > div:nth-child(2)').html() ||
|
||||
'';
|
||||
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string, pageNo?: number | undefined): Promise<Plugin.NovelItem[]> {
|
||||
const url = this.site + "page/" + pageNo + "/?s=" + searchTerm + "&post_type=wp-manga";
|
||||
async searchNovels(
|
||||
searchTerm: string,
|
||||
pageNo?: number | undefined,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const url =
|
||||
this.site +
|
||||
'page/' +
|
||||
pageNo +
|
||||
'/?s=' +
|
||||
searchTerm +
|
||||
'&post_type=wp-manga';
|
||||
const loadedCheerio = await this.getCheerio(url);
|
||||
return this.parseNovels(loadedCheerio);
|
||||
}
|
||||
@@ -229,66 +261,66 @@ class MadaraPlugin implements Plugin.PluginBase {
|
||||
|
||||
parseData = (date: string) => {
|
||||
const dayJSDate = dayjs(); // today
|
||||
const timeAgo = date.match(/\d+/)?.[0] || "";
|
||||
const timeAgo = date.match(/\d+/)?.[0] || '';
|
||||
const timeAgoInt = parseInt(timeAgo, 10);
|
||||
|
||||
if (!timeAgo) return date; // there is no number!
|
||||
|
||||
if (includesAny(date, ["detik", "segundo", "second", "วินาที"])) {
|
||||
dayJSDate.subtract(timeAgoInt, "second"); // go back N seconds
|
||||
if (includesAny(date, ['detik', 'segundo', 'second', 'วินาที'])) {
|
||||
dayJSDate.subtract(timeAgoInt, 'second'); // go back N seconds
|
||||
} else if (
|
||||
includesAny(date, [
|
||||
"menit",
|
||||
"dakika",
|
||||
"min",
|
||||
"minute",
|
||||
"minuto",
|
||||
"นาที",
|
||||
"دقائق",
|
||||
'menit',
|
||||
'dakika',
|
||||
'min',
|
||||
'minute',
|
||||
'minuto',
|
||||
'นาที',
|
||||
'دقائق',
|
||||
])
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "minute"); // go back N minute
|
||||
dayJSDate.subtract(timeAgoInt, 'minute'); // go back N minute
|
||||
} else if (
|
||||
includesAny(date, [
|
||||
"jam",
|
||||
"saat",
|
||||
"heure",
|
||||
"hora",
|
||||
"hour",
|
||||
"ชั่วโมง",
|
||||
"giờ",
|
||||
"ore",
|
||||
"ساعة",
|
||||
"小时",
|
||||
'jam',
|
||||
'saat',
|
||||
'heure',
|
||||
'hora',
|
||||
'hour',
|
||||
'ชั่วโมง',
|
||||
'giờ',
|
||||
'ore',
|
||||
'ساعة',
|
||||
'小时',
|
||||
])
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||
dayJSDate.subtract(timeAgoInt, 'hours'); // go back N hours
|
||||
} else if (
|
||||
includesAny(date, [
|
||||
"hari",
|
||||
"gün",
|
||||
"jour",
|
||||
"día",
|
||||
"dia",
|
||||
"day",
|
||||
"วัน",
|
||||
"ngày",
|
||||
"giorni",
|
||||
"أيام",
|
||||
"天",
|
||||
'hari',
|
||||
'gün',
|
||||
'jour',
|
||||
'día',
|
||||
'dia',
|
||||
'day',
|
||||
'วัน',
|
||||
'ngày',
|
||||
'giorni',
|
||||
'أيام',
|
||||
'天',
|
||||
])
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||
} else if (includesAny(date, ["week", "semana"])) {
|
||||
dayJSDate.subtract(timeAgoInt, "week"); // go back N a week
|
||||
} else if (includesAny(date, ["month", "mes"])) {
|
||||
dayJSDate.subtract(timeAgoInt, "month"); // go back N months
|
||||
} else if (includesAny(date, ["year", "año"])) {
|
||||
dayJSDate.subtract(timeAgoInt, "year"); // go back N years
|
||||
dayJSDate.subtract(timeAgoInt, 'days'); // go back N days
|
||||
} else if (includesAny(date, ['week', 'semana'])) {
|
||||
dayJSDate.subtract(timeAgoInt, 'week'); // go back N a week
|
||||
} else if (includesAny(date, ['month', 'mes'])) {
|
||||
dayJSDate.subtract(timeAgoInt, 'month'); // go back N months
|
||||
} else if (includesAny(date, ['year', 'año'])) {
|
||||
dayJSDate.subtract(timeAgoInt, 'year'); // go back N years
|
||||
} else {
|
||||
return date;
|
||||
}
|
||||
|
||||
return dayJSDate.format("LL");
|
||||
return dayJSDate.format('LL');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { FilterTypes, Filters } from "../../../libs/filterInputs";
|
||||
import { ScrpitGeneratorFunction } from "../generate";
|
||||
import list from "./sources.json";
|
||||
import defaultSettings from "./settings.json";
|
||||
import { ReadwnMetadata } from "./template";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { FilterTypes, Filters } from '../../../libs/filterInputs';
|
||||
import { ScrpitGeneratorFunction } from '../generate';
|
||||
import list from './sources.json';
|
||||
import defaultSettings from './settings.json';
|
||||
import { ReadwnMetadata } from './template';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
return list
|
||||
.map<ReadwnMetadata>((p) => {
|
||||
.map<ReadwnMetadata>(p => {
|
||||
p.filters.tags.options.unshift(...defaultSettings.filters.tags.options);
|
||||
p.filters.genres.options.unshift(...defaultSettings.filters.genres.options);
|
||||
p.filters.genres.options.unshift(
|
||||
...defaultSettings.filters.genres.options,
|
||||
);
|
||||
p.filters = Object.assign(defaultSettings.filters, p.filters);
|
||||
|
||||
const d = false;
|
||||
@@ -33,8 +35,8 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
};
|
||||
|
||||
const generator = function generator(metadata: ReadwnMetadata) {
|
||||
const readwnTemplate = readFileSync(path.join(__dirname, "template.ts"), {
|
||||
encoding: "utf-8",
|
||||
const readwnTemplate = readFileSync(path.join(__dirname, 'template.ts'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
//metadata.filters = {
|
||||
@@ -45,14 +47,15 @@ const generator = function generator(metadata: ReadwnMetadata) {
|
||||
|
||||
const pluginScript = `
|
||||
${readwnTemplate}
|
||||
const plugin = new ReadwnPlugin(${
|
||||
JSON.stringify(metadata).replace(/"type":"([^"]+)"/g, '"type":FilterTypes.$1')
|
||||
});
|
||||
const plugin = new ReadwnPlugin(${JSON.stringify(metadata).replace(
|
||||
/"type":"([^"]+)"/g,
|
||||
'"type":FilterTypes.$1',
|
||||
)});
|
||||
export default plugin;
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
lang: "english",
|
||||
lang: 'english',
|
||||
filename: metadata.sourceName,
|
||||
pluginScript,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import qs from "qs";
|
||||
import { fetchFile, fetchApi } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
import qs from 'qs';
|
||||
|
||||
export interface ReadwnMetadata {
|
||||
id: string;
|
||||
@@ -26,7 +26,7 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/readwn/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
this.version = '1.0.0';
|
||||
this.filters = metadata.filters;
|
||||
}
|
||||
|
||||
@@ -34,115 +34,115 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
pageNo: number,
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
let url = this.site + "/list/";
|
||||
url += (filters?.genres?.value || "all") + "/";
|
||||
url += (filters?.status?.value || "all") + "-";
|
||||
url += showLatestNovels ? "lastdotime" : filters?.sort?.value || "newstime";
|
||||
url += "-" + (pageNo - 1) + ".html";
|
||||
let url = this.site + '/list/';
|
||||
url += (filters?.genres?.value || 'all') + '/';
|
||||
url += (filters?.status?.value || 'all') + '-';
|
||||
url += showLatestNovels ? 'lastdotime' : filters?.sort?.value || 'newstime';
|
||||
url += '-' + (pageNo - 1) + '.html';
|
||||
|
||||
if (filters?.tags?.value) {
|
||||
//only 1 page
|
||||
url = this.site + "/tags/" + filters.tags.value + "-0.html";
|
||||
url = this.site + '/tags/' + filters.tags.value + '-0.html';
|
||||
}
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio("li.novel-item")
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('li.novel-item')
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find("h4").text() || "",
|
||||
name: loadedCheerio(element).find('h4').text() || '',
|
||||
cover:
|
||||
this.site +
|
||||
loadedCheerio(element).find(".novel-cover > img").attr("data-src"),
|
||||
path: loadedCheerio(element).find("a").attr("href") || "",
|
||||
loadedCheerio(element).find('.novel-cover > img').attr('data-src'),
|
||||
path: loadedCheerio(element).find('a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
|
||||
return novels;
|
||||
}
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
const body = await fetchApi(this.site + novelPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const novel: Plugin.SourceNovel = {
|
||||
path: novelPath,
|
||||
name: loadedCheerio("h1.novel-title").text() || "",
|
||||
name: loadedCheerio('h1.novel-title').text() || '',
|
||||
};
|
||||
|
||||
novel.author = loadedCheerio("span[itemprop=author]").text();
|
||||
novel.author = loadedCheerio('span[itemprop=author]').text();
|
||||
novel.cover =
|
||||
this.site + loadedCheerio("figure.cover > img").attr("data-src");
|
||||
this.site + loadedCheerio('figure.cover > img').attr('data-src');
|
||||
|
||||
novel.summary = loadedCheerio(".summary")
|
||||
novel.summary = loadedCheerio('.summary')
|
||||
.text()
|
||||
.replace("Summary", "")
|
||||
.replace('Summary', '')
|
||||
.trim();
|
||||
|
||||
novel.genres = loadedCheerio("div.categories > ul > li")
|
||||
novel.genres = loadedCheerio('div.categories > ul > li')
|
||||
.map((index, element) => loadedCheerio(element).text()?.trim())
|
||||
.get()
|
||||
.join(",");
|
||||
.join(',');
|
||||
|
||||
loadedCheerio("div.header-stats > span").each(function () {
|
||||
if (loadedCheerio(this).find("small").text() === "Status") {
|
||||
loadedCheerio('div.header-stats > span').each(function () {
|
||||
if (loadedCheerio(this).find('small').text() === 'Status') {
|
||||
novel.status =
|
||||
loadedCheerio(this).find("strong").text() === "Ongoing"
|
||||
loadedCheerio(this).find('strong').text() === 'Ongoing'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
}
|
||||
});
|
||||
|
||||
const latestChapterNo = parseInt(
|
||||
loadedCheerio(".header-stats")
|
||||
.find("span > strong")
|
||||
loadedCheerio('.header-stats')
|
||||
.find('span > strong')
|
||||
.first()
|
||||
.text()
|
||||
.trim(),
|
||||
);
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio(".chapter-list li")
|
||||
const chapters: Plugin.ChapterItem[] = loadedCheerio('.chapter-list li')
|
||||
.map((chapterIndex, element) => {
|
||||
const name = loadedCheerio(element)
|
||||
.find("a .chapter-title")
|
||||
.find('a .chapter-title')
|
||||
.text()
|
||||
.trim();
|
||||
const path = loadedCheerio(element).find("a").attr("href")?.trim();
|
||||
const path = loadedCheerio(element).find('a').attr('href')?.trim();
|
||||
if (!name || !path) return null;
|
||||
|
||||
let releaseTime = loadedCheerio(element)
|
||||
.find("a .chapter-update")
|
||||
.find('a .chapter-update')
|
||||
.text()
|
||||
.trim();
|
||||
if (releaseTime?.includes?.("ago")) {
|
||||
const timeAgo = releaseTime.match(/\d+/)?.[0] || "0";
|
||||
if (releaseTime?.includes?.('ago')) {
|
||||
const timeAgo = releaseTime.match(/\d+/)?.[0] || '0';
|
||||
const timeAgoInt = parseInt(timeAgo, 10);
|
||||
|
||||
if (timeAgoInt) {
|
||||
const dayJSDate = dayjs(); // today
|
||||
if (
|
||||
releaseTime.includes("hours ago") ||
|
||||
releaseTime.includes("hour ago")
|
||||
releaseTime.includes('hours ago') ||
|
||||
releaseTime.includes('hour ago')
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "hours"); // go back N hours
|
||||
dayJSDate.subtract(timeAgoInt, 'hours'); // go back N hours
|
||||
}
|
||||
|
||||
if (
|
||||
releaseTime.includes("days ago") ||
|
||||
releaseTime.includes("day ago")
|
||||
releaseTime.includes('days ago') ||
|
||||
releaseTime.includes('day ago')
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "days"); // go back N days
|
||||
dayJSDate.subtract(timeAgoInt, 'days'); // go back N days
|
||||
}
|
||||
|
||||
if (
|
||||
releaseTime.includes("months ago") ||
|
||||
releaseTime.includes("month ago")
|
||||
releaseTime.includes('months ago') ||
|
||||
releaseTime.includes('month ago')
|
||||
) {
|
||||
dayJSDate.subtract(timeAgoInt, "months"); // go back N months
|
||||
dayJSDate.subtract(timeAgoInt, 'months'); // go back N months
|
||||
}
|
||||
|
||||
releaseTime = dayJSDate.format("LL");
|
||||
releaseTime = dayJSDate.format('LL');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,18 +154,22 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
};
|
||||
})
|
||||
.get()
|
||||
.filter((chapter) => chapter);
|
||||
.filter(chapter => chapter);
|
||||
|
||||
if (latestChapterNo > chapters.length) {
|
||||
const lastChapterNo = parseInt(
|
||||
chapters[chapters.length - 1].path.match(/_(\d+)\.html/)?.[1] || "",
|
||||
chapters[chapters.length - 1].path.match(/_(\d+)\.html/)?.[1] || '',
|
||||
10,
|
||||
);
|
||||
|
||||
for (let i = (lastChapterNo || chapters.length) + 1; i <= latestChapterNo; i++) {
|
||||
for (
|
||||
let i = (lastChapterNo || chapters.length) + 1;
|
||||
i <= latestChapterNo;
|
||||
i++
|
||||
) {
|
||||
chapters.push({
|
||||
name: "Chapter " + i,
|
||||
path: novelPath.replace(".html", "_" + i + ".html"),
|
||||
name: 'Chapter ' + i,
|
||||
path: novelPath.replace('.html', '_' + i + '.html'),
|
||||
releaseTime: null,
|
||||
chapterNumber: i,
|
||||
});
|
||||
@@ -177,38 +181,40 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
||||
}
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
const body = await fetchApi(this.site + chapterPath).then((res) => res.text());
|
||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||
res.text(),
|
||||
);
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
const chapterText = loadedCheerio(".chapter-content").html() || "";
|
||||
const chapterText = loadedCheerio('.chapter-content').html() || '';
|
||||
return chapterText;
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const result = await fetchApi(this.site + "/e/search/index.php", {
|
||||
const result = await fetchApi(this.site + '/e/search/index.php', {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Referer: this.site + "/search.html",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Referer: this.site + '/search.html',
|
||||
Origin: this.site,
|
||||
},
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: qs.stringify({
|
||||
show: "title",
|
||||
show: 'title',
|
||||
tempid: 1,
|
||||
tbname: "news",
|
||||
tbname: 'news',
|
||||
keyboard: searchTerm,
|
||||
}),
|
||||
}).then((res) => res.text());
|
||||
}).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(result);
|
||||
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio("li.novel-item")
|
||||
const novels: Plugin.NovelItem[] = loadedCheerio('li.novel-item')
|
||||
.map((index, element) => ({
|
||||
name: loadedCheerio(element).find("h4").text() || "",
|
||||
cover: this.site + loadedCheerio(element).find("img").attr("data-src"),
|
||||
path: loadedCheerio(element).find("a").attr("href") || "",
|
||||
name: loadedCheerio(element).find('h4').text() || '',
|
||||
cover: this.site + loadedCheerio(element).find('img').attr('data-src'),
|
||||
path: loadedCheerio(element).find('a').attr('href') || '',
|
||||
}))
|
||||
.get()
|
||||
.filter((novel) => novel.name && novel.path);
|
||||
.filter(novel => novel.name && novel.path);
|
||||
return novels;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Filters, FilterTypes } from "../../../libs/filterInputs";
|
||||
import { ScrpitGeneratorFunction } from "../generate";
|
||||
import list from "./sources.json";
|
||||
import defaultSettings from "./settings.json";
|
||||
import { RulateMetadata } from "./template";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { Filters, FilterTypes } from '../../../libs/filterInputs';
|
||||
import { ScrpitGeneratorFunction } from '../generate';
|
||||
import list from './sources.json';
|
||||
import defaultSettings from './settings.json';
|
||||
import { RulateMetadata } from './template';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
return list
|
||||
.map<RulateMetadata>((p) => {
|
||||
.map<RulateMetadata>(p => {
|
||||
if (p.filters) {
|
||||
p.filters.cat.options.unshift(...defaultSettings.filters.cat.options);
|
||||
}
|
||||
@@ -34,21 +34,21 @@ export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||
};
|
||||
|
||||
const generator = function generator(metadata: RulateMetadata) {
|
||||
const rulateTemplate = readFileSync(path.join(__dirname, "template.ts"), {
|
||||
encoding: "utf-8",
|
||||
const rulateTemplate = readFileSync(path.join(__dirname, 'template.ts'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
const pluginScript = `
|
||||
${rulateTemplate}
|
||||
const plugin = new RulatePlugin(${JSON.stringify(metadata)
|
||||
.replace(/"type":"([^"]+)"/g, '"type":FilterTypes.$1')
|
||||
.replace(/\.XCheckbox/g, ".ExcludableCheckboxGroup") //remember to redo
|
||||
.replace(/\.Checkbox/g, ".CheckboxGroup")});
|
||||
.replace(/\.XCheckbox/g, '.ExcludableCheckboxGroup') //remember to redo
|
||||
.replace(/\.Checkbox/g, '.CheckboxGroup')});
|
||||
export default plugin;
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
lang: "russian",
|
||||
lang: 'russian',
|
||||
filename: metadata.sourceName,
|
||||
pluginScript,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { fetchFile, fetchApi } from "@libs/fetch";
|
||||
import { Filters, FilterTypes } from "@libs/filterInputs";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { NovelStatus } from "@libs/novelStatus";
|
||||
import { load as parseHTML } from "cheerio";
|
||||
import dayjs from "dayjs";
|
||||
import { fetchFile, fetchApi } from '@libs/fetch';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { load as parseHTML } from 'cheerio';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface RulateMetadata {
|
||||
id: string;
|
||||
@@ -25,7 +25,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/rulate/icons/${metadata.id}.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
this.version = "1.0.0";
|
||||
this.version = '1.0.0';
|
||||
this.filters = metadata.filters;
|
||||
}
|
||||
|
||||
@@ -34,35 +34,37 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
{ filters, showLatestNovels }: Plugin.PopularNovelsOptions,
|
||||
): Promise<Plugin.NovelItem[]> {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
let url = this.site + "/search?t=";
|
||||
url += "&cat=" + (filters?.cat?.value || "0");
|
||||
url += "&s_lang=" + (filters?.s_lang?.value || "0");
|
||||
url += "&t_lang=" + (filters?.t_lang?.value || "0");
|
||||
url += "&type=" + (filters?.type?.value || "0");
|
||||
url += "&sort=" + (showLatestNovels ? "4" : filters?.sort?.value || "6");
|
||||
url += "&atmosphere=" + (filters?.atmosphere?.value || "0");
|
||||
url += "&adult=" + (filters?.adult?.value || "0");
|
||||
let url = this.site + '/search?t=';
|
||||
url += '&cat=' + (filters?.cat?.value || '0');
|
||||
url += '&s_lang=' + (filters?.s_lang?.value || '0');
|
||||
url += '&t_lang=' + (filters?.t_lang?.value || '0');
|
||||
url += '&type=' + (filters?.type?.value || '0');
|
||||
url += '&sort=' + (showLatestNovels ? '4' : filters?.sort?.value || '6');
|
||||
url += '&atmosphere=' + (filters?.atmosphere?.value || '0');
|
||||
url += '&adult=' + (filters?.adult?.value || '0');
|
||||
|
||||
Object.entries(filters || {}).forEach(([type, { value }]) => {
|
||||
if (value instanceof Array && value.length) {
|
||||
url += "&" + value
|
||||
.map((val) => (type == "extra" ? val + "=1" : type + "[]=" + val))
|
||||
.join("&");
|
||||
url +=
|
||||
'&' +
|
||||
value
|
||||
.map(val => (type == 'extra' ? val + '=1' : type + '[]=' + val))
|
||||
.join('&');
|
||||
}
|
||||
});
|
||||
|
||||
url += "&Book_page=" + pageNo;
|
||||
url += '&Book_page=' + pageNo;
|
||||
|
||||
const body = await fetchApi(url).then((res) => res.text());
|
||||
const body = await fetchApi(url).then(res => res.text());
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(
|
||||
'ul[class="search-results"] > li:not([class="ad_type_catalog"])',
|
||||
).each((index, element) => {
|
||||
loadedCheerio(element).find("p > a").text();
|
||||
const name = loadedCheerio(element).find("p > a").text();
|
||||
const cover = loadedCheerio(element).find("img").attr("src");
|
||||
const path = loadedCheerio(element).find("p > a").attr("href");
|
||||
loadedCheerio(element).find('p > a').text();
|
||||
const name = loadedCheerio(element).find('p > a').text();
|
||||
const cover = loadedCheerio(element).find('img').attr('src');
|
||||
const path = loadedCheerio(element).find('p > a').attr('href');
|
||||
if (!name || !path) return;
|
||||
|
||||
novels.push({ name, cover: this.site + cover, path });
|
||||
@@ -73,13 +75,13 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
|
||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||
let result = await fetchApi(this.site + novelPath);
|
||||
if (result.url.includes("mature?path=")) {
|
||||
if (result.url.includes('mature?path=')) {
|
||||
const formData = new FormData();
|
||||
formData.append("path", novelPath);
|
||||
formData.append("ok", "Да");
|
||||
formData.append('path', novelPath);
|
||||
formData.append('ok', 'Да');
|
||||
|
||||
await fetchApi(result.url, {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
@@ -96,41 +98,41 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
.text()
|
||||
.trim(),
|
||||
};
|
||||
if (novel.name?.includes?.("[")) {
|
||||
novel.name = novel.name.split("[")[0].trim();
|
||||
if (novel.name?.includes?.('[')) {
|
||||
novel.name = novel.name.split('[')[0].trim();
|
||||
}
|
||||
novel.cover =
|
||||
this.site + loadedCheerio('div[class="images"] > div img').attr("src");
|
||||
novel.summary = loadedCheerio("#Info > div:nth-child(3), .book-description")
|
||||
this.site + loadedCheerio('div[class="images"] > div img').attr('src');
|
||||
novel.summary = loadedCheerio('#Info > div:nth-child(3), .book-description')
|
||||
.text()
|
||||
.trim();
|
||||
novel.author = loadedCheerio(
|
||||
".book-stats-icons_author > span:nth-child(2) > a:nth-child(1)",
|
||||
'.book-stats-icons_author > span:nth-child(2) > a:nth-child(1)',
|
||||
).text();
|
||||
const genres: string[] = [];
|
||||
|
||||
loadedCheerio("div.span5 > p, .span5 > div:nth-child(2) > p").each(
|
||||
loadedCheerio('div.span5 > p, .span5 > div:nth-child(2) > p').each(
|
||||
function () {
|
||||
switch (loadedCheerio(this).find("strong").text()) {
|
||||
case "Автор:":
|
||||
novel.author = loadedCheerio(this).find("em > a").text().trim();
|
||||
switch (loadedCheerio(this).find('strong').text()) {
|
||||
case 'Автор:':
|
||||
novel.author = loadedCheerio(this).find('em > a').text().trim();
|
||||
break;
|
||||
case "Выпуск:":
|
||||
case 'Выпуск:':
|
||||
novel.status =
|
||||
loadedCheerio(this).find("em").text().trim() === "продолжается"
|
||||
loadedCheerio(this).find('em').text().trim() === 'продолжается'
|
||||
? NovelStatus.Ongoing
|
||||
: NovelStatus.Completed;
|
||||
break;
|
||||
case "Тэги:":
|
||||
case 'Тэги:':
|
||||
loadedCheerio(this)
|
||||
.find("em > a")
|
||||
.find('em > a')
|
||||
.each(function () {
|
||||
genres.push(loadedCheerio(this).text());
|
||||
});
|
||||
break;
|
||||
case "Жанры:":
|
||||
case 'Жанры:':
|
||||
loadedCheerio(this)
|
||||
.find("em > a")
|
||||
.find('em > a')
|
||||
.each(function () {
|
||||
genres.push(loadedCheerio(this).text());
|
||||
});
|
||||
@@ -140,23 +142,23 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
);
|
||||
|
||||
if (genres.length) {
|
||||
novel.genres = genres.reverse().join(",");
|
||||
novel.genres = genres.reverse().join(',');
|
||||
}
|
||||
|
||||
const chapters: Plugin.ChapterItem[] = [];
|
||||
loadedCheerio("table > tbody > tr.chapter_row").each(
|
||||
loadedCheerio('table > tbody > tr.chapter_row').each(
|
||||
(chapterIndex, element) => {
|
||||
const chapterName = loadedCheerio(element)
|
||||
.find('td[class="t"] > a')
|
||||
.text()
|
||||
.trim();
|
||||
const releaseDate = loadedCheerio(element)
|
||||
.find("td > span")
|
||||
.attr("title")
|
||||
.find('td > span')
|
||||
.attr('title')
|
||||
?.trim();
|
||||
const chapterUrl = loadedCheerio(element)
|
||||
.find('td[class="t"] > a')
|
||||
.attr("href");
|
||||
.attr('href');
|
||||
|
||||
if (
|
||||
!loadedCheerio(element).find('td > span[class="disabled"]').length &&
|
||||
@@ -179,12 +181,12 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
|
||||
async parseChapter(chapterPath: string): Promise<string> {
|
||||
let result = await fetchApi(this.site + chapterPath);
|
||||
if (result.url.includes("mature?path=")) {
|
||||
if (result.url.includes('mature?path=')) {
|
||||
const formData = new FormData();
|
||||
formData.append("ok", "Да");
|
||||
formData.append('ok', 'Да');
|
||||
|
||||
await fetchApi(result.url, {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
@@ -193,26 +195,26 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
const body = await result.text();
|
||||
const loadedCheerio = parseHTML(body);
|
||||
|
||||
loadedCheerio(".content-text img").each((index, element) => {
|
||||
if (!loadedCheerio(element).attr("src")?.startsWith("http")) {
|
||||
const src = loadedCheerio(element).attr("src");
|
||||
loadedCheerio(element).attr("src", this.site + src);
|
||||
loadedCheerio('.content-text img').each((index, element) => {
|
||||
if (!loadedCheerio(element).attr('src')?.startsWith('http')) {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
loadedCheerio(element).attr('src', this.site + src);
|
||||
}
|
||||
});
|
||||
|
||||
const chapterText = loadedCheerio(".content-text").html();
|
||||
return chapterText || "";
|
||||
const chapterText = loadedCheerio('.content-text').html();
|
||||
return chapterText || '';
|
||||
}
|
||||
|
||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||
const novels: Plugin.NovelItem[] = [];
|
||||
const result = await fetchApi(
|
||||
this.site + "/search/autocomplete?query=" + searchTerm,
|
||||
this.site + '/search/autocomplete?query=' + searchTerm,
|
||||
);
|
||||
const json = (await result.json()) as response[];
|
||||
|
||||
json.forEach((novel) => {
|
||||
const name = novel.title_one + " / " + novel.title_two;
|
||||
json.forEach(novel => {
|
||||
const name = novel.title_one + ' / ' + novel.title_two;
|
||||
if (!novel.url) return;
|
||||
|
||||
novels.push({
|
||||
@@ -225,24 +227,26 @@ class RulatePlugin implements Plugin.PluginBase {
|
||||
return novels;
|
||||
}
|
||||
|
||||
parseDate = (dateString: string | undefined = "") => {
|
||||
parseDate = (dateString: string | undefined = '') => {
|
||||
const months: { [key: string]: number } = {
|
||||
"янв.": 1,
|
||||
"февр.": 2,
|
||||
"мар.": 3,
|
||||
"апр.": 4,
|
||||
'янв.': 1,
|
||||
'февр.': 2,
|
||||
'мар.': 3,
|
||||
'апр.': 4,
|
||||
мая: 5,
|
||||
"июн.": 6,
|
||||
"июл.": 7,
|
||||
"авг.": 8,
|
||||
"сент.": 9,
|
||||
"окт.": 10,
|
||||
"нояб.": 11,
|
||||
"дек.": 12,
|
||||
'июн.': 6,
|
||||
'июл.': 7,
|
||||
'авг.': 8,
|
||||
'сент.': 9,
|
||||
'окт.': 10,
|
||||
'нояб.': 11,
|
||||
'дек.': 12,
|
||||
};
|
||||
const [day, month, year, , time] = dateString.split(" ");
|
||||
const [day, month, year, , time] = dateString.split(' ');
|
||||
if (day && months[month] && year && time) {
|
||||
return dayjs(year + "-" + months[month] + "-" + day + " " + time).format("LLL");
|
||||
return dayjs(year + '-' + months[month] + '-' + day + ' ' + time).format(
|
||||
'LLL',
|
||||
);
|
||||
}
|
||||
return dateString || null;
|
||||
};
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
const { minify_sync } = require("terser");
|
||||
import * as fs from "fs";
|
||||
const { minify_sync } = require('terser');
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const minify = function (path: string) {
|
||||
const code = fs.readFileSync(path).toString();
|
||||
|
||||
+26
-28
@@ -1,10 +1,10 @@
|
||||
import fs from "fs";
|
||||
import { languages } from "@libs/languages";
|
||||
import path from "path";
|
||||
import { PluginList } from "@typings/types";
|
||||
import { Plugin } from "@typings/plugin";
|
||||
import { Filters } from "@libs/filterInputs";
|
||||
const root = path.dirname(require?.main?.filename || "");
|
||||
import fs from 'fs';
|
||||
import { languages } from '@libs/languages';
|
||||
import path from 'path';
|
||||
import { PluginList } from '@typings/types';
|
||||
import { Plugin } from '@typings/plugin';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
const root = path.dirname(require?.main?.filename || '');
|
||||
|
||||
export const fetchHeaders: Record<string, string> = {};
|
||||
|
||||
@@ -12,20 +12,15 @@ export const all_plugins = (): PluginList => {
|
||||
const res: PluginList = {};
|
||||
for (let languageEnglish in languages) {
|
||||
//language with English name
|
||||
const languageNative =
|
||||
languages[languageEnglish as keyof typeof languages];
|
||||
const langPath = path.join(
|
||||
root,
|
||||
"plugins",
|
||||
languageEnglish.toLowerCase()
|
||||
);
|
||||
const languageNative = languages[languageEnglish as keyof typeof languages];
|
||||
const langPath = path.join(root, 'plugins', languageEnglish.toLowerCase());
|
||||
if (!fs.existsSync(langPath)) continue;
|
||||
const plugins = fs.readdirSync(langPath);
|
||||
res[languageEnglish] = [];
|
||||
plugins.forEach((plugin) => {
|
||||
if (plugin.startsWith(".")) return;
|
||||
plugins.forEach(plugin => {
|
||||
if (plugin.startsWith('.')) return;
|
||||
const requirePath = `@plugins/${languageEnglish.toLowerCase()}/${
|
||||
plugin.split(".")[0]
|
||||
plugin.split('.')[0]
|
||||
}`;
|
||||
const instance = require(requirePath).default;
|
||||
const { id, name, version, icon } = instance;
|
||||
@@ -44,7 +39,7 @@ export const all_plugins = (): PluginList => {
|
||||
};
|
||||
|
||||
const getPlugin = async (
|
||||
requirePath: string
|
||||
requirePath: string,
|
||||
): Promise<Plugin.PluginBase | Plugin.PagePlugin | null> => {
|
||||
const plugin = await require(requirePath).default;
|
||||
return plugin;
|
||||
@@ -56,33 +51,36 @@ export const getFilter = async (pluginRequirePath: string) =>
|
||||
export const popularNovels = async (
|
||||
pluginRequirePath: string,
|
||||
page: number,
|
||||
options: Plugin.PopularNovelsOptions<Filters>
|
||||
options: Plugin.PopularNovelsOptions<Filters>,
|
||||
) => (await getPlugin(pluginRequirePath))?.popularNovels(page, options);
|
||||
|
||||
export const searchNovels = async (
|
||||
pluginRequirePath: string,
|
||||
page: number,
|
||||
searchTerm: string
|
||||
searchTerm: string,
|
||||
) => (await getPlugin(pluginRequirePath))?.searchNovels(searchTerm, page);
|
||||
|
||||
export const parseNovel = async (
|
||||
pluginRequirePath: string,
|
||||
novelPath: string
|
||||
novelPath: string,
|
||||
) => (await getPlugin(pluginRequirePath))?.parseNovel(novelPath);
|
||||
|
||||
export const hasParsePage = async (
|
||||
pluginRequirePath: string,
|
||||
) => (await getPlugin(pluginRequirePath) as Plugin.PagePlugin | null)?.parsePage;
|
||||
export const hasParsePage = async (pluginRequirePath: string) =>
|
||||
((await getPlugin(pluginRequirePath)) as Plugin.PagePlugin | null)?.parsePage;
|
||||
|
||||
export const parsePage = async(
|
||||
export const parsePage = async (
|
||||
pluginRequirePath: string,
|
||||
novelPath: string,
|
||||
page: string
|
||||
) => (await getPlugin(pluginRequirePath) as Plugin.PagePlugin | null)?.parsePage(novelPath, page);
|
||||
page: string,
|
||||
) =>
|
||||
((await getPlugin(pluginRequirePath)) as Plugin.PagePlugin | null)?.parsePage(
|
||||
novelPath,
|
||||
page,
|
||||
);
|
||||
|
||||
export const parseChapter = async (
|
||||
pluginRequirePath: string,
|
||||
chapterPath: string
|
||||
chapterPath: string,
|
||||
) => (await getPlugin(pluginRequirePath))?.parseChapter(chapterPath);
|
||||
|
||||
export const fetchImage = async (pluginRequirePath: string, url: string) =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user