@@ -7548,7 +7548,8 @@
|
|||||||
],
|
],
|
||||||
"type": "CheckboxGroup"
|
"type": "CheckboxGroup"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"versionIncrements": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "erolate",
|
"id": "erolate",
|
||||||
@@ -10015,6 +10016,7 @@
|
|||||||
],
|
],
|
||||||
"type": "CheckboxGroup"
|
"type": "CheckboxGroup"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"versionIncrements": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type RulateMetadata = {
|
|||||||
sourceSite: string;
|
sourceSite: string;
|
||||||
sourceName: string;
|
sourceName: string;
|
||||||
filters?: Filters;
|
filters?: Filters;
|
||||||
|
versionIncrements: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RulatePlugin implements Plugin.PluginBase {
|
class RulatePlugin implements Plugin.PluginBase {
|
||||||
@@ -25,7 +26,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.1';
|
this.version = '1.0.' + (1 + metadata.versionIncrements);
|
||||||
this.filters = metadata.filters;
|
this.filters = metadata.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,89 +91,106 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: loadedCheerio(
|
name: loadedCheerio('.span8 > h1, .book__title').text().trim(),
|
||||||
'div[class="container"] > div > div > h1, div.span8:nth-child(1) > h1:nth-child(2)',
|
|
||||||
)
|
|
||||||
.text()
|
|
||||||
.trim(),
|
|
||||||
};
|
};
|
||||||
if (novel.name?.includes?.('[')) {
|
if (novel.name?.includes?.('[')) {
|
||||||
novel.name = novel.name.split('[')[0].trim();
|
novel.name = novel.name.split('[')[0].trim();
|
||||||
}
|
}
|
||||||
novel.cover =
|
novel.cover =
|
||||||
this.site + loadedCheerio('div[class="images"] > div img').attr('src');
|
this.site +
|
||||||
novel.summary = loadedCheerio('#Info > div:nth-child(3), .book-description')
|
loadedCheerio('div[class="images"] > div img, .book__cover > img').attr(
|
||||||
|
'src',
|
||||||
|
);
|
||||||
|
novel.summary = loadedCheerio(
|
||||||
|
'#Info > div:nth-child(3), .book__description',
|
||||||
|
)
|
||||||
.text()
|
.text()
|
||||||
.trim();
|
.trim();
|
||||||
novel.author = loadedCheerio(
|
|
||||||
'.book-stats-icons_author > span:nth-child(2) > a:nth-child(1)',
|
|
||||||
).text();
|
|
||||||
const genres: string[] = [];
|
const genres: string[] = [];
|
||||||
|
|
||||||
loadedCheerio('div.span5 > p, .span5 > div:nth-child(2) > p').each(
|
loadedCheerio('div.span5 > p').each(function () {
|
||||||
function () {
|
switch (loadedCheerio(this).find('strong').text()) {
|
||||||
switch (loadedCheerio(this).find('strong').text()) {
|
case 'Автор:':
|
||||||
case 'Автор:':
|
novel.author = loadedCheerio(this).find('em > a').text().trim();
|
||||||
novel.author = loadedCheerio(this).find('em > a').text().trim();
|
break;
|
||||||
break;
|
case 'Выпуск:':
|
||||||
case 'Выпуск:':
|
novel.status =
|
||||||
novel.status =
|
loadedCheerio(this).find('em').text().trim() === 'продолжается'
|
||||||
loadedCheerio(this).find('em').text().trim() === 'продолжается'
|
? NovelStatus.Ongoing
|
||||||
? NovelStatus.Ongoing
|
: NovelStatus.Completed;
|
||||||
: NovelStatus.Completed;
|
break;
|
||||||
break;
|
case 'Тэги:':
|
||||||
case 'Тэги:':
|
loadedCheerio(this)
|
||||||
loadedCheerio(this)
|
.find('em > a')
|
||||||
.find('em > a')
|
.each(function () {
|
||||||
.each(function () {
|
genres.push(loadedCheerio(this).text());
|
||||||
genres.push(loadedCheerio(this).text());
|
});
|
||||||
});
|
break;
|
||||||
break;
|
case 'Жанры:':
|
||||||
case 'Жанры:':
|
loadedCheerio(this)
|
||||||
loadedCheerio(this)
|
.find('em > a')
|
||||||
.find('em > a')
|
.each(function () {
|
||||||
.each(function () {
|
genres.push(loadedCheerio(this).text());
|
||||||
genres.push(loadedCheerio(this).text());
|
});
|
||||||
});
|
break;
|
||||||
break;
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (genres.length) {
|
if (genres.length) {
|
||||||
novel.genres = genres.reverse().join(',');
|
novel.genres = genres.reverse().join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
loadedCheerio('table > tbody > tr.chapter_row').each(
|
if (this.id === 'rulate') {
|
||||||
(chapterIndex, element) => {
|
loadedCheerio('table > tbody > tr.chapter_row').each(
|
||||||
|
(chapterIndex, element) => {
|
||||||
|
const chapterName = loadedCheerio(element)
|
||||||
|
.find('td[class="t"] > a')
|
||||||
|
.text()
|
||||||
|
.trim();
|
||||||
|
const releaseDate = loadedCheerio(element)
|
||||||
|
.find('td > span')
|
||||||
|
.attr('title')
|
||||||
|
?.trim();
|
||||||
|
const chapterUrl = loadedCheerio(element)
|
||||||
|
.find('td[class="t"] > a')
|
||||||
|
.attr('href');
|
||||||
|
|
||||||
|
if (
|
||||||
|
!loadedCheerio(element).find('td > span[class="disabled"]')
|
||||||
|
.length &&
|
||||||
|
releaseDate &&
|
||||||
|
chapterUrl
|
||||||
|
) {
|
||||||
|
chapters.push({
|
||||||
|
name: chapterName,
|
||||||
|
path: chapterUrl,
|
||||||
|
releaseTime: this.parseDate(releaseDate),
|
||||||
|
chapterNumber: chapterIndex + 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
loadedCheerio('a.chapter').each((chapterIndex, element) => {
|
||||||
const chapterName = loadedCheerio(element)
|
const chapterName = loadedCheerio(element)
|
||||||
.find('td[class="t"] > a')
|
.find('div:nth-child(1) > span:nth-child(2)')
|
||||||
.text()
|
.text()
|
||||||
.trim();
|
.trim();
|
||||||
const releaseDate = loadedCheerio(element)
|
const chapterUrl = loadedCheerio(element).attr('href');
|
||||||
.find('td > span')
|
const isPaid = loadedCheerio(element).find(
|
||||||
.attr('title')
|
'span[data-can-buy="true"]',
|
||||||
?.trim();
|
).length;
|
||||||
const chapterUrl = loadedCheerio(element)
|
|
||||||
.find('td[class="t"] > a')
|
|
||||||
.attr('href');
|
|
||||||
|
|
||||||
if (
|
if (!isPaid && chapterUrl) {
|
||||||
!loadedCheerio(element).find('td > span[class="disabled"]').length &&
|
|
||||||
releaseDate &&
|
|
||||||
chapterUrl
|
|
||||||
) {
|
|
||||||
chapters.push({
|
chapters.push({
|
||||||
name: chapterName,
|
name: chapterName,
|
||||||
path: chapterUrl,
|
path: chapterUrl,
|
||||||
releaseTime: this.parseDate(releaseDate),
|
|
||||||
chapterNumber: chapterIndex + 1,
|
chapterNumber: chapterIndex + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
novel.chapters = chapters;
|
novel.chapters = chapters;
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
@@ -192,7 +210,7 @@ class RulatePlugin implements Plugin.PluginBase {
|
|||||||
const body = await result.text();
|
const body = await result.text();
|
||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
const chapterText = loadedCheerio('.content-text').html();
|
const chapterText = loadedCheerio('.content-text, #read-text').html();
|
||||||
return chapterText || '';
|
return chapterText || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+229
-201
@@ -1,14 +1,19 @@
|
|||||||
import { Plugin } from '@typings/plugin';
|
import { Plugin } from '@typings/plugin';
|
||||||
import { FilterTypes, Filters } from '@libs/filterInputs';
|
import { FilterTypes, Filters } from '@libs/filterInputs';
|
||||||
|
import { defaultCover } from '@libs/defaultCover';
|
||||||
import { fetchApi } from '@libs/fetch';
|
import { fetchApi } from '@libs/fetch';
|
||||||
import { load as parseHTML } from 'cheerio';
|
|
||||||
import dayjs from 'dayjs';
|
const headers: any = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Inertia': true,
|
||||||
|
'X-Inertia-Version': '6666cd76f96956469e7be39d750cc7d9',
|
||||||
|
};
|
||||||
|
|
||||||
class freedlit implements Plugin.PluginBase {
|
class freedlit implements Plugin.PluginBase {
|
||||||
id = 'freedlit.space';
|
id = 'freedlit.space';
|
||||||
name = 'LitSpace';
|
name = 'LitSpace';
|
||||||
site = 'https://freedlit.space';
|
site = 'https://freedlit.space';
|
||||||
version = '1.0.0';
|
version = '1.1.0';
|
||||||
icon = 'src/ru/freedlit/icon.png';
|
icon = 'src/ru/freedlit/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
@@ -18,136 +23,145 @@ class freedlit 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 + '/get-books/all/list/' + page + '?sort=';
|
let url = this.site + '/get-books/?sort=';
|
||||||
url += showLatestNovels ? 'recent' : filters?.sort?.value || 'popular';
|
url += showLatestNovels ? 'recent' : filters?.sort?.value || 'popular';
|
||||||
url += '&status=' + (filters?.status?.value || 'all');
|
|
||||||
url += '&access=' + (filters?.access?.value || 'all');
|
url += '&access=' + (filters?.access?.value || 'all');
|
||||||
url += '&adult=' + (filters?.adult?.value || 'hide');
|
url += '&hideAdult=' + (filters?.hideAdult?.value || false);
|
||||||
|
url += '&page=' + page;
|
||||||
if (filters?.genre?.value?.include?.length) {
|
|
||||||
url += filters.genre.value.include
|
|
||||||
.map(id => '&genres_included[]=' + id)
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters?.genre?.value?.exclude?.length) {
|
|
||||||
url += filters.genre.value.exclude
|
|
||||||
.map(id => '&genres_excluded[]=' + id)
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await fetchApi(url).then(res => res.text());
|
|
||||||
const loadedCheerio = parseHTML(body);
|
|
||||||
|
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
loadedCheerio('#bookListBlock > div > div').each((index, element) => {
|
|
||||||
const name = loadedCheerio(element).find('div > h4 > a').text()?.trim();
|
|
||||||
const cover = loadedCheerio(element)
|
|
||||||
.find('div > a > img')
|
|
||||||
.attr('src')
|
|
||||||
?.trim();
|
|
||||||
const url = loadedCheerio(element)
|
|
||||||
.find('div > h4 > a')
|
|
||||||
.attr('href')
|
|
||||||
?.trim();
|
|
||||||
if (!name || !url) return;
|
|
||||||
|
|
||||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
const { books }: { books: Books } = await fetchApi(url).then(res => {
|
||||||
|
this.getToken(res.headers);
|
||||||
|
return res.json();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
books.data.forEach(novel =>
|
||||||
|
novels.push({
|
||||||
|
name: novel.title,
|
||||||
|
cover: novel.cover
|
||||||
|
? this.site + '/storage/' + novel.cover
|
||||||
|
: defaultCover,
|
||||||
|
path: novel.item_id.toString(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
|
||||||
const body = await fetchApi(this.site + novelPath).then(res => res.text());
|
const {
|
||||||
const loadedCheerio = parseHTML(body);
|
props: { book },
|
||||||
|
}: { props: { book: DataEntity } } = await fetchApi(
|
||||||
|
this.resolveUrl(novelPath, true),
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
Referer: this.resolveUrl(novelPath, true),
|
||||||
|
},
|
||||||
|
).then(res => {
|
||||||
|
this.getToken(res.headers);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
|
|
||||||
const novel: Plugin.SourceNovel = {
|
const novel: Plugin.SourceNovel = {
|
||||||
path: novelPath,
|
path: novelPath,
|
||||||
name: loadedCheerio('.book-info h4').text(),
|
name: book.title,
|
||||||
cover: loadedCheerio('.book-cover > div > img').attr('src')?.trim(),
|
cover: book.cover ? this.site + '/storage/' + book.cover : defaultCover,
|
||||||
summary: loadedCheerio('#nav-home').text()?.trim(),
|
summary: book.annotation,
|
||||||
author: loadedCheerio('.book-info h5 > a').text(),
|
author: book.authors_names?.[0]?.name || '',
|
||||||
genres: loadedCheerio('.genre-list > a')
|
genres: book.tags?.map?.(tags => tags.name)?.join?.(', ') || '',
|
||||||
.map((index, element) => loadedCheerio(element).text())
|
|
||||||
.get()
|
|
||||||
.join(','),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { success }: { success: { items: chaptersData[] } } = await fetchApi(
|
||||||
|
this.site + '/api/bookpage/get-chapters',
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
headers,
|
||||||
|
Referer: this.resolveUrl(novelPath),
|
||||||
|
body: JSON.stringify({ book_id: novelPath }),
|
||||||
|
},
|
||||||
|
).then(res => {
|
||||||
|
this.getToken(res.headers);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
loadedCheerio('a.chapter-line').each((chapterIndex, element) => {
|
if (success?.items?.length) {
|
||||||
const name = loadedCheerio(element).find('h6').text();
|
success.items.forEach((chapter, chapterIndex) =>
|
||||||
const url = loadedCheerio(element).attr('href');
|
chapters.push({
|
||||||
if (!name || !url) return;
|
name: chapter.header,
|
||||||
|
path: novelPath + '/' + chapter.id,
|
||||||
const releaseDate = loadedCheerio(element)
|
releaseTime: chapter.first_published_formated,
|
||||||
.find('span[class="date"]')
|
chapterNumber: chapterIndex + 1,
|
||||||
.text();
|
}),
|
||||||
chapters.push({
|
);
|
||||||
name,
|
}
|
||||||
path: url.replace(this.site, ''),
|
|
||||||
releaseTime: this.parseDate(releaseDate),
|
|
||||||
chapterNumber: chapterIndex + 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
novel.chapters = chapters;
|
novel.chapters = chapters;
|
||||||
return novel;
|
return novel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseChapter(chapterPath: string): Promise<string> {
|
async parseChapter(chapterPath: string): Promise<string> {
|
||||||
const body = await fetchApi(this.site + chapterPath).then(res =>
|
const [book_id, chapter_id] = chapterPath.split('/');
|
||||||
res.text(),
|
if (!headers['X-XSRF-TOKEN']) {
|
||||||
);
|
await fetchApi(this.site).then(res => this.getToken(res.headers));
|
||||||
const loadedCheerio = parseHTML(body);
|
}
|
||||||
|
|
||||||
loadedCheerio('div.mobile-block').remove();
|
const { success }: { success: chapterContent } = await fetchApi(
|
||||||
loadedCheerio('div.standart-block').remove();
|
this.site + '/reader/get-content',
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
headers,
|
||||||
|
Referer: this.resolveUrl(chapterPath),
|
||||||
|
body: JSON.stringify({ book_id, chapter_id }),
|
||||||
|
},
|
||||||
|
).then(res => {
|
||||||
|
this.getToken(res.headers);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
|
|
||||||
const chapterText = loadedCheerio('div[class="chapter"]').html() || '';
|
const chapterText = success.content;
|
||||||
return chapterText;
|
return chapterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
|
||||||
const url = this.site + '/search?query=' + searchTerm + '&type=all';
|
const { success }: { success: book[] } = await fetchApi(
|
||||||
const body = await fetchApi(url).then(res => res.text());
|
this.site + '/api/search?query=' + searchTerm,
|
||||||
const loadedCheerio = parseHTML(body);
|
).then(res => {
|
||||||
|
this.getToken(res.headers);
|
||||||
const novels: Plugin.NovelItem[] = [];
|
return res.json();
|
||||||
loadedCheerio('#bookGridBlock > div').each((index, element) => {
|
|
||||||
const name = loadedCheerio(element).find('h4 > a').text()?.trim();
|
|
||||||
const cover = loadedCheerio(element).find('a > img').attr('src')?.trim();
|
|
||||||
const url = loadedCheerio(element).find('h4 > a').attr('href')?.trim();
|
|
||||||
if (!name || !url) return;
|
|
||||||
|
|
||||||
novels.push({ name, cover, path: url.replace(this.site, '') });
|
|
||||||
});
|
});
|
||||||
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
|
if (success?.length) {
|
||||||
|
success.forEach(novel => {
|
||||||
|
if (novel.type === 'book' && novel.title) {
|
||||||
|
novels.push({
|
||||||
|
name: novel.title,
|
||||||
|
cover: novel.cover
|
||||||
|
? this.site + '/storage/' + novel.cover
|
||||||
|
: defaultCover,
|
||||||
|
path: novel.id.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return novels;
|
return novels;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseDate = (dateString: string | undefined = '') => {
|
resolveUrl = (path: string, isNovel?: boolean) =>
|
||||||
const months: Record<string, number> = {
|
this.site + (isNovel ? '/book/' : '/reader/') + path;
|
||||||
января: 1,
|
|
||||||
февраля: 2,
|
|
||||||
марта: 3,
|
|
||||||
апреля: 4,
|
|
||||||
мая: 5,
|
|
||||||
июня: 6,
|
|
||||||
июля: 7,
|
|
||||||
августа: 8,
|
|
||||||
сентября: 9,
|
|
||||||
октября: 10,
|
|
||||||
ноября: 11,
|
|
||||||
декабря: 12,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [day, month, year] = dateString.split(' ');
|
getToken = (header: any) => {
|
||||||
if (day && months[month] && year) {
|
const cookies = header.map['set-cookie'] || '';
|
||||||
return dayjs(year + '-' + months[month] + '-' + day).format('LL');
|
for (const cookie of cookies.split('; ') as string[]) {
|
||||||
|
const [key, val] = cookie.split('=');
|
||||||
|
if (key === 'XSRF-TOKEN') {
|
||||||
|
headers['X-XSRF-TOKEN'] = decodeURIComponent(val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return dateString || null;
|
if (!headers['X-XSRF-TOKEN']) throw new Error('Failed to find the token');
|
||||||
};
|
};
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
@@ -156,104 +170,10 @@ class freedlit implements Plugin.PluginBase {
|
|||||||
value: 'popular',
|
value: 'popular',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'По популярности', value: 'popular' },
|
{ label: 'По популярности', value: 'popular' },
|
||||||
{ label: 'По количеству комментариев', value: 'comments' },
|
{ label: 'последние обновления', value: 'updated' },
|
||||||
{ label: 'По количеству лайков', value: 'likes' },
|
|
||||||
{ label: 'По новизне', value: 'recent' },
|
{ label: 'По новизне', value: 'recent' },
|
||||||
{ label: 'По просмотрам', value: 'views' },
|
{ label: 'По просмотрам', value: 'views' },
|
||||||
],
|
{ label: 'По количеству лайков', value: 'likes' },
|
||||||
type: FilterTypes.Picker,
|
|
||||||
},
|
|
||||||
genre: {
|
|
||||||
label: 'Жанры:',
|
|
||||||
value: { include: [], exclude: [] },
|
|
||||||
options: [
|
|
||||||
{ label: 'Любой жанр', value: 'all' },
|
|
||||||
{ label: 'Альтернативная история', value: 'alternative-history' },
|
|
||||||
{ label: 'Антиутопия', value: 'dystopia' },
|
|
||||||
{ label: 'Бизнес-литература', value: 'business-literature' },
|
|
||||||
{ label: 'Боевая фантастика', value: 'combat-fiction' },
|
|
||||||
{ label: 'Боевик', value: 'action' },
|
|
||||||
{ label: 'Боевое фэнтези', value: 'combat-fantasy' },
|
|
||||||
{ label: 'Бояръ-Аниме', value: 'boyar-anime' },
|
|
||||||
{ label: 'Героическая фантастика', value: 'heroic-fiction' },
|
|
||||||
{ label: 'Героическое фэнтези', value: 'heroic-fantasy' },
|
|
||||||
{ label: 'Городское фэнтези', value: 'urban-fantasy' },
|
|
||||||
{ label: 'Гримдарк', value: 'grimdark' },
|
|
||||||
{ label: 'Детектив', value: 'mystery' },
|
|
||||||
{ label: 'Детская литература', value: 'kids-literature' },
|
|
||||||
{ label: 'Документальная проза', value: 'biography' },
|
|
||||||
{ label: 'Историческая проза', value: 'historical-fiction' },
|
|
||||||
{ label: 'Исторический детектив', value: 'historical-mystery' },
|
|
||||||
{
|
|
||||||
label: 'Исторический любовный роман',
|
|
||||||
value: 'historical-romantic-novel',
|
|
||||||
},
|
|
||||||
{ label: 'Историческое фэнтези', value: 'historical-fantasy' },
|
|
||||||
{ label: 'Киберпанк', value: 'cyberpunk' },
|
|
||||||
{ label: 'Космическая фантастика', value: 'cosmic-fiction' },
|
|
||||||
{ label: 'ЛитРПГ', value: 'litrpg' },
|
|
||||||
{ label: 'Лоу / Низкое фэнтези', value: 'low-fantasy' },
|
|
||||||
{ label: 'Любовное фэнтези', value: 'romantic-fantasy' },
|
|
||||||
{ label: 'Любовный роман', value: 'romantic-novel' },
|
|
||||||
{ label: 'Мистика', value: 'mystic' },
|
|
||||||
{ label: 'Мистический детектив', value: 'mystic-detective' },
|
|
||||||
{ label: 'Научная фантастика', value: 'science-fiction' },
|
|
||||||
{ label: 'Подростковая проза', value: 'young-adult' },
|
|
||||||
{ label: 'Политический роман', value: 'political-romance' },
|
|
||||||
{ label: 'Попаданцы', value: 'accidental-travel' },
|
|
||||||
{ label: 'Попаданцы в магические миры', value: 'magic-worlds-travel' },
|
|
||||||
{ label: 'Попаданцы во времени', value: 'time-travel' },
|
|
||||||
{ label: 'Порнотика', value: 'pornotica' },
|
|
||||||
{ label: 'Постапокалипсис', value: 'post-apocalypse' },
|
|
||||||
{ label: 'Поэзия', value: 'poetry' },
|
|
||||||
{ label: 'Приключения', value: 'adventure' },
|
|
||||||
{ label: 'Публицистика', value: 'journalism' },
|
|
||||||
{ label: 'Пьеса', value: 'play' },
|
|
||||||
{ label: 'Развитие личности', value: 'how-to-book' },
|
|
||||||
{ label: 'Разное', value: 'other' },
|
|
||||||
{ label: 'Реализм', value: 'Realism' },
|
|
||||||
{ label: 'РеалРПГ', value: 'realrpg' },
|
|
||||||
{ label: 'Репликация', value: 'replication' },
|
|
||||||
{ label: 'Романтическая эротика', value: 'romantic-erotic-fiction' },
|
|
||||||
{ label: 'Сказка', value: 'fairy-tale' },
|
|
||||||
{ label: 'Слэш', value: 'slash' },
|
|
||||||
{ label: 'Современная проза', value: 'modern-prose' },
|
|
||||||
{ label: 'Современный любовный роман', value: 'modern-romantic-novel' },
|
|
||||||
{ label: 'Социальная фантастика', value: 'social-fiction' },
|
|
||||||
{ label: 'Стимпанк', value: 'steampunk' },
|
|
||||||
{ label: 'Сценарий', value: 'scenario' },
|
|
||||||
{ label: 'Сюаньхуань', value: 'xuanhuan' },
|
|
||||||
{ label: 'Сянься', value: 'xianxia' },
|
|
||||||
{ label: 'Тёмное фэнтези', value: 'dark-fantasy' },
|
|
||||||
{ label: 'Триллер', value: 'thriller' },
|
|
||||||
{ label: 'Уся', value: 'wuxia' },
|
|
||||||
{ label: 'Фантастика', value: 'fiction' },
|
|
||||||
{ label: 'Фантастический детектив', value: 'mystery-fiction' },
|
|
||||||
{ label: 'Фанфик', value: 'fan-fiction' },
|
|
||||||
{ label: 'Фемслэш', value: 'femslash' },
|
|
||||||
{ label: 'Фэнтези', value: 'fantasy' },
|
|
||||||
{ label: 'Хоррор', value: 'horror' },
|
|
||||||
{ label: 'Шпионский детектив', value: 'spy-crime' },
|
|
||||||
{ label: 'Эпическое фэнтези', value: 'epic-fantasy' },
|
|
||||||
{ label: 'Эротика', value: 'erotic-fiction' },
|
|
||||||
{ label: 'Эротическая фантастика', value: 'erotic-fiction' },
|
|
||||||
{ label: 'Эротический фанфик', value: 'erotic-fan-fiction' },
|
|
||||||
{ label: 'Эротическое фэнтези', value: 'erotic-fantasy' },
|
|
||||||
{ label: 'Этническое фэнтези', value: 'ethnic-fantasy' },
|
|
||||||
{ label: 'Юмор', value: 'humor' },
|
|
||||||
{ label: 'Юмористическая фантастика', value: 'humor-fiction' },
|
|
||||||
{ label: 'Юмористическое фэнтези', value: 'humor-fantasy' },
|
|
||||||
{ label: 'RPS', value: 'rps' },
|
|
||||||
],
|
|
||||||
type: FilterTypes.ExcludableCheckboxGroup,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Статус:',
|
|
||||||
value: 'all',
|
|
||||||
options: [
|
|
||||||
{ label: 'Любой статус', value: 'all' },
|
|
||||||
{ label: 'В процессе', value: 'in-process' },
|
|
||||||
{ label: 'Завершено', value: 'finished' },
|
|
||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
@@ -267,16 +187,124 @@ class freedlit implements Plugin.PluginBase {
|
|||||||
],
|
],
|
||||||
type: FilterTypes.Picker,
|
type: FilterTypes.Picker,
|
||||||
},
|
},
|
||||||
adult: {
|
hideAdult: {
|
||||||
label: 'Возрастные ограничения:',
|
label: 'Скрыть 18+',
|
||||||
value: 'hide',
|
value: true,
|
||||||
options: [
|
type: FilterTypes.Switch,
|
||||||
{ label: 'Скрыть 18+', value: 'hide' },
|
|
||||||
{ label: 'Показать +18', value: 'show' },
|
|
||||||
],
|
|
||||||
type: FilterTypes.Picker,
|
|
||||||
},
|
},
|
||||||
} satisfies Filters;
|
} satisfies Filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new freedlit();
|
export default new freedlit();
|
||||||
|
|
||||||
|
interface Books {
|
||||||
|
current_page: number;
|
||||||
|
data: DataEntity[];
|
||||||
|
first_page_url: string;
|
||||||
|
from: number;
|
||||||
|
last_page: number;
|
||||||
|
last_page_url: string;
|
||||||
|
next_page_url: string;
|
||||||
|
path: string;
|
||||||
|
per_page: number;
|
||||||
|
prev_page_url?: null;
|
||||||
|
to: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
interface DataEntity {
|
||||||
|
id: number;
|
||||||
|
item_id: number;
|
||||||
|
type: string;
|
||||||
|
form_id: number;
|
||||||
|
title: string;
|
||||||
|
authors: string;
|
||||||
|
cover: string;
|
||||||
|
access: string;
|
||||||
|
language: string;
|
||||||
|
rating: number;
|
||||||
|
intermediate_rating?: number;
|
||||||
|
first_published: string;
|
||||||
|
chapter_updated_at: string;
|
||||||
|
adults: number;
|
||||||
|
exclusive: number;
|
||||||
|
status: number;
|
||||||
|
total_views: number;
|
||||||
|
total_likes: number;
|
||||||
|
show_main_widget: number;
|
||||||
|
is_audio: boolean;
|
||||||
|
authors_names?: AuthorsNamesEntity[];
|
||||||
|
client_price?: number | null;
|
||||||
|
client_discount?: null;
|
||||||
|
seriesBook: boolean;
|
||||||
|
series_title: string;
|
||||||
|
main_author_link: string;
|
||||||
|
publisher_link?: null;
|
||||||
|
published_chapters_total: number;
|
||||||
|
total_characters: string;
|
||||||
|
total_author_sheet: number;
|
||||||
|
total_critics: number;
|
||||||
|
total_recommendations: number;
|
||||||
|
form_name: string;
|
||||||
|
genres?: GenresEntity[] | null;
|
||||||
|
tags?: TagsEntity[];
|
||||||
|
series_book_num: number;
|
||||||
|
series_book_id?: number | null;
|
||||||
|
annotation: string;
|
||||||
|
total_comments: number;
|
||||||
|
total_libraries: number;
|
||||||
|
}
|
||||||
|
interface AuthorsNamesEntity {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
interface GenresEntity {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
interface TagsEntity {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface chaptersData {
|
||||||
|
id: number;
|
||||||
|
header: string;
|
||||||
|
first_published_formated: string;
|
||||||
|
intro_snippet_end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface chapterContent {
|
||||||
|
id: number;
|
||||||
|
book_id: number;
|
||||||
|
header: string;
|
||||||
|
content: string;
|
||||||
|
number: number;
|
||||||
|
user_files?: null;
|
||||||
|
intro_snippet_end: number;
|
||||||
|
status: number;
|
||||||
|
first_published: number;
|
||||||
|
auto_publication?: null;
|
||||||
|
first_published_at?: null;
|
||||||
|
is_chapter_avalaible: boolean;
|
||||||
|
total_characters: number;
|
||||||
|
total_characters_clear: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface book {
|
||||||
|
id: number;
|
||||||
|
cover?: string | null;
|
||||||
|
title?: string | null;
|
||||||
|
type: string;
|
||||||
|
authors_names?: AuthorsNamesEntity[];
|
||||||
|
form_name?: string | null;
|
||||||
|
relevance: number;
|
||||||
|
for_blog?: number | null;
|
||||||
|
for_book?: number | null;
|
||||||
|
items_count?: number | null;
|
||||||
|
name?: string | null;
|
||||||
|
blog_category_id?: number | null;
|
||||||
|
author_name?: string | null;
|
||||||
|
user_link?: string | null;
|
||||||
|
img?: string | null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class Jaomix implements Plugin.PluginBase {
|
|||||||
id = 'jaomix.ru';
|
id = 'jaomix.ru';
|
||||||
name = 'Jaomix';
|
name = 'Jaomix';
|
||||||
site = 'https://jaomix.ru';
|
site = 'https://jaomix.ru';
|
||||||
version = '1.0.0';
|
version = '1.0.1';
|
||||||
icon = 'src/ru/jaomix/icon.png';
|
icon = 'src/ru/jaomix/icon.png';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
@@ -126,9 +126,10 @@ class Jaomix implements Plugin.PluginBase {
|
|||||||
const loadedCheerio = parseHTML(body);
|
const loadedCheerio = parseHTML(body);
|
||||||
|
|
||||||
loadedCheerio('div[class="adblock-service"]').remove();
|
loadedCheerio('div[class="adblock-service"]').remove();
|
||||||
const chapterText = loadedCheerio('div[class="entry-content"]').html();
|
const chapterText =
|
||||||
|
loadedCheerio('div[class="entry-content"]').html() || '';
|
||||||
|
|
||||||
return chapterText || '';
|
return chapterText.replace(/<a[^>]*>(.*?)<\/a>/gi, '$1');
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchNovels(
|
async searchNovels(
|
||||||
|
|||||||
@@ -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.2';
|
version = '1.0.3';
|
||||||
|
|
||||||
async popularNovels(
|
async popularNovels(
|
||||||
pageNo: number,
|
pageNo: number,
|
||||||
@@ -20,7 +20,7 @@ class ReN 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 + '/api/search/catalog/?count=30&ordering=-';
|
let url = this.site + '/api/v2/search/catalog/?count=30&ordering=-';
|
||||||
url += showLatestNovels ? 'chapter_date' : filters?.sort?.value || 'rating';
|
url += showLatestNovels ? 'chapter_date' : filters?.sort?.value || 'rating';
|
||||||
|
|
||||||
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
Object.entries(filters || {}).forEach(([type, { value }]: any) => {
|
||||||
@@ -41,12 +41,13 @@ class ReN implements Plugin.PluginBase {
|
|||||||
|
|
||||||
url += '&page=' + pageNo;
|
url += '&page=' + pageNo;
|
||||||
|
|
||||||
const { content }: { content: responseNovels[] } = await fetchApi(url).then(
|
const { results }: { results: responseNovels[] } = await fetchApi(url).then(
|
||||||
res => res.json(),
|
res => res.json(),
|
||||||
);
|
);
|
||||||
const novels: Plugin.NovelItem[] = content.map(novel => ({
|
const novels: Plugin.NovelItem[] = results.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.cover.high || novel.cover.mid || novel.cover.low),
|
||||||
path: novel.dir,
|
path: novel.dir,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -63,7 +64,9 @@ class ReN implements Plugin.PluginBase {
|
|||||||
name: content.main_name || content.secondary_name || content.another_name,
|
name: content.main_name || content.secondary_name || content.another_name,
|
||||||
summary: content.description,
|
summary: content.description,
|
||||||
cover:
|
cover:
|
||||||
this.site + (content.img.high || content.img.mid || content.img.low),
|
this.site +
|
||||||
|
'/media/' +
|
||||||
|
(content.cover.high || content.cover.mid || content.cover.low),
|
||||||
status:
|
status:
|
||||||
content.status.name === 'Продолжается'
|
content.status.name === 'Продолжается'
|
||||||
? NovelStatus.Ongoing
|
? NovelStatus.Ongoing
|
||||||
@@ -80,7 +83,7 @@ class ReN implements Plugin.PluginBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalPages =
|
const totalPages =
|
||||||
(content.branches?.[0]?.count_chapters || content.count_chapters) / 50;
|
(content.branches?.[0]?.count_chapters || content.count_chapters) / 100;
|
||||||
const branch_id = content.branches?.[0]?.id || content.id;
|
const branch_id = content.branches?.[0]?.id || content.id;
|
||||||
const chapters: Plugin.ChapterItem[] = [];
|
const chapters: Plugin.ChapterItem[] = [];
|
||||||
|
|
||||||
@@ -92,7 +95,7 @@ class ReN implements Plugin.PluginBase {
|
|||||||
this.site +
|
this.site +
|
||||||
'/api/titles/chapters/?branch_id=' +
|
'/api/titles/chapters/?branch_id=' +
|
||||||
branch_id +
|
branch_id +
|
||||||
'&ordering=index&user_data=1&count=50&page=' +
|
'&ordering=index&user_data=1&count=100&page=' +
|
||||||
(page + 1),
|
(page + 1),
|
||||||
).then(res => res.json());
|
).then(res => res.json());
|
||||||
let skip = false;
|
let skip = false;
|
||||||
@@ -133,16 +136,17 @@ class ReN implements Plugin.PluginBase {
|
|||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
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/v2/search/?query=${searchTerm}&count=100&field=titles&page=${pageNo}`;
|
||||||
const { content }: { content: responseNovels[] } = await fetchApi(url).then(
|
const { results }: { results: responseNovels[] } = await fetchApi(url).then(
|
||||||
res => res.json(),
|
res => res.json(),
|
||||||
);
|
);
|
||||||
const novels: Plugin.NovelItem[] = [];
|
const novels: Plugin.NovelItem[] = [];
|
||||||
|
|
||||||
content.forEach(novel =>
|
results.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.cover.high || novel.cover.mid || novel.cover.low),
|
||||||
path: novel.dir,
|
path: novel.dir,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -951,27 +955,25 @@ export default new ReN();
|
|||||||
|
|
||||||
type responseNovels = {
|
type responseNovels = {
|
||||||
id: number;
|
id: number;
|
||||||
secondary_name: string;
|
|
||||||
main_name: string;
|
main_name: string;
|
||||||
|
secondary_name: string;
|
||||||
|
type: ItemEntity;
|
||||||
|
status: ItemEntity;
|
||||||
|
translate_status: ItemEntity;
|
||||||
dir: string;
|
dir: string;
|
||||||
is_licensed: boolean;
|
|
||||||
issue_year: number;
|
issue_year: number;
|
||||||
avg_rating: string;
|
avg_rating: string;
|
||||||
admin_rating: string;
|
|
||||||
type: string;
|
|
||||||
total_views: number;
|
|
||||||
total_votes: number;
|
total_votes: number;
|
||||||
cover_high: string;
|
total_views: number;
|
||||||
cover_mid: string;
|
count_bookmarks: number;
|
||||||
cover_low: string;
|
is_licensed: boolean;
|
||||||
|
is_legal: boolean;
|
||||||
|
cover: Img;
|
||||||
|
count_chapters: number;
|
||||||
is_yaoi: boolean;
|
is_yaoi: boolean;
|
||||||
is_erotic: boolean;
|
is_erotic: boolean;
|
||||||
bookmark_type?: null;
|
bookmark_type: null;
|
||||||
genres?: ItemEntity[] | null;
|
is_forbidden: boolean;
|
||||||
img: Img;
|
|
||||||
categories?: ItemEntity[] | null;
|
|
||||||
rus_name?: string;
|
|
||||||
en_name?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Img = {
|
type Img = {
|
||||||
@@ -982,7 +984,7 @@ type Img = {
|
|||||||
|
|
||||||
type responseNovel = {
|
type responseNovel = {
|
||||||
id: number;
|
id: number;
|
||||||
img: Img;
|
cover: Img;
|
||||||
secondary_name: string;
|
secondary_name: string;
|
||||||
main_name: string;
|
main_name: string;
|
||||||
another_name: string;
|
another_name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user