Files
kodjodevf-mangayomi-extensions/javascript/manga/src/ru/mangalib.js
2024-12-01 05:17:01 +01:00

371 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const mangayomiSources = [{
"name": "Mangalib",
"lang": "ru",
"baseUrl": "https://mangalib.org/ru",
"apiUrl": "https://api.mangalib.me/api",
"iconUrl": "https://mangalib.org/static/images/logo/ml/icon-180.png",
"typeSource": "single",
"isManga": true,
"isNsfw": true,
"version": "0.0.1",
"dateFormat": "",
"dateFormatLocale": "",
"pkgPath": "manga/src/ru/mangalib.js"
}];
// filters: https://api.mangalib.me/api/constants?fields[]=genres&fields[]=tags&fields[]=types&fields[]=scanlateStatus&fields[]=status&fields[]=format&fields[]=ageRestriction
// directory: https://api.mangalib.me/api/manga?q=encodeURIComponent(query)&sort_by=(|chap_count|views|rate_avg|releaseDate|last_chapter_at|created_at|name|rus_name)&sort_type=(|asc)&page=2
// details: https://api.mangalib.me/api/manga/34466--jeonjijeog-dogja-sijeom_?fields[]=chap_count&fields[]=summary&fields[]=genres&fields[]=authors&fields[]=artists
// chapters: https://api.mangalib.me/api/manga/34466--jeonjijeog-dogja-sijeom_/chapters
// pages: https://api.mangalib.me/api/manga/34466--jeonjijeog-dogja-sijeom_/chapter?number=147&volume=1
// image servers: https://api.mangalib.me/api/constants?fields[]=imageServers
class DefaultExtension extends MProvider {
constructor () {
super();
this.client = new Client();
this.apiHeaders = {
'accept': '*/*',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'};
this.getFilterUrl = this.source.apiUrl + '/constants?fields[]=genres&fields[]=tags&fields[]=types&fields[]=scanlateStatus&fields[]=status&fields[]=format&fields[]=ageRestriction';
}
parseStatus(status) {
return {
"Онгоинг": 0,
"Завершён": 1,
"Приостановлен": 2,
"Выпуск прекращён": 3,
"Анонс": 4
}[status] ?? 5;
}
async parseMangaList(url) {
const res = await this.client.get(url, this.apiHeaders);
const json = JSON.parse(res.body);
let mangas = json.data.map(manga => ({
name: manga.rus_name,
imageUrl: manga.cover.default,
link: manga.slug_url
}));
return { "list": mangas, "hasNextPage": json.meta.has_next_page };
}
async getPopular(page) {
return await this.parseMangaList(this.source.apiUrl + `/manga?page=${page}`);
}
async getLatestUpdates(page) {
return await this.parseMangaList(this.source.apiUrl + `/manga?page=${page}&sort_by=last_chapter_at`);
}
async search(query, page, filters) {
let url = `${this.source.apiUrl}/manga?q=${encodeURIComponent(query)}`
// Search sometimes failed because filters were empty. I experienced this mostly on android...
if (!filters || filters.length == 0) {
return await this.parseMangaList(url + `&page=${page}`);
}
for (const filter of filters[0].state) {
if (filter.state == true)
url += `&types[]=${filter.value}`;
}
for (const filter of filters[1].state) {
if (filter.state == true)
url += `&caution[]=${filter.value}`;
}
const minChapF = filters[2].state[0];
const maxChapF = filters[2].state[1];
const minChap = minChapF.values[minChapF.state].value;
const maxChap = maxChapF.values[maxChapF.state].value;
url += minChap ? `&chap_count_min=${minChap}` : '';
url += maxChap ? `&chap_count_max=${maxChap}` : '';
const minYearF = filters[3].state[0];
const maxYearF = filters[3].state[1];
const minYear = minYearF.values[minYearF.state].value;
const maxYear = maxYearF.values[maxYearF.state].value;
url += minYear ? `&year_min=${minYear}` : '';
url += maxYear ? `&year_max=${maxYear}` : '';
for (const filter of filters[4].state) {
if (filter.state == 1)
url += `&genres[]=${filter.value}`;
else if (filter.state == 2)
url += `&genres_exclude[]=${filter.value}`;
}
for (const filter of filters[5].state) {
if (filter.state == true)
url += `&status[]=${filter.value}`;
}
for (const filter of filters[6].state) {
if (filter.state == true)
url += `&scanlate_status[]=${filter.value}`;
}
for (const filter of filters[7].state) {
if (filter.state == 1)
url += `&format[]=${filter.value}`;
else if (filter.state == 2)
url += `&format_exclude[]=${filter.value}`;
}
const sortVal = filters[8].values[filters[8].state.index].value;
const sortType = filters[8].state.ascending ? 'asc' : '';
url += sortVal ? `&sort_by=${sortVal}` : '';
url += sortType ? `&sort_type=${sortType}` : '';
return await this.parseMangaList(url + `&page=${page}`);
}
async getDetail(url) {
const infoRes = await this.client.get(`${this.source.apiUrl}/manga/${url}?fields[]=chap_count&fields[]=summary&fields[]=genres&fields[]=authors&fields[]=artists`, this.apiHeaders);
const chapterRes = await this.client.get(`${this.source.apiUrl}/manga/${url}/chapters`, this.apiHeaders);
const info = JSON.parse(infoRes.body).data;
const chapters = JSON.parse(chapterRes.body).data;
const chapterBaseUrl = `${this.source.apiUrl}/manga/${url}/chapter`;
return {
name: info.name,
imageUrl: info.cover.default,
author: info.authors.map(x => x.name).join(', '),
artist: info.artists.map(x => x.name).join(', '),
status: this.parseStatus(info.status.label),
description: info.summary,
genre: info.genres.map(x => x.name),
chapters: chapters.map(c => ({
name: `Том ${c.volume} Глава ${c.number}` + (c.name ? `: ${c.name}` : ''),
url: `${chapterBaseUrl}?number=${c.number}&volume=${c.volume}`,
dateUpload: new Date(c.branches[0].created_at).valueOf().toString(),
scanlator: c.branches[0].teams.map(x => x.name).join(', ')
})).reverse()
};
}
// For manga chapter pages
async getPageList(url) {
const serverId = new SharedPreferences().get('imageServer');
let res = await this.client.get(`${this.source.apiUrl}/constants?fields[]=imageServers`, this.apiHeaders);
const imageServers = JSON.parse(res.body).data.imageServers;
const imageServer = imageServers.find(x => x.id == serverId).url;
res = await this.client.get(url, this.apiHeaders);
const chapter = JSON.parse(res.body).data;
return chapter.pages.map(img => ({url: imageServer + img.url, headers: this.apiHeaders}));
}
getFilterList() {
const chapterCounts = ['1','5','10','20','30','40','50','100','200','500','1000','2000','5000','10000'].map(x => [x, x]);
const years = [...range(1980, new Date().getFullYear() + 1, -1), ...range(1930, 1971, -10)].map(x => {
x = x.toString();
return [x, x];
});
return [
{
type_name: "GroupFilter",
type: "type",
name: "Тип",
state: [
["Манга", 1],
["OEL-манга", 4],
["Манхва", 5],
["Маньхуа", 6],
["Руманга", 8],
["Комикс", 9]
].map(x => ({ type_name: 'CheckBox', name: x[0], value: `${x[1]}` }))
},
{
type_name: "GroupFilter",
type: "age_restriction",
name: "возрастной рейтинг",
state: [
["Нет", 0],
["6+", 1],
["12+", 2],
["16+", 3],
["18+", 4]
].map(x => ({ type_name: 'CheckBox', name: x[0], value: `${x[1]}` }))
},
{
type_name: "GroupFilter",
type: "chapter_count",
name: "Количество глав",
state: [
{
type_name: "SelectFilter",
type: "chap_count_min",
name: "от",
state: 0,
values: [['от', ''], ...chapterCounts].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
},
{
type_name: "SelectFilter",
type: "chap_count_max",
name: "до",
state: 0,
values: [['до', ''], ...chapterCounts].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
}
]
},
{
type_name: "GroupFilter",
type: "years",
name: "Год выпуска",
state: [
{
type_name: "SelectFilter",
type: "year_min",
name: "от",
state: 0,
values: [['от', ''], ...years].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
},
{
type_name: "SelectFilter",
type: "year_max",
name: "до",
state: 0,
values: [['до', ''], ...years].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
}
]
},
{
type_name: "GroupFilter",
type: "genre",
name: "Жанры",
state: [
["Арт", 32, false],
["Безумие", 91, false],
["Боевик", 34, false],
["Боевые искусства", 35, false],
["Вампиры", 36, false],
["Военное", 89, false],
["Гарем", 37, false],
["Гендерная интрига", 38, false],
["Героическое фэнтези", 39, false],
["Демоны", 81, false],
["Детектив", 40, false],
["Детское", 88, false],
["Драма", 43, false],
["Игра", 44, false],
["Исекай", 79, false],
["История", 45, false],
["Киберпанк", 46, false],
["Кодомо", 76, false],
["Комедия", 47, false],
["Космос", 83, false],
["Магия", 85, false],
["Махо-сёдзё", 48, false],
["Машины", 90, false],
["Меха", 49, false],
["Мистика", 50, false],
["Музыка", 80, false],
["Научная фантастика", 51, false],
["Омегаверс", 77, false],
["Пародия", 86, false],
["Повседневность", 52, false],
["Полиция", 82, false],
["Постапокалиптика", 53, false],
["Приключения", 54, false],
["Психология", 55, false],
["Романтика", 56, false],
["Самурайский боевик", 57, false],
["Сверхъестественное", 58, false],
["Сёдзё", 59, false],
["Сёдзё-ай", 60, false],
["Сёнэн-ай", 62, true],
["Спорт", 63, false],
["Супер сила", 87, false],
["Сэйнэн", 64, false],
["Трагедия", 65, false],
["Триллер", 66, false],
["Ужасы", 67, false],
["Фантастика", 68, false],
["Фэнтези", 69, false],
["Хентай", 84, false],
["Эротика", 71, true],
["Этти", 72, false]
].map(x => ({ type_name: 'TriState', name: x[0], value: `${x[1]}` }))
},
{
type_name: "GroupFilter",
type: "status",
name: "Статус титула",
state: [
["Онгоинг", 1],
["Завершён", 2],
["Анонс", 3],
["Приостановлен", 4],
["Выпуск прекращён", 5]
].map(x => ({ type_name: 'CheckBox', name: x[0], value: `${x[1]}` }))
},
{
type_name: "GroupFilter",
type: "translation_status",
name: "Статус перевода",
state: [
["Продолжается", 1],
["Завершён", 2],
["Заморожен", 3],
["Заброшен", 4]
].map(x => ({ type_name: 'CheckBox', name: x[0], value: `${x[1]}` }))
},
{
type_name: "GroupFilter",
type: "format",
name: "Формат выпуска",
state: [
["4-кома (Ёнкома)", 1],
["Сборник", 2],
["Додзинси", 3],
["В цвете", 4],
["Сингл", 5],
["Веб", 6],
["Вебтун", 7]
].map(x => ({ type_name: 'TriState', name: x[0], value: `${x[1]}` }))
},
{
type_name: "SortFilter",
type: "sort",
name: "Сортировать",
state: {
type_name: "SortState",
index: 0,
ascending: false
},
values: [
['По популярности', ''],
['По рейтингу', 'rate_avg'],
['По просмотрам', 'views'],
['Количество глав', 'chap_count'],
['дата релиза', 'releaseDate'],
['дата обновления', 'last_chapter_at'],
['дата добавления', 'created_at'],
['По названию (A-Z)', 'name'],
['По названию (A-Я)', 'rus_name']
].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
}
];
}
getSourcePreferences() {
const imageServers = ['Первый', 'Второй', 'Сжатия', 'Скачивание', 'Crop pages'];
const imageServerValuess = ['main', 'secondary', 'compress', 'download', 'crop'];
return [
{
key: 'imageServer',
listPreference: {
title: 'Image Server',
summary: '',
valueIndex: 0,
entries: imageServers,
entryValues: imageServerValuess
}
}
];
}
}
function range (first, last, step) {
if (last <= first)
return [];
if (!step) {
step = 1;
}
if (!last) {
last = first;
first = 0;
}
const start = step > 0 ? first : last - 1;
let length = Math.ceil((last - first) / Math.abs(step));
return Array.from(new Array(length), (x, i) => start + i * step);
}