diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt new file mode 100644 index 00000000..3dbd5406 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt @@ -0,0 +1,184 @@ +package org.koitharu.kotatsu.parsers.site.ar + +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Element +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar") +internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) { + + override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val configKeyDomain = ConfigKey.Domain("teamxnovel.com") + + 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("/series?genre=") + append(tag?.key.orEmpty()) + if (page > 1) { + append("&page=") + append(page) + } + } else if (!query.isNullOrEmpty()) { + append("/series?search=") + append(query.urlEncoded()) + if (page > 1) { + append("&page=") + append(page) + } + } else { + when (sortOrder) { + SortOrder.POPULARITY -> append("/series") + SortOrder.UPDATED -> append("/") + else -> append("/") + } + if (page > 1) { + append("?page=") + append(page) + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.listupd .bs .bsx").ifEmpty { + doc.select("div.post-body .box") + }.map { div -> + val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + Manga( + id = generateUid(href), + title = div.select(".tt, h3").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), + tags = emptySet(), + state = when (div.selectFirst(".status")?.text()) { + "مستمرة" -> MangaState.ONGOING + "متوقف", "مكتمل" -> MangaState.FINISHED + else -> null + }, + author = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/series").parseHtml() + return doc.requireElementById("select_genre").select("option").mapNotNullToSet { + MangaTag( + key = it.attr("value"), + title = it.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val mangaUrl = manga.url.toAbsoluteUrl(domain) + + val maxPageChapterSelect = doc.select(".pagination .page-item a") + var maxPageChapter = 1 + if (!maxPageChapterSelect.isNullOrEmpty()) { + maxPageChapterSelect.map { + val i = it.attr("href").substringAfterLast("=").toInt() + if (i > maxPageChapter) { + maxPageChapter = i + } + } + } + + return manga.copy( + altTitle = null, + state = when (doc.selectFirstOrThrow(".full-list-info:contains(الحالة:) a").text()) { + "مستمرة" -> MangaState.ONGOING + "متوقف", "مكتمل" -> MangaState.FINISHED + else -> null + }, + tags = doc.select(".review-author-info a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("="), + title = a.text(), + source = source, + ) + }, + author = null, + description = doc.selectFirstOrThrow(".review-content").text(), + chapters = run { + if (maxPageChapter == 1) { + parseChapters(doc) + } else { + coroutineScope { + val result = ArrayList(parseChapters(doc)) + result.ensureCapacity(result.size * maxPageChapter) + (2..maxPageChapter).map { i -> + async { + loadChapters(mangaUrl, i) + } + }.awaitAll() + .flattenTo(result) + result + } + } + }.reversed(), + ) + } + + private suspend fun loadChapters(baseUrl: String, page: Int): List { + return parseChapters(webClient.httpGet("$baseUrl?page=$page").parseHtml().body()) + } + + private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", sourceLocale) + + private fun parseChapters(root: Element): List { + return root.requireElementById("chapter-contact").select(".eplister ul li") + .map { li -> + val url = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(url), + name = li.selectFirstOrThrow(".epl-title").text(), + number = url.substringAfterLast('/').toIntOrNull() ?: 0, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(li.selectFirstOrThrow(".epl-date").text()), + 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(".image_list 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/MangaGeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt new file mode 100644 index 00000000..d79f7bae --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt @@ -0,0 +1,147 @@ +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("MANGAGEKO", "MangaGeko", "en") +internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGAGEKO, 30) { + + override val sortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST) + + override val configKeyDomain = ConfigKey.Domain("www.mangageko.com") + + 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 = if (!query.isNullOrEmpty()) { + if (page > 1) { + return emptyList() + } + buildString { + append("https://$domain/search/?search=") + append(query.urlEncoded()) + } + } else { + buildString { + append("https://$domain/browse-comics/?results=") + append(page) + append("&filter=") + when (sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("Updated") + SortOrder.NEWEST -> append("New") + else -> append("Updated") + } + if (!tags.isNullOrEmpty()) { + append("&genre=") + append(tag?.key.orEmpty()) + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("li.novel-item").map { div -> + val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h4").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 = div.selectFirstOrThrow("h6").text().removePrefix("Author(S): "), + source = source, + ) + } + } + + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/browse-comics/").parseHtml() + return doc.select("label.checkbox-inline").mapNotNullToSet { label -> + MangaTag( + key = label.selectFirstOrThrow("input").attr("value"), + title = label.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat("MMM dd, yyyy", sourceLocale) + return manga.copy( + altTitle = doc.selectFirstOrThrow(".alternative-title").text(), + state = when (doc.selectFirstOrThrow(".header-stats span:contains(Status) strong").text()) { + "Ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + }, + tags = doc.select(".categories ul li a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("="), + title = a.text(), + source = source, + ) + }, + author = doc.selectFirstOrThrow(".author").text(), + description = doc.selectFirstOrThrow(".description").html(), + chapters = doc.requireElementById("chapters").select("ul.chapter-list li") + .mapChapters(reversed = true) { i, li -> + val a = li.selectFirstOrThrow("a") + val url = a.attrAsRelativeUrl("href") + val name = li.selectFirstOrThrow(".chapter-title").text() + val dateText = li.select(".chapter-update").attr("datetime").substringBeforeLast(",") + .replace(".", "").replace("Sept", "Sep") + MangaChapter( + id = generateUid(url), + name = name, + number = i + 1, + 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.requireElementById("chapter-reader").select("img").map { img -> + val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt index e3a71e68..4632d2dd 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,7 +155,7 @@ internal abstract class MadaraParser( !tags.isNullOrEmpty() -> { append("/$tagPrefix") append(tag?.key.orEmpty()) - if (page > 1) { + if (pages > 1) { append("/page/") append(pages.toString()) } @@ -165,7 +165,7 @@ internal abstract class MadaraParser( else -> { append("/$listUrl") - if (page > 1) { + if (pages > 1) { append("page/") append(pages) } @@ -278,9 +278,9 @@ internal abstract class MadaraParser( val doc = webClient.httpGet(fullUrl).parseHtml() val body = doc.body() - val testchekasync = body.select(selectTestAsync) + val testCheckAsync = body.select(selectTestAsync) - val chaptersDeferred = if (testchekasync.isNullOrEmpty()) { + val chaptersDeferred = if (testCheckAsync.isNullOrEmpty()) { async { loadChapters(manga.url, doc) } } else { async { getChapters(manga, doc) } @@ -373,6 +373,7 @@ internal abstract class MadaraParser( val url = mangaUrl.toAbsoluteUrl(domain).removeSuffix('/') + "/ajax/chapters/" webClient.httpPost(url, emptyMap()).parseHtml() } + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) return doc.select(selectChapter).mapChapters(reversed = true) { i, li -> @@ -563,7 +564,13 @@ internal abstract class MadaraParser( ) }.timeInMillis - WordSet("month", "months").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -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 } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Ero18x.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Ero18x.kt new file mode 100644 index 00000000..89d26a74 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Ero18x.kt @@ -0,0 +1,56 @@ +package org.koitharu.kotatsu.parsers.site.madara.all + +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.Manga +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.attrAsRelativeUrlOrNull +import org.koitharu.kotatsu.parsers.util.generateUid +import org.koitharu.kotatsu.parsers.util.mapChapters +import org.koitharu.kotatsu.parsers.util.parseFailed +import java.text.SimpleDateFormat +import java.util.Locale + +@MangaSourceParser("ERO18X", "Ero18x", "", ContentType.HENTAI) +internal class Ero18x(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ERO18X, "ero18x.com", 10) { + override val datePattern = "MMMM d" + override val sourceLocale: Locale = Locale.ENGLISH + override val withoutAjax = true + + override suspend fun getChapters(manga: Manga, doc: Document): List { + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val 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), + name = name, + number = i + 1, + url = link, + uploadDate = if (dateText == "Newly Published!") { + parseChapterDate( + dateFormat, + "today", + ) + } else { + parseChapterDate( + dateFormat, + dateText, + ) + }, + source = source, + scanlator = null, + branch = null, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/ManhwaRaw.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/ManhwaRaw.kt new file mode 100644 index 00000000..4241fed7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/ManhwaRaw.kt @@ -0,0 +1,57 @@ +package org.koitharu.kotatsu.parsers.site.madara.all + +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.Manga +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.attrAsRelativeUrlOrNull +import org.koitharu.kotatsu.parsers.util.generateUid +import org.koitharu.kotatsu.parsers.util.mapChapters +import org.koitharu.kotatsu.parsers.util.parseFailed +import java.text.SimpleDateFormat +import java.util.Locale + +@MangaSourceParser("MANHWARAW", "Manhwa Raw", "", ContentType.HENTAI) +internal class ManhwaRaw(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWARAW, "manhwa-raw.com", 10) { + override val datePattern = "MMMM d" + override val sourceLocale: Locale = Locale.ENGLISH + override val withoutAjax = true + + + override suspend fun getChapters(manga: Manga, doc: Document): List { + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val 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), + name = name, + number = i + 1, + url = link, + uploadDate = if (dateText == "Newly Published!") { + parseChapterDate( + dateFormat, + "today", + ) + } else { + parseChapterDate( + dateFormat, + dateText, + ) + }, + source = source, + scanlator = null, + branch = null, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/GoodGirls.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/GoodGirls.kt new file mode 100644 index 00000000..381ccb65 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/GoodGirls.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("GOODGIRLS", "GoodGirls", "en") +internal class GoodGirls(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.GOODGIRLS, "goodgirls.moe", 10) { + override val selectDesc = "div.post-content_item:contains(Synopsis) div.summary-content" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.kt new file mode 100644 index 00000000..bd5b7a47 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.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("HIPERDEX", "HiperDex", "en", ContentType.HENTAI) +internal class HiperDex(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.HIPERDEX, "hiperdex.com", 36) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreatOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDinoTop.kt similarity index 55% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreatOrg.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDinoTop.kt index 6a28fb12..2b6a9c0f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreatOrg.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDinoTop.kt @@ -5,6 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("MANGAGREAT_ORG", "MangaGreat Org", "en") -internal class MangaGreatOrg(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MANGAGREAT_ORG, "mangagreat.org") +@MangaSourceParser("MANGADINOTOP", "MangaDino Top", "en") +internal class MangaDinoTop(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGADINOTOP, "mangadino.top", 10) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreat.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreat.kt index 5fd20b23..6917c62b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreat.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaGreat.kt @@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MANGAGREAT", "MangaGreat", "en") internal class MangaGreat(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MANGAGREAT, "mangagreat.com") + MadaraParser(context, MangaSource.MANGAGREAT, "mangagreat.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlBlog.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlBlog.kt new file mode 100644 index 00000000..7376d452 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaOwlBlog.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("MANGAOWLBLOG", "MangaOwl Blog (unoriginal)", "en", ContentType.HENTAI) +internal class MangaOwlBlog(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAOWLBLOG, "mangaowl.blog", 20) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRawInfo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRawInfo.kt new file mode 100644 index 00000000..9fd1279b --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaRawInfo.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("MANGARAWINFO", "Manga-Raw Info (unoriginal)", "en") +internal class MangaRawInfo(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGARAWINFO, "manga-raw.info", 20) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaUpdatesTop.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaUpdatesTop.kt new file mode 100644 index 00000000..0067e287 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaUpdatesTop.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("MANGAUPDATESTOP", "MangaUpdates Top (unoriginal)", "en") +internal class MangaUpdatesTop(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGAUPDATESTOP, "mangaupdates.top", 10) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaScanInfo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaScanInfo.kt new file mode 100644 index 00000000..db645bf3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManhuaScanInfo.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("MANHUASCANINFO", "ManhuaScan Info (unoriginal)", "en") +internal class ManhuaScanInfo(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHUASCANINFO, "manhuascan.info", 10) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaBlog.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaBlog.kt new file mode 100644 index 00000000..0d46de08 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/StkissMangaBlog.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("STKISSMANGABLOG", "StkissManga Blog", "en") +internal class StkissMangaBlog(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.STKISSMANGABLOG, "1stkissmanga.blog", 10) { + override val postreq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/ManhwaEs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/ManhwaEs.kt new file mode 100644 index 00000000..1c58527c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/ManhwaEs.kt @@ -0,0 +1,55 @@ +package org.koitharu.kotatsu.parsers.site.madara.es + +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.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrlOrNull +import org.koitharu.kotatsu.parsers.util.generateUid +import org.koitharu.kotatsu.parsers.util.mapChapters +import org.koitharu.kotatsu.parsers.util.parseFailed +import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow +import java.text.SimpleDateFormat + +@MangaSourceParser("MANHWA_ES", "Manhwa Es", "es") +internal class ManhwaEs(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWA_ES, "manhwa-es.com", 10) { + + override val withoutAjax = true + override val datePattern = "d 'de' MMMM" + + override suspend fun getChapters(manga: Manga, doc: Document): List { + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") + val link = href + stylepage + val dateText = li.selectFirst("a.c-new-tag")?.attr("title") ?: li.selectFirst(selectDate)?.text() + + val name = li.selectFirstOrThrow(".mini-letters a").text() + MangaChapter( + id = generateUid(href), + name = name, + number = i + 1, + url = link, + uploadDate = if (dateText == "¡Recién publicado!") { + parseChapterDate( + dateFormat, + "today", + ) + } else { + parseChapterDate( + dateFormat, + dateText, + ) + }, + source = source, + scanlator = null, + branch = null, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/UniToonOficial.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/UniToonOficial.kt new file mode 100644 index 00000000..72a94576 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/UniToonOficial.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.madara.es + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + + +@MangaSourceParser("UNITOONOFICIAL", "UniToonOficial", "es") +internal class UniToonOficial(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.UNITOONOFICIAL, "unitoonoficial.com") { + + override val datePattern = "dd/MM/yyyy" + override val tagPrefix = "generos/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Hentaizone.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Hentaizone.kt index 8b84945e..b2fc38da 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Hentaizone.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Hentaizone.kt @@ -31,11 +31,12 @@ internal class Hentaizone(context: MangaLoaderContext) : // correct parse date missing a "." val dateOrg = li.selectFirst("span.chapter-release-date i")?.text() ?: "janv 1, 2000" val dateCorrectParse = dateOrg - .replace("janv", "janv.") - .replace("févr", "févr.") + .replace("Jan", "janv.") + .replace("Fév", "févr.") + .replace("Mar", "mars") .replace("avr", "avr.") .replace("juil", "juil.") - .replace("sept", "sept.") + .replace("Sep", "sept.") .replace("nov", "nov.") .replace("oct", "oct.") .replace("déc", "déc.") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Readergen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Readergen.kt new file mode 100644 index 00000000..ce7c32b2 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/Readergen.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.madara.fr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("READERGEN", "Readergen", "fr") +internal class Readergen(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.READERGEN, "fr.readergen.fr", 18) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt new file mode 100644 index 00000000..88742d71 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt @@ -0,0 +1,121 @@ +package org.koitharu.kotatsu.parsers.site.madara.id + + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.util.* + +@MangaSourceParser("MANHWAHUB", "ManhwaHub", "id", ContentType.HENTAI) +internal class ManhwaHub(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANHWAHUB, "manhwahub.net", 40) { + + override val tagPrefix = "genre/" + override val datePattern = "MMMM d, yyyy" + override val sourceLocale: Locale = Locale.ENGLISH + override val withoutAjax = true + override val listUrl = "genre/manhwa" + override val selectTestAsync = "ul.box-list-chapter" + 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?s=") + append(query.urlEncoded()) + append("&page=") + append(pages) + } + + !tags.isNullOrEmpty() -> { + append("/$tagPrefix") + append(tag?.key.orEmpty()) + append("?page=") + append(pages) + append("&") + } + + else -> { + + append("/$listUrl") + append("?page=") + append(pages) + append("&") + } + + } + + append("m_orderby=") + when (sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("new-manga") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + } + } + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.row.c-tabs-item__content").ifEmpty { + doc.select("div.page-item-detail") + }.map { div -> + val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") + 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") + ?: div.selectFirst("h5.series-title"))?.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() + ?.lowercase()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain").parseHtml() + return doc.select("div.genres li").mapNotNullToSet { li -> + val a = li.selectFirst("a") ?: return@mapNotNullToSet null + val href = a.attr("href").removeSuffix("/").substringAfterLast(tagPrefix, "") + MangaTag( + key = href, + title = a.ownText().trim().ifEmpty { + a.selectFirst(".menu-image-title")?.text()?.trim() ?: return@mapNotNullToSet null + }.toTitleCase(), + source = source, + ) + } + } + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/it/Beyondtheataraxia.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/it/Beyondtheataraxia.kt new file mode 100644 index 00000000..0e501837 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/it/Beyondtheataraxia.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.madara.it + +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("BEYONDTHEATARAXIA", "Beyondtheataraxia", "it") +internal class Beyondtheataraxia(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.BEYONDTHEATARAXIA, "www.beyondtheataraxia.com", 10) { + override val datePattern = "d MMMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CeriseScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CeriseScans.kt index fdf881b8..6e136927 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CeriseScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CeriseScans.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("CERISE_SCANS", "Cerise Scans", "pt") internal class CeriseScans(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.CERISE_SCANS, "cerisescans.com") { + MadaraParser(context, MangaSource.CERISE_SCANS, "cerisescan.com/") { override val datePattern: String = "dd 'de' MMMMM 'de' yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/GhostScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/GhostScan.kt new file mode 100644 index 00000000..766b4755 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/GhostScan.kt @@ -0,0 +1,13 @@ +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.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("GHOSTSCAN", "GhostScan", "pt") +internal class GhostScan(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.GHOSTSCAN, "ghostscan.com.br", 24) { + + override val datePattern: String = "dd 'de' MMMMM 'de' yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/AlliedFansub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/AlliedFansub.kt new file mode 100644 index 00000000..4bee57fc --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/AlliedFansub.kt @@ -0,0 +1,13 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("ALLIED_FANSUB", "Allied Fansub", "tr", ContentType.HENTAI) +internal class AlliedFansub(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.ALLIED_FANSUB, "alliedfansub.online", 20) { + override val datePattern = "dd/MM/yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/SarcasmScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/SarcasmScans.kt new file mode 100644 index 00000000..3ba78e00 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/SarcasmScans.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("SARCASMSCANS", "Sarcasm Scans", "tr", ContentType.HENTAI) +internal class SarcasmScans(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.SARCASMSCANS, "sarcasmscans.com", 16) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Mi2Manga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Mi2Manga.kt index 6124ae06..d835e1aa 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Mi2Manga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Mi2Manga.kt @@ -9,7 +9,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MI2MANGA", "Mi2Manga", "vi") internal class Mi2Manga(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MI2MANGA, "www.mi2manga2.com"){ + MadaraParser(context, MangaSource.MI2MANGA, "www.mi2manga2.com") { override val listUrl = "truyen-tranh/" override val tagPrefix = "the-loai/" override val datePattern = "d MMMM, yyyy" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt index 7a9dfdf9..5fed39c7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt @@ -204,7 +204,7 @@ internal abstract class MangaReaderParser( Manga( id = generateUid(relativeUrl), url = relativeUrl, - title = it.selectFirstOrThrow(selectMangaListTitle).text() ?: a.attr("title"), + title = it.selectFirst(selectMangaListTitle)?.text() ?: a.attr("title"), altTitle = null, publicUrl = a.attrAsAbsoluteUrl("href"), rating = rating, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/AresManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/AresManga.kt new file mode 100644 index 00000000..9da0c5e0 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/AresManga.kt @@ -0,0 +1,12 @@ +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("ARESMANGA", "AresManga", "ar") +internal class AresManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.ARESMANGA, "aresmanga.org", pageSize = 20, searchPageSize = 10) { + override val listUrl = "/series" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt new file mode 100644 index 00000000..0f3ed836 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt @@ -0,0 +1,12 @@ +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("ENARESMANGA", "EnAresManga", "ar") +internal class EnAresManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.ENARESMANGA, "en-aresmanga.com", pageSize = 20, searchPageSize = 10) { + override val listUrl = "/series" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt index 1bb01818..6fc55787 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt @@ -5,9 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -@MangaSourceParser("MANGAPROTM", "MangaProtm", "ar") +@MangaSourceParser("MANGAPROTM", "Manga Pro", "ar") internal class MangaProtm(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MANGAPROTM, "mangaprotm.com", pageSize = 20, searchPageSize = 20) { + MangaReaderParser(context, MangaSource.MANGAPROTM, "mangapro.co", pageSize = 20, searchPageSize = 20) { override val listUrl = "/series" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt index f370055f..61de14c6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt @@ -10,7 +10,7 @@ import java.text.SimpleDateFormat @MangaSourceParser("SWATEAM", "Swa Team", "ar") internal class SwaTeam(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.SWATEAM, "swatop.club", pageSize = 42, searchPageSize = 39) { + MangaReaderParser(context, MangaSource.SWATEAM, "stmgs.com", pageSize = 42, searchPageSize = 39) { override val datePattern = "MMMM dd, yyyy" override val selectMangalist = ".listupd .bs .bsx" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/VexManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/VexManga.kt new file mode 100644 index 00000000..11949c15 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/VexManga.kt @@ -0,0 +1,98 @@ +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.* +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Calendar + +@MangaSourceParser("VEXMANGA", "Vex Manga", "ar") +internal class VexManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.VEXMANGA, "vexmanga.net", pageSize = 10, searchPageSize = 10) { + override val selectMangalist = ".listupd .latest-series" + override val selectChapter = ".ulChapterList > a" + + override suspend fun getDetails(manga: Manga): Manga { + val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + val chapters = docs.select(selectChapter).mapChapters(reversed = true) { index, element -> + val url = element.selectFirst("a")?.attrAsRelativeUrl("href") ?: return@mapChapters null + MangaChapter( + id = generateUid(url), + name = element.selectFirst(".chapternum")?.text() ?: "Chapter ${index + 1}", + url = url, + number = index + 1, + scanlator = null, + uploadDate = parseChapterDate( + dateFormat, + element.selectFirst(".chapterdate")?.text(), + ), + branch = null, + source = source, + ) + } + return parseInfo(docs, manga, chapters) + } + + private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + val d = date?.lowercase() ?: return 0 + return when { + d.startsWith("منذ") -> parseRelativeDate(date) + d.startsWith("جديد") -> Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + else -> dateFormat.tryParse(date) + } + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet( + "أيام", + ).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + + WordSet( + "أسابيع", + ).anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis + + WordSet( + "ساعة", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.HOUR, + -number, + ) + }.timeInMillis + + WordSet( + "دقائق", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.MINUTE, + -number, + ) + }.timeInMillis + + WordSet("ثوان").anyWordIn(date) -> cal.apply { + add( + Calendar.SECOND, + -number, + ) + }.timeInMillis + + WordSet("أشهر").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + + else -> 0 + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AsuraScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AsuraScansParser.kt index 6d897ac3..22ae237d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AsuraScansParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AsuraScansParser.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("ASURASCANS", "Asura Scans", "en") internal class AsuraScansParser(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.ASURASCANS, "asura.nacm.xyz", pageSize = 20, searchPageSize = 10) { + MangaReaderParser(context, MangaSource.ASURASCANS, "asuracomics.com", pageSize = 20, searchPageSize = 10) { override val datePattern = "MMM d, yyyy" override val selectPage = "div#readerarea p img" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/FreakComic.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/FreakComic.kt new file mode 100644 index 00000000..fbbb4cf5 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/FreakComic.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("FREAKCOMIC", "FreakComic", "en") +internal class FreakComic(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.FREAKCOMIC, "freakcomic.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt new file mode 100644 index 00000000..0b040ab6 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt @@ -0,0 +1,237 @@ +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.* +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Calendar + + +@MangaSourceParser("MANHWA_FREAK", "Manhwa Freak", "en") +internal class ManhwaFreak(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANHWA_FREAK, "manhwa-freak.com", pageSize = 0, searchPageSize = 10) { + + override val selectMangalist = ".listupd .lastest-serie" + override val selectMangaListImg = "img" + + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + if (!query.isNullOrEmpty()) { + if (page > lastSearchPage) { + return emptyList() + } + + val url = buildString { + append("https://") + append(domain) + append("/page/") + append(page) + append("/?s=") + append(query.urlEncoded()) + } + + val docs = webClient.httpGet(url).parseHtml() + lastSearchPage = docs.selectFirst(".pagination .next") + ?.previousElementSibling() + ?.text()?.toIntOrNull() ?: 1 + return parseMangaList(docs) + } + + if (!tags.isNullOrEmpty()) { + + if (page > 1) { + return emptyList() + } + + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + append("/genres/?genre=") + append(tag?.key.orEmpty()) + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + if (page > 1) { + return emptyList() + } + val sortQuery = when (sortOrder) { + SortOrder.ALPHABETICAL -> "az" + SortOrder.NEWEST -> "new" + SortOrder.POPULARITY -> "views" + SortOrder.UPDATED -> "" + else -> "" + } + + val url = buildString { + append("https://") + append(domain) + append(listUrl) + append("/?order=") + append(sortQuery) + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/genres/").parseHtml() + + return doc.select("ul.genre-list li a").mapNotNullToSet { a -> + val href = a.attr("href").substringAfterLast("=") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + val chapters = docs.select("div.chapter-li a").mapChapters(reversed = true) { index, a -> + val url = a.attrAsRelativeUrl("href") + val dateText = a.selectFirst(".chapter-info p.new")?.text() ?: a.select(".chapter-info p")[1].text() + MangaChapter( + id = generateUid(url), + name = a.selectFirst(".chapter-info p:contains(Chapter)")?.text() ?: "Chapter ${index + 1}", + url = url, + number = index + 1, + scanlator = null, + uploadDate = if (dateText == "NEW") { + parseChapterDate( + dateFormat, + "today", + ) + } else { + parseChapterDate( + dateFormat, + dateText, + ) + }, + branch = null, + source = source, + ) + } + return parseInfo(docs, manga, chapters) + } + + override suspend fun parseInfo(docs: Document, manga: Manga, chapters: List): Manga { + + val tagMap = getOrCreateTagMap() + val selectTag = docs.requireElementById("info").select("div:contains(Genre) > p:last-child").text().split(",") + val tags = selectTag.mapNotNullToSet { tagMap[it] } + + val mangaState = docs.requireElementById("info").select("div:contains(Status) > p:last-child").text().let { + when (it) { + "Ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + } + } + val author = docs.requireElementById("info").select("div:contains(Author(s)) > p:last-child").text() + + return manga.copy( + altTitle = docs.requireElementById("info").select("div:contains(Alternative) > p:last-child").text(), + description = docs.requireElementById("summary").html(), + state = mangaState, + author = author, + isNsfw = manga.isNsfw, + tags = tags, + chapters = chapters, + ) + } + + 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") -> parseRelativeDate(date) + // Handle 'yesterday' and 'today', using midnight + d.startsWith("year") -> Calendar.getInstance().apply { + add(Calendar.DAY_OF_MONTH, -1) // yesterday + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + d.startsWith("today") -> Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { + if (it.contains(Regex("""\d\D\D"""))) { + it.replace(Regex("""\D"""), "") + } else { + it + } + }.let { dateFormat.tryParse(it.joinToString(" ")) } + + 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", + ).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + + WordSet( + "hour", + "hours", + "h", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.HOUR, + -number, + ) + }.timeInMillis + + WordSet( + "minute", + "minutes", + "mins", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.MINUTE, + -number, + ) + }.timeInMillis + + WordSet("second").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 + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaShiina.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaShiina.kt new file mode 100644 index 00000000..6f155061 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/MangaShiina.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.es + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("MANGASHIINA", "Manga Shiina", "es") +internal class MangaShiina(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGASHIINA, "mangashiina.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt new file mode 100644 index 00000000..6f6313b7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt @@ -0,0 +1,238 @@ +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.* +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + + +@MangaSourceParser("MANHWA_FREAK_FR", "Manhwa Freak Fr", "fr") +internal class ManhwaFreakFr(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANHWA_FREAK_FR, "manhwafreak.fr", pageSize = 0, searchPageSize = 10) { + + override val selectMangalist = ".listupd .lastest-serie" + override val selectMangaListImg = "img" + override val sourceLocale: Locale = Locale.ENGLISH + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + if (!query.isNullOrEmpty()) { + if (page > lastSearchPage) { + return emptyList() + } + + val url = buildString { + append("https://") + append(domain) + append("/page/") + append(page) + append("/?s=") + append(query.urlEncoded()) + } + + val docs = webClient.httpGet(url).parseHtml() + lastSearchPage = docs.selectFirst(".pagination .next") + ?.previousElementSibling() + ?.text()?.toIntOrNull() ?: 1 + return parseMangaList(docs) + } + + if (!tags.isNullOrEmpty()) { + + if (page > 1) { + return emptyList() + } + + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + append("/genres/?genre=") + append(tag?.key.orEmpty()) + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + if (page > 1) { + return emptyList() + } + val sortQuery = when (sortOrder) { + SortOrder.ALPHABETICAL -> "az" + SortOrder.NEWEST -> "new" + SortOrder.POPULARITY -> "views" + SortOrder.UPDATED -> "" + else -> "" + } + + val url = buildString { + append("https://") + append(domain) + append(listUrl) + append("/?order=") + append(sortQuery) + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/genres/").parseHtml() + + return doc.select("ul.genre-list li a").mapNotNullToSet { a -> + val href = a.attr("href").substringAfterLast("=") + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + val chapters = docs.select("div.chapter-li a").mapChapters(reversed = true) { index, a -> + val url = a.attrAsRelativeUrl("href") + val dateText = a.selectFirst(".chapter-info p.new")?.text() ?: a.select(".chapter-info p")[1].text() + MangaChapter( + id = generateUid(url), + name = a.selectFirst(".chapter-info p:contains(Chapter)")?.text() ?: "Chapter ${index + 1}", + url = url, + number = index + 1, + scanlator = null, + uploadDate = if (dateText == "NEW") { + parseChapterDate( + dateFormat, + "today", + ) + } else { + parseChapterDate( + dateFormat, + dateText, + ) + }, + branch = null, + source = source, + ) + } + return parseInfo(docs, manga, chapters) + } + + override suspend fun parseInfo(docs: Document, manga: Manga, chapters: List): Manga { + + val tagMap = getOrCreateTagMap() + val selectTag = docs.requireElementById("info").select("div:contains(Genre) > p:last-child").text().split(",") + val tags = selectTag.mapNotNullToSet { tagMap[it] } + + val mangaState = docs.requireElementById("info").select("div:contains(Status) > p:last-child").text().let { + when (it) { + "Ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + } + } + val author = docs.requireElementById("info").select("div:contains(Author(s)) > p:last-child").text() + + return manga.copy( + altTitle = docs.requireElementById("info").select("div:contains(Alternative) > p:last-child").text(), + description = docs.requireElementById("summary").html(), + state = mangaState, + author = author, + isNsfw = manga.isNsfw, + tags = tags, + chapters = chapters, + ) + } + + 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") -> parseRelativeDate(date) + // Handle 'yesterday' and 'today', using midnight + d.startsWith("year") -> Calendar.getInstance().apply { + add(Calendar.DAY_OF_MONTH, -1) // yesterday + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + d.startsWith("today") -> Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { + if (it.contains(Regex("""\d\D\D"""))) { + it.replace(Regex("""\D"""), "") + } else { + it + } + }.let { dateFormat.tryParse(it.joinToString(" ")) } + + 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", + ).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + + WordSet( + "hour", + "hours", + "h", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.HOUR, + -number, + ) + }.timeInMillis + + WordSet( + "minute", + "minutes", + "mins", + ).anyWordIn(date) -> cal.apply { + add( + Calendar.MINUTE, + -number, + ) + }.timeInMillis + + WordSet("second").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 + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MasterKomik.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MasterKomik.kt index b546c6a0..73e0e890 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MasterKomik.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MasterKomik.kt @@ -6,9 +6,10 @@ import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -@MangaSourceParser("MASTERKOMIK", "MasterKomik", "id") +@MangaSourceParser("MASTERKOMIK", "Tenshi ( MasterKomik )", "id") internal class MasterKomik(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MASTERKOMIK, "masterkomik.com", pageSize = 20, searchPageSize = 20) { + MangaReaderParser(context, MangaSource.MASTERKOMIK, "tenshi.id", pageSize = 20, searchPageSize = 20) { override val datePattern = "MMM d, yyyy" + override val listUrl = "/komik" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/OmKomik.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/OmKomik.kt new file mode 100644 index 00000000..ceb2261e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/OmKomik.kt @@ -0,0 +1,11 @@ +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("OMKOMIK", "OmKomik", "id") +internal class OmKomik(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.OMKOMIK, "omkomik.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/Mangaschan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/Mangaschan.kt index c07c77b2..b1098628 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/Mangaschan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/Mangaschan.kt @@ -7,8 +7,8 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("MANGASCHAN", "Mangaschan", "pt") internal class Mangaschan(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MANGASCHAN, "mangaschan.com", pageSize = 20, searchPageSize = 20) { + MangaReaderParser(context, MangaSource.MANGASCHAN, "mangaschan.net", pageSize = 20, searchPageSize = 20) { - override val datePattern = "MMM d, yyyy" + override val datePattern = "MMMM d, yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/Lshistoria.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/Lshistoria.kt new file mode 100644 index 00000000..22d59298 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/Lshistoria.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser + +@MangaSourceParser("LSHISTORIA", "Lshistoria", "tr") +internal class Lshistoria(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.LSHISTORIA, "omkomik.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt new file mode 100644 index 00000000..7c694c77 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.tr + +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 + +@MangaSourceParser("MOONDAISY_SCANS", "MoonDaisyScans", "tr", ContentType.HENTAI) +internal class MoonDaisyScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MOONDAISY_SCANS, "moondaisyscans.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/SummerToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/SummerToon.kt new file mode 100644 index 00000000..bb34e46f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/SummerToon.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser + +@MangaSourceParser("SUMMERTOON", "Summer Toon", "tr") +internal class SummerToon(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.SUMMERTOON, "summertoon.com", pageSize = 10, searchPageSize = 10) + diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt index aad06724..d2a5125b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt @@ -43,6 +43,7 @@ internal abstract class MmrcmsParser( "Ongoing", "En cours", "En curso", + "DEVAM EDİYOR", ) @JvmField @@ -51,6 +52,7 @@ internal abstract class MmrcmsParser( "Completo", "Complete", "Terminé", + "TAMAMLANDI", ) protected open val imgUpdated = "/cover/cover_250x350.jpg" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/tr/MangaDenizi.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/tr/MangaDenizi.kt new file mode 100644 index 00000000..5716cd8b --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/tr/MangaDenizi.kt @@ -0,0 +1,20 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.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.mmrcms.MmrcmsParser +import java.util.Locale + + +@MangaSourceParser("MANGA_DENIZI", "MangaDenizi", "tr") +internal class MangaDenizi(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.MANGA_DENIZI, "www.mangadenizi.net") { + override val selectState = "dt:contains(Durum)" + override val selectAlt = "dt:contains(Diğer Adları)" + override val selectAut = "dt:contains(Yazar & Çizer)" + override val selectTag = "dt:contains(Kategoriler)" + override val sourceLocale: Locale = Locale.ENGLISH + override val datePattern = "dd.MM.yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt new file mode 100644 index 00000000..c1bc6fb1 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt @@ -0,0 +1,95 @@ +package org.koitharu.kotatsu.parsers.site.tr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaParser +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("SADSCANS", "SadScans", "tr") +internal class SadScans(context: MangaLoaderContext) : MangaParser(context, MangaSource.SADSCANS) { + + override val sortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val configKeyDomain = ConfigKey.Domain("sadscans.com") + + override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { + if (offset > 0) { + return emptyList() + } + val url = buildString { + append("https://$domain/series") + if (!query.isNullOrEmpty()) { + append("?search=") + append(query.urlEncoded()) + } + } + val doc = webClient.httpGet(url).parseHtml() + return doc.select(".series-list").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()?.toAbsoluteUrl(domain).orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set = emptySet() + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat("dd MMM, yy", Locale.ENGLISH) + return manga.copy( + altTitle = null, + state = when (doc.select(".status span").last()?.text()) { + "Devam ediyor" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + tags = emptySet(), + author = doc.select(".author span").last()?.text(), + description = doc.selectFirstOrThrow(".summary").text(), + chapters = doc.select(".chap-section .chap") + .mapChapters(reversed = true) { i, div -> + val a = div.selectFirstOrThrow("a") + val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) + MangaChapter( + id = generateUid(url), + name = a.text(), + number = i + 1, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(div.select(".detail span").last()?.text()), + 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(".swiper-slide 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, + ) + } + } +}