From eeda61b22542a82a5c47793d9c591c0eec9175a9 Mon Sep 17 00:00:00 2001 From: John Ramberger Date: Thu, 23 Jan 2025 04:01:19 -0500 Subject: [PATCH] feat: add MangaTR new source (#1452) --- public/static/src/tr/mangatr/icon.png | Bin 0 -> 11035 bytes src/plugins/turkish/MangaTR.ts | 384 ++++++++++++++++++++++++++ 2 files changed, 384 insertions(+) create mode 100644 public/static/src/tr/mangatr/icon.png create mode 100644 src/plugins/turkish/MangaTR.ts diff --git a/public/static/src/tr/mangatr/icon.png b/public/static/src/tr/mangatr/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8c2dba7095b99fb5d6120336750aee29ec6b62da GIT binary patch literal 11035 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 T3hV!9R|r`tCCPemlhFSG^W1g3 literal 0 HcmV?d00001 diff --git a/src/plugins/turkish/MangaTR.ts b/src/plugins/turkish/MangaTR.ts new file mode 100644 index 0000000..bd98723 --- /dev/null +++ b/src/plugins/turkish/MangaTR.ts @@ -0,0 +1,384 @@ +import { CheerioAPI, load as parseHTML } from 'cheerio'; +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@typings/plugin'; +import { Filters, FilterTypes } from '@libs/filterInputs'; + +class MangaTR implements Plugin.PluginBase { + id = 'mangatr'; + name = 'MangaTR'; + icon = 'src/tr/mangatr/icon.png'; + site = 'https://manga-tr.com/'; + version = '1.0.0'; + + opts = { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'x-requested-with': 'XMLHttpRequest', + }, + }; + + /** + * @param novelPath + * @returns novel metadata and its first page + */ + async parseNovel(novelPath: string): Promise { + const body = await fetchApi(this.site + novelPath).then(r => r.text()); + const loadedCheerio = parseHTML(body); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: loadedCheerio('#tables').text(), + cover: loadedCheerio( + '#myCarousel > div.container > div.col-lg-4.col-sm-4 > img', + ).attr('src'), + status: loadedCheerio( + '#tab1 > table:nth-child(2) > tbody > tr:nth-child(2) > td:nth-last-child(2) > a', + ).text(), + chapters: [], + author: loadedCheerio( + '#tab1 > table:nth-child(3) > tbody > tr:nth-child(2) > td:nth-child(1) > a', + ) + .map((i, el) => loadedCheerio(el).text()) + .get() + .join(','), + artist: loadedCheerio( + '#tab1 > table:nth-child(3) > tbody > tr:nth-child(2) > td:nth-child(2) > a', + ) + .map((i, el) => loadedCheerio(el).text()) + .get() + .join(','), + genres: loadedCheerio( + '#tab1 > table:nth-child(3) > tbody > tr:nth-child(2) > td:nth-child(3) > a', + ) + .map((i, el) => loadedCheerio(el).text()) + .get() + .join(','), + }; + + const summary = loadedCheerio('#tab1 > div.well'); + // remove h3 and div from children + summary.children().remove('h3, div'); + novel.summary = summary.text().trim(); + + const chapters: Plugin.ChapterItem[] = []; + + // path = manga-the-last-adventurer.html + // title = the-last-adventurer + const title = novelPath.split('.html')[0].slice(6); + + const url = `${this.site}cek/fetch_pages_manga.php?manga_cek=${title}`; + + // make post request to url with form data page=1 + const response = await fetchApi(url, { + ...this.opts, + body: 'page=1', + }); + + const page1 = parseHTML(await response.text()); + + const firstPage = 1; + const lastPage = parseInt( + page1('a[title="Last"]').first().attr('data-page') ?? '1', + ); + + let pageDatas = await Promise.all( + Array.from({ length: lastPage - firstPage }, (_, i) => { + return fetchApi(url, { + ...this.opts, + body: `page=${firstPage + i + 1}`, + }).then(r => r.text()); + }), + ).then(pages => pages.map(p => parseHTML(p))); + + pageDatas = [page1, ...pageDatas]; + + // Most titles have the year number in them (e.g. (2021)) and chapter titles do not, so remove + const novelTitle = novel.name + .toLocaleLowerCase() + .replace(/\([0-9]+\)/g, '') + .trim(); + + // Go through each page and get the chapters + for (const page of pageDatas) { + // For each chapter + page('body > ul > table > tbody > tr').each((_, el) => { + const chap = page(el); + + const chapTitle1 = chap.find('td:nth-child(1) > a').text(); + const updatedChapTitle1 = chapTitle1 + .toLocaleLowerCase() + .replace(novelTitle, 'Ch') + .trim(); + const chapTitle2 = chap.find('td:nth-child(1) > div').text(); + + const chapPath = chap.find('td:nth-child(1) > a').attr('href') ?? ''; + + if (chapPath === '') return; + + chapters.push({ + name: + chapTitle2 !== '' + ? `${updatedChapTitle1}: ${chapTitle2}` + : updatedChapTitle1, + path: chapPath, + chapterNumber: parseFloat(chapTitle1.replace(/[^0-9.]/g, '')), + }); + }); + } + + if (chapters.length > 0) { + novel.chapters = chapters.reverse(); + } + return novel; + } + + popularNovels( + pageNo: number, + { showLatestNovels, filters }: Plugin.PopularNovelsOptions, + ): Promise { + const params = new URLSearchParams(); + params.append('page', pageNo.toString()); + if (showLatestNovels == true) { + params.append('sort', 'last_update'); + params.append('sort_type', 'DESC'); + } else { + params.append('durum', filters.status.value.toString()); // status + params.append('ceviri', ''); // translation status + params.append('yas', filters.age.value.toString()); // age + params.append('icerik', '2'); // content type -> always novel (2) + params.append('tur', filters.genre.value.toString()); // genre (only 1) + params.append('sort', filters.sort.value.toString()); + params.append('sort_type', filters.sort_type.value.toString()); + } + + const url = `${this.site}manga-list-sayfala.html?${params.toString()}`; + + const parseNovels = (loadedCheerio: CheerioAPI): Plugin.NovelItem[] => { + return loadedCheerio( + '#myCarousel > div.container > div:nth-child(3) > div.col-lg-9.col-md-8 > div.col-md-12', + ) + .map((_, el) => { + const novel = loadedCheerio(el); + return { + name: novel.find('#tables').text(), + path: novel.find('#tables > a').attr('href') ?? '', + cover: novel.find('img.img-thumb').first().attr('src') ?? '', + }; + }) + .toArray(); + }; + + return fetchApi(url) + .then(r => r.text()) + .then(body => { + const loadedCheerio = parseHTML(body); + return parseNovels(loadedCheerio); + }); + } + + async parseChapter(chapterPath: string): Promise { + const body = await fetchApi(this.site + chapterPath).then(r => r.text()); + const loadedCheerio = parseHTML(body); + + const content = loadedCheerio('#well'); + + return content.html() ?? ''; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + // Not paginated, so we should paginate results ourselves to prevent overloading + const ITEMS_PER_PAGE = 50; + + const params = new URLSearchParams(); + params.append('icerik', searchTerm); + + const url = `${this.site}arama.html?${params.toString()}`; + + const parseNovels = async ( + loadedCheerio: CheerioAPI, + ): Promise => { + const novels: Plugin.NovelItem[] = []; + + let curr = 0; + + for (const el of loadedCheerio('div.char > a + span').toArray()) { + // Done! + if (novels.length === ITEMS_PER_PAGE) break; + // Skip if not a novel + if ( + loadedCheerio(el).text().trim().toLowerCase() != 'novel' && + loadedCheerio(el).next().text().trim().toLowerCase() != 'novel' + ) { + continue; + } + // Skip for pagination + if ((pageNo - 1) * ITEMS_PER_PAGE > curr) { + curr++; + continue; + } + + const novelCheerio = loadedCheerio(el).prev(); + + const mangaSlug = novelCheerio.attr('manga-slug') ?? ''; + + novels.push({ + name: novelCheerio.text(), + path: novelCheerio.attr('href') ?? '', + cover: mangaSlug, // NOTE: This slug gets replaced with the actual cover image later (see below) + }); + } + + // Get all cover images in parallel + return await Promise.all( + novels.map(async novel => { + const url = `${this.site}app/manga/controllers/cont.pop.php`; + const response = await fetchApi(url, { + ...this.opts, + body: `slug=${novel.cover}`, + }); + const body = await response.text(); + const imgCheerio = parseHTML(body); + const img = imgCheerio('img').first().attr('src'); + + novel.cover = img; + return novel; + }), + ); + }; + + return fetchApi(url) + .then(r => r.text()) + .then(body => { + const loadedCheerio = parseHTML(body); + return parseNovels(loadedCheerio); + }); + } + + resolveUrl(path: string, isNovel?: boolean): string { + return this.site + path; + } + + filters = { + sort: { + value: 'views', + label: 'Sırala', + options: [ + { label: 'Adı', value: 'name' }, + { label: 'Popülarite', value: 'views' }, + { label: 'Son Güncelleme', value: 'last_update' }, + ], + type: FilterTypes.Picker, + }, + sort_type: { + value: 'DESC', + label: 'Sırala Türü', + options: [ + { label: 'ASC', value: 'ASC' }, + { label: 'DESC', value: 'DESC' }, + ], + type: FilterTypes.Picker, + }, + status: { + value: '', + label: 'Durum', + options: [ + { label: 'Hepsi', value: '' }, + { label: 'Yayınlanması Tamamlanan', value: '1' }, + { label: 'Devam Eden', value: '2' }, + ], + type: FilterTypes.Picker, + }, + translation: { + value: '', + label: 'Çeviri Durumu', + options: [ + { label: 'Hepsi', value: '' }, + { label: 'Çevirisi Tamamlanan', value: '1' }, + { label: 'Devam Eden', value: '4' }, + { label: 'Bırakılan', value: '2' }, + { label: 'Olmayan', value: '3' }, + ], + type: FilterTypes.Picker, + }, + age: { + value: '', + label: 'Yas', + options: [ + { label: 'Hepsi', value: '' }, + { label: '+16', value: '16' }, + { label: '+18', value: '18' }, + ], + type: FilterTypes.Picker, + }, + genre: { + value: '', + label: 'Tür', + options: [ + { label: 'Hepsi', value: '' }, + { label: '4 Koma', value: '4_Koma' }, + { label: 'Action', value: 'Action' }, + { label: 'Adventure', value: 'Adventure' }, + { label: 'Aliens', value: 'Aliens' }, + { label: 'Art', value: 'Art' }, + { label: 'Biography', value: 'Biography' }, + { label: 'Bishoujo', value: 'Bishoujo' }, + { label: 'Bishounen', value: 'Bishounen' }, + { label: 'Comedy', value: 'Comedy' }, + { label: 'Crime', value: 'Crime' }, + { label: 'Demons', value: 'Demons' }, + { label: 'Doujinshi', value: 'Doujinshi' }, + { label: 'Drama', value: 'Drama' }, + { label: 'Ecchi', value: 'Ecchi' }, + { label: 'Fantasy', value: 'Fantasy' }, + { label: 'Gore', value: 'Gore' }, + { label: 'Harem', value: 'Harem' }, + { label: 'History', value: 'History' }, + { label: 'Horror', value: 'Horror' }, + { label: 'Isekai', value: 'Isekai' }, + { label: 'Josei', value: 'Josei' }, + { label: 'Magic', value: 'Magic' }, + { label: 'Manhua', value: 'Manhua' }, + { label: 'Manhwa', value: 'Manhwa' }, + { label: 'Martial', value: 'Martial' }, + { label: 'Mecha', value: 'Mecha' }, + { label: 'Military', value: 'Military' }, + { label: 'Miscellaneous', value: 'Miscellaneous' }, + { label: 'Monster', value: 'Monster' }, + { label: 'Musical', value: 'Musical' }, + { label: 'Mystery', value: 'Mystery' }, + { label: 'Novel', value: 'Novel' }, + { label: 'Nudity', value: 'Nudity' }, + { label: 'One Shot', value: 'One_Shot' }, + { label: 'Romance', value: 'Romance' }, + { label: 'Psychological', value: 'Psychological' }, + { label: 'Seinen', value: 'Seinen' }, + { label: 'School', value: 'School' }, + { label: 'Sci fi', value: 'Sci_fi' }, + { label: 'Short', value: 'Short' }, + { label: 'Shoujo', value: 'Shoujo' }, + { label: 'Shoujo Ai', value: 'Shoujo_Ai' }, + { label: 'Shounen', value: 'Shounen' }, + { label: 'Shounen Ai', value: 'Shounen_Ai' }, + { label: 'Slice of life', value: 'Slice of life' }, + { label: 'Supernatural', value: 'Supernatural' }, + { label: 'Space', value: 'Space' }, + { label: 'Sports', value: 'Sports' }, + { label: 'Thriller', value: 'Thriller' }, + { label: 'Tragedy', value: 'Tragedy' }, + { label: 'Türkçe Novel', value: 'Türkçe Novel' }, + { label: 'Vampires', value: 'Vampires' }, + { label: 'Violence', value: 'Violence' }, + { label: 'War', value: 'War' }, + { label: 'Webtoon', value: 'Webtoon' }, + { label: 'Western', value: 'Western' }, + ], + type: FilterTypes.Picker, + }, + } satisfies Filters; +} + +export default new MangaTR();