From 28135aed6626edd91f0099582da02a79ae486cbb Mon Sep 17 00:00:00 2001 From: devi Date: Tue, 25 Jul 2023 21:30:07 +0200 Subject: [PATCH] add 2 new template and new source --- .../parsers/site/manga18/Manga18Parser.kt | 225 +++++++++++++++++ .../parsers/site/manga18/en/Hentai3zCc.kt | 96 ++++++++ .../parsers/site/manga18/en/Manga18.kt | 15 ++ .../parsers/site/manga18/en/PornComic18.kt | 16 ++ .../parsers/site/manga18/es/Tumanhwas.kt | 17 ++ .../site/mangareader/en/ManhuaScanUs.kt | 17 ++ .../parsers/site/mmrcms/MmrcmsParser.kt | 230 ++++++++++++++++++ .../site/mmrcms/en/ReadComicsOnline.kt | 16 ++ .../parsers/site/mmrcms/es/MangaDoor.kt | 22 ++ .../parsers/site/mmrcms/fr/JpMangas.kt | 17 ++ .../parsers/site/mmrcms/fr/LelScanVf.kt | 17 ++ .../kotatsu/parsers/site/mmrcms/fr/MangaFr.kt | 17 ++ .../parsers/site/mmrcms/fr/MangaScan.kt | 16 ++ .../parsers/site/mmrcms/fr/ScanFrOrg.kt | 18 ++ .../kotatsu/parsers/site/mmrcms/fr/ScanVf.kt | 17 ++ .../kotatsu/parsers/site/mmrcms/id/KomikId.kt | 21 ++ .../kotatsu/parsers/site/mmrcms/id/Mangaid.kt | 21 ++ .../parsers/site/mmrcms/pt/Animaregia.kt | 74 ++++++ 18 files changed, 872 insertions(+) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Manga18.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/PornComic18.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhuaScanUs.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/es/MangaDoor.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpMangas.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/LelScanVf.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaFr.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaScan.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanFrOrg.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanVf.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/KomikId.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/pt/Animaregia.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt new file mode 100644 index 00000000..d6338eec --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt @@ -0,0 +1,225 @@ +package org.koitharu.kotatsu.parsers.site.madara + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.koitharu.kotatsu.parsers.MangaLoaderContext +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.SimpleDateFormat +import java.util.* + +internal abstract class Manga18Parser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 20, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.ALPHABETICAL, + ) + + protected open val listeurl = "list-manga" + protected open val tagUrl = "manga-list" + protected open val isNsfwSource = false + protected open val datePattern = "dd-MM-yyyy" + + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + + @JvmField + protected val ongoing: Set = hashSetOf( + "On Going", + ) + + @JvmField + protected val finished: Set = hashSetOf( + "Completed", + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val url = buildString { + append("https://") + append(domain) + when { + !query.isNullOrEmpty() -> { + append("/$listeurl/") + append(page.toString()) + append("?search=") + append(query.urlEncoded()) + append("&") + } + + !tags.isNullOrEmpty() -> { + append("/$tagUrl/") + for (tag in tags) { + append(tag.key) + } + append("/") + append(page.toString()) + append("?") + } + + else -> { + append("/$listeurl/") + append(page.toString()) + append("?") + } + } + append("order_by=") + when (sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("lastest") + SortOrder.ALPHABETICAL -> append("name") + else -> append("latest") + } + } + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.story_item").map { div -> + val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = div.selectFirstOrThrow("div.mg_info").selectFirst("div.mg_name a")?.text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/$listeurl/").parseHtml() + return doc.select("div.grid_cate li").mapNotNullToSet { li -> + val a = li.selectFirst("a") ?: return@mapNotNullToSet null + val href = a.attr("href").substringAfterLast("/") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } + + protected open val selectdesc = "div.detail_reviewContent" + protected open val selectdate = "div.item p" + protected open val selectchapter = "div.chapter_box li" + protected open val selectState = "div.item:contains(Status) div.info_value" + protected open val selectAlt = "div.item:contains(Other name) div.info_value" + protected open val selectTag = "div.item:contains(Categories) div.info_value a" + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val body = doc.body().selectFirstOrThrow("div.detail_listInfo") + + val chaptersDeferred = async { getChapters(manga, doc) } + + val desc = doc.select(selectdesc).let { + if (it.select("p").text().isNotEmpty()) { + it.select("p").joinToString(separator = "\n\n") { p -> + p.text().replace("
", "\n") + } + } else { + it.text() + } + } + + val stateDiv = body.selectFirst(selectState) + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + + val alt = doc.body().select(selectAlt).text() + + manga.copy( + tags = doc.body().select(selectTag).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = alt, + state = state, + chapters = chaptersDeferred.await(), + ) + } + + + protected open suspend fun getChapters(manga: Manga, doc: Document): List { + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return doc.body().select(selectchapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val dateText = li.selectFirst(selectdate)?.text() + MangaChapter( + id = generateUid(href), + name = a.text(), + number = i + 1, + url = href, + uploadDate = dateFormat.tryParse(dateText), + source = source, + scanlator = null, + branch = null, + ) + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + val script = doc.selectFirstOrThrow("script:containsData(slides_p_path)") + val urlencoed = script.data().substringAfter('[').substringBefore(",]").replace("\"", "").split(",") + return urlencoed.map { url -> + val img = context.decodeBase64(url).toString(Charsets.UTF_8) + + MangaPage( + id = generateUid(img), + url = img, + preview = null, + source = source, + ) + } + } + + + protected fun Element.src(): String? { + var result = absUrl("data-src") + if (result.isEmpty()) result = absUrl("data-cfsrc") + if (result.isEmpty()) result = absUrl("src") + return result.ifEmpty { null } + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt new file mode 100644 index 00000000..3a6ebeb8 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt @@ -0,0 +1,96 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN +import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.site.madara.Manga18Parser +import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrlOrNull +import org.koitharu.kotatsu.parsers.util.domain +import org.koitharu.kotatsu.parsers.util.generateUid +import org.koitharu.kotatsu.parsers.util.host +import org.koitharu.kotatsu.parsers.util.parseFailed +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 + + +@MangaSourceParser("HENTAI3ZCC", "Hentai3z Cc", "en") +internal class Hentai3zCc(context: MangaLoaderContext) : + Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") { + + override val isNsfwSource = true + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val url = buildString { + append("https://") + append(domain) + val pages = page + 1 + when { + !query.isNullOrEmpty() -> { + append("/$listeurl/") + append(pages.toString()) + append("?search=") + append(query.urlEncoded()) + append("&") + } + + !tags.isNullOrEmpty() -> { + append("/$tagUrl/") + for (tag in tags) { + append(tag.key) + } + append("/") + append(pages.toString()) + append("?") + } + + else -> { + append("/$listeurl/") + append(pages.toString()) + append("?") + } + } + append("order_by=") + when (sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("lastest") + SortOrder.ALPHABETICAL -> append("name") + else -> append("latest") + } + } + val doc = webClient.httpGet(url).parseHtml() + + + return doc.select("div.story_item").map { div -> + val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src() + ?.replace("cover_thumb_2.webp", "cover_250x350.jpg") + ?.replace("admin.manga18.us", "bk.18porncomic.com") + .orEmpty(), + title = div.selectFirstOrThrow("div.mg_info").selectFirst("div.mg_name a")?.text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Manga18.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Manga18.kt new file mode 100644 index 00000000..0bded4e8 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Manga18.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + + +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.Manga18Parser + + +@MangaSourceParser("MANGA18", "Manga18", "en") +internal class Manga18(context: MangaLoaderContext) : + Manga18Parser(context, MangaSource.MANGA18, "manga18.club") { + + override val isNsfwSource = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/PornComic18.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/PornComic18.kt new file mode 100644 index 00000000..98fb41dc --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/PornComic18.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + + +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.Manga18Parser + + +@MangaSourceParser("PORNCOMIC18", "18 Porn Comic", "en") +internal class PornComic18(context: MangaLoaderContext) : + Manga18Parser(context, MangaSource.PORNCOMIC18, "18porncomic.com") { + + override val selectTag = "div.item:not(.info_label) div.info_value a" + override val isNsfwSource = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt new file mode 100644 index 00000000..19c0e9b3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.madara.es + + +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.Manga18Parser + + +@MangaSourceParser("TUMANHWAS", "Tumanhwas", "es") +internal class Tumanhwas(context: MangaLoaderContext) : + Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") { + + override val isNsfwSource = true + override val selectTag = "div.item:contains(Géneros) div.info_value a" + override val selectAlt = "div.item:contains(Títulos alternativos) div.info_value" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhuaScanUs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhuaScanUs.kt new file mode 100644 index 00000000..7f14fabe --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhuaScanUs.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.en + +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.mangareader.MangaReaderParser + + +@MangaSourceParser("MANHUASCANUS", "Manhua Scan Us", "en") +internal class ManhuaScanUs(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANHUASCANUS, "manhuascan.us", pageSize = 30, searchPageSize = 30) { + + override val isNsfwSource: Boolean = true + override val datePattern = "dd-MM-yyyy" + override val listUrl = "/manga-list" + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt new file mode 100644 index 00000000..77dc112e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt @@ -0,0 +1,230 @@ +package org.koitharu.kotatsu.parsers.site.madara + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.koitharu.kotatsu.parsers.MangaLoaderContext +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.SimpleDateFormat +import java.util.* + +internal abstract class MmrcmsParser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 20, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of( + SortOrder.POPULARITY, + SortOrder.ALPHABETICAL, + ) + + protected open val listeurl = "filterList" + protected open val tagUrl = "manga-list" + protected open val isNsfwSource = false + protected open val datePattern = "dd MMM. yyyy" + + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + + @JvmField + protected val ongoing: Set = hashSetOf( + "On Going", + "Ongoing", + "En cours", + "En curso", + ) + + @JvmField + protected val finished: Set = hashSetOf( + "Completed", + "Completo", + "Complete", + "Terminé", + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val url = buildString { + append("https://") + append(domain) + + append("/$listeurl/") + append("?page=") + append(page.toString()) + append("&asc=true&author=&tag=") + + append("&alpha=") + if (!query.isNullOrEmpty()) { + append(query.urlEncoded()) + } + + append("&cat=") + if (!tags.isNullOrEmpty()) { + + for (tag in tags) { + append(tag.key) + } + } + + append("&sortBy=") + when (sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.ALPHABETICAL -> append("name") + else -> append("views") + } + } + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.media").map { div -> + val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = div.selectFirstOrThrow("div.media-body h5").text().orEmpty(), + altTitle = null, + rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: -1f, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/$tagUrl/").parseHtml() + return doc.select("ul.list-category li").mapNotNullToSet { li -> + val a = li.selectFirst("a") ?: return@mapNotNullToSet null + val href = a.attr("href").substringAfterLast("cat=") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } + + protected open val selectdesc = "div.well" + protected open val selectState = "dt:contains(Statut)" + protected open val selectAlt = "dt:contains(Autres noms)" + protected open val selectAut = "dt:contains(Auteur(s))" + protected open val selectTag = "dt:contains(Catégories)" + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val body = doc.body().selectFirstOrThrow("dl.dl-horizontal") + + val chaptersDeferred = async { getChapters(manga, doc) } + + val desc = doc.select(selectdesc).let { + if (it.select("p").text().isNotEmpty()) { + it.select("p").joinToString(separator = "\n\n") { p -> + p.text().replace("
", "\n") + } + } else { + it.text() + } + } + + val stateDiv = body.selectFirst(selectState)?.nextElementSibling() + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + + val alt = doc.body().selectFirst(selectAlt)?.nextElementSibling()?.text() + val auth = doc.body().selectFirst(selectAut)?.nextElementSibling()?.text() + + val tags = doc.body().selectFirst(selectTag)?.nextElementSibling()?.select("a") ?: emptySet() + + manga.copy( + tags = tags.mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("/"), + title = a.text().toTitleCase(), + source = source, + ) + }, + author = auth, + description = desc, + altTitle = alt, + state = state, + chapters = chaptersDeferred.await(), + ) + } + + + protected open val selectdate = "div.date-chapter-title-rtl" + protected open val selectchapter = "ul.chapters > li:not(.btn)" + + protected open suspend fun getChapters(manga: Manga, doc: Document): List { + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + + return doc.body().select(selectchapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val dateText = li.selectFirst(selectdate)?.text() + MangaChapter( + id = generateUid(href), + name = li.selectFirstOrThrow("h5").text(), + number = i + 1, + url = href, + uploadDate = dateFormat.tryParse(dateText), + source = source, + scanlator = null, + branch = null, + ) + } + } + + protected open val selectPage = "div#all img" + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + + return doc.select(selectPage).map { url -> + val img = url.src()?.toRelativeUrl(domain) ?: url.parseFailed("Image src not found") + MangaPage( + id = generateUid(img), + url = img, + preview = null, + source = source, + ) + } + } + + + protected fun Element.src(): String? { + var result = absUrl("data-src") + if (result.isEmpty()) result = absUrl("data-cfsrc") + if (result.isEmpty()) result = absUrl("src") + return result.ifEmpty { null } + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt new file mode 100644 index 00000000..abcdc546 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.en + + +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.MmrcmsParser + + +@MangaSourceParser("READCOMICSONLINE", "Read Comics Online", "en") +internal class ReadComicsOnline(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.READCOMICSONLINE, "readcomicsonline.ru") { + + override val selectState = "dt:contains(Status)" + override val selectTag = "dt:contains(Categories)" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/es/MangaDoor.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/es/MangaDoor.kt new file mode 100644 index 00000000..771354fc --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/es/MangaDoor.kt @@ -0,0 +1,22 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.es + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("MANGADOOR", "Manga Door", "es") +internal class MangaDoor(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.MANGADOOR, "mangadoor.com") { + + + override val sourceLocale: Locale = Locale.ENGLISH + + override val selectState = "dt:contains(Estado)" + override val selectAlt = "dt:contains(Otros nombres)" + override val selectAut = "dt:contains(Autor(es))" + override val selectTag = "dt:contains(Categorías)" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpMangas.kt new file mode 100644 index 00000000..53c4a5d8 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpMangas.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("JPMANGAS", "JpMangas", "fr") +internal class JpMangas(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.JPMANGAS, "jpmangas.xyz") { + + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/LelScanVf.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/LelScanVf.kt new file mode 100644 index 00000000..c3b35912 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/LelScanVf.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("LELSCANVF", "Lel Scan Vf", "fr") +internal class LelScanVf(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.LELSCANVF, "lelscanvf.cc") { + + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaFr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaFr.kt new file mode 100644 index 00000000..e5fdd00d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaFr.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("MANGAFR", "Manga Fr", "fr") +internal class MangaFr(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.MANGAFR, "manga-fr.me") { + + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaScan.kt new file mode 100644 index 00000000..3c7a6808 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/MangaScan.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("MANGA_SCAN", "Manga-Scan", "fr") +internal class MangaScan(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.MANGA_SCAN, "manga-scan.co") { + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanFrOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanFrOrg.kt new file mode 100644 index 00000000..81fff394 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanFrOrg.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("SCAN_FR_ORG", "Scan-Fr Org", "fr") +internal class ScanFrOrg(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.SCAN_FR_ORG, "www.scan-fr.org") { + + + override val sourceLocale: Locale = Locale.ENGLISH + override val selectchapter = "ul.chapterszozo li" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanVf.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanVf.kt new file mode 100644 index 00000000..0d3031a9 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/ScanVf.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("SCANVF", "Scan Vf", "fr") +internal class ScanVf(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.SCANVF, "www.scan-vf.net") { + + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/KomikId.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/KomikId.kt new file mode 100644 index 00000000..20bac082 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/KomikId.kt @@ -0,0 +1,21 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.id + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("KOMIKID", "KomikId", "id") +internal class KomikId(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.KOMIKID, "komikid.com") { + + + override val selectState = "dt:contains(Status)" + override val selectAlt = "dt:contains(Other names)" + override val selectAut = "dt:contains(Author(s))" + override val selectTag = "dt:contains(Categories)" + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt new file mode 100644 index 00000000..fe361a9e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt @@ -0,0 +1,21 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.id + + +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.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("MANGAID", "Mangaid", "id") +internal class Mangaid(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.MANGAID, "mangaid.click") { + + + override val selectState = "dt:contains(Status)" + override val selectAlt = "dt:contains(Other names)" + override val selectAut = "dt:contains(Author(s))" + override val selectTag = "dt:contains(Categories)" + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/pt/Animaregia.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/pt/Animaregia.kt new file mode 100644 index 00000000..db19266a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/pt/Animaregia.kt @@ -0,0 +1,74 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.pt + + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.model.MangaState +import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.site.madara.MmrcmsParser +import org.koitharu.kotatsu.parsers.util.domain +import org.koitharu.kotatsu.parsers.util.mapNotNullToSet +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.toTitleCase +import java.util.Locale + + +@MangaSourceParser("ANIMAREGIA", "Animaregia", "pt") +internal class Animaregia(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.ANIMAREGIA, "animaregia.net") { + + override val selectdate = "div.col-md-4" + override val sourceLocale: Locale = Locale.ENGLISH + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val body = doc.body().selectFirstOrThrow("ul.list-group") + + val chaptersDeferred = async { getChapters(manga, doc) } + + val desc = doc.select(selectdesc).let { + if (it.select("p").text().isNotEmpty()) { + it.select("p").joinToString(separator = "\n\n") { p -> + p.text().replace("
", "\n") + } + } else { + it.text() + } + } + + val stateDiv = body.selectFirst("li.list-group-item:contains(Status)")?.lastElementChild() + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + val auth = doc.body().selectFirst("li.list-group-item:contains(Autor(es)) a")?.text() + + val tags = doc.body().select("li.list-group-item:contains(Autor(es)) a") ?: emptySet() + + manga.copy( + tags = tags.mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("/"), + title = a.text().toTitleCase(), + source = source, + ) + }, + author = auth, + description = desc, + altTitle = null, + state = state, + chapters = chaptersDeferred.await(), + ) + } +}