Plugins fixed (#903)
* test * fast fix * fix * fix * Update ranobelib.ts
This commit is contained in:
@@ -14,7 +14,8 @@ class NovelBin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
loadedCheerio('.col-novel-main .list-novel .row').each((i, el) => {
|
loadedCheerio('.col-novel-main .list-novel .row').each((i, el) => {
|
||||||
const novelName = loadedCheerio(el).find('h3.novel-title > a').text();
|
const novelName = loadedCheerio(el).find('h3.novel-title > a').text();
|
||||||
const novelCover = loadedCheerio(el).find('img.cover')
|
const novelCover = loadedCheerio(el)
|
||||||
|
.find('img.cover')
|
||||||
.attr('data-src')
|
.attr('data-src')
|
||||||
?.replace('_200_89', '');
|
?.replace('_200_89', '');
|
||||||
const novelUrl = loadedCheerio(el)
|
const novelUrl = loadedCheerio(el)
|
||||||
@@ -146,7 +147,7 @@ class NovelBin implements Plugin.PluginBase {
|
|||||||
return fetchFile(url, {
|
return fetchFile(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'referrer': this.site,
|
'referrer': this.site,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class IndoWebNovel implements Plugin.PluginBase {
|
|||||||
if (filters.genre.value.length)
|
if (filters.genre.value.length)
|
||||||
link += filters.genre.value.map(i => `&genre[]=${i}`).join('');
|
link += filters.genre.value.map(i => `&genre[]=${i}`).join('');
|
||||||
*/
|
*/
|
||||||
let link = this.site + `page/${page}/?s`
|
let link = this.site + `page/${page}/?s`;
|
||||||
const result = await fetchApi(link);
|
const result = await fetchApi(link);
|
||||||
const body = await result.text();
|
const body = await result.text();
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ class IndoWebNovel implements Plugin.PluginBase {
|
|||||||
link += `&order=${this.filters.sort.value}`;
|
link += `&order=${this.filters.sort.value}`;
|
||||||
link += this.filters.lang.value.map(i => `&country[]=${i}`).join('');
|
link += this.filters.lang.value.map(i => `&country[]=${i}`).join('');
|
||||||
*/
|
*/
|
||||||
let link = this.site + `page/${page}/?s=${searchTerm}`
|
let link = this.site + `page/${page}/?s=${searchTerm}`;
|
||||||
const result = await fetchApi(link);
|
const result = await fetchApi(link);
|
||||||
const body = await result.text();
|
const body = await result.text();
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import { Plugin } from '@typings/plugin';
|
|||||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||||
import { NovelStatus } from '@libs/novelStatus';
|
import { NovelStatus } from '@libs/novelStatus';
|
||||||
import { load as parseHTML } from 'cheerio';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const regex =
|
||||||
|
/<script id="__NEXT_DATA__" type="application\/json">(\{.*?\})<\/script>/;
|
||||||
|
|
||||||
class Bookriver implements Plugin.PluginBase {
|
class Bookriver implements Plugin.PluginBase {
|
||||||
id = 'bookriver';
|
id = 'bookriver';
|
||||||
name = 'Bookriver';
|
name = 'Bookriver';
|
||||||
site = 'https://bookriver.ru';
|
site = 'https://bookriver.ru';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/bookriver/icon.png';
|
icon = 'src/ru/bookriver/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
@@ -29,12 +31,12 @@ class Bookriver implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
const jsonRaw = result.match(regex);
|
||||||
if (jsonRaw) {
|
if (jsonRaw instanceof Array && jsonRaw[1]) {
|
||||||
const json: response = JSON.parse(jsonRaw);
|
const json: response = JSON.parse(jsonRaw[1]);
|
||||||
|
|
||||||
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach(novel =>
|
json.props.pageProps.state.pagesFilter?.genre?.books?.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.name,
|
name: novel.name,
|
||||||
@@ -43,6 +45,7 @@ class Bookriver implements Plugin.PluginBase {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,31 +53,37 @@ class Bookriver implements Plugin.PluginBase {
|
|||||||
const result = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
const result = await fetchApi(this.resolveUrl(novelPath, true)).then(res =>
|
||||||
res.text(),
|
res.text(),
|
||||||
);
|
);
|
||||||
const loadedCheerio = parseHTML(result);
|
|
||||||
|
|
||||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
|
||||||
const json: response = JSON.parse(jsonRaw || '{}');
|
|
||||||
const book = json.props.pageProps.state.book?.bookPage;
|
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: book?.name || '',
|
name: '',
|
||||||
cover: book?.coverImages[0].url,
|
|
||||||
summary: book?.annotation,
|
|
||||||
author: book?.author?.name,
|
|
||||||
genres: book?.tags?.map(tag => tag.name).join(', '),
|
|
||||||
status:
|
|
||||||
book?.statusComplete === 'writing'
|
|
||||||
? NovelStatus.Ongoing
|
|
||||||
: NovelStatus.Completed,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const jsonRaw = result.match(regex);
|
||||||
|
if (jsonRaw instanceof Array && jsonRaw[1]) {
|
||||||
|
const book: BooksEntity = JSON.parse(jsonRaw[1]).props.pageProps.state
|
||||||
|
.book.bookPage;
|
||||||
|
|
||||||
|
novel.name = book.name || '';
|
||||||
|
novel.cover = book.coverImages[0].url;
|
||||||
|
novel.summary = book.annotation;
|
||||||
|
novel.author = book.author?.name;
|
||||||
|
|
||||||
|
novel.status =
|
||||||
|
book?.statusComplete === 'writing'
|
||||||
|
? NovelStatus.Ongoing
|
||||||
|
: NovelStatus.Completed;
|
||||||
|
|
||||||
|
if (book.tags?.length)
|
||||||
|
novel.genres = book.tags?.map(tag => tag.name).join(',');
|
||||||
|
|
||||||
|
if (book?.ebook?.chapters?.length) {
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
book?.ebook?.chapters?.forEach((chapter, chapterIndex) => {
|
|
||||||
|
book.ebook.chapters.forEach((chapter, chapterIndex) => {
|
||||||
if (chapter.available) {
|
if (chapter.available) {
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: chapter.name,
|
name: chapter.name,
|
||||||
path: book?.slug + '/' + chapter.chapterId,
|
path: novelPath + '/' + chapter.chapterId,
|
||||||
releaseTime: dayjs(
|
releaseTime: dayjs(
|
||||||
chapter.firstPublishedAt || chapter.createdAt || undefined,
|
chapter.firstPublishedAt || chapter.createdAt || undefined,
|
||||||
).format('LLL'),
|
).format('LLL'),
|
||||||
@@ -82,18 +91,22 @@ class Bookriver implements Plugin.PluginBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
novel.chapters = chapters;
|
novel.chapters = chapters;
|
||||||
|
}
|
||||||
|
}
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const url = 'https://api.bookriver.ru/api/v1/books/chapter/text/';
|
const url = 'https://api.bookriver.ru/api/v1/books/chapter/text/';
|
||||||
const result = await fetchApi(url + chapterPath.split('/').pop());
|
const { data }: responseChapter = await fetchApi(
|
||||||
const json = (await result.json()) as responseChapter;
|
url + chapterPath.split('/').pop(),
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
let chapterText = json.data.content || 'Конец произведения';
|
let chapterText = data.content || 'Конец произведения';
|
||||||
if (json.data?.audio?.available) {
|
if (data?.audio?.available) {
|
||||||
chapterText += '\n' + json.data.audio.url;
|
chapterText += '\n' + data.audio.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chapterText;
|
return chapterText;
|
||||||
@@ -109,11 +122,12 @@ class Bookriver implements Plugin.PluginBase {
|
|||||||
'&page=' +
|
'&page=' +
|
||||||
pageNo +
|
pageNo +
|
||||||
'&perPage=10';
|
'&perPage=10';
|
||||||
const result = await fetchApi(url);
|
const { data }: responseSearch = await fetchApi(url).then(res =>
|
||||||
const json = (await result.json()) as responseSearch;
|
res.json(),
|
||||||
|
);
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
json.data?.books?.forEach(novel =>
|
data?.books?.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.name,
|
name: novel.name,
|
||||||
cover: novel.coverImages[0].url,
|
cover: novel.coverImages[0].url,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class ficbook implements Plugin.PluginBase {
|
|||||||
id = 'ficbook';
|
id = 'ficbook';
|
||||||
name = 'ficbook';
|
name = 'ficbook';
|
||||||
site = 'https://ficbook.net';
|
site = 'https://ficbook.net';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/ficbook/icon.png';
|
icon = 'src/ru/ficbook/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
@@ -149,14 +149,17 @@ class ficbook implements Plugin.PluginBase {
|
|||||||
formData.append('term', searchTerm);
|
formData.append('term', searchTerm);
|
||||||
formData.append('page', pageNo.toString());
|
formData.append('page', pageNo.toString());
|
||||||
|
|
||||||
const result = await fetchApi(this.site + '/search/fanfic', {
|
const { data }: { data: Data } = await fetchApi(
|
||||||
|
this.site + '/search/fanfic',
|
||||||
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
},
|
||||||
const json = (await result.json()) as { data: Data };
|
).then(res => res.json());
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
json.data?.data?.forEach(novel => {
|
data?.data?.forEach(novel => {
|
||||||
const name = novel.title.trim();
|
const name = novel.title.trim();
|
||||||
const path = '/readfic/' + novel.slug;
|
const path = '/readfic/' + novel.slug;
|
||||||
const cover = novel.cover
|
const cover = novel.cover
|
||||||
|
|||||||
+12
-10
@@ -16,7 +16,7 @@ class Neobook implements Plugin.PluginBase {
|
|||||||
id = 'neobook';
|
id = 'neobook';
|
||||||
name = 'Neobook';
|
name = 'Neobook';
|
||||||
site = 'https://neobook.org';
|
site = 'https://neobook.org';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/neobook/icon.png';
|
icon = 'src/ru/neobook/icon.png';
|
||||||
|
|
||||||
async fetchNovels(
|
async fetchNovels(
|
||||||
@@ -46,15 +46,17 @@ class Neobook implements Plugin.PluginBase {
|
|||||||
);
|
);
|
||||||
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/', {
|
const { bundle_books }: { bundle_books: BundleBooks } = await fetchApi(
|
||||||
|
'https://api.neobook.org/',
|
||||||
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
},
|
||||||
const json = (await result.json()) as { bundle_books: BundleBooks };
|
).then(res => res.json());
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
if (json.bundle_books.feed?.length) {
|
if (bundle_books.feed?.length) {
|
||||||
json.bundle_books.feed?.forEach(novel =>
|
bundle_books.feed?.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.title,
|
name: novel.title,
|
||||||
cover: novel?.attachment?.image?.m || defaultCover,
|
cover: novel?.attachment?.image?.m || defaultCover,
|
||||||
@@ -91,8 +93,8 @@ class Neobook implements Plugin.PluginBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const bookRaw = body.match(/var postData = ({.*?});/);
|
const bookRaw = body.match(/var postData = ({.*?});/);
|
||||||
if (bookRaw instanceof Array && bookRaw.length >= 2) {
|
if (bookRaw instanceof Array && bookRaw[1]) {
|
||||||
const book = JSON.parse(bookRaw[1]) as Novels;
|
const book: Novels = JSON.parse(bookRaw[1]);
|
||||||
|
|
||||||
novel.name = book.title;
|
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;
|
||||||
@@ -131,9 +133,9 @@ class Neobook implements Plugin.PluginBase {
|
|||||||
const bookRaw = body.match(/var data = ({.*?});/);
|
const bookRaw = body.match(/var data = ({.*?});/);
|
||||||
let chapterText = '';
|
let chapterText = '';
|
||||||
|
|
||||||
if (bookRaw instanceof Array && bookRaw.length >= 2) {
|
if (bookRaw instanceof Array && bookRaw[1]) {
|
||||||
const book = JSON.parse(bookRaw[1]) as Novels;
|
|
||||||
const token = chapterPath.split('=')[2];
|
const token = chapterPath.split('=')[2];
|
||||||
|
const book: Novels = JSON.parse(bookRaw[1]);
|
||||||
const chapter = book.chapters?.find?.(chapter => chapter.token == token);
|
const chapter = book.chapters?.find?.(chapter => chapter.token == token);
|
||||||
chapterText = (chapter?.data?.html || '').replace(/<br>/g, '');
|
chapterText = (chapter?.data?.html || '').replace(/<br>/g, '');
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-28
@@ -8,7 +8,7 @@ class novelOvh implements Plugin.PluginBase {
|
|||||||
id = 'novelovh';
|
id = 'novelovh';
|
||||||
name = 'НовелОВХ';
|
name = 'НовелОВХ';
|
||||||
site = 'https://novel.ovh';
|
site = 'https://novel.ovh';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/novelovh/icon.png';
|
icon = 'src/ru/novelovh/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
@@ -23,11 +23,12 @@ class novelOvh implements Plugin.PluginBase {
|
|||||||
: filters?.sort?.value || 'averageRating') +
|
: filters?.sort?.value || 'averageRating') +
|
||||||
',desc';
|
',desc';
|
||||||
|
|
||||||
const result = await fetchApi(url + '&_data=routes/reader/book/index');
|
const { books }: { books: BooksEntity[] } = await fetchApi(
|
||||||
const body = (await result.json()) as { books: BooksEntity[] };
|
url + '&_data=routes/reader/book/index',
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
body.books.forEach(novel =>
|
books.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.name.ru,
|
name: novel.name.ru,
|
||||||
cover: novel.poster,
|
cover: novel.poster,
|
||||||
@@ -39,25 +40,22 @@ class novelOvh implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const result = await fetchApi(
|
const { book, chapters }: responseNovel = await fetchApi(
|
||||||
this.resolveUrl(novelPath, true) +
|
this.resolveUrl(novelPath, true) +
|
||||||
'?_data=routes/reader/book/$slug/index',
|
'?_data=routes/reader/book/$slug/index',
|
||||||
);
|
).then(res => res.json());
|
||||||
const json = (await result.json()) as responseNovel;
|
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: json.book.name.ru,
|
name: book.name.ru,
|
||||||
cover: json.book.poster,
|
cover: book.poster,
|
||||||
genres: json.book.labels?.map?.(label => label.name).join(','),
|
genres: book.labels?.map?.(label => label.name).join(','),
|
||||||
summary: json.book.description,
|
summary: book.description,
|
||||||
status:
|
status:
|
||||||
json.book.status == 'ONGOING'
|
book.status == 'ONGOING' ? NovelStatus.Ongoing : NovelStatus.Completed,
|
||||||
? NovelStatus.Ongoing
|
|
||||||
: NovelStatus.Completed,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
json.book.relations?.forEach(person => {
|
book.relations?.forEach(person => {
|
||||||
switch (person.type) {
|
switch (person.type) {
|
||||||
case 'AUTHOR':
|
case 'AUTHOR':
|
||||||
novel.author = person.publisher.name;
|
novel.author = person.publisher.name;
|
||||||
@@ -66,33 +64,32 @@ class novelOvh implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chaptersRes: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
json.chapters.forEach((chapter, chapterIndex) =>
|
chapters.forEach((chapter, chapterIndex) =>
|
||||||
chapters.push({
|
chaptersRes.push({
|
||||||
name:
|
name:
|
||||||
chapter.title ||
|
chapter.title ||
|
||||||
'Том ' +
|
'Том ' +
|
||||||
(chapter.volume || 0) +
|
(chapter.volume || 0) +
|
||||||
' ' +
|
' ' +
|
||||||
(chapter.name ||
|
(chapter.name ||
|
||||||
'Глава ' +
|
'Глава ' + (chapter.number || chapters.length - chapterIndex)),
|
||||||
(chapter.number || json.chapters.length - chapterIndex)),
|
|
||||||
path: novelPath + '/' + chapter.id + '/0',
|
path: novelPath + '/' + chapter.id + '/0',
|
||||||
releaseTime: dayjs(chapter.createdAt).format('LLL'),
|
releaseTime: dayjs(chapter.createdAt).format('LLL'),
|
||||||
chapterNumber: json.chapters.length - chapterIndex,
|
chapterNumber: chapters.length - chapterIndex,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
novel.chapters = chapters.reverse();
|
novel.chapters = chaptersRes.reverse();
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const result = await fetchApi(
|
const book: responseChapter = await fetchApi(
|
||||||
'https://api.novel.ovh/v2/chapters/' + chapterPath.split('/')[1],
|
'https://api.novel.ovh/v2/chapters/' + chapterPath.split('/')[1],
|
||||||
);
|
).then(res => res.json());
|
||||||
const book = (await result.json()) as responseChapter;
|
|
||||||
const image = Object.fromEntries(
|
const image = Object.fromEntries(
|
||||||
book?.pages?.map(({ id, image }) => [id, image]) || [],
|
book?.pages?.map(({ id, image }) => [id, image]) || [],
|
||||||
);
|
);
|
||||||
@@ -106,11 +103,12 @@ class novelOvh implements Plugin.PluginBase {
|
|||||||
page: number | undefined = 1,
|
page: number | undefined = 1,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
const url = `${this.site}/novel?search=${searchTerm}&page=${page - 1}`;
|
const url = `${this.site}/novel?search=${searchTerm}&page=${page - 1}`;
|
||||||
const result = await fetchApi(url + '&_data=routes/reader/book/index');
|
const { books }: { books: BooksEntity[] } = await fetchApi(
|
||||||
const body = (await result.json()) as { books: BooksEntity[] };
|
url + '&_data=routes/reader/book/index',
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
body.books.forEach(novel =>
|
books.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.name.ru,
|
name: novel.name.ru,
|
||||||
cover: novel.poster,
|
cover: novel.poster,
|
||||||
|
|||||||
+57
-75
@@ -3,7 +3,6 @@ import { FilterTypes, Filters } from '@libs/filterInputs';
|
|||||||
import { defaultCover } from '@libs/defaultCover';
|
import { defaultCover } from '@libs/defaultCover';
|
||||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||||
import { NovelStatus } from '@libs/novelStatus';
|
import { NovelStatus } from '@libs/novelStatus';
|
||||||
import { load as parseHTML } from 'cheerio';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const statusKey: { [key: string]: string } = {
|
const statusKey: { [key: string]: string } = {
|
||||||
@@ -17,14 +16,17 @@ class TL implements Plugin.PluginBase {
|
|||||||
id = 'TL';
|
id = 'TL';
|
||||||
name = 'NovelTL';
|
name = 'NovelTL';
|
||||||
site = 'https://novel.tl';
|
site = 'https://novel.tl';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/noveltl/icon.png';
|
icon = 'src/ru/noveltl/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async fetchNovels(
|
||||||
page: number,
|
page: number,
|
||||||
{ filters }: Plugin.PopularNovelsOptions,
|
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
|
searchTerm?: string,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
const result = await fetchApi(this.site + '/api/site/v2/graphql', {
|
const { data }: response = await fetchApi(
|
||||||
|
this.site + '/api/site/v2/graphql',
|
||||||
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*',
|
Accept: 'application/json, text/plain, */*',
|
||||||
@@ -37,20 +39,24 @@ class TL implements Plugin.PluginBase {
|
|||||||
'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}',
|
'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}',
|
||||||
variables: {
|
variables: {
|
||||||
filter: {
|
filter: {
|
||||||
tags: filters?.tags?.value || [],
|
query: searchTerm || undefined,
|
||||||
genres: filters?.genres?.value || [],
|
tags: filters.tags?.value?.length
|
||||||
|
? filters.tags.value
|
||||||
|
: undefined,
|
||||||
|
genres: filters.genres?.value?.length
|
||||||
|
? filters.genres.value
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
hostname: 'novel.tl',
|
hostname: 'novel.tl',
|
||||||
limit: 40,
|
limit: 40,
|
||||||
page,
|
page,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
},
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
const json = (await result.json()) as response;
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
data?.projects?.content?.forEach(novel =>
|
||||||
json.data?.projects?.content?.forEach(novel =>
|
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.title,
|
name: novel.title,
|
||||||
path: novel.fullUrl,
|
path: novel.fullUrl,
|
||||||
@@ -63,8 +69,22 @@ class TL implements Plugin.PluginBase {
|
|||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
popularNovels = this.fetchNovels;
|
||||||
|
|
||||||
|
async searchNovels(
|
||||||
|
searchTerm: string,
|
||||||
|
page: number,
|
||||||
|
): Promise<Plugin.NovelItem[]> {
|
||||||
|
const defaultOptions: any = {
|
||||||
|
filters: {},
|
||||||
|
};
|
||||||
|
return this.fetchNovels(page, defaultOptions, searchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const result = await fetchApi(this.site + '/api/site/v2/graphql', {
|
const { data }: response = await fetchApi(
|
||||||
|
this.site + '/api/site/v2/graphql',
|
||||||
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*',
|
Accept: 'application/json, text/plain, */*',
|
||||||
@@ -79,21 +99,22 @@ class TL implements Plugin.PluginBase {
|
|||||||
url: novelPath,
|
url: novelPath,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
},
|
||||||
const json = (await result.json()) as response;
|
).then(res => res.json());
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: json.data.project?.title || '',
|
name: data.project?.title || '',
|
||||||
cover: json.data.project?.covers?.[0]?.url
|
cover: data.project?.covers?.[0]?.url
|
||||||
? this.site + json.data.project.covers[0].url
|
? this.site + data.project.covers[0].url
|
||||||
: defaultCover,
|
: defaultCover,
|
||||||
summary: json.data.project?.annotation?.text,
|
summary: data.project?.annotation?.text,
|
||||||
status:
|
status:
|
||||||
statusKey[json.data.project?.translationStatus || 'unknown'] ||
|
statusKey[data.project?.translationStatus || 'unknown'] ||
|
||||||
NovelStatus.Unknown,
|
NovelStatus.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
const genres = [json.data.project?.tags, json.data.project?.genres]
|
const genres = [data.project?.tags, data.project?.genres]
|
||||||
.flat()
|
.flat()
|
||||||
.map(item => item?.nameRu || item?.nameEng)
|
.map(item => item?.nameRu || item?.nameEng)
|
||||||
.filter(item => item);
|
.filter(item => item);
|
||||||
@@ -102,7 +123,7 @@ class TL implements Plugin.PluginBase {
|
|||||||
novel.genres = genres.join(', ');
|
novel.genres = genres.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
json.data.project?.persons?.forEach(person => {
|
data.project?.persons?.forEach(person => {
|
||||||
if (person.role == 'author' && person.name.firstName) {
|
if (person.role == 'author' && person.name.firstName) {
|
||||||
novel.author =
|
novel.author =
|
||||||
person.name.firstName + ' ' + (person.name?.lastName || '');
|
person.name.firstName + ' ' + (person.name?.lastName || '');
|
||||||
@@ -115,7 +136,7 @@ class TL implements Plugin.PluginBase {
|
|||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
json.data.project?.subprojects?.content?.forEach(work =>
|
data.project?.subprojects?.content?.forEach(work =>
|
||||||
work.volumes.content.forEach((volume, volumeIndex) =>
|
work.volumes.content.forEach((volume, volumeIndex) =>
|
||||||
volume.chapters.forEach((chapter, chapterIndex) => {
|
volume.chapters.forEach((chapter, chapterIndex) => {
|
||||||
if (chapter.published) {
|
if (chapter.published) {
|
||||||
@@ -139,7 +160,7 @@ class TL implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const result = await fetchApi(this.site + '/api/site/v2/graphql', {
|
const { data } = await fetchApi(this.site + '/api/site/v2/graphql', {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*',
|
Accept: 'application/json, text/plain, */*',
|
||||||
@@ -154,63 +175,24 @@ class TL implements Plugin.PluginBase {
|
|||||||
url: decodeURI(chapterPath),
|
url: decodeURI(chapterPath),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
}).then(res => res.json());
|
||||||
const json = (await result.json()) as response;
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(json.data.chapter?.text?.text || '');
|
const chapterText = data.chapter?.text?.text || '';
|
||||||
loadedCheerio('p > a[href]').each((index, element) => {
|
if (chapterText.includes('<img')) {
|
||||||
let src = loadedCheerio(element).attr('href') || '';
|
return chapterText.replace(
|
||||||
if (!src.startsWith('http')) {
|
/src="(.*?)"|href="(.*?)"/g,
|
||||||
src = this.site + src;
|
(match: string, src: string, href: string) => {
|
||||||
|
if (src && !src.startsWith('http')) {
|
||||||
|
return `src="${this.site + src}"`;
|
||||||
|
} else if (href && !href.startsWith('http')) {
|
||||||
|
return `href="${this.site + href}"`;
|
||||||
}
|
}
|
||||||
loadedCheerio(element).find('picture').remove();
|
return match;
|
||||||
loadedCheerio(element).removeAttr('href');
|
|
||||||
loadedCheerio(`<img src="${src}">`).appendTo(element);
|
|
||||||
});
|
|
||||||
|
|
||||||
const chapterText = loadedCheerio.html();
|
|
||||||
return chapterText;
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchNovels(
|
|
||||||
searchTerm: string,
|
|
||||||
page: number | undefined = 1,
|
|
||||||
): Promise<Plugin.NovelItem[]> {
|
|
||||||
const result = await fetchApi(this.site + '/api/site/v2/graphql', {
|
|
||||||
method: 'post',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json, text/plain, */*',
|
|
||||||
'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
},
|
||||||
Referer: this.site,
|
|
||||||
body: JSON.stringify({
|
|
||||||
query:
|
|
||||||
'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title fullUrl covers{url}}}}',
|
|
||||||
variables: {
|
|
||||||
filter: {
|
|
||||||
query: searchTerm,
|
|
||||||
},
|
|
||||||
hostname: 'novel.tl',
|
|
||||||
limit: 40,
|
|
||||||
page,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const json = (await result.json()) as response;
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
|
||||||
|
|
||||||
json?.data?.projects?.content?.forEach(novel =>
|
|
||||||
novels.push({
|
|
||||||
name: novel.title,
|
|
||||||
path: novel.fullUrl,
|
|
||||||
cover: novel?.covers?.[0]?.url
|
|
||||||
? this.site + novel.covers[0].url
|
|
||||||
: defaultCover,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return novels;
|
return chapterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchImage = fetchFile;
|
fetchImage = fetchFile;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Plugin } from '@typings/plugin';
|
|||||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||||
import { NovelStatus } from '@libs/novelStatus';
|
import { NovelStatus } from '@libs/novelStatus';
|
||||||
import { load as parseHTML } from 'cheerio';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const statusKey: { [key: number]: string } = {
|
const statusKey: { [key: number]: string } = {
|
||||||
@@ -14,7 +13,7 @@ const statusKey: { [key: number]: string } = {
|
|||||||
class RNBH implements Plugin.PluginBase {
|
class RNBH implements Plugin.PluginBase {
|
||||||
id = 'RNBH.org';
|
id = 'RNBH.org';
|
||||||
name = 'RanobeHub';
|
name = 'RanobeHub';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
site = 'https://ranobehub.org';
|
site = 'https://ranobehub.org';
|
||||||
icon = 'src/ru/ranobehub/icon.png';
|
icon = 'src/ru/ranobehub/icon.png';
|
||||||
|
|
||||||
@@ -58,12 +57,12 @@ class RNBH implements Plugin.PluginBase {
|
|||||||
url += '&tags:negative=' + excludeTags.join(',');
|
url += '&tags:negative=' + excludeTags.join(',');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
url += '&take=40';
|
const { resource }: { resource: responseNovels[] } = await fetchApi(
|
||||||
const result = await fetchApi(url);
|
url + '&take=40',
|
||||||
const body = (await result.json()) as { resource: responseNovels[] };
|
).then(res => res.json());
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
body.resource.forEach(novel =>
|
resource.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.names.rus || novel.names.eng || novel.names.original,
|
name: novel.names.rus || novel.names.eng || novel.names.original,
|
||||||
cover: novel.poster.medium,
|
cover: novel.poster.medium,
|
||||||
@@ -75,19 +74,20 @@ class RNBH implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const result = await fetchApi(this.site + '/api/ranobe/' + novelPath);
|
const { data }: { data: responseNovel } = await fetchApi(
|
||||||
const json = (await result.json()) as { data: responseNovel };
|
this.site + '/api/ranobe/' + novelPath,
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: json.data.names.rus || json.data.names.eng || '',
|
name: data.names.rus || data.names.eng || '',
|
||||||
cover: json.data.posters.medium,
|
cover: data.posters.medium,
|
||||||
summary: json.data.description.trim(),
|
summary: data.description.trim(),
|
||||||
author: json.data?.authors?.[0]?.name_eng || '',
|
author: data?.authors?.[0]?.name_eng || '',
|
||||||
status: statusKey[json.data.status.id] || NovelStatus.Unknown,
|
status: statusKey[data.status.id] || NovelStatus.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tags = [json.data.tags.events, json.data.tags.genres]
|
const tags = [data.tags.events, data.tags.genres]
|
||||||
.flat()
|
.flat()
|
||||||
.map(tags => tags?.names?.rus || tags?.names?.eng || tags?.title)
|
.map(tags => tags?.names?.rus || tags?.names?.eng || tags?.title)
|
||||||
.filter(tags => tags);
|
.filter(tags => tags);
|
||||||
@@ -97,21 +97,15 @@ class RNBH implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
const chaptersRaw = await fetchApi(
|
const chaptersJSON: { volumes: VolumesEntity[] } = await fetchApi(
|
||||||
this.site + '/api/ranobe/' + novelPath + '/contents',
|
this.site + '/api/ranobe/' + novelPath + '/contents',
|
||||||
);
|
).then(res => res.json());
|
||||||
const chaptersJSON = (await chaptersRaw.json()) as {
|
|
||||||
volumes: VolumesEntity[];
|
|
||||||
};
|
|
||||||
|
|
||||||
chaptersJSON.volumes.forEach(volume =>
|
chaptersJSON.volumes.forEach(volume =>
|
||||||
volume.chapters?.forEach(chapter =>
|
volume.chapters?.forEach(chapter =>
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: chapter.name,
|
name: chapter.name,
|
||||||
path:
|
path: novelPath + '/' + volume.num + '/' + chapter.num,
|
||||||
volume.num && chapter.num
|
|
||||||
? novelPath + '/' + volume.num + '/' + chapter.num
|
|
||||||
: chapter.url.replace(this.site, ''),
|
|
||||||
releaseTime: dayjs(parseInt(chapter.changed_at, 10) * 1000).format(
|
releaseTime: dayjs(parseInt(chapter.changed_at, 10) * 1000).format(
|
||||||
'LLL',
|
'LLL',
|
||||||
),
|
),
|
||||||
@@ -125,38 +119,34 @@ class RNBH implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const result = await fetchApi(this.resolveUrl(chapterPath));
|
const body = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||||
const body = await result.text();
|
res.text(),
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
loadedCheerio('.chapter-hoticons').remove();
|
|
||||||
loadedCheerio('div.text:nth-child(1) img').each((index, element) => {
|
|
||||||
if (!loadedCheerio(element).attr('src')?.startsWith('http')) {
|
|
||||||
const dataMediaId = loadedCheerio(element).attr('data-media-id');
|
|
||||||
loadedCheerio(element).attr(
|
|
||||||
'src',
|
|
||||||
this.site + '/api/media/' + dataMediaId,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let chapterText = loadedCheerio('div.text:nth-child(1)').html() || '';
|
const indexA = body.indexOf('<div class="title-wrapper">');
|
||||||
|
const indexB = body.indexOf(
|
||||||
// Remove script tags
|
'<div class="ui text container" style>',
|
||||||
chapterText = chapterText?.replace(
|
indexA,
|
||||||
/<\s*script[^>]*>[\s\S]*?<\/script>/gim,
|
|
||||||
'',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const chapterText = body
|
||||||
|
.substring(indexA, indexB)
|
||||||
|
.replace(
|
||||||
|
/<img data-media-id="(.*?)".*?>/g,
|
||||||
|
`<img src="${this.site}/api/media/$1"/>`,
|
||||||
|
);
|
||||||
|
|
||||||
return chapterText;
|
return chapterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const url = `${this.site}/api/fulltext/global?query=${searchTerm}&take=10`;
|
const url = `${this.site}/api/fulltext/global?query=${searchTerm}&take=10`;
|
||||||
const result = await fetchApi(url);
|
const result: responseSearch[] = await fetchApi(url).then(res =>
|
||||||
const data = (await result.json()) as responseSearch[];
|
res.json(),
|
||||||
|
);
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
data
|
result
|
||||||
?.find(item => item?.meta?.key === 'ranobe')
|
?.find(item => item?.meta?.key === 'ranobe')
|
||||||
?.data?.forEach(novel =>
|
?.data?.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
|
|||||||
+120
-211
@@ -3,20 +3,12 @@ import { FilterTypes, Filters } from '@libs/filterInputs';
|
|||||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||||
import { NovelStatus } from '@libs/novelStatus';
|
import { NovelStatus } from '@libs/novelStatus';
|
||||||
import { load as parseHTML } from 'cheerio';
|
import { load as parseHTML } from 'cheerio';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
const statusKey: { [key: number]: string } = {
|
|
||||||
1: NovelStatus.Ongoing,
|
|
||||||
2: NovelStatus.Completed,
|
|
||||||
3: NovelStatus.OnHiatus,
|
|
||||||
4: NovelStatus.Cancelled,
|
|
||||||
};
|
|
||||||
|
|
||||||
class RLIB implements Plugin.PluginBase {
|
class RLIB implements Plugin.PluginBase {
|
||||||
id = 'RLIB';
|
id = 'RLIB';
|
||||||
name = 'RanobeLib';
|
name = 'RanobeLib (OLD)';
|
||||||
site = 'https://ranobelib.me';
|
site = 'https://old.ranobelib.me/old/';
|
||||||
version = '1.0.0';
|
version = '1.1.0';
|
||||||
icon = 'src/ru/ranobelib/icon.png';
|
icon = 'src/ru/ranobelib/icon.png';
|
||||||
ui: string | undefined = undefined;
|
ui: string | undefined = undefined;
|
||||||
|
|
||||||
@@ -27,51 +19,71 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
filters,
|
filters,
|
||||||
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
}: Plugin.PopularNovelsOptions<typeof this.filters>,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
let url = this.site + '/manga-list?sort=';
|
let url = this.site + 'manga-list?page=' + pageNo;
|
||||||
url += showLatestNovels
|
url +=
|
||||||
|
'&sort_by=' +
|
||||||
|
(showLatestNovels
|
||||||
? 'last_chapter_at'
|
? 'last_chapter_at'
|
||||||
: filters?.sort?.value || 'rate';
|
: filters?.sort_by?.value || 'rating_score');
|
||||||
url += '&dir=' + (filters?.order?.value || 'desc');
|
url += '&sort_type=' + (filters?.sort_type?.value || 'desc');
|
||||||
url += '&chapters[min]=' + (filters?.require_chapters?.value ? '1' : '0');
|
|
||||||
|
|
||||||
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
if (filters?.require_chapters?.value) {
|
||||||
if (value instanceof Array && value.length) {
|
url += '&chapters[min]=1';
|
||||||
url += '&' + type + '[]=' + value.join('&' + type + '[]=');
|
|
||||||
}
|
}
|
||||||
if (value?.include instanceof Array && value.include.length) {
|
if (filters?.types?.value?.length) {
|
||||||
|
url += '&types[]=' + filters.types.value.join('&types[]=');
|
||||||
|
}
|
||||||
|
if (filters?.scanlateStatus?.value?.length) {
|
||||||
url +=
|
url +=
|
||||||
'&' +
|
'&scanlateStatus[]=' +
|
||||||
type +
|
filters.scanlateStatus.value.join('&scanlateStatus[]=');
|
||||||
'[include][]=' +
|
|
||||||
value.include.join('&' + type + '[include][]=');
|
|
||||||
}
|
}
|
||||||
if (value?.exclude instanceof Array && value.exclude.length) {
|
if (filters?.manga_status?.value?.length) {
|
||||||
url +=
|
url +=
|
||||||
'&' +
|
'&manga_status[]=' +
|
||||||
type +
|
filters.manga_status.value.join('&manga_status[]=');
|
||||||
'[exclude][]=' +
|
|
||||||
value.exclude.join('&' + type + '[exclude][]=');
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
url += '&page=' + pageNo;
|
if (filters?.genres) {
|
||||||
|
if (filters.genres.value?.include?.length) {
|
||||||
|
url += '&genres[]=' + filters.genres.value.include.join('&genres[]=');
|
||||||
|
}
|
||||||
|
if (filters.genres.value?.exclude?.length) {
|
||||||
|
url +=
|
||||||
|
'&genres_exclude[]=' +
|
||||||
|
filters.genres.value.exclude.join('&genres_exclude[]=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filters?.tags) {
|
||||||
|
if (filters.tags.value?.include?.length) {
|
||||||
|
url += '&tags[]=' + filters.tags.value.include.join('&tags[]=');
|
||||||
|
}
|
||||||
|
if (filters.tags.value?.exclude?.length) {
|
||||||
|
url +=
|
||||||
|
'&tags_exclude[]=' +
|
||||||
|
filters.tags.value.exclude.join('&tags_exclude[]=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = await fetchApi(url).then(res => res.text());
|
const result = await fetchApi(url).then(res => res.text());
|
||||||
const loadedCheerio = parseHTML(result);
|
const loadedCheerio = parseHTML(result);
|
||||||
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
this.ui = loadedCheerio('a.header-right-menu__item')
|
this.ui = loadedCheerio('a.header-right-menu__item')
|
||||||
.attr('href')
|
.attr('href')
|
||||||
?.replace?.(/[^0-9]/g, '');
|
?.replace?.(/[^0-9]/g, '');
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novelsRaw = result.match(/window\.__CATALOG_ITEMS__ = (\[.*?\]);/);
|
||||||
loadedCheerio('.media-card-wrap').each((index, element) => {
|
if (novelsRaw instanceof Array && novelsRaw.length >= 2) {
|
||||||
const name = loadedCheerio(element).find('.media-card__title').text();
|
const novelsJson: resNovels[] = JSON.parse(novelsRaw[1]);
|
||||||
const cover = loadedCheerio(element)
|
novelsJson.forEach(novel => {
|
||||||
.find('a.media-card')
|
novels.push({
|
||||||
.attr('data-src');
|
name: novel.rus_name,
|
||||||
const url = loadedCheerio(element).find('a.media-card').attr('href');
|
cover: novel.cover.default,
|
||||||
if (!url) return;
|
path: novel.slug_url,
|
||||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
@@ -97,7 +109,7 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
.replace(/ /g, '');
|
.replace(/ /g, '');
|
||||||
|
|
||||||
loadedCheerio(
|
loadedCheerio(
|
||||||
'div[class="media-info-list paper"] > [class="media-info-list__item"]',
|
'div.media-info-list > div[class="media-info-list__item"]',
|
||||||
).each(function () {
|
).each(function () {
|
||||||
let name = loadedCheerio(this)
|
let name = loadedCheerio(this)
|
||||||
.find('div[class="media-info-list__title"]')
|
.find('div[class="media-info-list__title"]')
|
||||||
@@ -120,77 +132,26 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
.attr('href')
|
.attr('href')
|
||||||
?.replace?.(/[^0-9]/g, '');
|
?.replace?.(/[^0-9]/g, '');
|
||||||
|
|
||||||
const chaptersRaw = body.match(/window\.__DATA__ = ({.*?});/);
|
const chaptersRaw = body.match(/window\.__CHAPTERS__ = (\[.*?\]);/);
|
||||||
if (chaptersRaw instanceof Array && chaptersRaw.length >= 2) {
|
if (chaptersRaw instanceof Array && chaptersRaw.length >= 2) {
|
||||||
const chaptersJson: responseBook = JSON.parse(chaptersRaw[1]);
|
const chaptersJson: resChapters[] = JSON.parse(chaptersRaw[1]);
|
||||||
|
|
||||||
if (!novel.name) {
|
if (!chaptersJson?.length) return novel;
|
||||||
novel.name =
|
|
||||||
chaptersJson.manga.rusName ||
|
|
||||||
chaptersJson.manga.engName ||
|
|
||||||
chaptersJson.manga.name;
|
|
||||||
}
|
|
||||||
novel.status =
|
|
||||||
statusKey[chaptersJson.manga.status] || NovelStatus.Unknown;
|
|
||||||
this.ui = chaptersJson?.user?.id;
|
|
||||||
|
|
||||||
if (!chaptersJson.chapters.list?.length) return novel;
|
|
||||||
const totalChapters = chaptersJson.chapters.list.length;
|
|
||||||
|
|
||||||
const customPage: { [key: number]: string } = {};
|
|
||||||
const customOrder: { [key: number]: number } = {};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//fixes the chapter's position.
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
chaptersJson.chapters.list.forEach((chapter, chapterIndex) =>
|
chaptersJson.forEach(chapter =>
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name:
|
name:
|
||||||
'Том ' +
|
'Том ' +
|
||||||
chapter.chapter_volume +
|
chapter.volume +
|
||||||
' Глава ' +
|
' Глава ' +
|
||||||
chapter.chapter_number +
|
chapter.number +
|
||||||
(chapter.chapter_name ? ' ' + chapter.chapter_name.trim() : ''),
|
(chapter.name ? ' ' + chapter.name.trim() : ''),
|
||||||
path:
|
path: novelPath + '/v' + chapter.volume + '/c' + chapter.number,
|
||||||
novelPath +
|
chapterNumber: chapter.index + 1,
|
||||||
'/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] || 'Основной перевод',
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
novel.chapters =
|
novel.chapters = chapters;
|
||||||
chaptersJson.chapters.branches?.length &&
|
|
||||||
chaptersJson.chapters.branches.length > 1
|
|
||||||
? chapters.sort((a, b) => {
|
|
||||||
if ((a.page || 0) > (b.page || 0)) return 1;
|
|
||||||
if ((a.page || 0) < (b.page || 0)) return -1;
|
|
||||||
return (a.chapterNumber || 0) - (b.chapterNumber || 0);
|
|
||||||
})
|
|
||||||
: chapters.reverse();
|
|
||||||
}
|
}
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
@@ -218,17 +179,15 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const result = await fetchApi(
|
const result = await fetchApi(this.site + 'api/manga?q=' + searchTerm);
|
||||||
this.site + '/search?q=' + searchTerm + '&type=manga',
|
const { data }: { data: resNovels[] } = await result.json();
|
||||||
);
|
|
||||||
const body = (await result.json()) as Manga[];
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
body.forEach(novel =>
|
data.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.rus_name || novel.name,
|
name: novel.rus_name || novel.name,
|
||||||
cover: novel.coverImage,
|
cover: novel?.cover?.default || '',
|
||||||
path: '/' + novel.slug,
|
path: novel.slug_url,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -237,23 +196,25 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
|
|
||||||
fetchImage = fetchFile;
|
fetchImage = fetchFile;
|
||||||
resolveUrl = (path: string, isNovel?: boolean) =>
|
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||||
this.site + path + (this.ui ? (isNovel ? '?' : '&') + 'ui=' + this.ui : '');
|
this.site + path + (this.ui ? '?ui=' + this.ui : '');
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
sort: {
|
sort_by: {
|
||||||
label: 'Сортировка',
|
label: 'Сортировка',
|
||||||
value: 'rate',
|
value: 'rating_score',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Рейтинг', value: 'rate' },
|
{ label: 'По рейтингу', value: 'rate_avg' },
|
||||||
{ label: 'Имя', value: 'name' },
|
{ label: 'По популярности', value: 'rating_score' },
|
||||||
{ label: 'Просмотры', value: 'views' },
|
{ label: 'По просмотрам', value: 'views' },
|
||||||
{ label: 'Дате добавления', value: 'created_at' },
|
{ label: 'Количеству глав', value: 'chap_count' },
|
||||||
{ label: 'Дате обновления', value: 'last_chapter_at' },
|
{ label: 'Дате обновления', value: 'last_chapter_at' },
|
||||||
{ label: 'Количество глав', value: 'chap_count' },
|
{ label: 'Дате добавления', value: 'created_at' },
|
||||||
|
{ label: 'По названию (A-Z)', value: 'name' },
|
||||||
|
{ label: 'По названию (А-Я)', value: 'rus_name' },
|
||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
order: {
|
sort_type: {
|
||||||
label: 'Порядок',
|
label: 'Порядок',
|
||||||
value: 'desc',
|
value: 'desc',
|
||||||
options: [
|
options: [
|
||||||
@@ -262,34 +223,20 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
type: {
|
types: {
|
||||||
label: 'Тип',
|
label: 'Тип',
|
||||||
value: [],
|
value: [],
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Авторский', value: '14' },
|
|
||||||
{ label: 'Английский', value: '13' },
|
|
||||||
{ label: 'Китай', value: '12' },
|
|
||||||
{ label: 'Корея', value: '11' },
|
|
||||||
{ label: 'Фанфик', value: '15' },
|
|
||||||
{ label: 'Япония', value: '10' },
|
{ label: 'Япония', value: '10' },
|
||||||
|
{ label: 'Корея', value: '11' },
|
||||||
|
{ label: 'Китай', value: '12' },
|
||||||
|
{ label: 'Английский', value: '13' },
|
||||||
|
{ label: 'Авторский', value: '14' },
|
||||||
|
{ label: 'Фанфик', value: '15' },
|
||||||
],
|
],
|
||||||
type: FilterTypes.CheckboxGroup,
|
type: FilterTypes.CheckboxGroup,
|
||||||
},
|
},
|
||||||
format: {
|
scanlateStatus: {
|
||||||
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' },
|
|
||||||
],
|
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Статус перевода',
|
label: 'Статус перевода',
|
||||||
value: [],
|
value: [],
|
||||||
options: [
|
options: [
|
||||||
@@ -366,6 +313,7 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
{ label: 'Ужасы', value: '67' },
|
{ label: 'Ужасы', value: '67' },
|
||||||
{ label: 'Фантастика', value: '68' },
|
{ label: 'Фантастика', value: '68' },
|
||||||
{ label: 'Фэнтези', value: '69' },
|
{ label: 'Фэнтези', value: '69' },
|
||||||
|
{ label: 'Хентай', value: '84' },
|
||||||
{ label: 'Школа', value: '70' },
|
{ label: 'Школа', value: '70' },
|
||||||
{ label: 'Эротика', value: '71' },
|
{ label: 'Эротика', value: '71' },
|
||||||
{ label: 'Этти', value: '72' },
|
{ label: 'Этти', value: '72' },
|
||||||
@@ -379,7 +327,7 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
value: { include: [], exclude: [] },
|
value: { include: [], exclude: [] },
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Авантюристы', value: '328' },
|
{ label: 'Авантюристы', value: '328' },
|
||||||
{ label: 'Антигерой', value: '176' },
|
{ label: 'Антигерой', value: '175' },
|
||||||
{ label: 'Бессмертные', value: '333' },
|
{ label: 'Бессмертные', value: '333' },
|
||||||
{ label: 'Боги', value: '218' },
|
{ label: 'Боги', value: '218' },
|
||||||
{ label: 'Борьба за власть', value: '309' },
|
{ label: 'Борьба за власть', value: '309' },
|
||||||
@@ -462,85 +410,46 @@ class RLIB implements Plugin.PluginBase {
|
|||||||
|
|
||||||
export default new RLIB();
|
export default new RLIB();
|
||||||
|
|
||||||
interface responseBook {
|
interface resNovels {
|
||||||
hasStickyPermission: boolean;
|
|
||||||
bookmark?: null;
|
|
||||||
auth: boolean;
|
|
||||||
comments_version: string;
|
|
||||||
manga: Manga;
|
|
||||||
chapters: Chapters;
|
|
||||||
user?: User;
|
|
||||||
}
|
|
||||||
interface Manga {
|
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
rusName?: string;
|
rus_name: string;
|
||||||
rus_name?: string;
|
eng_name: string;
|
||||||
engName?: string;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
status: number;
|
slug_url: string;
|
||||||
chapters_count: number;
|
cover: Cover;
|
||||||
altNames?: string[] | null;
|
ageRestriction: AgeRestrictionOrTypeOrStatus;
|
||||||
coverImage?: string;
|
site: number;
|
||||||
href?: string;
|
type: AgeRestrictionOrTypeOrStatus;
|
||||||
|
rating: Rating;
|
||||||
|
is_licensed: boolean;
|
||||||
|
model: string;
|
||||||
|
status: AgeRestrictionOrTypeOrStatus;
|
||||||
|
releaseDateString: string;
|
||||||
}
|
}
|
||||||
interface Chapters {
|
interface Cover {
|
||||||
list?: ListEntity[];
|
filename: string;
|
||||||
teams?: TeamsEntity[];
|
thumbnail: string;
|
||||||
branches?: BranchesEntity[];
|
default: string;
|
||||||
is_paid?: string[] | null;
|
|
||||||
}
|
}
|
||||||
interface ListEntity {
|
interface AgeRestrictionOrTypeOrStatus {
|
||||||
index?: number; //crutch
|
|
||||||
chapter_id: number;
|
|
||||||
chapter_slug: string;
|
|
||||||
chapter_name: string;
|
|
||||||
chapter_number: string;
|
|
||||||
chapter_volume: number;
|
|
||||||
chapter_moderated: number;
|
|
||||||
chapter_user_id: number;
|
|
||||||
chapter_expired_at: string;
|
|
||||||
chapter_scanlator_id: number;
|
|
||||||
chapter_created_at: string;
|
|
||||||
status?: null;
|
|
||||||
price: number;
|
|
||||||
branch_id?: number;
|
|
||||||
username: string;
|
|
||||||
}
|
|
||||||
interface TeamsEntity {
|
|
||||||
name: string;
|
|
||||||
alt_name: string;
|
|
||||||
cover: string;
|
|
||||||
slug: string;
|
|
||||||
id: number;
|
id: number;
|
||||||
branch_id: number;
|
label: string;
|
||||||
sale: number;
|
|
||||||
href: string;
|
|
||||||
pivot: Pivot;
|
|
||||||
}
|
}
|
||||||
interface Pivot {
|
interface Rating {
|
||||||
manga_id: number;
|
average: string;
|
||||||
team_id: number;
|
averageFormated: string;
|
||||||
|
votes: number;
|
||||||
|
votesFormated: string;
|
||||||
|
user: number;
|
||||||
}
|
}
|
||||||
interface BranchesEntity {
|
|
||||||
|
interface resChapters {
|
||||||
id: number;
|
id: number;
|
||||||
manga_id: number;
|
index: number;
|
||||||
|
item_number: number;
|
||||||
|
volume: string;
|
||||||
|
number: string;
|
||||||
|
number_secondary: string;
|
||||||
name: string;
|
name: string;
|
||||||
teams?: TeamsEntity1[] | null;
|
|
||||||
is_subscribed: boolean;
|
|
||||||
}
|
|
||||||
interface TeamsEntity1 {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
cover: string;
|
|
||||||
branch_id: number;
|
|
||||||
is_active: number;
|
|
||||||
}
|
|
||||||
interface User {
|
|
||||||
id: string;
|
|
||||||
avatar: string;
|
|
||||||
access: boolean;
|
|
||||||
isAdmin: boolean;
|
|
||||||
paid: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-45
@@ -3,14 +3,16 @@ import { FilterTypes, Filters } from '@libs/filterInputs';
|
|||||||
import { defaultCover } from '@libs/defaultCover';
|
import { defaultCover } from '@libs/defaultCover';
|
||||||
import { fetchApi, fetchFile } from '@libs/fetch';
|
import { fetchApi, fetchFile } from '@libs/fetch';
|
||||||
import { NovelStatus } from '@libs/novelStatus';
|
import { NovelStatus } from '@libs/novelStatus';
|
||||||
import { load as parseHTML } from 'cheerio';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const regex =
|
||||||
|
/<script id="__NEXT_DATA__" type="application\/json">(\{.*?\})<\/script>/;
|
||||||
|
|
||||||
class RNRF implements Plugin.PluginBase {
|
class RNRF implements Plugin.PluginBase {
|
||||||
id = 'RNRF';
|
id = 'RNRF';
|
||||||
name = 'РанобэРФ';
|
name = 'РанобэРФ';
|
||||||
site = 'https://ранобэ.рф';
|
site = 'https://ранобэ.рф';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/ranoberf/icon.png';
|
icon = 'src/ru/ranoberf/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
@@ -23,14 +25,13 @@ class RNRF implements Plugin.PluginBase {
|
|||||||
: filters?.sort?.value || 'popular';
|
: filters?.sort?.value || 'popular';
|
||||||
url += '&page=' + pageNo;
|
url += '&page=' + pageNo;
|
||||||
|
|
||||||
const result = await fetchApi(url);
|
const body = await fetchApi(url).then(res => res.text());
|
||||||
const body = await result.text();
|
|
||||||
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
|
||||||
const json: response = JSON.parse(jsonRaw || '{}');
|
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
|
const jsonRaw = body.match(regex);
|
||||||
|
if (jsonRaw instanceof Array && jsonRaw[1]) {
|
||||||
|
const json: response = JSON.parse(jsonRaw[1]);
|
||||||
|
|
||||||
json.props.pageProps?.totalData?.items?.forEach(novel =>
|
json.props.pageProps?.totalData?.items?.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.title,
|
name: novel.title,
|
||||||
@@ -40,35 +41,39 @@ class RNRF implements Plugin.PluginBase {
|
|||||||
path: '/' + novel.slug,
|
path: '/' + novel.slug,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const result = await fetchApi(this.site + novelPath);
|
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
||||||
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 novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: book?.title || '',
|
name: '',
|
||||||
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('Активен')
|
|
||||||
? NovelStatus.Ongoing
|
|
||||||
: NovelStatus.Completed,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const jsonRaw = body.match(regex);
|
||||||
|
if (jsonRaw instanceof Array && jsonRaw[1]) {
|
||||||
|
const book: PagePropsBook = JSON.parse(jsonRaw[1]).props.pageProps.book;
|
||||||
|
|
||||||
book?.chapters.forEach((chapter, chapterIndex) => {
|
novel.name = book.title;
|
||||||
|
novel.summary = book.description;
|
||||||
|
|
||||||
|
novel.cover = book.verticalImage?.url
|
||||||
|
? this.site + book.verticalImage.url
|
||||||
|
: defaultCover;
|
||||||
|
novel.status = book?.additionalInfo.includes('Активен')
|
||||||
|
? NovelStatus.Ongoing
|
||||||
|
: NovelStatus.Completed;
|
||||||
|
|
||||||
|
if (book.author) novel.author = book.author;
|
||||||
|
if (book.genres?.length)
|
||||||
|
novel.genres = book?.genres.map(item => item.title).join(',');
|
||||||
|
|
||||||
|
if (book.chapters?.length) {
|
||||||
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
book.chapters.forEach((chapter, chapterIndex) => {
|
||||||
if (!chapter.isDonate || chapter.isUserPaid) {
|
if (!chapter.isDonate || chapter.isUserPaid) {
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: chapter.title,
|
name: chapter.title,
|
||||||
@@ -80,40 +85,43 @@ class RNRF implements Plugin.PluginBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
novel.chapters = chapters.reverse();
|
novel.chapters = chapters.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const result = await fetchApi(this.site + chapterPath).then(res =>
|
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||||
res.text(),
|
res.text(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let loadedCheerio = parseHTML(result);
|
const jsonRaw = body.match(regex);
|
||||||
const jsonRaw = loadedCheerio('#__NEXT_DATA__').html();
|
if (jsonRaw instanceof Array && jsonRaw[1]) {
|
||||||
const json: response = JSON.parse(jsonRaw || '{}');
|
let chapter: string =
|
||||||
|
JSON.parse(jsonRaw[1])?.props?.pageProps?.chapter?.content?.text || '';
|
||||||
|
|
||||||
loadedCheerio = parseHTML(
|
if (chapter.includes('<img')) {
|
||||||
json.props.pageProps?.chapter?.content?.text || '',
|
return chapter.replace(/src="(.*?)"/g, (match, url) => {
|
||||||
);
|
if (!url.startsWith('http')) {
|
||||||
|
return `src="${this.site}${url}"`;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
return `src="${url}"`;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
|
||||||
const chapterText = loadedCheerio.html();
|
return '';
|
||||||
return chapterText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const url = `${this.site}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`;
|
const url = `${this.site}/v3/books?filter[or][0][title][like]=${searchTerm}&filter[or][1][titleEn][like]=${searchTerm}&filter[or][2][fullTitle][like]=${searchTerm}&filter[status][]=active&filter[status][]=abandoned&filter[status][]=completed&expand=verticalImage`;
|
||||||
const result = await fetchApi(url);
|
const { items }: { items: Item[] } = await fetchApi(url).then(res =>
|
||||||
const body = (await result.json()) as { items: Item[] };
|
res.json(),
|
||||||
|
);
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
body.items.forEach(novel =>
|
items.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.title,
|
name: novel.title,
|
||||||
cover: novel?.verticalImage?.url
|
cover: novel?.verticalImage?.url
|
||||||
@@ -142,6 +150,7 @@ class RNRF implements Plugin.PluginBase {
|
|||||||
},
|
},
|
||||||
} satisfies Filters;
|
} satisfies Filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new RNRF();
|
export default new RNRF();
|
||||||
|
|
||||||
interface response {
|
interface response {
|
||||||
|
|||||||
+27
-30
@@ -11,7 +11,7 @@ class ReN implements Plugin.PluginBase {
|
|||||||
name = 'Renovels';
|
name = 'Renovels';
|
||||||
icon = 'src/ru/renovels/icon.png';
|
icon = 'src/ru/renovels/icon.png';
|
||||||
site = 'https://renovels.org';
|
site = 'https://renovels.org';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
pageNo: number,
|
pageNo: number,
|
||||||
@@ -41,10 +41,10 @@ class ReN implements Plugin.PluginBase {
|
|||||||
|
|
||||||
url += '&page=' + pageNo;
|
url += '&page=' + pageNo;
|
||||||
|
|
||||||
const result = await fetchApi(url);
|
const { content }: { content: responseNovels[] } = await fetchApi(url).then(
|
||||||
const body = (await result.json()) as { content: responseNovels[] };
|
res => res.json(),
|
||||||
|
);
|
||||||
const novels: Plugin.NovelItem[] = body.content.map(novel => ({
|
const novels: Plugin.NovelItem[] = content.map(novel => ({
|
||||||
name: novel.main_name || novel.secondary_name,
|
name: novel.main_name || novel.secondary_name,
|
||||||
cover: this.site + (novel.img.high || novel.img.mid || novel.img.low),
|
cover: this.site + (novel.img.high || novel.img.mid || novel.img.low),
|
||||||
path: novel.dir,
|
path: novel.dir,
|
||||||
@@ -54,26 +54,23 @@ class ReN implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseNovel(novelID: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelID: string): Promise<Plugin.SourceNovel> {
|
||||||
const result = await fetchApi(this.site + '/api/titles/' + novelID);
|
const { content }: { content: responseNovel } = await fetchApi(
|
||||||
const body = (await result.json()) as { content: responseNovel };
|
this.site + '/api/titles/' + novelID,
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelID,
|
path: novelID,
|
||||||
name:
|
name: content.main_name || content.secondary_name || content.another_name,
|
||||||
body.content.main_name ||
|
summary: content.description,
|
||||||
body.content.secondary_name ||
|
|
||||||
body.content.another_name,
|
|
||||||
summary: body.content.description,
|
|
||||||
cover:
|
cover:
|
||||||
this.site +
|
this.site + (content.img.high || content.img.mid || content.img.low),
|
||||||
(body.content.img.high || body.content.img.mid || body.content.img.low),
|
|
||||||
status:
|
status:
|
||||||
body.content.status.name === 'Продолжается'
|
content.status.name === 'Продолжается'
|
||||||
? NovelStatus.Ongoing
|
? NovelStatus.Ongoing
|
||||||
: NovelStatus.Completed,
|
: NovelStatus.Completed,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tags = [body.content?.genres, body.content?.categories]
|
const tags = [content?.genres, content?.categories]
|
||||||
.flat()
|
.flat()
|
||||||
.map(tags => tags?.name)
|
.map(tags => tags?.name)
|
||||||
.filter(tags => tags);
|
.filter(tags => tags);
|
||||||
@@ -83,23 +80,21 @@ class ReN implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalPages =
|
const totalPages =
|
||||||
(body.content.branches?.[0]?.count_chapters ||
|
(content.branches?.[0]?.count_chapters || content.count_chapters) / 100;
|
||||||
body.content.count_chapters) / 100;
|
const branch_id = content.branches?.[0]?.id || content.id;
|
||||||
const branch_id = body.content.branches?.[0]?.id || body.content.id;
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
for (let page = 0; page < totalPages; page++) {
|
for (let page = 0; page < totalPages; page++) {
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
const chapterResult = await fetchApi(
|
const volumes: {
|
||||||
|
content: responseСhapters[];
|
||||||
|
} = await fetchApi(
|
||||||
this.site +
|
this.site +
|
||||||
'/api/titles/chapters/?branch_id=' +
|
'/api/titles/chapters/?branch_id=' +
|
||||||
branch_id +
|
branch_id +
|
||||||
'&count=100&page=' +
|
'&count=100&page=' +
|
||||||
(page + 1),
|
(page + 1),
|
||||||
);
|
).then(res => res.json());
|
||||||
const volumes = (await chapterResult.json()) as {
|
|
||||||
content: responseСhapters[];
|
|
||||||
};
|
|
||||||
|
|
||||||
volumes.content.forEach(chapter => {
|
volumes.content.forEach(chapter => {
|
||||||
if (!chapter.is_paid || chapter.is_bought) {
|
if (!chapter.is_paid || chapter.is_bought) {
|
||||||
@@ -124,10 +119,11 @@ class ReN implements Plugin.PluginBase {
|
|||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const url = this.site + '/api/titles/chapters/' + chapterPath.split('/')[1];
|
const url = this.site + '/api/titles/chapters/' + chapterPath.split('/')[1];
|
||||||
const result = await fetchApi(url);
|
const result: { content: responseСhapter } = await fetchApi(url).then(res =>
|
||||||
const body = (await result.json()) as { content: responseСhapter };
|
res.json(),
|
||||||
|
);
|
||||||
|
|
||||||
return body.content.content;
|
return result.content.content || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(
|
async searchNovels(
|
||||||
@@ -135,11 +131,12 @@ class ReN implements Plugin.PluginBase {
|
|||||||
pageNo: number | undefined = 1,
|
pageNo: number | undefined = 1,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
const url = `${this.site}/api/search/?query=${searchTerm}&count=100&field=titles&page=${pageNo}`;
|
const url = `${this.site}/api/search/?query=${searchTerm}&count=100&field=titles&page=${pageNo}`;
|
||||||
const result = await fetchApi(url);
|
const { content }: { content: responseNovels[] } = await fetchApi(url).then(
|
||||||
const body = (await result.json()) as { content: responseNovels[] };
|
res => res.json(),
|
||||||
|
);
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
body.content.forEach(novel =>
|
content.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.main_name || novel.secondary_name,
|
name: novel.main_name || novel.secondary_name,
|
||||||
cover: this.site + (novel.img.high || novel.img.mid || novel.img.low),
|
cover: this.site + (novel.img.high || novel.img.mid || novel.img.low),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { fetchFile, fetchApi } from '@libs/fetch';
|
|||||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||||
import { Plugin } from '@typings/plugin';
|
import { Plugin } from '@typings/plugin';
|
||||||
import { NovelStatus } from '@libs/novelStatus';
|
import { NovelStatus } from '@libs/novelStatus';
|
||||||
import { load as parseHTML } from 'cheerio';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
export interface HotNovelPubMetadata {
|
export interface HotNovelPubMetadata {
|
||||||
@@ -33,7 +32,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
this.icon = `multisrc/hotnovelpub/${metadata.id.toLowerCase()}/icon.png`;
|
this.icon = `multisrc/hotnovelpub/${metadata.id.toLowerCase()}/icon.png`;
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.apiSite = metadata.sourceSite.replace('://', '://api.');
|
this.apiSite = metadata.sourceSite.replace('://', '://api.');
|
||||||
this.version = '1.0.0';
|
this.version = '1.0.1';
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
this.lang = metadata.options?.lang || 'en';
|
this.lang = metadata.options?.lang || 'en';
|
||||||
}
|
}
|
||||||
@@ -50,20 +49,19 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
url += '/?page=' + (pageNo - 1) + '&limit=20';
|
url += '/?page=' + (pageNo - 1) + '&limit=20';
|
||||||
|
|
||||||
const result = await fetchApi(url, {
|
const result: responseNovels = await fetchApi(url, {
|
||||||
headers: {
|
headers: {
|
||||||
lang: this.lang,
|
lang: this.lang,
|
||||||
},
|
},
|
||||||
});
|
}).then(res => res.json());
|
||||||
const json = (await result.json()) as responseNovels;
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
if (json.status && json.data.books.data?.length) {
|
if (result.status && result.data.books.data?.length) {
|
||||||
json.data.books.data.forEach(novel =>
|
result.data.books.data.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.name,
|
name: novel.name,
|
||||||
cover: this.site + novel.image,
|
cover: this.site + novel.image,
|
||||||
path: '/' + novel.slug,
|
path: novel.slug,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -71,12 +69,14 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const result = await fetchApi(this.apiSite + '/book' + novelPath, {
|
const json: responseNovel = await fetchApi(
|
||||||
|
this.apiSite + '/book/' + novelPath,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
lang: this.lang,
|
lang: this.lang,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
const json = (await result.json()) as responseNovel;
|
).then(res => res.json());
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
name: json.data.book.name,
|
name: json.data.book.name,
|
||||||
@@ -99,7 +99,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
json.data.chapters.forEach((chapter, chapterIndex) =>
|
json.data.chapters.forEach((chapter, chapterIndex) =>
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: chapter.title,
|
name: chapter.title,
|
||||||
path: '/' + chapter.slug,
|
path: chapter.slug,
|
||||||
releaseTime: undefined,
|
releaseTime: undefined,
|
||||||
chapterNumber: (chapter.index || chapterIndex) + 1,
|
chapterNumber: (chapter.index || chapterIndex) + 1,
|
||||||
}),
|
}),
|
||||||
@@ -111,12 +111,13 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
const body = await fetchApi(this.resolveUrl(chapterPath)).then(res =>
|
||||||
res.text(),
|
res.text(),
|
||||||
);
|
);
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
let chapterText = loadedCheerio('#content-item').html() || '';
|
let chapterText =
|
||||||
|
body.match(/<div id="content-item" ([\s\S]*?)<\/div>/g)?.[0] || '';
|
||||||
|
|
||||||
if (chapterText) {
|
if (chapterText) {
|
||||||
const result = await fetchApi(
|
const result = await fetchApi(
|
||||||
this.site + '/server/getContent?slug=' + chapterPath,
|
this.site + '/server/getContent?slug=' + chapterPath,
|
||||||
@@ -138,7 +139,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
pageNo: number,
|
pageNo: number,
|
||||||
): Promise<Plugin.NovelItem[]> {
|
): Promise<Plugin.NovelItem[]> {
|
||||||
const result = await fetchApi(this.apiSite + '/search', {
|
const result: responseSearch = await fetchApi(this.apiSite + '/search', {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
Referer: this.site,
|
Referer: this.site,
|
||||||
@@ -147,16 +148,14 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
},
|
},
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ key_search: searchTerm }),
|
body: JSON.stringify({ key_search: searchTerm }),
|
||||||
});
|
}).then(res => res.json());
|
||||||
const json = (await result.json()) as responseSearch;
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
if (json.status && json.data.books?.length) {
|
if (result.status && result.data.books?.length) {
|
||||||
json.data.books.forEach(novel =>
|
result.data.books.forEach(novel =>
|
||||||
novels.push({
|
novels.push({
|
||||||
name: novel.name,
|
name: novel.name,
|
||||||
cover: undefined,
|
path: novel.slug,
|
||||||
path: '/' + novel.slug,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -165,6 +164,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchImage = fetchFile;
|
fetchImage = fetchFile;
|
||||||
|
resolveUrl = (path: string, isNovel?: boolean) => this.site + '/' + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface responseNovels {
|
interface responseNovels {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
|||||||
this.name = metadata.sourceName;
|
this.name = metadata.sourceName;
|
||||||
this.icon = `multisrc/ifreedom/${metadata.id.toLowerCase()}/icon.png`;
|
this.icon = `multisrc/ifreedom/${metadata.id.toLowerCase()}/icon.png`;
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = '1.0.0';
|
this.version = '1.0.1';
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,20 +139,26 @@ class IfreedomPlugin implements Plugin.PluginBase {
|
|||||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
const body = await fetchApi(this.site + chapterPath).then(res =>
|
||||||
res.text(),
|
res.text(),
|
||||||
);
|
);
|
||||||
const loadedCheerio = parseHTML(body);
|
let chapterText =
|
||||||
|
body.match(/<article id="([\s\S]*?)<\/article>/g)?.[0] || '';
|
||||||
|
chapterText = chapterText.replace(/<script[^>]*>[\s\S]*?<\/script>/gim, '');
|
||||||
|
|
||||||
loadedCheerio('.entry-content img').each((index, element) => {
|
if (chapterText.includes('<img')) {
|
||||||
const srcset = loadedCheerio(element).attr('srcset')?.split?.(' ');
|
return chapterText.replace(/srcset="([^"]+)"/g, (match, src) => {
|
||||||
if (srcset?.length) {
|
if (!src) return match;
|
||||||
loadedCheerio(element).removeAttr('srcset');
|
const bestlink = src
|
||||||
const bestlink: string[] = srcset.filter(url => url.startsWith('http'));
|
.split(' ')
|
||||||
if (bestlink[bestlink.length - 1]) {
|
.filter((url: string) => url.startsWith('http'))
|
||||||
loadedCheerio(element).attr('src', bestlink[bestlink.length - 1]);
|
.pop();
|
||||||
}
|
|
||||||
|
if (bestlink) {
|
||||||
|
if (bestlink.startsWith('http')) return `src="${bestlink}"`;
|
||||||
|
return `src="${this.site}${bestlink}"`;
|
||||||
}
|
}
|
||||||
|
return match;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const chapterText = loadedCheerio('.entry-content').html() || '';
|
|
||||||
return chapterText;
|
return chapterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,35 +1,18 @@
|
|||||||
import { FilterTypes, Filters } from '../../../libs/filterInputs';
|
|
||||||
import { ScrpitGeneratorFunction } from '../generate';
|
import { ScrpitGeneratorFunction } from '../generate';
|
||||||
import list from './sources.json';
|
|
||||||
import defaultSettings from './settings.json';
|
|
||||||
import { ReadwnMetadata } from './template';
|
import { ReadwnMetadata } from './template';
|
||||||
|
import list from './sources.json';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
export const generateAll: ScrpitGeneratorFunction = function (name) {
|
||||||
return list
|
return list.map((metadata: ReadwnMetadata) => {
|
||||||
.map<ReadwnMetadata>(p => {
|
try {
|
||||||
p.filters.tags.options.unshift(...defaultSettings.filters.tags.options);
|
const { filters }: any = require(`./filters/${metadata.id}`);
|
||||||
p.filters.genres.options.unshift(
|
metadata.filters = filters;
|
||||||
...defaultSettings.filters.genres.options,
|
} catch (e) {}
|
||||||
|
console.log(
|
||||||
|
`[${name}] Generating: ${metadata.id}${' '.repeat(20 - metadata.id.length)} ${metadata.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
|
||||||
);
|
);
|
||||||
p.filters = Object.assign(defaultSettings.filters, p.filters);
|
|
||||||
|
|
||||||
const d = false;
|
|
||||||
const filters: Filters = {};
|
|
||||||
for (const k in p.filters) {
|
|
||||||
const f = p.filters[k as keyof typeof p.filters];
|
|
||||||
if (f) {
|
|
||||||
filters[k] = {
|
|
||||||
...f,
|
|
||||||
type: FilterTypes.Picker,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ...p, filters: d ? undefined : filters };
|
|
||||||
})
|
|
||||||
.map((metadata: ReadwnMetadata) => {
|
|
||||||
console.log(`[${name}]: Generating`, metadata.id);
|
|
||||||
return generator(metadata);
|
return generator(metadata);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -39,18 +22,9 @@ const generator = function generator(metadata: ReadwnMetadata) {
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
});
|
});
|
||||||
|
|
||||||
//metadata.filters = {
|
|
||||||
// sort: { ...defaultSettings.filters.sort, type: FilterTypes.Picker },
|
|
||||||
// status: { ...defaultSettings.filters.status, type: FilterTypes.Picker },
|
|
||||||
// ...metadata.filters,
|
|
||||||
//};
|
|
||||||
|
|
||||||
const pluginScript = `
|
const pluginScript = `
|
||||||
${readwnTemplate}
|
${readwnTemplate}
|
||||||
const plugin = new ReadwnPlugin(${JSON.stringify(metadata).replace(
|
const plugin = new ReadwnPlugin(${JSON.stringify(metadata)});
|
||||||
/"type":"([^"]+)"/g,
|
|
||||||
'"type":FilterTypes.$1',
|
|
||||||
)});
|
|
||||||
export default plugin;
|
export default plugin;
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
require('module-alias/register');
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { FilterTypes, FilterOption } from '@libs/filterInputs';
|
||||||
|
const type: string[] = ['genres', 'status', 'sort'];
|
||||||
|
|
||||||
|
async function getFilters(name: string, url: string) {
|
||||||
|
const html = await fetch(url + '/list/all/all-newstime-0.html').then(res =>
|
||||||
|
res.text(),
|
||||||
|
);
|
||||||
|
const $: cheerio.CheerioAPI = cheerio.load(html);
|
||||||
|
const filters: any = {
|
||||||
|
'sort': {
|
||||||
|
type: FilterTypes.Picker,
|
||||||
|
label: 'Sort By',
|
||||||
|
value: 'onclick',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
type: FilterTypes.Picker,
|
||||||
|
label: 'Status',
|
||||||
|
value: 'all',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
'genres': {
|
||||||
|
type: FilterTypes.Picker,
|
||||||
|
label: 'Genre / Category',
|
||||||
|
value: '',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
'tags': {
|
||||||
|
type: FilterTypes.Picker,
|
||||||
|
label: 'Tags',
|
||||||
|
value: '',
|
||||||
|
options: [{ label: 'NONE', value: '' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$('ul.proplist')?.each?.(function (index, ulElement) {
|
||||||
|
if (!type[index]) return;
|
||||||
|
$(ulElement)
|
||||||
|
.find('li > a')
|
||||||
|
.each((indx, liElement) =>
|
||||||
|
filters[type[index]].options.push({
|
||||||
|
label: $(liElement).text(),
|
||||||
|
value: $(liElement).attr('href'),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
filters[type[index]].options = filters[type[index]].options.map(
|
||||||
|
(item: FilterOption) => {
|
||||||
|
let res = item.value;
|
||||||
|
if (index == 0) res = item.value.split('/')[2];
|
||||||
|
if (index == 1)
|
||||||
|
res = item.value.replace(/\/list\/all\/(.*?)-.*$/, '$1');
|
||||||
|
if (index == 2) res = item.value.replace(/.*\/all-(.*?)-.*/, '$1');
|
||||||
|
return {
|
||||||
|
label: item.label,
|
||||||
|
value: res,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) as FilterOption[];
|
||||||
|
filters[type[index]].options.sort((a: FilterOption, b: FilterOption) => {
|
||||||
|
if (a.label === 'All') {
|
||||||
|
return -1;
|
||||||
|
} else if (b.label === 'All') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return a.label.localeCompare(b.label);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(url + '/browsetags/').then(res => res.text());
|
||||||
|
const loadedCheerio: cheerio.CheerioAPI = cheerio.load(response);
|
||||||
|
const allPage = loadedCheerio('.tag-letters > a')
|
||||||
|
.map((index, element) => loadedCheerio(element).attr('href'))
|
||||||
|
.get();
|
||||||
|
// ===================== tags ======================
|
||||||
|
for (let i = 0; i < allPage.length; i++) {
|
||||||
|
console.log('fetch', url + allPage[i]);
|
||||||
|
const resTags = await fetch(url + allPage[i]).then(res => res.text());
|
||||||
|
const $: cheerio.CheerioAPI = cheerio.load(resTags);
|
||||||
|
$('.tag-items > li > a').each((index, element) =>
|
||||||
|
filters['tags'].options.push({
|
||||||
|
label: $(element).text()?.trim(),
|
||||||
|
value: $(element)
|
||||||
|
.attr('href')
|
||||||
|
?.split('/')
|
||||||
|
?.pop()
|
||||||
|
?.replace('-0.html', ''),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let nextPage = $('.pagination > li:last-child > a').attr('href');
|
||||||
|
if (nextPage) {
|
||||||
|
const allpage = parseInt(nextPage.replace(/[^0-9]/g, '') || '0', 10);
|
||||||
|
for (let pageNo = 0; pageNo < allpage; pageNo++) {
|
||||||
|
await sleep(3000);
|
||||||
|
console.log(
|
||||||
|
'fetch',
|
||||||
|
url + allPage[i].replace('-0.html', `-${pageNo + 1}.html`),
|
||||||
|
);
|
||||||
|
const resTags = await fetch(
|
||||||
|
url + allPage[i].replace('-0.html', `-${pageNo + 1}.html`),
|
||||||
|
).then(res => res.text());
|
||||||
|
const $: cheerio.CheerioAPI = cheerio.load(resTags);
|
||||||
|
|
||||||
|
$('.tag-items > li > a').each((index, element) =>
|
||||||
|
filters['tags'].options.push({
|
||||||
|
label: $(element).text()?.trim(),
|
||||||
|
value: $(element).attr('href')?.split('/')?.pop()?.split('-')?.[0],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await sleep(3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, 'filters', name + '.json'),
|
||||||
|
JSON.stringify({ filters }, null, 2),
|
||||||
|
);
|
||||||
|
console.log(`✅Filters created successfully for ${name}✅`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep(ms: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function askGetFilter() {
|
||||||
|
const readline = require('readline').createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
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 + 'Enter the URL (same one as in sources.json): ',
|
||||||
|
async (url: string) => {
|
||||||
|
try {
|
||||||
|
await getFilters(name, url);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('Error while getting filters from', url);
|
||||||
|
console.log(e.message || e);
|
||||||
|
}
|
||||||
|
readline.close();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
askGetFilter();
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ export interface ReadwnMetadata {
|
|||||||
id: string;
|
id: string;
|
||||||
sourceSite: string;
|
sourceSite: string;
|
||||||
sourceName: string;
|
sourceName: string;
|
||||||
filters?: Filters;
|
filters?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReadwnPlugin implements Plugin.PluginBase {
|
class ReadwnPlugin implements Plugin.PluginBase {
|
||||||
@@ -26,7 +26,7 @@ class ReadwnPlugin implements Plugin.PluginBase {
|
|||||||
this.name = metadata.sourceName;
|
this.name = metadata.sourceName;
|
||||||
this.icon = `multisrc/readwn/${metadata.id.toLowerCase()}/icon.png`;
|
this.icon = `multisrc/readwn/${metadata.id.toLowerCase()}/icon.png`;
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = '1.0.0';
|
this.version = '1.0.1';
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
this.name = metadata.sourceName;
|
this.name = metadata.sourceName;
|
||||||
this.icon = `multisrc/rulate/${metadata.id.toLowerCase()}/icon.png`;
|
this.icon = `multisrc/rulate/${metadata.id.toLowerCase()}/icon.png`;
|
||||||
this.site = metadata.sourceSite;
|
this.site = metadata.sourceSite;
|
||||||
this.version = '1.0.0';
|
this.version = '1.0.1';
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,12 +80,10 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
formData.append('path', novelPath);
|
formData.append('path', novelPath);
|
||||||
formData.append('ok', 'Да');
|
formData.append('ok', 'Да');
|
||||||
|
|
||||||
await fetchApi(result.url, {
|
result = await fetchApi(result.url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
result = await fetchApi(this.site + novelPath);
|
|
||||||
}
|
}
|
||||||
const body = await result.text();
|
const body = await result.text();
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
@@ -183,14 +181,13 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
let result = await fetchApi(this.site + chapterPath);
|
let result = await fetchApi(this.site + chapterPath);
|
||||||
if (result.url.includes('mature?path=')) {
|
if (result.url.includes('mature?path=')) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
formData.append('path', chapterPath.split('/').slice(0, 3).join('/'));
|
||||||
formData.append('ok', 'Да');
|
formData.append('ok', 'Да');
|
||||||
|
|
||||||
await fetchApi(result.url, {
|
result = await fetchApi(result.url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
result = await fetchApi(this.site + chapterPath);
|
|
||||||
}
|
}
|
||||||
const body = await result.text();
|
const body = await result.text();
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
@@ -208,12 +205,11 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
const result = await fetchApi(
|
const result: response[] = await fetchApi(
|
||||||
this.site + '/search/autocomplete?query=' + searchTerm,
|
this.site + '/search/autocomplete?query=' + searchTerm,
|
||||||
);
|
).then(res => res.json());
|
||||||
const json = (await result.json()) as response[];
|
|
||||||
|
|
||||||
json.forEach(novel => {
|
result.forEach(novel => {
|
||||||
const name = novel.title_one + ' / ' + novel.title_two;
|
const name = novel.title_one + ' / ' + novel.title_two;
|
||||||
if (!novel.url) return;
|
if (!novel.url) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user