From b44059323019d9d397cb6766f2f7880a160a5683 Mon Sep 17 00:00:00 2001 From: RndDev123 Date: Sat, 30 Nov 2024 15:16:06 +0100 Subject: [PATCH 1/3] New Source: Mangalib (RU) --- javascript/manga/src/ru/mangalib.js | 371 ++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 javascript/manga/src/ru/mangalib.js diff --git a/javascript/manga/src/ru/mangalib.js b/javascript/manga/src/ru/mangalib.js new file mode 100644 index 00000000..28e01ce1 --- /dev/null +++ b/javascript/manga/src/ru/mangalib.js @@ -0,0 +1,371 @@ +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 { + 'In corso': 0, + 'Finito': 1, + 'In pausa': 2, + 'Droppato': 3, + 'Cancellato': 3 + }[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: 2, + 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); +} \ No newline at end of file From 56d7e74ed452765f6b54eacb2f31ed32afccd8f9 Mon Sep 17 00:00:00 2001 From: RndDev123 Date: Sat, 30 Nov 2024 15:25:38 +0100 Subject: [PATCH 2/3] Mangalib: Fix status --- javascript/manga/src/ru/mangalib.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/manga/src/ru/mangalib.js b/javascript/manga/src/ru/mangalib.js index 28e01ce1..3b400f45 100644 --- a/javascript/manga/src/ru/mangalib.js +++ b/javascript/manga/src/ru/mangalib.js @@ -30,11 +30,11 @@ class DefaultExtension extends MProvider { } parseStatus(status) { return { - 'In corso': 0, - 'Finito': 1, - 'In pausa': 2, - 'Droppato': 3, - 'Cancellato': 3 + "Онгоинг": 0, + "Завершён": 1, + "Приостановлен": 2, + "Выпуск прекращён": 3, + "Анонс": 4 }[status] ?? 5; } async parseMangaList(url) { From a2a01b9cc2a0d9c6331903e1aa52e79564e2b41b Mon Sep 17 00:00:00 2001 From: RndDev123 Date: Sat, 30 Nov 2024 15:34:06 +0100 Subject: [PATCH 3/3] Mangalib: Change default image server --- javascript/manga/src/ru/mangalib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/manga/src/ru/mangalib.js b/javascript/manga/src/ru/mangalib.js index 3b400f45..d3fd6886 100644 --- a/javascript/manga/src/ru/mangalib.js +++ b/javascript/manga/src/ru/mangalib.js @@ -346,7 +346,7 @@ class DefaultExtension extends MProvider { listPreference: { title: 'Image Server', summary: '', - valueIndex: 2, + valueIndex: 0, entries: imageServers, entryValues: imageServerValuess }