add VyNovel (#1036)

* add VyNovel

* Update vynovel.ts

* fix
This commit is contained in:
Rider21
2024-07-12 17:53:00 +03:00
committed by GitHub
parent 25905e7ca0
commit bd6f6a7ed9
7 changed files with 195 additions and 10 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

+16 -7
View File
@@ -27,13 +27,15 @@ const skip = new Set([
'wuxiaworld.site', 'wuxiaworld.site',
]); ]);
const folder = path.join('public', 'static');
const used = new Set([ const used = new Set([
path.join('public', 'icons', 'coverNotAvailable.webp'), path.join(folder, 'coverNotAvailable.webp'),
path.join('public', 'icons', 'siteNotAvailable.png'), path.join(folder, 'siteNotAvailable.png'),
]); ]);
const notAvailableImage = fs.readFileSync( const notAvailableImage = fs.readFileSync(
path.join('public', 'icons', 'siteNotAvailable.png'), path.join(folder, 'siteNotAvailable.png'),
); );
(async () => { (async () => {
@@ -48,8 +50,9 @@ const notAvailableImage = fs.readFileSync(
console.log('\nDownloading icons ⌛'); console.log('\nDownloading icons ⌛');
let language; let language;
for (let plugin in plugins) { for (let plugin in plugins) {
const { id, name, site, iconUrl, lang } = plugins[plugin]; const { id, name, site, iconUrl, lang, customJS, customCSS } =
const icon = iconUrl && 'public/icons/' + iconUrl.split('icons/')[1]; plugins[plugin];
const icon = iconUrl && path.join(folder, iconUrl.split('/static/')[1]);
if (language !== lang) { if (language !== lang) {
language = lang; language = lang;
@@ -61,7 +64,13 @@ const notAvailableImage = fs.readFileSync(
} }
try { try {
if (icon) used.add(path.join(icon)); if (customJS) {
used.add(path.join(folder, customJS.split('/static/')[1]));
}
if (customCSS) {
used.add(path.join(folder, customCSS.split('/static/')[1]));
}
if (icon) used.add(icon);
if (!skip.has(id) && icon && site) { if (!skip.has(id) && icon && site) {
const image = await fetch( const image = await fetch(
`https://www.google.com/s2/favicons?domain=${site}&sz=${size}&type=png`, `https://www.google.com/s2/favicons?domain=${site}&sz=${size}&type=png`,
@@ -121,7 +130,7 @@ const notAvailableImage = fs.readFileSync(
} }
console.log('\nDeleting unused icons ⌛'); console.log('\nDeleting unused icons ⌛');
fileList(path.join('public', 'icons')).forEach(path => { fileList(folder).forEach(path => {
if (!used.has(path)) { if (!used.has(path)) {
console.log('🗑️', path); console.log('🗑️', path);
fs.rmSync(path, { force: true }); fs.rmSync(path, { force: true });
+1 -1
View File
@@ -115,7 +115,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
); );
let chapterText = let chapterText =
body.match(/<div id="content-item" ([\s\S]*?)<\/div>/g)?.[0] || ''; body.match(/<div id="content-item" ([\s\S]*?)<\/div>/)?.[0] || '';
if (chapterText) { if (chapterText) {
const result = await fetchApi( const result = await fetchApi(
+1 -1
View File
@@ -140,7 +140,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
res.text(), res.text(),
); );
let chapterText = let chapterText =
body.match(/<article id="([\s\S]*?)<\/article>/g)?.[0] || ''; body.match(/<article id="([\s\S]*?)<\/article>/)?.[0] || '';
chapterText = chapterText.replace(/<script[^>]*>[\s\S]*?<\/script>/gim, ''); chapterText = chapterText.replace(/<script[^>]*>[\s\S]*?<\/script>/gim, '');
if (chapterText.includes('<img')) { if (chapterText.includes('<img')) {
+176
View File
@@ -0,0 +1,176 @@
import { Plugin } from '@typings/plugin';
import { FilterTypes, Filters } from '@libs/filterInputs';
import { defaultCover } from '@libs/defaultCover';
import { fetchApi } from '@libs/fetch';
import { NovelStatus } from '@libs/novelStatus';
import { load as parseHTML } from 'cheerio';
import dayjs from 'dayjs';
class VyNovel implements Plugin.PluginBase {
id = 'vynovel';
name = 'VyNovel';
site = 'https://vynovel.com';
version = '1.0.0';
icon = 'src/en/vynovel/icon.png';
async fetchNovels(
page: number,
{
filters,
showLatestNovels,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
searchTerm?: string,
): Promise<Plugin.NovelItem[]> {
const data = new URLSearchParams({
sort: showLatestNovels ? 'updated_at' : filters?.sort?.value || 'viewed',
page,
});
if (searchTerm) data.append('q', searchTerm);
const url = this.site + '/search?' + data.toString();
const body = await fetchApi(url).then(res => res.text());
const loadedCheerio = parseHTML(body);
const novels: Plugin.NovelItem[] = [];
loadedCheerio('div[class="comic-item"] > a').each((index, element) => {
const name = loadedCheerio(element)
.find('div[class="comic-title"]')
.text()
?.trim();
const cover =
loadedCheerio(element)
.find('div[class="comic-image lozad "]')
.attr('data-background-image') || defaultCover;
const url = loadedCheerio(element).attr('href');
if (!name || !url) return;
novels.push({ name, cover, path: url.replace('/novel/', '') });
});
return novels;
}
popularNovels = this.fetchNovels;
async searchNovels(
searchTerm: string,
page: number,
): Promise<Plugin.NovelItem[]> {
const defaultOptions: any = {
filters: undefined,
showLatestNovels: false,
};
return this.fetchNovels(page, defaultOptions, searchTerm);
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
res.text(),
);
const loadedCheerio = parseHTML(body);
const novel: Plugin.SourceNovel = {
path: novelPath,
name: loadedCheerio('h1[class="title"]').text().trim(),
cover:
loadedCheerio('div[class="img-manga"] > img').attr('src') ||
defaultCover,
summary: loadedCheerio('div[class="summary"] > p[class="content"]')
.text()
.trim(),
author: loadedCheerio('div[class="col-md-7"] > p:nth-child(5) > a')
.text()
.trim(),
status:
loadedCheerio('span[class="text-ongoing"]').text() === 'Ongoing'
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
const chapters: Plugin.ChapterItem[] = [];
const totalChapters = loadedCheerio('div[class="list-group"] > a').length;
loadedCheerio('div[class="list-group"] > a').each(
(chapterIndex, element) => {
const name = loadedCheerio(element).find('span').text().trim();
const id = loadedCheerio(element).attr('id')?.replace(/\D/g, '');
if (!name || !id) return;
const releaseDate = loadedCheerio(element).find('p').text();
chapters.push({
name,
path: novelPath + '/' + id,
releaseTime: this.parseDate(releaseDate?.trim()),
chapterNumber: totalChapters - chapterIndex,
});
},
);
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const body = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
res.text(),
);
const loadedCheerio = parseHTML(body);
const chapterText = loadedCheerio('.content').html();
return chapterText || '';
}
parseDate = (date = '') => {
if (!date) return null;
if (date.includes('ago')) {
const [value, type] = date.split(' ');
if (!value || !type) return null;
switch (type.toLowerCase()) {
case 'minutes': {
const minutes = parseInt(value, 10);
date = Date.now() - minutes * 60 * 1000;
break;
}
case 'hour':
case 'hours': {
const hours = parseInt(value, 10);
date = Date.now() - hours * 60 * 60 * 1000;
break;
}
case 'day':
case 'days': {
const days = parseInt(value, 10);
date = Date.now() - days * 24 * 60 * 60 * 1000;
break;
}
default:
console.log(date);
date = undefined;
}
return dayjs(date).format('LLL');
}
return date;
};
resolveUrl = (path: string, isNovel?: boolean) =>
this.site + (isNovel ? '/novel/' : '/read/') + path;
filters = {
sort: {
label: 'Sort By:',
value: 'viewed',
options: [
{ label: 'Viewed', value: 'viewed' },
{ label: 'Scored', value: 'scored' },
{ label: 'Newest', value: 'created_at' },
{ label: 'Latest Update', value: 'updated_at' },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new VyNovel();
+1 -1
View File
@@ -175,7 +175,7 @@ class AuthorToday implements Plugin.PluginBase {
} }
resolveUrl = (path: string, isNovel?: boolean) => resolveUrl = (path: string, isNovel?: boolean) =>
isNovel ? this.site + '/work/' + path : this.site + '/reader/' + path; this.site + (isNovel ? '/work/' : '/reader/') + path;
user: authorization | undefined; user: authorization | undefined;
getUser = async () => { getUser = async () => {