Files
lnreader-plugins/plugins/russian/novelOvh.ts
T
nyagami d818d1758f Beta phase (#801)
* beta host

* Refactor: url -> path (#757)

* refactor: url -> path

* clean up

* fix: json branch

* PagePlugin (#769)

* paginated novel support

* fix: truyenfull

* fix: tunovel, remove pageList

* parsePage support for webtest (#773)

* add parsePage api

* add parsePage UI

* add totalPages

* (FR src) Beta plugin & Added filters (#775)

* noveldeglace:
 - added filters
 - added latest
 - defaultCover fallback
 - change id to noveldeglace.com
 - added complementary infos in summary
 - added cover artist
 - chapters are now parts
 - show volume number for multi volume novels
 - better search
 - fix search page number
 - refactored for plugin beta
 - releaseDate as Date.toString()
 - separted parseNovels
 - separated getCheerio
 - loadedCheerio => $ for shorter lines & simpler test on web

* chireads:
 - fix different parsing for mobile version
 - added filters
 - added author
 - added status
 - added released date
 - refactored for plugin beta
 - separted parseNovels
 - separated getCheerio
 - loadedCheerio => $ for shorter lines & simpler test on web

* wpmangastream => lightnovelwp rework & new sources (#776)

* Added sources:
 - Knoxt (EN)
 - Baca Light Novel (ID)
 - AllNovelRead (ES)
 - DaoTranslate (EN)
 - جنة الروايات (AR)
 - Universal Novel (EN)
 - NovelTR (TR)
old sources auto-generated:
 - GenesisTls (EN)
 - NobleMTL (EN)
wpmangastream => lightnovelwp:
 - added doc
 - added filters in a separte file
 - semi-automatic filter generator
 - made it compatible with version 1.0.X and 1.1.X versions of the theme
 - loadedCheerio => $
 - icons in "/icons/lightnovel/id.png"
 - code cleanup

* removed auto-implemented sources

* Added README.md to lightnovelwp

* removed ai generated text for kolnovel

* added novelsknight

* added centralNovel

* pandamtl accual name

* adding folder plugin/arabic

* Fix: Vietnamese plugins (#777)

* fix: lightnovelvn

* add nettruyen

* add truyenchu

* fix: truyenconect

* Fix: Migrate plugins (#771)

* Add novel.ovh (#756)

* fix

* test

* fix

* date fix + master merge (#761)

* date fix

* Added Brazilian Portuguese E-Novel (#758)

* fixed single digit dates (#767)

Co-authored-by: NaddH1 <naderhorani@hotmail.com>

* fix

* test

* fix order

* Update renovels.ts

* require_chapters

* Delete plugins/english/genesistl.broken.ts

* Update novelTL.ts

---------

Co-authored-by: NaddH1 <naderhorani@hotmail.com>

* (src update) ENG (#772)

* allnovelfull

* bestlightnovel

* divinedaolibrary

* earlynovel

* lightnovelpub

* L

* M

* novelbin

* novelfull

* novelbuddy

* N S

* novel updates

* async sleep

* rename file

* pagePlugin

* Feat: minify js dir (#781)

* Multisrc: HotNovelPub (#784)

* HotNovelPub

* fix search

* Squashed commit of the following:

commit 3b759c68eddc06c0adfb959f775de069a85adca7
Author: nyagami <hoangquan05112002@gmail.com>
Date:   Sat Feb 17 20:33:47 2024 +0700

    Feat: minify js dir (#781)

* jsmin changed to terser

* test

* why I did it.

* test

* Update ranobelib.ts

* fix page

* Update ranobelib.ts

* updated modules

* Update terser.json

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Update scripts/multisrc/hotnovelpub/template.ts

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* terser api

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Added sources & fix sources (#787)

* lightnovelwp:
 - fix author name
 - added more localisation of strings
 - README typo
 - error on !r.ok new message

* noveldeglace:
 - fix chapterNumber for parts
 - better dates
 - better search

* Added sources:
 - White Moonlight Novels (EN)
 - System Translation (EN)

* Resized centralnovel.png

* Add Faqwiki.us source (#792)

* Add Faqwiki.us source (beta branch)

* Fix Path, Fix Chapter Parsing, Fix Novel Title

* search novel path fix

* fix chapter parsing. some chapter in this site store their whole text in 1-4 <p>,

* Add WTR-LAB Source (#797)

* Add WTR-LAB Source (Beta Branch)

* Add other language from WTR-LAB source

* change lang to sourceLang

* remove id es tr lang from WTR-LAB

* Update plugins/english/wtrlab.ts

agree

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Update plugins/english/wtrlab.ts

agree

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* revert: scripts to master

---------

Co-authored-by: Justin COLLON <48163201+error7404@users.noreply.github.com>
Co-authored-by: Rider21 <58046032+Rider21@users.noreply.github.com>
Co-authored-by: NaddH1 <naderhorani@hotmail.com>
Co-authored-by: K1ngfish3r <trulyihu@gmail.com>
Co-authored-by: bregassa <bregas.sat1@gmail.com>
2024-02-25 14:52:37 +07:00

416 lines
10 KiB
TypeScript

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";
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";
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) =>
novels.push({
name: novel.name.ru,
cover: novel.poster,
path: "/novel/" + novel.slug,
}),
);
return novels;
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const result = await fetchApi(
this.site + novelPath + "?_data=routes/reader/book/$slug/index",
);
const json = (await result.json()) as responseNovel;
const novel: Plugin.SourceNovel = {
path: novelPath,
name: json.book.name.ru,
cover: json.book.poster,
genres: json.book.labels?.map?.((label) => label.name).join(","),
summary: json.book.description,
status:
json.book.status == "ONGOING"
? NovelStatus.Ongoing
: NovelStatus.Completed,
};
json.book.relations?.forEach((person) => {
switch (person.type) {
case "AUTHOR":
novel.author = person.publisher.name;
case "ARTIST":
novel.artist = person.publisher.name;
}
});
const chapters: Plugin.ChapterItem[] = [];
json.chapters.forEach((chapter, chapterIndex) =>
chapters.push({
name:
chapter.title ||
"Том " + (chapter.volume || 0) + " " +
(chapter.name || "Глава " +
(chapter.number || json.chapters.length - chapterIndex)),
path: novelPath + "/" + chapter.id + "/0",
releaseTime: dayjs(chapter.createdAt).format("LLL"),
chapterNumber: json.chapters.length - chapterIndex,
}),
);
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(chapterPath: string): Promise<string> {
const result = await fetchApi(
"https://api.novel.ovh/v2/chapters/" + chapterPath.split("/")[3],
);
const book = (await result.json()) as responseChapter;
const image = Object.fromEntries(
book?.pages?.map(({ id, image }) => [id, image]) || [],
);
const chapterText = this.jsonToHtml(book.content.content || [], image);
return chapterText;
}
async searchNovels(
searchTerm: string,
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 body = (await result.json()) as { books: BooksEntity[] };
const novels: Plugin.NovelItem[] = [];
body.books.forEach((novel) =>
novels.push({
name: novel.name.ru,
cover: novel.poster,
path: "/novel/" + novel.slug,
}),
);
return novels;
}
jsonToHtml = (
json: ContentEntity[],
image: { [key: string]: string },
html: string = "",
) => {
json.forEach((element) => {
switch (element.type) {
case "image":
if (element.attrs?.pages?.[0]) {
html += `<img src="${image[element.attrs.pages[0]]}"/>`;
}
break;
case "hardBreak":
html += "<br>";
break;
case "horizontalRule":
case "delimiter":
html += '<h2 style="text-align: center">***</h2>';
break;
case "paragraph":
html +=
"<p" +
(element?.attrs?.textAlign
? ` style="text-align: ${element.attrs.textAlign}"`
: "") +
">" +
(element.content ? this.jsonToHtml(element.content, image) : "<br>") +
"</p>";
break;
case "text":
html += element.text;
break;
default:
html += JSON.stringify(element, null, "\t"); //maybe I missed something.
break;
}
});
return html;
};
fetchImage = fetchFile;
filters = {
sort: {
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" },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new novelOvh();
interface BooksEntity {
id: string;
slug: string;
type: string;
serviceName: string;
poster: string;
background?: string | null;
featuredCharacter?: null;
featuredCharacterBackground?: null;
featuredCharacterAnimation?: null;
featuredCharacterAnimationWithMask?: null;
featuredCharacterAnimationFirstFrame?: null;
status: string;
contentStatus: string;
name: Name;
altNames?: AltNamesEntity[] | null;
country: string;
year: number;
formats?: null[] | null;
featured: boolean;
viewsCount: number;
likesCount: number;
bookmarksCount: number;
ratingVotesCount: number;
averageRating: number;
chaptersCount: number;
createdAt: string;
updatedAt: string;
}
interface Name {
ru: string;
original: string;
en: string;
}
interface AltNamesEntity {
language: string;
name: string;
}
interface responseNovel {
book: Book;
branches: BranchesEntity[];
chapters: ChaptersEntity[];
related?: RelatedEntityOrBook[] | null;
associated?: AssociatedEntity[] | null;
chapterLikes?: null[] | null;
covers?: CoversEntity[] | null;
promotionVideos?: null[] | null;
folderItems?: null[] | null;
contentNotices?: null[] | null;
}
interface Book {
id: string;
slug: string;
type: string;
serviceName: string;
poster: string;
background?: null;
featuredCharacter?: null;
featuredCharacterBackground?: null;
featuredCharacterAnimation?: null;
featuredCharacterAnimationWithMask?: null;
featuredCharacterAnimationFirstFrame?: null;
contentBlockedCountries?: null[] | null;
status: string;
contentStatus: string;
moderationStatus: string;
name: Name;
altNames?: null[] | null;
externalLinks?: string[] | null;
description: string;
country: string;
year: number;
formats?: null[] | null;
featured: boolean;
viewsCount: number;
viewsDayCount: number;
viewsWeekCount: number;
viewsMonthCount: number;
likesCount: number;
bookmarksCount: number;
ratingVotesCount: number;
averageRating: number;
chaptersCount: number;
labels?: LabelsEntity[] | null;
relations?: RelationsEntity[] | null;
createdById: string;
createdAt: string;
updatedAt: string;
rating?: null;
}
interface LabelsEntity {
id: string;
slug: string;
name: string;
}
interface RelationsEntity {
type: string;
publisher: Publisher;
}
interface Publisher {
id: string;
kind: string;
slug: string;
name: string;
altNames?: (AltNamesEntity | null)[] | null;
poster: string;
background?: null;
trusted: boolean;
donationsEnabled: boolean;
vkGroupId?: null;
vkGroupName?: null;
tonAddress?: null;
createdAt: string;
updatedAt: string;
}
interface BranchesEntity {
id: string;
licensed: boolean;
editorsChoice: boolean;
chaptersCount: number;
createdAt: string;
updatedAt: string;
publishers?: PublishersEntity[] | null;
subscription?: null;
bookmark?: null;
}
interface PublishersEntity {
id: string;
kind: string;
slug: string;
name: string;
altNames?: AltNamesEntity[] | null;
poster: string;
background?: null;
trusted: boolean;
donationsEnabled: boolean;
vk?: null;
tonAddress?: null;
createdAt: string;
updatedAt: string;
}
interface ChaptersEntity {
id: string;
name?: string | null;
title?: string | null;
number: number;
volume: number;
likesCount: number;
commentsCount: number;
donut: boolean;
corrupted: boolean;
createdById: string;
branchId: string;
bookId: string;
createdAt: string;
expiredAt: string;
updatedAt: string;
}
interface RelatedEntityOrBook {
id: string;
type: string;
slug: string;
poster: string;
motion?: null;
status: string;
contentStatus: string;
name: Name;
country: string;
year: number;
formats?: null[] | null;
viewsCount: number;
likesCount: number;
bookmarksCount: number;
ratingVotesCount: number;
averageRating: number;
createdAt: string;
updatedAt: string;
}
interface AssociatedEntity {
id: string;
kind: string;
createdAt: string;
updatedAt: string;
book: RelatedEntityOrBook;
}
interface CoversEntity {
id: string;
volume: number;
image: string;
createdAt: string;
updatedAt: string;
}
interface responseChapter {
id: string;
name?: string;
title: string;
number: number;
volume: number;
likesCount: number;
commentsCount: number;
moderationStatus: string;
content: Content;
pages?: PagesEntity[];
donut: boolean;
corrupted: boolean;
createdById: string;
branchId: string;
bookId: string;
publishers?: PublishersEntity[] | null;
createdAt: string;
expiredAt: string;
updatedAt: string;
}
interface Content {
type: string;
content?: ContentEntity[] | null;
}
interface ContentEntity {
text?: string | null;
type: string;
attrs?: Attrs | null;
content?: Content[] | null;
}
interface Attrs {
textAlign?: string;
pages?: [string];
}
interface PagesEntity {
id: string;
image: string;
index: number;
commentsCount: number;
height: number;
width: number;
createdAt: string;
}