From 5e4dd82b8d5ceb371956bd7d20970112a7bb6840 Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 4 Nov 2023 18:56:46 +0100 Subject: [PATCH] Minor daily update --- .../kotatsu/parsers/site/en/ManhwasMen.kt | 150 ++++++++++++++++ .../site/galleryadults/GalleryAdultsParser.kt | 4 +- .../site/galleryadults/all/DoujinDesuUk.kt | 58 ++++++ .../site/galleryadults/all/HentaiEnvy.kt | 13 -- .../site/galleryadults/all/HentaiEra.kt | 18 +- .../site/galleryadults/all/HentaiForce.kt | 11 -- .../site/galleryadults/all/HentaiFox.kt | 11 -- .../site/galleryadults/all/HentaiRox.kt | 13 ++ .../site/galleryadults/all/NHentaiParser.kt | 2 +- .../site/galleryadults/all/NHentaiUk.kt | 71 ++++++++ .../parsers/site/id/DoujinDesuParser.kt | 2 +- .../parsers/site/madara/MadaraParser.kt | 4 +- .../parsers/site/madara/ar/MangalinkNet.kt | 10 ++ .../parsers/site/madara/ar/MangalinkParser.kt | 2 +- .../parsers/site/madara/en/InstaManhwa.kt | 4 +- .../parsers/site/madara/en/ManhwaTop.kt | 4 +- .../site/mangareader/id/DoujinDesuRip.kt | 2 +- .../kotatsu/parsers/site/tr/TrWebtoon.kt | 168 ++++++++++++++++++ 18 files changed, 494 insertions(+), 53 deletions(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/DoujinDesuUk.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkNet.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt new file mode 100644 index 000000000..16d82661d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt @@ -0,0 +1,150 @@ +package org.koitharu.kotatsu.parsers.site.en + +import kotlinx.coroutines.coroutineScope +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI) +class ManhwasMen(context: MangaLoaderContext) : + PagedMangaParser(context, MangaSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) { + + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwas.men") + + override val sortOrders: Set + get() = EnumSet.of(SortOrder.POPULARITY) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + append("/manga-list") + append("?page=") + append(page) + when { + !query.isNullOrEmpty() -> { + append("&search=") + append(query.urlEncoded()) + } + + !tags.isNullOrEmpty() -> { + append("&genero=") + append(tag?.key.orEmpty()) + } + } + } + val doc = webClient.httpGet(url).parseHtml() + return doc.select("ul.animes li").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = li.selectFirst("img")?.src().orEmpty(), + title = li.selectFirst(".title")?.text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getTags(): Set { + val tags = webClient.httpGet("https://$domain/manga-list").parseHtml() + .selectLastOrThrow(".filter-bx .form-group select.custom-select").select("option").drop(1) + return tags.mapNotNullToSet { option -> + MangaTag( + key = option.attr("value").substringAfterLast("="), + title = option.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + manga.copy( + tags = doc.body().select(".genres a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast('='), + title = a.text(), + source = source, + ) + }, + description = doc.select(".sinopsis").html(), + state = when (doc.selectLast(".anime-type-peli")?.text()?.lowercase()) { + "ongoing" -> MangaState.ONGOING + "completed" -> MangaState.FINISHED + else -> null + }, + chapters = doc.select(".episodes-list li").mapChapters(reversed = true) { i, li -> + val url = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(url), + name = li.selectFirstOrThrow(".flex-grow-1 span").text(), + number = i + 1, + url = url, + scanlator = null, + uploadDate = parseChapterDate( + SimpleDateFormat("dd/MM/yyyy", sourceLocale), + li.selectLastOrThrow(".flex-grow-1 span").text(), + ), + branch = null, + source = source, + ) + }, + ) + } + + private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + val d = date?.lowercase() ?: return 0 + return when { + d.endsWith(" ago") -> parseRelativeDate(date) + else -> dateFormat.tryParse(date) + } + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + return when { + WordSet("second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minute", "minutes").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hour", "hours").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("week", "weeks").anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis + WordSet("month", "months").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml().requireElementById("chapter_imgs") + return doc.select("img").map { img -> + val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt index f751aba70..177c106f7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt @@ -130,9 +130,9 @@ internal abstract class GalleryAdultsParser( return root.parseTags() + tagLanguage } - protected open fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { + protected open fun Element.parseTags() = select("a").mapToSet { val key = it.attr("href").removeSuffix('/').substringAfterLast('/') - val name = it.selectFirst(".item_name")?.text() ?: it.text() + val name = it.html().substringBefore("<") MangaTag( key = key, title = name, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/DoujinDesuUk.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/DoujinDesuUk.kt new file mode 100644 index 000000000..cd406c74a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/DoujinDesuUk.kt @@ -0,0 +1,58 @@ +package org.koitharu.kotatsu.parsers.site.galleryadults.all + +import org.jsoup.nodes.Document +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser +import org.koitharu.kotatsu.parsers.util.* + +@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI) +internal class DoujinDesuUk(context: MangaLoaderContext) : + GalleryAdultsParser(context, MangaSource.DOUJINDESUUK, "doujindesu.uk", 50) { + override val selectGallery = ".gallery" + override val selectGalleryLink = "a" + override val selectGalleryTitle = ".caption" + override val pathTagUrl = "/tags?page=" + override val selectTags = "#tag-container" + override val selectTag = "div.tag-container:contains(Tags) span.tags" + override val selectAuthor = "div.tag-container:contains(Artists) a" + override val selectLanguageChapter = "div.tag-container:contains(Languages) a" + override val idImg = "image-container" + override val listLanguage = arrayOf( + "/english", + "/japanese", + "/chinese", + ) + + override fun parseMangaList(doc: Document): List { + val regexBrackets = Regex("\\[[^]]+]|\\([^)]+\\)") + val regexSpaces = Regex("\\s+") + return doc.select(selectGallery).map { div -> + val href = div.selectFirstOrThrow(selectGalleryLink).attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.select(selectGalleryTitle).text().replace(regexBrackets, "") + .replace(regexSpaces, " ") + .trim(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = isNsfwSource, + coverUrl = div.selectLastOrThrow(selectGalleryImg).src().orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getPageUrl(page: MangaPage): String { + val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() + val root = doc.body() + return root.requireElementById(idImg).selectFirstOrThrow("img").src() ?: root.parseFailed("Image src not found") + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt index 8e4e9b3cd..54e3e4b5b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt @@ -1,15 +1,12 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all -import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.util.domain -import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.removeSuffix import org.koitharu.kotatsu.parsers.util.urlEncoded @MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI) @@ -34,16 +31,6 @@ internal class HentaiEnvy(context: MangaLoaderContext) : "/portuguese", ) - override fun Element.parseTags() = select("a").mapToSet { - val key = it.attr("href").removeSuffix('/').substringAfterLast('/') - val name = it.html().substringBefore("<") - MangaTag( - key = key, - title = name, - source = source, - ) - } - override suspend fun getListPage( page: Int, query: String?, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt index cb811af93..d84f1cb53 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt @@ -1,15 +1,11 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all +import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser -import org.koitharu.kotatsu.parsers.util.domain -import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany -import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow -import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.urlEncoded +import org.koitharu.kotatsu.parsers.util.* @MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI) internal class HentaiEra(context: MangaLoaderContext) : @@ -28,6 +24,16 @@ internal class HentaiEra(context: MangaLoaderContext) : "/russian", ) + override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { + val key = it.attr("href").removeSuffix('/').substringAfterLast('/') + val name = it.selectFirst(".item_name")?.text() ?: it.text() + MangaTag( + key = key, + title = name, + source = source, + ) + } + override suspend fun getListPage( page: Int, query: String?, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt index b696e182d..2c3e8a034 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all -import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* @@ -35,16 +34,6 @@ internal class HentaiForce(context: MangaLoaderContext) : "/vietnamese", ) - override fun Element.parseTags() = select("a").mapToSet { - val key = it.attr("href").removeSuffix('/').substringAfterLast('/') - val name = it.html().substringBefore("<") - MangaTag( - key = key, - title = name, - source = source, - ) - } - override suspend fun getPageUrl(page: MangaPage): String { val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt index 866d60b6a..3b0cb30f4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all -import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* @@ -76,14 +75,4 @@ internal class HentaiFox(context: MangaLoaderContext) : } return parseMangaList(webClient.httpGet(url).parseHtml()) } - - override fun Element.parseTags() = select("a").mapToSet { - val key = it.attr("href").removeSuffix('/').substringAfterLast('/') - val name = it.html().substringBefore("<") - MangaTag( - key = key, - title = name, - source = source, - ) - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiRox.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiRox.kt index 717684c70..fe510247e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiRox.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiRox.kt @@ -1,9 +1,12 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all +import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser +import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.parsers.util.removeSuffix @MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI) internal class HentaiRox(context: MangaLoaderContext) : @@ -22,4 +25,14 @@ internal class HentaiRox(context: MangaLoaderContext) : "/korean", "/german", ) + + override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { + val key = it.attr("href").removeSuffix('/').substringAfterLast('/') + val name = it.selectFirst(".item_name")?.text() ?: it.text() + MangaTag( + key = key, + title = name, + source = source, + ) + } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt index fb3d68960..6ac010784 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.util.* -@MangaSourceParser("NHENTAI", "NHentai", type = ContentType.HENTAI) +@MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI) internal class NHentaiParser(context: MangaLoaderContext) : GalleryAdultsParser(context, MangaSource.NHENTAI, "nhentai.net", 25) { override val selectGallery = "div.index-container:not(.index-popular) .gallery, #related-container .gallery" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt new file mode 100644 index 000000000..0a8b8403f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt @@ -0,0 +1,71 @@ +package org.koitharu.kotatsu.parsers.site.galleryadults.all + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser +import org.koitharu.kotatsu.parsers.util.* +import java.lang.IllegalArgumentException + +@MangaSourceParser("NHENTAIUK", "NHentai.uk", type = ContentType.HENTAI) +internal class NHentaiUk(context: MangaLoaderContext) : + GalleryAdultsParser(context, MangaSource.NHENTAIUK, "nhentai.uk", 50) { + override val selectGallery = ".gallery" + override val selectGalleryLink = "a" + override val selectGalleryTitle = ".caption" + override val pathTagUrl = "/tags/popular?p=" + override val selectTags = "#tag-container" + override val selectTag = "div.tag-container:contains(Tags:) span.tags" + override val selectAuthor = "div.tag-container:contains(Artists:) a" + override val selectLanguageChapter = "div.tag-container:contains(Languages:) a" + override val idImg = "image-container" + override val listLanguage = arrayOf( + "/english", + "/french", + "/japanese", + "/chinese", + "/spanish", + "/russian", + "/korean", + "/german", + "/italian", + "/portuguese", + "/turkish", + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + if (!tags.isNullOrEmpty()) { + if (tag?.key == "languageKey") { + append("/language") + append(tag.title) + append("/?p=") + } else { + append("/tag/") + append(tag?.key) + append("/?p=") + } + } else if (!query.isNullOrEmpty()) { + throw IllegalArgumentException("Search is not supported by this source") + } else { + append("/home?p=") + } + append(page) + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + override suspend fun getPageUrl(page: MangaPage): String { + val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() + val root = doc.body() + return root.requireElementById(idImg).selectFirstOrThrow("img").src() ?: root.parseFailed("Image src not found") + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt index ed6000d01..047d439a0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt @@ -9,7 +9,7 @@ import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat import java.util.* -@MangaSourceParser("DOUJINDESU", "DoujinDesu", "id") +@MangaSourceParser("DOUJINDESU", "DoujinDesu.tv", "id") class DoujinDesuParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.DOUJINDESU, pageSize = 18) { override val configKeyDomain: ConfigKey.Domain diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt index 353fc4e26..d2f1fff34 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt @@ -379,8 +379,8 @@ internal abstract class MadaraParser( val doc = if (postReq) { val mangaId = document.select("div#manga-chapters-holder").attr("data-id") val url = "https://$domain/wp-admin/admin-ajax.php" - val postdata = "action=manga_get_chapters&manga=$mangaId" - webClient.httpPost(url, postdata).parseHtml() + val postData = "action=manga_get_chapters&manga=$mangaId" + webClient.httpPost(url, postData).parseHtml() } else { val url = mangaUrl.toAbsoluteUrl(domain).removeSuffix('/') + "/ajax/chapters/" webClient.httpPost(url, emptyMap()).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkNet.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkNet.kt new file mode 100644 index 000000000..eade338a0 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkNet.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.madara.ar + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGALINKNET", "MangaLink.net", "ar") +internal class MangalinkNet(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGALINKNET, "manga-link.net", pageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkParser.kt index 023194d20..00104b04c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangalinkParser.kt @@ -5,7 +5,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("MANGALINK_AR", "MangaLink", "ar") +@MangaSourceParser("MANGALINK_AR", "MangaLink.org", "ar") internal class MangalinkParser(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGALINK_AR, "manga-link.org", pageSize = 10) { override val listUrl = "readcomics/" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt index 475c52418..fe21d595e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt @@ -105,8 +105,8 @@ internal class InstaManhwa(context: MangaLoaderContext) : val mangaId = document.select("div#manga-chapters-holder").attr("data-id") val token = document.select("meta")[2].attr("content") val url = "https://$domain/ajax" - val postdata = "_token=$token&action=manga_get_chapters&manga=$mangaId" - val doc = webClient.httpPost(url, postdata).parseHtml() + val postData = "_token=$token&action=manga_get_chapters&manga=$mangaId" + val doc = webClient.httpPost(url, postData).parseHtml() val dateFormat = SimpleDateFormat(datePattern, sourceLocale) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaTop.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaTop.kt index 201ed5134..10846c05b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaTop.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaTop.kt @@ -19,8 +19,8 @@ internal class ManhwaTop(context: MangaLoaderContext) : val mangaId = document.select("div#manga-chapters-holder").attr("data-id") val url = "https://$domain/wp-admin/admin-ajax.php" - val postdata = "action=manga_get_chapters&manga=$mangaId" - val doc = webClient.httpPost(url, postdata).parseHtml() + val postData = "action=manga_get_chapters&manga=$mangaId" + val doc = webClient.httpPost(url, postData).parseHtml() val dateFormat = SimpleDateFormat(datePattern, sourceLocale) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt index 35a2cd27f..148c32101 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt @@ -6,6 +6,6 @@ import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -@MangaSourceParser("DOUJINDESURIP", "DoujinDesuRip", "id", ContentType.HENTAI) +@MangaSourceParser("DOUJINDESURIP", "DoujinDesu.cfd", "id", ContentType.HENTAI) internal class DoujinDesuRip(context: MangaLoaderContext) : MangaReaderParser(context, MangaSource.DOUJINDESURIP, "doujindesu.cfd", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt new file mode 100644 index 000000000..0139c3186 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt @@ -0,0 +1,168 @@ +package org.koitharu.kotatsu.parsers.site.tr + +import kotlinx.coroutines.coroutineScope +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("TRWEBTOON", "TrWebtoon", "tr") +class TrWebtoon(context: MangaLoaderContext) : + PagedMangaParser(context, MangaSource.TRWEBTOON, pageSize = 21) { + + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("trwebtoon.com") + + override val sortOrders: Set + get() = EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + append("/webtoon-listesi") + append("?page=") + append(page) + when { + !query.isNullOrEmpty() -> { + append("&q=") + append(query.urlEncoded()) + } + + !tags.isNullOrEmpty() -> { + append("&genre=") + append(tag?.key.orEmpty()) + } + } + append("&sort=") + when (sortOrder) { + SortOrder.POPULARITY -> append("views&short_type=DESC") + SortOrder.ALPHABETICAL -> append("name&short_type=ASC") + else -> append("views&short_type=DESC") + } + } + val doc = webClient.httpGet(url).parseHtml() + return doc.select(".row .col-xl-4 .card-body").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = li.selectFirst("img")?.src().orEmpty(), + title = li.selectFirst(".table-responsive a")?.text().orEmpty(), + altTitle = null, + rating = li.selectFirst(".row .col-xl-4 .mt-2 .my-1 .text-muted")?.text()?.substringBefore("/") + ?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = when (doc.selectLast(".row .col-xl-4 .mt-2 .rounded-pill")?.text()) { + "Devam Ediyor", "Güncel" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getTags(): Set { + val tags = + webClient.httpGet("https://$domain/webtoon-listesi").parseHtml().requireElementById("collapseExample") + .select(".pt-12 a").drop(1) + return tags.mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("genre=").substringBefore("&sort"), + title = a.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + manga.copy( + tags = doc.body().select("li.movie__year a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast('='), + title = a.text(), + source = source, + ) + }, + description = doc.select("p.movie__plot").html(), + state = when (doc.selectFirstOrThrow(".movie__credits span.rounded-pill").text()) { + "Devam Ediyor", "Güncel" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + chapters = doc.requireElementById("chapters").select("tbody tr").mapChapters(reversed = true) { i, tr -> + val url = tr.selectFirstOrThrow("a").attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(url), + name = tr.selectFirstOrThrow("a").text(), + number = i + 1, + url = url, + scanlator = null, + uploadDate = parseChapterDate( + SimpleDateFormat("dd/MM/yyyy", sourceLocale), + tr.selectLastOrThrow("td").selectFirstOrThrow("span").text(), + ), + branch = null, + source = source, + ) + }, + ) + } + + private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + val d = date?.lowercase() ?: return 0 + return when { + d.startsWith("saat ") -> Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + d.endsWith(" önce") -> parseRelativeDate(date) + + else -> dateFormat.tryParse(date) + } + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + return when { + WordSet("saat").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("gün").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("hafta").anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis + WordSet("ay").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("yıl").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml().requireElementById("images") + return doc.select("img").map { img -> + val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +}