diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt new file mode 100644 index 00000000..9e89a034 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt @@ -0,0 +1,151 @@ +package org.koitharu.kotatsu.parsers.site.en + +import okhttp3.Headers +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.network.UserAgents +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("COMICEXTRA", "ComicExtra", "en") +internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.COMICEXTRA, 25) { + + override val sortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST) + + override val configKeyDomain = ConfigKey.Domain("comicextra.net") + + override val headers: Headers = Headers.Builder() + .add("User-Agent", UserAgents.CHROME_DESKTOP) + .build() + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://$domain/") + if (!tags.isNullOrEmpty()) { + append(tag?.key.orEmpty()) + if (page > 1) { + append(page) + } + } else if (!query.isNullOrEmpty()) { + append("comic-search?key=") + append(query.urlEncoded()) + if (page > 1) { + append("&page=") + append(page) + } + } else { + when (sortOrder) { + SortOrder.POPULARITY -> append("popular-comic/") + SortOrder.UPDATED -> append("new-comic/") + SortOrder.NEWEST -> append("recent-comic/") + else -> append("new-comic/") + } + if (page > 1) { + append(page) + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.movie-list-index div.cartoon-box").map { div -> + val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h3").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), + tags = emptySet(), + state = when (div.selectFirstOrThrow(".detail:contains(Stasus: )").text()) { + "Stasus: Ongoing" -> MangaState.ONGOING + "Stasus: Completed" -> MangaState.FINISHED + else -> null + }, + author = null, + source = source, + ) + } + } + + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/popular-comic").parseHtml() + return doc.select("li.tag-item a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("/"), + title = a.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + return manga.copy( + altTitle = doc.selectFirstOrThrow("dt.movie-dt:contains(Alternate name:) + dd").text(), + state = when (doc.selectFirstOrThrow("dt.movie-dt:contains(Status:) + dd a").text()) { + "Ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + }, + tags = doc.select("dt.movie-dt:contains(Genres:) + dd a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("/"), + title = a.text(), + source = source, + ) + }, + author = doc.select("dt.movie-dt:contains(Author:) + dd").text(), + description = doc.getElementById("film-content")?.text(), + chapters = doc.requireElementById("list").select("tr") + .mapChapters(reversed = true) { i, tr -> + + val a = tr.selectFirstOrThrow("a") + val url = a.attrAsRelativeUrl("href") + "/full" + val name = a.text() + val dateText = tr.select("td").last()?.text() + val dateFormat = SimpleDateFormat("MM/dd/yy", sourceLocale) + MangaChapter( + id = generateUid(url), + name = name, + number = i, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(dateText), + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + return doc.select(".chapter-container img.chapter_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/en/Comicastle.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Comicastle.kt new file mode 100644 index 00000000..eaf9da4f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Comicastle.kt @@ -0,0 +1,161 @@ +package org.koitharu.kotatsu.parsers.site.en + +import okhttp3.Headers +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.network.UserAgents +import org.koitharu.kotatsu.parsers.util.* +import java.util.* + +@MangaSourceParser("COMICASTLE", "Comicastle", "en") +internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.COMICASTLE, 42) { + + override val sortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) + + override val configKeyDomain = ConfigKey.Domain("comicastle.org") + + override val headers: Headers = Headers.Builder() + .add("User-Agent", UserAgents.CHROME_DESKTOP) + .build() + + init { + paginator.firstPage = 0 + searchPaginator.firstPage = 0 + } + + override suspend fun getFavicons(): Favicons { + return Favicons( + listOf( + Favicon("https://$domain/assets/static/app-assets/images/logo/logo-primary.png", 54, null), + ), + domain, + ) + } + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val doc = if (!query.isNullOrEmpty()) { + val url = buildString { + append("https://$domain/library/search/result/") + append(page + 1) + } + val postdata = "submit=Submit&search=" + query.urlEncoded() + webClient.httpPost(url, postdata).parseHtml() + + } else if (!tags.isNullOrEmpty()) { + val url = buildString { + append("https://$domain/library/search/genre/") + append(page + 1) + } + val postdata = "submit=Submit&search=" + tag?.key.orEmpty() + webClient.httpPost(url, postdata).parseHtml() + + } else { + val url = buildString { + append("https://$domain") + append("/library/") + when (sortOrder) { + SortOrder.POPULARITY -> append("popular/desc/") + SortOrder.UPDATED -> append("postdate/desc") + else -> append("postdate/desc") + } + append("/index/") + append(page * pageSize) + } + webClient.httpGet(url).parseHtml() + } + + return doc.select("div.card-body div.match-height div.col-6") + .map { div -> + val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("p").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("data-src"), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/library/").parseHtml() + return doc.requireElementById("sidebar").selectFirstOrThrow(".card-body").select("button") + .mapNotNullToSet { button -> + MangaTag( + key = button.attr("value"), + title = button.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + return manga.copy( + altTitle = null, + state = when (root.selectFirstOrThrow(".card-body p span.mr-1 strong").text()) { + "Ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + }, + tags = root.select("p:contains(Genre) ~ div form").mapNotNullToSet { form -> + MangaTag( + key = form.selectFirstOrThrow("input").attr("value"), + title = form.selectFirstOrThrow("button").text(), + source = source, + ) + }, + author = root.select("thead:contains(Writer) + tbody button").text(), + description = root.getElementById("comic-desc")?.text(), + chapters = root.select("div.card-body > .table-responsive tr a") + .mapChapters { i, a -> + + val url = a.attrAsRelativeUrl("href") + val name = a.text() + MangaChapter( + id = generateUid(url), + name = name, + number = i, + url = url, + scanlator = null, + uploadDate = 0, + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + return doc.selectFirstOrThrow(".card-content .form-control.pr-3").select("option").map { option -> + val url = option.attr("alt") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/JapScanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/JapScanParser.kt index 2591e772..7a08792d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/JapScanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/JapScanParser.kt @@ -101,97 +101,21 @@ internal class JapScanParser(context: MangaLoaderContext) : PagedMangaParser(con ) } - private fun extractQuotedContent(input: String): List { - val regex = Regex("'(.*?)'") - return regex.findAll(input).map { it.groupValues[1] }.toList() - } - - private fun listJSToKey(jsList: MutableList, offsettab: Int, listKey: List): MutableList { - for (i in 0 until jsList.size) { - if (jsList[i].contains("0x")) { - var decoupeHexa = jsList[i].split("('")[1] - decoupeHexa = decoupeHexa.split("')")[0] - var indexkey = Integer.decode(decoupeHexa) - offsettab - 1 - if (indexkey < 0) { - indexkey = listKey.size - 1 - } - jsList[i] = listKey[indexkey] - } - } - - return jsList - } override suspend fun getPages(chapter: MangaChapter): List { val chapterUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(chapterUrl).parseHtml() - val scriptUrl = doc.getElementsByTag("script").firstNotNullOf { script -> - script.attrAsAbsoluteUrlOrNull("src")?.takeIf { it.contains("/zjs/") } - } - val embeddedData = doc.requireElementById("data").attr("data-data") - val script = webClient.httpGet(scriptUrl).parseRaw() - - var tabKey = "'" + script.split("=['")[1] - tabKey = tabKey.split("];")[0] - val listKey = tabKey.split("','").toMutableList() - - var decoupeOffset = script.split("-0x")[1] - decoupeOffset = "0x" + decoupeOffset.split(";")[0] - - val offsettab = Integer.decode(decoupeOffset) - var decoupeFuncOrder = script.split("while(!![])")[1] - decoupeFuncOrder = decoupeFuncOrder.split("if")[0] - - val listKeyOrder = extractQuotedContent(decoupeFuncOrder).toMutableList() - - if (listKeyOrder.size < 3) { - throw Exception("L'ordre des clés n'a pas pu être déterminé") - } - var goodorder = false - for (i in 0 until listKey.size) { - for (z in 0 until listKeyOrder.size) { - if (listKey[Integer.decode(listKeyOrder[z]) - offsettab - 1].contains("[0-9]".toRegex())) { - goodorder = true - } else { - goodorder = false - break - } - } - - if (goodorder) { - break - } - - val firstElement = listKey.removeAt(0) - listKey.add(firstElement) - } - - if (!goodorder) { - throw Exception("L'ordre des clés n'a pas pu être déterminé") - } - - val zjscalc = script.split("/[A-Z0-9]/gi,")[1] - - val calc1 = zjscalc.split(",")[0] - var calc1tab = calc1.split("+").toMutableList() - calc1tab = listJSToKey(calc1tab, offsettab, listKey) - - val calc2 = zjscalc.split(",")[1] - var calc2tab = calc2.split("+").toMutableList() - calc2tab = listJSToKey(calc2tab, offsettab, listKey) - - var key1 = calc1tab.joinToString("") - var key2 = calc2tab.joinToString("") + val embeddedData = doc.requireElementById("data").attr("data-data") - key1 = key1.filterNot { c -> c == '\'' || c == ' ' } - key2 = key2.filterNot { c -> c == '\'' || c == ' ' } + val jsonkey = + webClient.httpGet("https://gist.githubusercontent.com/zormy111/f6abdc7f9385e95203e2b6a64af15ea3/raw/") + .parseJsonArray() val keyTables = listOf( - key1.reversed(), - key2.reversed(), + jsonkey[0].toString(), + jsonkey[1].toString(), ) - var error: Exception? = null repeat(2) { i -> val key = keyTables[i].zip(keyTables[1 - i]).toMap() 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 98cc191b..e3a71e68 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 @@ -155,16 +155,20 @@ internal abstract class MadaraParser( !tags.isNullOrEmpty() -> { append("/$tagPrefix") append(tag?.key.orEmpty()) - append("/page/") - append(pages.toString()) + if (page > 1) { + append("/page/") + append(pages.toString()) + } append("?") } else -> { append("/$listUrl") - append("/page/") - append(pages.toString()) + if (page > 1) { + append("page/") + append(pages) + } append("?") } } @@ -448,6 +452,7 @@ internal abstract class MadaraParser( d.endsWith(" назад") || // other translated 'ago' in Russian d.endsWith(" önce") || // Handle translated 'ago' in Turkish. d.endsWith(" trước") || // Handle translated 'ago' in Viêt Nam. + d.endsWith("مضت") || // Handle translated 'ago' in Arabic d.startsWith("il y a") || // Handle translated 'ago' in French. //If there is no ago but just a motion of time // short Hours @@ -516,7 +521,17 @@ internal abstract class MadaraParser( "день", ).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis - WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h").anyWordIn(date) -> cal.apply { + WordSet( + "jam", + "saat", + "heure", + "hora", + "horas", + "hour", + "hours", + "h", + "ساعات", + ).anyWordIn(date) -> cal.apply { add( Calendar.HOUR, -number, @@ -533,6 +548,7 @@ internal abstract class MadaraParser( "mins", "phút", "минут", + "دقيقة", ).anyWordIn(date) -> cal.apply { add( Calendar.MINUTE, @@ -540,7 +556,7 @@ internal abstract class MadaraParser( ) }.timeInMillis - WordSet("detik", "segundo", "second").anyWordIn(date) -> cal.apply { + WordSet("detik", "segundo", "second", "ثوان").anyWordIn(date) -> cal.apply { add( Calendar.SECOND, -number, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/MangaCrazy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/MangaCrazy.kt new file mode 100644 index 00000000..e409c776 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/MangaCrazy.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.madara.all + + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import java.util.Locale + +@MangaSourceParser("MANGACRAZY", "Manga Crazy", "", ContentType.HENTAI) +internal class MangaCrazy(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGACRAZY, "mangacrazy.net") { + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/MangaTop.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/MangaTop.kt new file mode 100644 index 00000000..474c1224 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/MangaTop.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.madara.all + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import java.util.Locale + +@MangaSourceParser("MANGATOP", "Manga Top", "", ContentType.HENTAI) +internal class MangaTop(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGATOP, "mangatop.site") { + override val datePattern = "d MMMM yyyy" + override val sourceLocale: Locale = Locale.ENGLISH + override val stylepage = "" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaRose.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaRose.kt new file mode 100644 index 00000000..72728c90 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaRose.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("MANGAROSE", "Manga Rose", "ar") +internal class MangaRose(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAROSE, "mangarose.net", pageSize = 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt index f3ac84c8..caee966b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MANGASTARZ", "Manga Starz", "ar") internal class MangaStarz(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MANGASTARZ, "mangastarz.com", pageSize = 10) { + MadaraParser(context, MangaSource.MANGASTARZ, "mangalike.org", pageSize = 10) { override val datePattern = "d MMMM، yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ArcherScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ArcherScans.kt new file mode 100644 index 00000000..b7458661 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ArcherScans.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("ARCHERSCANS", "ArcherScans", "en") +internal class ArcherScans(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ARCHERSCANS, "www.archerscans.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AsuraScansUs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AsuraScansUs.kt new file mode 100644 index 00000000..5ea92e58 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AsuraScansUs.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("ASURASCANS_US", "AsuraScans Us", "en") +internal class AsuraScansUs(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ASURASCANS_US, "asurascans.us") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/BananaManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/BananaManga.kt new file mode 100644 index 00000000..c74d9b81 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/BananaManga.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("BANANA_MANGA", "Banana Manga", "en") +internal class BananaManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.BANANA_MANGA, "bananamanga.net", 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/CoffeeMangaTop.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/CoffeeMangaTop.kt new file mode 100644 index 00000000..0b3c21b2 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/CoffeeMangaTop.kt @@ -0,0 +1,143 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + +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.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.EnumSet + +@MangaSourceParser("COFFEE_MANGA_TOP", "Coffee Manga Top", "en") +internal class CoffeeMangaTop(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.COFFEE_MANGA_TOP, "coffeemanga.top") { + override val tagPrefix = "mangas/" + override val listUrl = "latest-manga/" + override val datePattern = "MMMM d, HH:mm" + + override val sortOrders: Set = EnumSet.of( + SortOrder.POPULARITY, + SortOrder.UPDATED, + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + val pages = page + 1 + + when { + !query.isNullOrEmpty() -> { + + append("/?search=") + append(query.urlEncoded()) + append("&page=") + append(pages.toString()) + append("&post_type=wp-manga") + } + + !tags.isNullOrEmpty() -> { + append("/mangas/") + append(tag?.key.orEmpty()) + append("?orderby=2&page=") + append(pages.toString()) + + } + + else -> { + + if (sortOrder == SortOrder.POPULARITY) { + append("/popular-manga") + } + if (sortOrder == SortOrder.UPDATED) { + append("/latest-manga") + } + append("?page=") + append(pages.toString()) + } + } + } + val doc = webClient.httpGet(url).parseHtml() + return doc.select("div.row.c-tabs-item__content").ifEmpty { + doc.select("div.page-item-detail.manga") + }.map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = (summary?.selectFirst("h3") ?: summary?.selectFirst("h4"))?.text().orEmpty(), + altTitle = null, + rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, + tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text().ifEmpty { return@mapNotNullToSet null }.toTitleCase(), + source = source, + ) + }.orEmpty(), + author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), + state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()?.trim() + ?.lowercase()) { + "Ongoing" -> MangaState.ONGOING + "Completed " -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun loadChapters(mangaUrl: String, document: Document): List { + + + val mangaId = document.select("div[id^=manga-chapters-holder]").attr("data-id") + + val doc = webClient.httpGet("https://$domain/ajax-list-chapter?mangaID=$mangaId").parseHtml() + + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + + return doc.select(selectChapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val link = href + stylepage + MangaChapter( + id = generateUid(href), + url = link, + name = a.ownText(), + number = i + 1, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + li.selectFirst(selectDate)?.text(), + ), + scanlator = null, + source = source, + ) + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val urlarray = doc.select("p#arraydata").text().split(",").toTypedArray() + return urlarray.map { url -> + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ComicScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ComicScans.kt new file mode 100644 index 00000000..71ba037a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ComicScans.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("COMICSCANS", "Comic Scans", "en") +internal class ComicScans(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.COMICSCANS, "www.comicscans.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/EliteManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/EliteManga.kt new file mode 100644 index 00000000..09a8560c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/EliteManga.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("ELITEMANGA", "Elite Manga", "en") +internal class EliteManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ELITEMANGA, "www.elitemanga.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/FactManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/FactManga.kt new file mode 100644 index 00000000..1bddea49 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/FactManga.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("FACTMANGA", "FactManga", "en") +internal class FactManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.FACTMANGA, "factmanga.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Todaymic.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/GlManga.kt similarity index 62% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Todaymic.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/GlManga.kt index 6d28230c..1b5f76b5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Todaymic.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/GlManga.kt @@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("TODAYMIC", "Todaymic", "en") -internal class Todaymic(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.TODAYMIC, "todaymic.com") +@MangaSourceParser("GLMANGA", "Gl Manga", "en") +internal class GlManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.GLMANGA, "glmanga.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt index 6f51fa8b..20cc8233 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt @@ -65,10 +65,9 @@ internal class IsekaiScan(context: MangaLoaderContext) : } } } + val doc = webClient.httpGet(url).parseHtml() - if (url != "ursdvsdvl") { - throw Exception(doc.toString()) - } + return doc.select("div.row.c-tabs-item__content").ifEmpty { doc.select("div.page-item-detail.manga") }.map { div -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/LuffyManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/LuffyManga.kt new file mode 100644 index 00000000..a81aef20 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/LuffyManga.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("LUFFYMANGA", "Luffy Manga", "en") +internal class LuffyManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.LUFFYMANGA, "luffymanga.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga1001.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga1001.kt new file mode 100644 index 00000000..4c054797 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga1001.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGA1001", "Manga 1001", "en") +internal class Manga1001(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA1001, "manga-1001.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga18h.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga18h.kt new file mode 100644 index 00000000..93488d6f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga18h.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGA18H", "Manga18h", "en", ContentType.HENTAI) +internal class Manga18h(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA18H, "manga18h.com", 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga18x.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga18x.kt new file mode 100644 index 00000000..676f8727 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga18x.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGA18X", "Manga18x", "en", ContentType.HENTAI) +internal class Manga18x(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA18X, "manga18x.net", 24) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaBee.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaBee.kt new file mode 100644 index 00000000..1286488d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaBee.kt @@ -0,0 +1,13 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGABEE", "MangaBee", "en", ContentType.HENTAI) +internal class MangaBee(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGABEE, "mangabee.net") { + override val datePattern = "MM/dd/yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaClashTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaClashTv.kt new file mode 100644 index 00000000..31b6383d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaClashTv.kt @@ -0,0 +1,13 @@ +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.MadaraParser + +@MangaSourceParser("MANGACLASH_TV", "Mangaclash Tv (unoriginal)", "en") +internal class MangaClashTv(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGACLASH_TV, "mangaclash.tv", pageSize = 10) { + override val datePattern = "MM/dd/yyyy" + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaKitsu.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaKitsu.kt new file mode 100644 index 00000000..a7b51b30 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaKitsu.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGAKITSU", "MangaKitsu", "en") +internal class MangaKitsu(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAKITSU, "mangakitsu.com", 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaNerds.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaNerds.kt new file mode 100644 index 00000000..f228d0f6 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaNerds.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGA_NERDS", "Manga Nerds", "en") +internal class MangaNerds(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA_NERDS, "manganerds.com", 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOnline.kt new file mode 100644 index 00000000..fb3e2974 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOnline.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGA_ONLINE", "Manga Online", "en") +internal class MangaOnline(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA_ONLINE, "mangaonline.team", 18) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlIo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlIo.kt new file mode 100644 index 00000000..43183bfd --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlIo.kt @@ -0,0 +1,12 @@ +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.MadaraParser + +@MangaSourceParser("MANGAOWL_IO", "MangaOwl Io (unoriginal)", "en") +internal class MangaOwlIo(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAOWL_IO, "mangaowl.io") { + override val listUrl = "mangaowl-all/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlUs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlUs.kt new file mode 100644 index 00000000..c4f90655 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlUs.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGAOWL_US", "MangaOwl Us (unoriginal)", "en", ContentType.HENTAI) +internal class MangaOwlUs(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAOWL_US, "mangaowl.us") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt new file mode 100644 index 00000000..605da5ae --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt @@ -0,0 +1,145 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + +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.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.EnumSet + +@MangaSourceParser("MANGAPURE", "Manga Pure", "en") +internal class MangaPure(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAPURE, "mangapure.net") { + override val tagPrefix = "mangas/" + override val listUrl = "latest-manga/" + override val datePattern = "MMMM d, HH:mm" + + override val sortOrders: Set = EnumSet.of( + SortOrder.POPULARITY, + SortOrder.UPDATED, + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + val pages = page + 1 + + when { + !query.isNullOrEmpty() -> { + + append("/?search=") + append(query.urlEncoded()) + append("&page=") + append(pages.toString()) + append("&post_type=wp-manga") + } + + !tags.isNullOrEmpty() -> { + append("/mangas/") + append(tag?.key.orEmpty()) + append("?orderby=2&page=") + append(pages.toString()) + + } + + else -> { + + if (sortOrder == SortOrder.POPULARITY) { + append("/popular-manga") + } + if (sortOrder == SortOrder.UPDATED) { + append("/latest-manga") + } + append("?page=") + append(pages.toString()) + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.row.c-tabs-item__content").ifEmpty { + doc.select("div.page-item-detail.manga") + }.map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = (summary?.selectFirst("h3") ?: summary?.selectFirst("h4"))?.text().orEmpty(), + altTitle = null, + rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, + tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text().ifEmpty { return@mapNotNullToSet null }.toTitleCase(), + source = source, + ) + }.orEmpty(), + author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), + state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()?.trim() + ?.lowercase()) { + "Ongoing" -> MangaState.ONGOING + "Completed " -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun loadChapters(mangaUrl: String, document: Document): List { + + + val mangaId = document.select("div[id^=manga-chapters-holder]").attr("data-id") + + val doc = webClient.httpGet("https://$domain/ajax-list-chapter?mangaID=$mangaId").parseHtml() + + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + + return doc.select(selectChapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val link = href + stylepage + MangaChapter( + id = generateUid(href), + url = link, + name = a.ownText(), + number = i + 1, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + li.selectFirst(selectDate)?.text(), + ), + scanlator = null, + source = source, + ) + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val urlarray = doc.select("p#arraydata").text().split(",").toTypedArray() + return urlarray.map { url -> + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaQueen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaQueen.kt new file mode 100644 index 00000000..5a76742c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaQueen.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGA_QUEEN", "Manga Queen", "en") +internal class MangaQueen(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA_QUEEN, "mangaqueen.com", 16) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaQueenOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaQueenOnline.kt new file mode 100644 index 00000000..05cb79c4 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaQueenOnline.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGA_QUEEN_ONLINE", "Manga Queen Online", "en") +internal class MangaQueenOnline(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA_QUEEN_ONLINE, "mangaqueen.online", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRockTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRockTeam.kt new file mode 100644 index 00000000..a9a82f91 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRockTeam.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGAROCKTEAM", "Manga Rock (unoriginal)", "en") +internal class MangaRockTeam(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAROCKTEAM, "mangarock.team", 18) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRuby.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRuby.kt new file mode 100644 index 00000000..5d3e24c0 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRuby.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGARUBY", "Manga Ruby", "en") +internal class MangaRuby(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGARUBY, "mangaruby.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRyu.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRyu.kt new file mode 100644 index 00000000..0c830296 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRyu.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGARYU", "Manga Ryu", "en", ContentType.HENTAI) +internal class MangaRyu(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGARYU, "mangaryu.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaTxGg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaTxGg.kt new file mode 100644 index 00000000..8fd42ad1 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaTxGg.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGATX_GG", "MangaTx Gg", "en") +internal class MangaTxGg(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGATX_GG, "mangatx.gg") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaTyrant.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaTyrant.kt new file mode 100644 index 00000000..1c5a489a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaTyrant.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANGATYRANT", "MangaTyrant", "en") +internal class MangaTyrant(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGATYRANT, "mangatyrant.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangakakalotIo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangakakalotIo.kt new file mode 100644 index 00000000..bcc7a9ae --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangakakalotIo.kt @@ -0,0 +1,12 @@ +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.MadaraParser + +@MangaSourceParser("MANGAKAKALOT_IO", "Mangakakalot Io", "en") +internal class MangakakalotIo(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAKAKALOT_IO, "mangakakalot.io", 20) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manganelo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manganelo.kt new file mode 100644 index 00000000..d17febdb --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manganelo.kt @@ -0,0 +1,12 @@ +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.MadaraParser + +@MangaSourceParser("MANGANELO", "Manganelo", "en") +internal class Manganelo(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGANELO, "manganelo.biz", 10) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManganeloWebsite.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManganeloWebsite.kt new file mode 100644 index 00000000..fe0f0472 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManganeloWebsite.kt @@ -0,0 +1,12 @@ +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.MadaraParser + +@MangaSourceParser("MANGANELO_WEBSITE", "Manganelo Website", "en") +internal class ManganeloWebsite(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGANELO_WEBSITE, "manganelo.website", 20) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaowlOne.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaowlOne.kt new file mode 100644 index 00000000..f54dbf11 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaowlOne.kt @@ -0,0 +1,13 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGAOWL_ONE", "MangaOwl One (unoriginal)", "en", ContentType.HENTAI) +internal class MangaowlOne(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAOWL_ONE, "mangaowl.one") { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaDex.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaDex.kt new file mode 100644 index 00000000..b752ed8a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaDex.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANHUADEX", "Manhua Dex", "en") +internal class ManhuaDex(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHUADEX, "manhuadex.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaManhwa.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaManhwa.kt new file mode 100644 index 00000000..371ea23c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaManhwa.kt @@ -0,0 +1,12 @@ +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.MadaraParser + +@MangaSourceParser("MANHUAMANHWA", "Manhua Manhwa", "en") +internal class ManhuaManhwa(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHUAMANHWA, "manhuamanhwa.com") { + override val datePattern = "MM/dd/yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaManhwaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaManhwaOnline.kt new file mode 100644 index 00000000..b90cc33a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaManhwaOnline.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANHUAMANHWAONLINE", "Manhua Manhwa Online", "en") +internal class ManhuaManhwaOnline(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHUAMANHWAONLINE, "manhuamanhwa.online", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaZonghe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaZonghe.kt new file mode 100644 index 00000000..09cf270f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaZonghe.kt @@ -0,0 +1,13 @@ +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.MadaraParser + +@MangaSourceParser("MANHUAZONGHE", "Manhua Zonghe", "en") +internal class ManhuaZonghe(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHUAZONGHE, "manhuazonghe.com") { + override val tagPrefix = "genre/" + override val listUrl = "manhua/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwa2Read.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwa2Read.kt new file mode 100644 index 00000000..f586ae74 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwa2Read.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANHWA2READ", "Manhwa2Read", "en") +internal class Manhwa2Read(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWA2READ, "manhwa2read.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaHentaiTo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaHentaiTo.kt new file mode 100644 index 00000000..83c75cc9 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaHentaiTo.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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANHWAHENTAITO", "ManhwaHentai To", "en", ContentType.HENTAI) +internal class ManhwaHentaiTo(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWAHENTAITO, "manhwahentai.to", 10) { + + override val tagPrefix = "pornhwa-genre/" + override val listUrl = "pornhwa/" + override val datePattern = "d MMMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaManhua.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaManhua.kt new file mode 100644 index 00000000..26cf3695 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaManhua.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("MANHWAMANHUA", "Manhwa Manhua", "en") +internal class ManhwaManhua(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWAMANHUA, "manhwamanhua.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaNew.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaNew.kt new file mode 100644 index 00000000..3320ff6d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhwaNew.kt @@ -0,0 +1,12 @@ +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.MadaraParser + +@MangaSourceParser("MANHWANEW", "Manhwa New", "en") +internal class ManhwaNew(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWANEW, "manhwanew.com") { + override val datePattern = "MM/dd/yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaKio.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwas.kt similarity index 62% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaKio.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwas.kt index 1eecb469..763e905e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaKio.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwas.kt @@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("MANGAKIO", "Manga Kio", "en") -internal class MangaKio(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MANGAKIO, "mangakio.me", 10) +@MangaSourceParser("MANHWAS", "Free Manhwa", "en") +internal class Manhwas(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWAS, "manhwas.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/OnlyManhwa.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/OnlyManhwa.kt new file mode 100644 index 00000000..2b7ed821 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/OnlyManhwa.kt @@ -0,0 +1,14 @@ +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.MadaraParser + +@MangaSourceParser("ONLYMANHWA", "OnlyManhwa", "en") +internal class OnlyManhwa(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ONLYMANHWA, "onlymanhwa.org") { + + override val listUrl = "manhwa/" + override val datePattern = "d 'de' MMMM 'de' yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/PawManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/PawManga.kt new file mode 100644 index 00000000..3774e6da --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/PawManga.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("PAWMANGA", "PawManga", "en", ContentType.HENTAI) +internal class PawManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.PAWMANGA, "pawmanga.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/PonyManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/PonyManga.kt new file mode 100644 index 00000000..b9a1fcfb --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/PonyManga.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("PONYMANGA", "PonyManga", "en", ContentType.HENTAI) +internal class PonyManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.PONYMANGA, "ponymanga.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StManhwa.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StManhwa.kt new file mode 100644 index 00000000..da84b190 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StManhwa.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("STMANHWA", "1st Manhwa", "en") +internal class StManhwa(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.STMANHWA, "1stmanhwa.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaCom.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaCom.kt new file mode 100644 index 00000000..e208fba2 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaCom.kt @@ -0,0 +1,12 @@ +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.MadaraParser + + +@MangaSourceParser("STKISSMANGA_COM", "1st Kiss-Manga (unoriginal)", "en") +internal class StkissMangaCom(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.STKISSMANGA_COM, "1stkiss-manga.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaTv.kt new file mode 100644 index 00000000..7e2137bc --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaTv.kt @@ -0,0 +1,14 @@ +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.MadaraParser + + +@MangaSourceParser("STKISSMANGA_TV", "1stKissManga Tv", "en") +internal class StkissMangaTv(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.STKISSMANGA_TV, "1stkissmanga.tv", 20) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Toonizy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Toonizy.kt new file mode 100644 index 00000000..aa73d7b3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Toonizy.kt @@ -0,0 +1,14 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("TOONIZY", "Toonizy", "en", ContentType.HENTAI) +internal class Toonizy(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.TOONIZY, "toonizy.com", 24) { + override val datePattern = "MMM d, yy" + override val listUrl = "webtoon/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/WebDexScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/WebDexScans.kt new file mode 100644 index 00000000..152c8d19 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/WebDexScans.kt @@ -0,0 +1,10 @@ +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.MadaraParser + +@MangaSourceParser("WEBDEXSCANS", "WebDex Scans", "en") +internal class WebDexScans(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.WEBDEXSCANS, "webdexscans.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/WebtoonCity.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/WebtoonCity.kt new file mode 100644 index 00000000..969cfeb4 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/WebtoonCity.kt @@ -0,0 +1,13 @@ +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.MadaraParser + +@MangaSourceParser("WEBTOONCITY", "Webtoon City", "en") +internal class WebtoonCity(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.WEBTOONCITY, "webtooncity.com", 20) { + override val listUrl = "webtoon/" + override val tagPrefix = "webtoon-genre/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinMangaTop.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinMangaTop.kt new file mode 100644 index 00000000..e264a1c8 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinMangaTop.kt @@ -0,0 +1,11 @@ +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.MadaraParser + +@MangaSourceParser("ZINMANGA_TOP", "ZinManga Top (unoriginal)", "en") +internal class ZinMangaTop(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ZINMANGA_TOP, "zinmanga.top", 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Komikgue.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Komikgue.kt new file mode 100644 index 00000000..d786e578 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Komikgue.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.madara.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.MadaraParser + +@MangaSourceParser("KOMIKGUE", "Komikgue", "id") +internal class Komikgue(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.KOMIKGUE, "komikgue.pro", 10) { + + override val tagPrefix = "komik-genre/" + override val listUrl = "komik/" + override val datePattern = "MMMM dd, yyyy" + override val withoutAjax = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/LimaScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/LimaScans.kt deleted file mode 100644 index 5b9576c3..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/LimaScans.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.madara.pt - -import org.jsoup.nodes.Document -import org.koitharu.kotatsu.parsers.MangaLoaderContext -import org.koitharu.kotatsu.parsers.MangaSourceParser -import org.koitharu.kotatsu.parsers.model.ContentType -import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -import org.koitharu.kotatsu.parsers.util.* -import java.text.SimpleDateFormat - -@MangaSourceParser("LIMASCANS", "Lima Scans", "pt", ContentType.HENTAI) -internal class LimaScans(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.LIMASCANS, "limascans.xyz/v2", 10) { - - override val postreq = true - override val datePattern: String = "dd 'de' MMMMM 'de' yyyy" - - override suspend fun loadChapters(mangaUrl: String, document: Document): List { - - - 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 dateFormat = SimpleDateFormat(datePattern, sourceLocale) - - return doc.select(selectChapter).mapChapters(reversed = true) { i, li -> - val a = li.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - val link = href + stylepage - val dateText = li.selectFirst("a.c-new-tag")?.attr("title") ?: li.selectFirst(selectDate)?.text() - val name = a.selectFirst("p")?.text() ?: a.ownText() - MangaChapter( - id = generateUid(href), - url = link.replace("/v2", ""), - name = name, - number = i + 1, - branch = null, - uploadDate = parseChapterDate(dateFormat, dateText), - scanlator = null, - source = source, - ) - } - } -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/TheSugarScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/TheSugarScan.kt deleted file mode 100644 index 43cc17e5..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/TheSugarScan.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.madara.pt - - -import org.koitharu.kotatsu.parsers.MangaLoaderContext -import org.koitharu.kotatsu.parsers.MangaSourceParser -import org.koitharu.kotatsu.parsers.model.ContentType -import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.parsers.site.madara.MadaraParser - -@MangaSourceParser("THESUGARSCAN", "The Sugar Scan", "pt", ContentType.HENTAI) -internal class TheSugarScan(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.THESUGARSCAN, "thesugarscan.com", pageSize = 15) { - - override val datePattern: String = "dd/MM/yyyy" -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/th/KingsManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/th/KingsManga.kt new file mode 100644 index 00000000..07bf217a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/th/KingsManga.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.madara.th + + +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("KINGS_MANGA", "Kings Manga", "th") +internal class KingsManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.KINGS_MANGA, "www.kings-manga.co") { + override val postreq = true + override val datePattern = "d MMMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/th/NekoPost.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/th/NekoPost.kt new file mode 100644 index 00000000..22a88dfd --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/th/NekoPost.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.madara.th + +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("NEKOPOST", "Neko Post", "th") +internal class NekoPost(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.NEKOPOST, "www.nekopost.co") { + + override val postreq = true + override val datePattern = "d MMMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/MonoManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/MonoManga.kt new file mode 100644 index 00000000..dd86ea52 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/MonoManga.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.madara.tr + +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("MONOMANGA", "Mono Manga", "tr") +internal class MonoManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MONOMANGA, "monomanga.com") { + override val datePattern = "d MMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt index 20b9cf16..8623f32d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt @@ -38,7 +38,7 @@ internal class Mangakakalot(context: MangaLoaderContext) : append("?page=") append(page.toString()) - }else { + } else { append("$listUrl/") when (sortOrder) { SortOrder.POPULARITY -> append("?type=topview") @@ -49,7 +49,7 @@ internal class Mangakakalot(context: MangaLoaderContext) : if (!tags.isNullOrEmpty()) { append("&category=") append(tag?.key.orEmpty()) - }else{ + } else { append("&category=all") } append("&state=all&page=") @@ -89,11 +89,9 @@ internal class Mangakakalot(context: MangaLoaderContext) : val a = li.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") val dateText = li.select(selectDate).last()?.text() ?: "0" - val dateFormat = if(dateText.contains("-")) - { + val dateFormat = if (dateText.contains("-")) { SimpleDateFormat("MMM-dd-yy", sourceLocale) - }else - { + } else { SimpleDateFormat(datePattern, sourceLocale) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/PotatoManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/PotatoManga.kt new file mode 100644 index 00000000..073595ef --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/PotatoManga.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser + +@MangaSourceParser("POTATOMANGA", "Potato Manga", "ar") +internal class PotatoManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.POTATOMANGA, "potatomanga.xyz", pageSize = 30, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/OpScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/OpScans.kt new file mode 100644 index 00000000..0783bf94 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/OpScans.kt @@ -0,0 +1,10 @@ +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("OPSCANS", "OpScans", "en") +internal class OpScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.OPSCANS, "opscans.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt index cc355e83..d071f531 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt @@ -1,15 +1,94 @@ package org.koitharu.kotatsu.parsers.site.mangareader.en +import org.jsoup.nodes.Document 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.MangaChapter 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.model.SortOrder import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import org.koitharu.kotatsu.parsers.util.domain +import org.koitharu.kotatsu.parsers.util.mapNotNullToSet +import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany +import org.koitharu.kotatsu.parsers.util.parseHtml +import java.util.EnumSet @MangaSourceParser("REALMSCANS", "RealmScans", "en") internal class RealmScans(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.REALMSCANS, "realmscans.xyz", pageSize = 30, searchPageSize = 50) { + MangaReaderParser(context, MangaSource.REALMSCANS, "realmscans.to", pageSize = 52, searchPageSize = 50) { override val listUrl = "/series" override val datePattern = "dd MMM yyyy" + + override val sortOrders: Set + get() = EnumSet.of(SortOrder.NEWEST) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + if (!query.isNullOrEmpty()) { + return emptyList() // to do + } + + if (!tags.isNullOrEmpty()) { + if (page > 1) { + return emptyList() + } + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + append("genre/") + append(tag?.key.orEmpty()) + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + if (page > 1) { + return emptyList() + } + val url = buildString { + append("https://") + append(domain) + append(listUrl) + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + override suspend fun parseInfo(docs: Document, manga: Manga, chapters: List): Manga { + val tagMap = getOrCreateTagMap() + val selectTag = docs.select(".wd-full .mgen > a") + val tags = selectTag.mapNotNullToSet { tagMap[it.text()] } + val mangaState = docs.selectFirst(".bs-status")?.let { + when (it.text()) { + "ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + } + } + val author = docs.selectFirst(".tsinfo div:contains(Author)")?.lastElementChild()?.text() + val nsfw = docs.selectFirst(".restrictcontainer") != null + || docs.selectFirst(".info-right .alr") != null + || docs.selectFirst(".postbody .alr") != null + + // Description in markdown renders it unattractive and unclear on the synopsis + // val desc = docs.selectFirstOrThrow("script:containsData(var description)").data().substringAfter("var description = \"").substringBefore("\";") + + return manga.copy( + description = null, + state = mangaState, + author = author, + isNsfw = manga.isNsfw || nsfw, + tags = tags, + chapters = chapters, + ) + } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Mangadop.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Mangadop.kt new file mode 100644 index 00000000..4d559711 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Mangadop.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.id + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.util.Locale + +@MangaSourceParser("MANGADOP", "Mangadop", "id", ContentType.HENTAI) +internal class Mangadop(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGADOP, "mangadop.net", pageSize = 20, searchPageSize = 20) { + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Noromax.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Noromax.kt new file mode 100644 index 00000000..128202f0 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Noromax.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser +import java.util.Locale + +@MangaSourceParser("NOROMAX", "Noromax", "id") +internal class Noromax(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.NOROMAX, "noromax.my.id", pageSize = 20, searchPageSize = 10) { + + override val listUrl = "/Komik" + override val sourceLocale: Locale = Locale.ENGLISH + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt index 9becaf00..e2fd42aa 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt @@ -7,7 +7,13 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("SKANLACJEFENIKSY", "SkanlacjeFeniksy", "pl") internal class SkanlacjeFeniksy(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.SKANLACJEFENIKSY, "skanlacje-feniksy.pl", pageSize = 10, searchPageSize = 10) { + MangaReaderParser( + context, + MangaSource.SKANLACJEFENIKSY, + "skanlacje-feniksy.pl", + pageSize = 10, + searchPageSize = 10, + ) { override val datePattern = "d MMMM, yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/ArkhamScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/ArkhamScan.kt new file mode 100644 index 00000000..ea44d80a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/ArkhamScan.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.pt + +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("ARKHAMSCAN", "ArkhamScan", "pt") +internal class ArkhamScan(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.ARKHAMSCAN, "arkhamscan.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt new file mode 100644 index 00000000..7a860000 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt @@ -0,0 +1,440 @@ +package org.koitharu.kotatsu.parsers.site.otakusanctuary + +import kotlinx.coroutines.coroutineScope +import okhttp3.HttpUrl.Companion.toHttpUrl +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.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +internal abstract class OtakuSanctuaryParser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 32, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.NEWEST, + ) + + protected open val listeurl = "Manga/Newest" + protected open val datePattern = "dd/MM/yyyy" + protected open val lang = "" + + @JvmField + protected val ongoing: Set = setOf( + "Ongoing", + ) + + @JvmField + protected val finished: Set = setOf( + "Completed", + "Done", + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + + val doc = if (!tags.isNullOrEmpty()) { + val url = buildString { + + append("https://$domain/Genre/MangaGenrePartial?id=") + append(tag?.key.orEmpty()) + append("&lang=$lang") + append("&offset=") + append(page * pageSize) + append("&pagesize=$pageSize") + } + + webClient.httpGet(url).parseHtml() + } else if (!query.isNullOrEmpty()) { + if (page > 1) { + return emptyList() + } + + val url = buildString { + append("https://$domain/Home/Search?search=") + append(query.urlEncoded()) + } + + webClient.httpGet(url).parseHtml() + } else { + val url = "https://$domain/$listeurl" + val payload = HashMap() + payload["Lang"] = lang + payload["Page"] = page.toString() + payload["Type"] = "Include" + when (sortOrder) { + SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" + SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" + else -> payload["Dir"] = "NewPostedDate" + } + webClient.httpPost(url, payload).parseHtml() + } + + + val mangas = if (!query.isNullOrEmpty()) { + doc.requireElementById("collection-manga").select("div.picture-card") + } else { + doc.select("div.picture-card") + } + + return mangas.map { div -> + + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), + title = div.selectFirstOrThrow("h4").text().orEmpty(), + altTitle = null, + rating = div.selectFirst(".rating")?.ownText()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + protected open val selectBodyTag = "div#genre-table a" + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/Home/LoadingGenresMenu").parseHtml() + return doc.select(selectBodyTag).mapNotNullToSet { a -> + val href = a.attr("href").substringAfterLast("/").substringBefore("?") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } + + protected open val selectDesc = "div.summary" + protected open val selectState = ".table-info tr:contains(Tình Trạng) td" + protected open val selectAlt = ".table-info tr:contains(Other Name) + tr" + protected open val selectAut = ".table-info tr a.capitalize" + protected open val selectTag = ".genres a" + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + val desc = doc.selectFirstOrThrow(selectDesc).html() + + val stateDiv = doc.selectFirst(selectState) + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + + val alt = doc.body().selectFirst(selectAlt)?.text()?.replace("Other names", "") + val auth = doc.body().selectFirst(selectAut)?.text() + + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + + manga.copy( + tags = doc.body().select(selectTag).mapNotNullToSet { a -> + val href = a.attr("href").substringAfterLast("/").substringBefore("?") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + }, + description = desc, + altTitle = alt, + author = auth, + state = state, + chapters = doc.body().requireElementById("chapter").select("tr.chapter") + .mapChapters(reversed = true) { i, tr -> + val dateText = tr.select("td")[3].text() + val a = tr.selectFirstOrThrow("td.read-chapter a") + val url = a.attrAsRelativeUrl("href") + val name = a.text() + MangaChapter( + id = generateUid(url), + name = name, + number = i, + url = url, + scanlator = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + branch = null, + source = source, + ) + }, + ) + } + + + protected open val selectPage = "div#rendering .image-wraper img" + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + if (doc.select(selectPage).attr("src").isNullOrEmpty()) { + val chapterId = doc.select("#inpit-c").attr("data-chapter-id") + val url = "https://$domain/Manga/UpdateView" + val postdata = "chapId=$chapterId" + + val json = webClient.httpPost(url, postdata).parseRaw() + + val urls = json.replace("\\u0022", "").substringAfter("{\"view\":\"[").substringBefore("]\",\"isSuccess") + .split(",") + return urls.map { + + val urlImage = processUrl(it) + MangaPage( + id = generateUid(urlImage), + url = urlImage, + preview = null, + source = source, + ) + } + } else { + return doc.select(selectPage).map { img -> + val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + + } + + protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + // Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it + val d = date?.lowercase() ?: return 0 + return when { + d.endsWith(" ago") || d.endsWith(" atrás") || d.startsWith("cách đây ") -> parseRelativeDate(date) + + else -> dateFormat.tryParse(date) + } + } + + // Parses dates in this form: + // 21 hours ago + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet( + "day", + "days", + "d", + "ngày", + ).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + + WordSet( + "tiếng", + "hour", + "hours", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.HOUR, + -number, + ) + }.timeInMillis + + WordSet( + "min", + "minute", + "minutes", + "phút", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.MINUTE, + -number, + ) + }.timeInMillis + + WordSet("second", "giây").anyWordIn(date) -> cal.apply { + add( + Calendar.SECOND, + -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 + } + } + + + fun processUrl(url: String, vi: String = ""): String { + var url = url.replace("_h_", "http") + .replace("_e_", "/extendContent/Manga") + .replace("_r_", "/extendContent/MangaRaw") + + if (url.startsWith("//")) { + url = "https:$url" + } + if (url.contains("drive.google.com")) { + return url + } + + url = when (url.slice(0..4)) { + "[GDP]" -> url.replace("[GDP]", "https://drive.google.com/uc?export=view&id=") + "[GDT]" -> if (lang == "us") { + url.replace("image2.otakuscan.net", "image3.shopotaku.net") + .replace("image2.otakusan.net", "image3.shopotaku.net") + } else { + url + } + + "[IS1]" -> { + val url = url.replace("[IS1]", "https://imagepi.otakuscan.net/") + if (url.contains("vi") && url.contains("otakusan.net_")) { + url + } else { + url.toHttpUrl().newBuilder().apply { + addQueryParameter("vi", vi) + }.build().toString() + } + } + + "[IS3]" -> url.replace("[IS3]", "https://image3.otakusan.net/") + "[IO3]" -> url.replace("[IO3]", "http://image3.shopotaku.net/") + else -> url + } + + if (url.contains("/Content/Workshop") || url.contains("otakusan") || url.contains("myrockmanga")) { + return url + } + + if (url.contains("file-bato-orig.anyacg.co")) { + url = url.replace("file-bato-orig.anyacg.co", "file-bato-orig.bato.to") + } + + if (url.contains("file-comic")) { + if (url.contains("file-comic-1")) { + url = url.replace("file-comic-1.anyacg.co", "z-img-01.mangapark.net") + } + if (url.contains("file-comic-2")) { + url = url.replace("file-comic-2.anyacg.co", "z-img-02.mangapark.net") + } + if (url.contains("file-comic-3")) { + url = url.replace("file-comic-3.anyacg.co", "z-img-03.mangapark.net") + } + if (url.contains("file-comic-4")) { + url = url.replace("file-comic-4.anyacg.co", "z-img-04.mangapark.net") + } + if (url.contains("file-comic-5")) { + url = url.replace("file-comic-5.anyacg.co", "z-img-05.mangapark.net") + } + if (url.contains("file-comic-6")) { + url = url.replace("file-comic-6.anyacg.co", "z-img-06.mangapark.net") + } + if (url.contains("file-comic-9")) { + url = url.replace("file-comic-9.anyacg.co", "z-img-09.mangapark.net") + } + if (url.contains("file-comic-10")) { + url = url.replace("file-comic-10.anyacg.co", "z-img-10.mangapark.net") + } + if (url.contains("file-comic-99")) { + url = url.replace("file-comic-99.anyacg.co/uploads", "file-bato-0001.bato.to") + } + } + + if (url.contains("cdn.nettruyen.com")) { + url = url.replace( + "cdn.nettruyen.com/Data/Images/", + "truyen.cloud/data/images/", + ) + } + if (url.contains("url=")) { + url = url.substringAfter("url=") + } + if (url.contains("blogspot") || url.contains("fshare")) { + url = url.replace("http:", "https:") + } + if (url.contains("blogspot") && !url.contains("http")) { + url = "https://$url" + } + if (url.contains("app/manga/uploads/") && !url.contains("http")) { + url = "https://lhscan.net$url" + } + url = url.replace("//cdn.adtrue.com/rtb/async.js", "") + + if (url.contains(".webp")) { + url = "https://otakusan.net/api/Value/ImageSyncing?ip=34512351".toHttpUrl().newBuilder() + .apply { + addQueryParameter("url", url) + }.build().toString() + } else if ( + ( + url.contains("merakiscans") || + url.contains("mangazuki") || + url.contains("ninjascans") || + url.contains("anyacg.co") || + url.contains("mangakatana") || + url.contains("zeroscans") || + url.contains("mangapark") || + url.contains("mangadex") || + url.contains("uptruyen") || + url.contains("hocvientruyentranh") || + url.contains("ntruyen.info") || + url.contains("chancanvas") || + url.contains("bato.to") + ) && + ( + !url.contains("googleusercontent") && + !url.contains("otakusan") && + !url.contains("otakuscan") && + !url.contains("shopotaku") + ) + ) { + url = + "https://images2-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&gadget=a&no_expand=1&resize_h=0&rewriteMime=image%2F*".toHttpUrl() + .newBuilder().apply { + addQueryParameter("url", url) + }.build().toString() + } else if (url.contains("imageinstant.com")) { + url = "https://images.weserv.nl/".toHttpUrl().newBuilder().apply { + addQueryParameter("url", url) + }.build().toString() + } else if (!url.contains("otakusan.net")) { + url = "https://otakusan.net/api/Value/ImageSyncing?ip=34512351".toHttpUrl().newBuilder() + .apply { + addQueryParameter("url", url) + }.build().toString() + } + + return if (url.contains("vi=") && !url.contains("otakusan.net_")) { + url + } else { + url.toHttpUrl().newBuilder().apply { + addQueryParameter("vi", vi) + }.build().toString() + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/en/MyrockMangaEn.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/en/MyrockMangaEn.kt new file mode 100644 index 00000000..8c213908 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/en/MyrockMangaEn.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.otakusanctuary.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.otakusanctuary.OtakuSanctuaryParser + + +@MangaSourceParser("MYROCKMANGA_EN", "MyrockManga En", "en") +internal class MyrockMangaEn(context: MangaLoaderContext) : + OtakuSanctuaryParser(context, MangaSource.MYROCKMANGA_EN, "myrockmanga.com") { + override val lang = "us" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/en/OtakusanEn.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/en/OtakusanEn.kt new file mode 100644 index 00000000..e022672f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/en/OtakusanEn.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.otakusanctuary.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.otakusanctuary.OtakuSanctuaryParser + + +@MangaSourceParser("OTAKUSAN_EN", "Otakusan En", "en") +internal class OtakusanEn(context: MangaLoaderContext) : + OtakuSanctuaryParser(context, MangaSource.OTAKUSAN_EN, "otakusan.net") { + override val lang = "us" + override val selectState = ".table-info tr:contains(Status) td" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/vi/MyrockMangaVi.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/vi/MyrockMangaVi.kt new file mode 100644 index 00000000..f4d9e57f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/vi/MyrockMangaVi.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.otakusanctuary.vi + + +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.otakusanctuary.OtakuSanctuaryParser + + +@MangaSourceParser("MYROCKMANGA_VI", "MyrockManga Vi", "en") +internal class MyrockMangaVi(context: MangaLoaderContext) : + OtakuSanctuaryParser(context, MangaSource.MYROCKMANGA_VI, "myrockmanga.com") { + override val lang = "vn" +} + diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/vi/OtakusanVi.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/vi/OtakusanVi.kt new file mode 100644 index 00000000..9c9b0def --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/vi/OtakusanVi.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.otakusanctuary.vi + + +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.otakusanctuary.OtakuSanctuaryParser + + +@MangaSourceParser("OTAKUSAN_VI", "Otakusan Vi", "vi") +internal class OtakusanVi(context: MangaLoaderContext) : + OtakuSanctuaryParser(context, MangaSource.OTAKUSAN_VI, "otakusan.net") { + + override val selectState = ".table-info tr:contains(Status) td" + override val lang = "vn" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt new file mode 100644 index 00000000..9a9e106f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt @@ -0,0 +1,145 @@ +package org.koitharu.kotatsu.parsers.site.pt + +import okhttp3.Headers +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.network.UserAgents +import org.koitharu.kotatsu.parsers.util.* +import java.util.* + +@MangaSourceParser("BRMANGAS", "BrMangas", "pt") +internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.BRMANGAS, 25) { + + override val sortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) + + override val configKeyDomain = ConfigKey.Domain("www.brmangas.net") + + override val headers: Headers = Headers.Builder() + .add("User-Agent", UserAgents.CHROME_DESKTOP) + .build() + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://$domain/") + if (!tags.isNullOrEmpty()) { + append("category/") + append(tag?.key.orEmpty()) + if (page > 1) { + append("/page/$page/") + } + } else if (!query.isNullOrEmpty()) { + if (page > 1) { + append("/page/$page/") + } + append("/?s=") + append(query.urlEncoded()) + + } else { + when (sortOrder) { + SortOrder.POPULARITY -> append("/") + SortOrder.UPDATED -> append("manga/") + else -> append("manga/") + } + if (page > 1) { + append("page/$page/") + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + + val item = if (sortOrder == SortOrder.POPULARITY) { + doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page + } else { + doc.select("div.listagem div.item") + } + + return item.map { div -> + val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h2").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/lista-de-generos-de-manga/").parseHtml() + return doc.select(".genres_page a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast("/"), + title = a.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + return manga.copy( + altTitle = null, + state = null, + tags = doc.select("div.serie-infos li:contains(Categorias:) a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast("/"), + title = a.text(), + source = source, + ) + }, + author = doc.select("div.serie-infos li:contains(Autor:)").text().replace("Autor:", ""), + description = doc.select(".serie-texto p").text(), + isNsfw = doc.select("div.serie-infos li:contains(Categorias:)").text().contains("Hentai"), + chapters = doc.select(".capitulos li a") + .mapChapters(reversed = true) { i, a -> + val url = a.attrAsRelativeUrl("href") + val name = a.text() + MangaChapter( + id = generateUid(url), + name = name, + number = i, + url = url, + scanlator = null, + uploadDate = 0, + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val scriptData = + doc.selectFirstOrThrow("script:containsData(imageArray)").data().substringAfter("[").substringBefore("]") + .split(",") + return scriptData.map { data -> + val url = data.replace("\\\"", "").replace("\\/", "/") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt new file mode 100644 index 00000000..95fac21a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt @@ -0,0 +1,203 @@ +package org.koitharu.kotatsu.parsers.site.sinmh + +import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Document +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.util.* + +internal abstract class SinmhParser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 36, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + ) + + protected open val searchUrl = "search/" + protected open val listUrl = "list/" + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + @JvmField + protected val ongoing: Set = hashSetOf( + "连载中", + ) + + @JvmField + protected val finished: Set = hashSetOf( + "已完结", + ) + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + when { + !query.isNullOrEmpty() -> { + append("/$searchUrl?keywords=") + append(query.urlEncoded()) + append("&page=") + append(page) + } + + !tags.isNullOrEmpty() -> { + append("/$listUrl") + append(tag?.key.orEmpty()) + append("/$page/") + } + + else -> { + + append("/$listUrl") + when (sortOrder) { + SortOrder.POPULARITY -> append("click/") + SortOrder.UPDATED -> append("update/") + else -> append("") + } + append("?page=") + append(page) + } + } + + } + + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("#contList > li, li.list-comic").map { div -> + val href = div.selectFirstOrThrow("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.selectFirst("p > a, h3 > a")?.text().orEmpty(), + altTitle = null, + rating = div.selectFirst("span.total_votes")?.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/$listUrl").parseHtml() + return doc.select(".filter-item:contains(按剧情) li a:not(.active)").mapNotNullToSet { a -> + val href = a.attr("href").removeSuffix("/").substringAfterLast("/") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } + + protected open val selectDesc = "div#intro-all p" + protected open val selectGenre = "ul.detail-list li:contains(漫画类型) a" + protected open val selectState = "ul.detail-list li:contains(漫画状态) 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() + + val chapters = getChapters(manga, doc) + + val desc = body.selectFirst(selectDesc)?.html() + + val state = body.selectFirst(selectState)?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + manga.copy( + tags = doc.body().select(selectGenre).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + state = state, + chapters = chapters, + ) + } + + + protected open val selectChapter = "ul#chapter-list-1 li" + + protected open suspend fun getChapters(manga: Manga, doc: Document): List { + return doc.body().select(selectChapter).mapChapters { i, li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val name = li.selectFirstOrThrow("a").text() + MangaChapter( + id = generateUid(href), + name = name, + number = i + 1, + url = href, + uploadDate = 0, + source = source, + scanlator = null, + branch = null, + ) + } + } + + protected open val selectTestScript = "script:containsData(chapterImages = )" + + override suspend fun getPages(chapter: MangaChapter): List { + + val host = webClient.httpGet("/js/config.js".toAbsoluteUrl(domain)).parseRaw().substringAfter("domain\":[\"") + .substringBefore("\"]}") + + val chapterUrl = chapter.url.toAbsoluteUrl(domain) + val docs = webClient.httpGet(chapterUrl).parseHtml() + val script = docs.selectFirstOrThrow(selectTestScript).html() + val images = + script.substringAfter("chapterImages = [").substringBefore("];var chapterPath").replace("\"", "").split(",") + val path = script.substringAfter("chapterPath = \"").substringBefore("\";var ") + + val pages = ArrayList() + images.map { + val imageUrl = when { + it.startsWith("https:\\/\\/") -> it.replace("\\", "") + it.startsWith("/") -> "$host$it" + else -> "$host/$path$it" + } + pages.add( + MangaPage( + id = generateUid(imageUrl), + url = imageUrl, + preview = null, + source = source, + ), + ) + } + return pages + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/zh/Gufengmh.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/zh/Gufengmh.kt new file mode 100644 index 00000000..9596e5b0 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/zh/Gufengmh.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.sinmh.zh + +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.sinmh.SinmhParser + +@MangaSourceParser("GUFENGMH", "Gufengmh", "zh") +internal class Gufengmh(context: MangaLoaderContext) : + SinmhParser(context, MangaSource.GUFENGMH, "www.gufengmh.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/zh/Imitui.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/zh/Imitui.kt new file mode 100644 index 00000000..1302a633 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/zh/Imitui.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.sinmh.zh + +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.sinmh.SinmhParser + +@MangaSourceParser("IMITUI", "Imitui", "zh") +internal class Imitui(context: MangaLoaderContext) : + SinmhParser(context, MangaSource.IMITUI, "www.imitui.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt index 2f8e9c5a..d4091b9e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt @@ -155,7 +155,7 @@ fun Element.attrOrNull(vararg names: String): String? { } @JvmOverloads -fun Element.src(names: Array = arrayOf("data-src", "data-cfsrc", "data-original", "data-cdn", "data-sizes", "data-lazy-src", "data-srcset", "src")): String? { +fun Element.src(names: Array = arrayOf("data-src", "data-cfsrc", "data-original", "data-cdn", "data-sizes", "data-lazy-src", "data-srcset", "original-src", "src")): String? { for (name in names) { val value = attrAsAbsoluteUrlOrNull(name) if (value != null) {