From 1f7fe2aed3f12c2e35d297960784650015d9fcb9 Mon Sep 17 00:00:00 2001 From: devi Date: Sun, 11 Aug 2024 16:15:16 +0200 Subject: [PATCH] Fix Sources Add sources Correct detect login on MadaraParser close #988 fix oocini.biz close #987 Changing the latest DEPRECATION ( Technically we can raise the old support for getList() and MangaChapter() ) --- .../kotatsu/parsers/site/be/AnibelParser.kt | 36 ++-- .../kotatsu/parsers/site/en/MangaKawaiiEn.kt | 172 +++++++++++++++++ .../kotatsu/parsers/site/fr/MangaKawaii.kt | 171 +++++++++++++++++ .../parsers/site/madara/MadaraParser.kt | 29 +-- .../parsers/site/madara/ar/RocksManga.kt | 2 +- .../parsers/site/madara/en/AdultWebtoon.kt | 176 +++++++++++++++++- .../parsers/site/madara/en/AquaManga.kt | 2 + .../parsers/site/madara/en/HentaiManga.kt | 173 ++++++++++++++++- .../parsers/site/madara/en/HentaiWebtoon.kt | 173 ++++++++++++++++- .../parsers/site/madara/en/HunLight.kt | 40 +++- .../parsers/site/madara/en/InstaManhwa.kt | 130 +------------ .../kotatsu/parsers/site/madara/en/Kiara18.kt | 12 ++ .../parsers/site/madara/en/MadaraDex.kt | 15 ++ .../parsers/site/madara/en/MangaHall.kt | 2 +- .../parsers/site/madara/en/ManyToon.kt | 176 +++++++++++++++++- .../parsers/site/madara/en/Rio2MangaNet.kt | 2 +- .../parsers/site/madara/en/ZinChanManga.kt | 2 +- .../parsers/site/madara/en/ZinchanMangaNet.kt | 10 + .../parsers/site/madara/pt/Fbsquads.kt | 2 +- .../parsers/site/madara/tr/Jiangzaitoon.kt | 2 +- .../parsers/site/madara/tr/StrayFansub.kt | 12 ++ .../parsers/site/mangareader/ar/Manjanoon.kt | 2 +- .../parsers/site/mangareader/ar/Normoyun.kt | 2 +- .../parsers/site/mangareader/en/DexHentai.kt | 11 ++ .../parsers/site/mangareader/en/EnryuManga.kt | 12 ++ .../parsers/site/mangareader/en/MagusManga.kt | 2 +- .../site/mangareader/en/ManhwaFreak.kt | 2 +- .../parsers/site/mangareader/en/SnowScans.kt | 12 ++ .../parsers/site/mangareader/en/VarnaScan.kt | 10 + .../site/mangareader/en/VoidScansCo.kt | 10 + .../parsers/site/mangareader/en/YdComics.kt | 12 ++ .../parsers/site/mangareader/fr/JapScansFR.kt | 13 ++ .../parsers/site/mangareader/id/AlceaScan.kt | 2 +- .../site/mangareader/id/ManhwaListOrg.kt | 15 ++ .../site/mangareader/id/ManhwalistParser.kt | 2 +- .../site/mangareader/tr/StrayFansub.kt | 11 -- .../site/mmrcms/en/ReadComicsOnline.kt | 2 +- .../kotatsu/parsers/site/ru/NudeMoonParser.kt | 55 +++--- .../parsers/site/uk/HentaiUkrParser.kt | 43 +++-- 39 files changed, 1328 insertions(+), 229 deletions(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Kiara18.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MadaraDex.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinchanMangaNet.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/StrayFansub.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/DexHentai.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/EnryuManga.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/SnowScans.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VarnaScan.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VoidScansCo.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/YdComics.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/JapScansFR.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaListOrg.kt delete mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/StrayFansub.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt index 5f56e0cb..7ea8ce1e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt @@ -31,23 +31,29 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, override suspend fun getList( offset: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, + filter: MangaListFilter?, ): List { - if (!query.isNullOrEmpty()) { - return if (offset == 0) { - search(query) - } else { - emptyList() + val filters = + when (filter) { + is MangaListFilter.Search -> { + return if (offset == 0) { + search(filter.query) + } else { + emptyList() + } + } + + is MangaListFilter.Advanced -> { + filter.tags.takeUnless { it.isEmpty() }?.joinToString( + separator = ",", + prefix = "genres: [", + postfix = "]", + ) { "\"${it.key}\"" }.orEmpty() + } + + null -> "" } - } - val filters = tags?.takeUnless { it.isEmpty() }?.joinToString( - separator = ",", - prefix = "genres: [", - postfix = "]", - ) { "\"${it.key}\"" }.orEmpty() + val array = apiCall( """ getMediaList(offset: $offset, limit: 20, mediaType: manga, filters: {$filters}) { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt new file mode 100644 index 00000000..db7912c3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt @@ -0,0 +1,172 @@ +package org.koitharu.kotatsu.parsers.site.en + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +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.util.* +import java.util.* + +@MangaSourceParser("MANGAKAWAII_EN", "MangaKawaii En", "en") +internal class MangaKawaiiEn(context: MangaLoaderContext) : + PagedMangaParser(context, MangaParserSource.MANGAKAWAII_EN, 50) { + + override val availableSortOrders: Set = + EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) + + override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io") + + override val headers: Headers = Headers.Builder() + .add("Accept-Language", "en") + .build() + + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/search?query=") + append(filter.query.urlEncoded()) + append("&search_type=manga&page=") + append(page) + } + + is MangaListFilter.Advanced -> { + + if (filter.sortOrder == SortOrder.UPDATED && filter.tags.isNotEmpty()) { + throw IllegalArgumentException("Filter part tag is not available with sort not updated") + } + + if (filter.sortOrder == SortOrder.ALPHABETICAL) { + append("/manga-list") + filter.tags.oneOrThrowIfMany()?.let { + append("/category/") + append(it.key) + } + } + + if (page > 1) { + return emptyList() + } + } + + null -> { + if (page > 1) { + return emptyList() + } + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + return doc.select("li.section__list-group-item").ifEmpty { + doc.select("div.media-thumbnail") + }.map { div -> + val a = div.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = (div.selectFirst("img")?.src() ?: a.attr("data-bg")).orEmpty(), + title = div.selectFirstOrThrow("h4, .media-thumbnail__name").text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val firstChapter = doc.selectFirst("tr[class*='volume-'] a")?.attr("href") + val chaptersDeferred = async { loadChapters(firstChapter) } + manga.copy( + description = doc.selectFirst("dd.text-justify.text-break")?.text().orEmpty(), + altTitle = doc.select("span[itemprop*=alternativeHeadline]").joinToString { ", " }, + author = doc.select("a[href*=author]").text(), + state = when (doc.selectFirst("span.badge.bg-success.text-uppercase")?.text()) { + "Ongoing" -> MangaState.ONGOING + "" -> MangaState.FINISHED + else -> null + }, + tags = doc.select("a[href*=category]").mapToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + chapters = chaptersDeferred.await(), + ) + } + + private suspend fun loadChapters(chapterUrl: String?): List { + if (chapterUrl.isNullOrEmpty()) { + return emptyList() + } + + val doc = webClient.httpGet(chapterUrl.toAbsoluteUrl(domain)).parseHtml() + return doc.select("#dropdownMenuOffset+ul li") + .mapChapters(reversed = true) { i, li -> + val a = li.selectFirstOrThrow("a") + val url = a.attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(url), + name = a.text(), + number = i + 1f, + volume = 0, + 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 chapterSlug = Regex("""var chapter_slug = "([^"]*)";""").find(doc.toString())?.groupValues?.get(1) + val mangaSlug = Regex("""var oeuvre_slug = "([^"]*)";""").find(doc.toString())?.groupValues?.get(1) + val cdn = Regex("""var chapter_server = "([^"]*)";""").find(doc.toString())?.groupValues?.get(1) + val cdnDomain = cdn + domain.removePrefix("www") + return Regex(""""page_image":"([^"]*)"""").findAll(doc.toString()).asIterable().map { + val url = + "https://" + cdnDomain + "/uploads/manga/" + mangaSlug + "/chapters_en/" + chapterSlug + "/" + it.groupValues[1] + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + + override suspend fun getAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain/manga-list/").parseHtml() + return doc.select("ul li a.category").mapNotNullToSet { a -> + val name = a.text() + val key = name.lowercase().replace(" ", "-").replace("é", "e").replace("è", "e") + MangaTag( + key = key, + title = name, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt new file mode 100644 index 00000000..9ccf4d31 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt @@ -0,0 +1,171 @@ +package org.koitharu.kotatsu.parsers.site.fr + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +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.util.* +import java.util.* + +@MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr") +internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) { + + override val availableSortOrders: Set = + EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) + + override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io") + + override val headers: Headers = Headers.Builder() + .add("Accept-Language", "fr") + .build() + + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/search?query=") + append(filter.query.urlEncoded()) + append("&search_type=manga&page=") + append(page) + } + + is MangaListFilter.Advanced -> { + + if (filter.sortOrder == SortOrder.UPDATED && filter.tags.isNotEmpty()) { + throw IllegalArgumentException("Filtrer part tag n'est pas disponible avec le tri pas mis à jour") + } + + if (filter.sortOrder == SortOrder.ALPHABETICAL) { + append("/manga-list") + filter.tags.oneOrThrowIfMany()?.let { + append("/category/") + append(it.key) + } + } + + if (page > 1) { + return emptyList() + } + } + + null -> { + if (page > 1) { + return emptyList() + } + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + return doc.select("li.section__list-group-item").ifEmpty { + doc.select("div.media-thumbnail") + }.map { div -> + val a = div.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = (div.selectFirst("img")?.src() ?: a.attr("data-bg")).orEmpty(), + title = div.selectFirstOrThrow("h4, .media-thumbnail__name").text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val firstChapter = doc.selectFirst("tr[class*='volume-'] a")?.attr("href") + val chaptersDeferred = async { loadChapters(firstChapter) } + manga.copy( + description = doc.selectFirst("dd.text-justify.text-break")?.text().orEmpty(), + altTitle = doc.select("span[itemprop*=alternativeHeadline]").joinToString { ", " }, + author = doc.select("a[href*=author]").text(), + state = when (doc.selectFirst("span.badge.bg-success.text-uppercase")?.text()) { + "En Cours" -> MangaState.ONGOING + "Terminé" -> MangaState.FINISHED + else -> null + }, + tags = doc.select("a[href*=category]").mapToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + chapters = chaptersDeferred.await(), + ) + } + + private suspend fun loadChapters(chapterUrl: String?): List { + if (chapterUrl.isNullOrEmpty()) { + return emptyList() + } + + val doc = webClient.httpGet(chapterUrl.toAbsoluteUrl(domain)).parseHtml() + return doc.select("#dropdownMenuOffset+ul li") + .mapChapters(reversed = true) { i, li -> + val a = li.selectFirstOrThrow("a") + val url = a.attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(url), + name = a.text(), + number = i + 1f, + volume = 0, + 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 chapterSlug = Regex("""var chapter_slug = "([^"]*)";""").find(doc.toString())?.groupValues?.get(1) + val mangaSlug = Regex("""var oeuvre_slug = "([^"]*)";""").find(doc.toString())?.groupValues?.get(1) + val cdn = Regex("""var chapter_server = "([^"]*)";""").find(doc.toString())?.groupValues?.get(1) + val cdnDomain = cdn + domain.removePrefix("www") + return Regex(""""page_image":"([^"]*)"""").findAll(doc.toString()).asIterable().map { + val url = + "https://" + cdnDomain + "/uploads/manga/" + mangaSlug + "/chapters_fr/" + chapterSlug + "/" + it.groupValues[1] + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + + override suspend fun getAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain/manga-list/").parseHtml() + return doc.select("ul li a.category").mapNotNullToSet { a -> + val name = a.text() + val key = name.lowercase().replace(" ", "-").replace("é", "e").replace("è", "e") + MangaTag( + key = key, + title = name, + 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 87612cb6..14becf6e 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 @@ -575,24 +575,31 @@ internal abstract class MadaraParser( } protected open val selectBodyPage = "div.main-col-inner div.reading-content" - protected open val selectPage = "div.page-break, div.login-required" + protected open val selectPage = "div.page-break" + protected open val selectRequiredLogin = ".content-blocked, .login-required" override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val chapterProtector = doc.getElementById("chapter-protector-data") if (chapterProtector == null) { - val root = doc.body().selectFirst(selectBodyPage) - ?: throw ParseException("No image found, try to log in", fullUrl) - return root.select(selectPage).map { div -> - val img = div.selectFirstOrThrow("img") - val url = img.src()?.toRelativeUrl(domain) ?: div.parseFailed("Image src not found") - MangaPage( - id = generateUid(url), - url = url, - preview = null, - source = source, + throw if (doc.selectFirst(selectRequiredLogin) != null) { + AuthRequiredException(source) + } else { + val root = doc.body().selectFirst(selectBodyPage) ?: throw ParseException( + "No image found, try to log in", + fullUrl, ) + return root.select(selectPage).map { div -> + val img = div.selectFirstOrThrow("img") + val url = img.src()?.toRelativeUrl(domain) ?: div.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } } } else { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/RocksManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/RocksManga.kt index ec7eba39..c07c2fdc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/RocksManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/RocksManga.kt @@ -14,7 +14,7 @@ import java.text.SimpleDateFormat @MangaSourceParser("ROCKSMANGA", "RocksManga", "ar") internal class RocksManga(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.ROCKSMANGA, "rocks-manga.com") { + MadaraParser(context, MangaParserSource.ROCKSMANGA, "rocksmanga.com") { override val selectChapter = "ul#chapter-list li.chapter-item" override val datePattern = "d MMMM yyyy" override val selectDate = ".ch-post-time" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt index a31197c9..3ff82dff 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AdultWebtoon.kt @@ -1,14 +1,186 @@ package org.koitharu.kotatsu.parsers.site.madara.en +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import okhttp3.Headers +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +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.MangaParserSource +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat @MangaSourceParser("ADULT_WEBTOON", "AdultWebtoon", "en", ContentType.HENTAI) internal class AdultWebtoon(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.ADULT_WEBTOON, "adultwebtoon.com") { override val tagPrefix = "adult-webtoon-genre/" + override val listUrl = "adult-webtoon/" override val postReq = true + override val withoutAjax = true + + override val availableStates: Set = emptySet() + + override val availableContentRating: Set = emptySet() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val pages = page + 1 + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + if (pages > 1) { + append("/page/") + append(pages.toString()) + } + append("/?s=") + append(filter.query.urlEncoded()) + append("&post_type=wp-manga") + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append('/') + append(tagPrefix) + append(it.key) + append('/') + } + } else { + append('/') + append(listUrl) + } + + if (pages > 1) { + append("page/") + append(pages) + append('/') + } + + append("?m_orderby=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("new-manga") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + else -> append("latest") + } + } + + null -> { + append('/') + append(listUrl) + if (pages > 1) { + append("page/") + append(pages) + append("/?m_orderby=latest") + } + } + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + 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 chaptersDeferred = async { loadChapters(manga.url, doc) } + val desc = body.select(selectDesc).html() + val stateDiv = if (selectState.isEmpty()) { + body.selectFirst("div.post-content_item:contains(Status)")?.selectLast("div.summary-content") + } else { + body.selectFirst(selectState) + } + + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + in abandoned -> MangaState.ABANDONED + in paused -> MangaState.PAUSED + else -> null + } + } + + val alt = + doc.body().select(".post-content_item:contains(Alt) .summary-content").firstOrNull()?.tableValue()?.text() + ?.trim() + + manga.copy( + tags = doc.body().select(selectGenre).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = alt, + state = state, + chapters = chaptersDeferred.await(), + ) + } + + 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 = "post_id=$mangaId&action=ajax_chap" + val headers = Headers.Builder().add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build() + val doc = makeRequest(url, postData.toRequestBody(), headers) + 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 + 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, + name = name, + number = i + 1f, + volume = 0, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + scanlator = null, + source = source, + ) + } + } + + private suspend fun makeRequest(url: String, payload: RequestBody, headers: Headers): Document { + var retryCount = 0 + val backoffDelay = 2000L // Initial delay (milliseconds) + val request = Request.Builder().url(url).post(payload).headers(headers).build() + while (true) { + try { + return context.httpClient.newCall(request).execute().parseHtml() + + } catch (e: Exception) { + // Log or handle the exception as needed + if (++retryCount <= 5) { + withContext(Dispatchers.Default) { + delay(backoffDelay) + } + } else { + throw e + } + } + } + } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AquaManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AquaManga.kt index 288e23c3..321cc1f7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AquaManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/AquaManga.kt @@ -1,10 +1,12 @@ package org.koitharu.kotatsu.parsers.site.madara.en +import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +@Broken @MangaSourceParser("AQUAMANGA", "AquaManga", "en") internal class AquaManga(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.AQUAMANGA, "aquareader.net", 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt index 6f40805d..1791b889 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiManga.kt @@ -1,13 +1,182 @@ package org.koitharu.kotatsu.parsers.site.madara.en +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import okhttp3.Headers +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +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.MangaParserSource +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat @MangaSourceParser("HENTAIMANGA", "HentaiManga", "en", ContentType.HENTAI) internal class HentaiManga(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.HENTAIMANGA, "hentaimanga.me", 36) { override val postReq = true + override val withoutAjax = true + override val availableStates: Set = emptySet() + override val availableContentRating: Set = emptySet() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val pages = page + 1 + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + if (pages > 1) { + append("/page/") + append(pages.toString()) + } + append("/?s=") + append(filter.query.urlEncoded()) + append("&post_type=wp-manga") + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append('/') + append(tagPrefix) + append(it.key) + append('/') + } + } else { + append('/') + append(listUrl) + } + + if (pages > 1) { + append("page/") + append(pages) + append('/') + } + + append("?m_orderby=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("new-manga") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + else -> append("latest") + } + } + + null -> { + append('/') + append(listUrl) + if (pages > 1) { + append("page/") + append(pages) + append("/?m_orderby=latest") + } + } + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + 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 chaptersDeferred = async { loadChapters(manga.url, doc) } + val desc = body.select(selectDesc).html() + val stateDiv = if (selectState.isEmpty()) { + body.selectFirst("div.post-content_item:contains(Status)")?.selectLast("div.summary-content") + } else { + body.selectFirst(selectState) + } + + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + in abandoned -> MangaState.ABANDONED + in paused -> MangaState.PAUSED + else -> null + } + } + + val alt = + doc.body().select(".post-content_item:contains(Alt) .summary-content").firstOrNull()?.tableValue()?.text() + ?.trim() + + manga.copy( + tags = doc.body().select(selectGenre).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = alt, + state = state, + chapters = chaptersDeferred.await(), + ) + } + + 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 = "post_id=$mangaId&action=ajax_chap" + val headers = Headers.Builder().add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build() + val doc = makeRequest(url, postData.toRequestBody(), headers) + 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 + 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, + name = name, + number = i + 1f, + volume = 0, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + scanlator = null, + source = source, + ) + } + } + + private suspend fun makeRequest(url: String, payload: RequestBody, headers: Headers): Document { + var retryCount = 0 + val backoffDelay = 2000L // Initial delay (milliseconds) + val request = Request.Builder().url(url).post(payload).headers(headers).build() + while (true) { + try { + return context.httpClient.newCall(request).execute().parseHtml() + + } catch (e: Exception) { + // Log or handle the exception as needed + if (++retryCount <= 5) { + withContext(Dispatchers.Default) { + delay(backoffDelay) + } + } else { + throw e + } + } + } + } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt index 770b4287..1af32bc5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HentaiWebtoon.kt @@ -1,13 +1,182 @@ package org.koitharu.kotatsu.parsers.site.madara.en +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import okhttp3.Headers +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +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.MangaParserSource +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat @MangaSourceParser("HENTAIWEBTOON", "HentaiWebtoon", "en", ContentType.HENTAI) internal class HentaiWebtoon(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.HENTAIWEBTOON, "hentaiwebtoon.com") { override val postReq = true + override val withoutAjax = true + override val availableStates: Set = emptySet() + override val availableContentRating: Set = emptySet() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val pages = page + 1 + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + if (pages > 1) { + append("/page/") + append(pages.toString()) + } + append("/?s=") + append(filter.query.urlEncoded()) + append("&post_type=wp-manga") + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append('/') + append(tagPrefix) + append(it.key) + append('/') + } + } else { + append('/') + append(listUrl) + } + + if (pages > 1) { + append("page/") + append(pages) + append('/') + } + + append("?m_orderby=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("new-manga") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + else -> append("latest") + } + } + + null -> { + append('/') + append(listUrl) + if (pages > 1) { + append("page/") + append(pages) + append("/?m_orderby=latest") + } + } + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + 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 chaptersDeferred = async { loadChapters(manga.url, doc) } + val desc = body.select(selectDesc).html() + val stateDiv = if (selectState.isEmpty()) { + body.selectFirst("div.post-content_item:contains(Status)")?.selectLast("div.summary-content") + } else { + body.selectFirst(selectState) + } + + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + in abandoned -> MangaState.ABANDONED + in paused -> MangaState.PAUSED + else -> null + } + } + + val alt = + doc.body().select(".post-content_item:contains(Alt) .summary-content").firstOrNull()?.tableValue()?.text() + ?.trim() + + manga.copy( + tags = doc.body().select(selectGenre).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = alt, + state = state, + chapters = chaptersDeferred.await(), + ) + } + + 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 = "post_id=$mangaId&action=ajax_chap" + val headers = Headers.Builder().add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build() + val doc = makeRequest(url, postData.toRequestBody(), headers) + 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 + 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, + name = name, + number = i + 1f, + volume = 0, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + scanlator = null, + source = source, + ) + } + } + + private suspend fun makeRequest(url: String, payload: RequestBody, headers: Headers): Document { + var retryCount = 0 + val backoffDelay = 2000L // Initial delay (milliseconds) + val request = Request.Builder().url(url).post(payload).headers(headers).build() + while (true) { + try { + return context.httpClient.newCall(request).execute().parseHtml() + + } catch (e: Exception) { + // Log or handle the exception as needed + if (++retryCount <= 5) { + withContext(Dispatchers.Default) { + delay(backoffDelay) + } + } else { + throw e + } + } + } + } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HunLight.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HunLight.kt index 0a590286..94bbb695 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HunLight.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HunLight.kt @@ -1,10 +1,48 @@ 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.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrlOrNull +import org.koitharu.kotatsu.parsers.util.domain +import org.koitharu.kotatsu.parsers.util.generateUid +import org.koitharu.kotatsu.parsers.util.mapChapters +import org.koitharu.kotatsu.parsers.util.parseFailed +import org.koitharu.kotatsu.parsers.util.parseHtml +import org.koitharu.kotatsu.parsers.util.removeSuffix +import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl +import java.text.SimpleDateFormat @MangaSourceParser("HUNLIGHT", "HunLight", "en") internal class HunLight(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.HUNLIGHT, "hunlight.com") + MadaraParser(context, MangaParserSource.HUNLIGHT, "hunlight.com") { + override suspend fun loadChapters(mangaUrl: String, document: Document): List { + val url = mangaUrl.toAbsoluteUrl(domain).removeSuffix('/') + "/ajax/chapters/" + val doc = webClient.httpPost(url, emptyMap()).parseHtml() + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return doc.select(selectChapter).mapChapters { 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), + url = link, + name = name, + number = i + 1f, + volume = 0, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + scanlator = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt index 21544a05..00a42595 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt @@ -1,136 +1,16 @@ package org.koitharu.kotatsu.parsers.site.madara.en -import org.jsoup.nodes.Document +import org.koitharu.kotatsu.parsers.Broken 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.* +@Broken // Redirect to @XMANHWA @MangaSourceParser("INSTAMANHWA", "InstaManhwa", "en", ContentType.HENTAI) internal class InstaManhwa(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.INSTAMANHWA, "www.instamanhwa.com", 15) { - override val tagPrefix = "genre/" - override val listUrl = "latest/" - override val postReq = true - override val datePattern = "d MMMM, yyyy" - override val availableSortOrders: Set = EnumSet.of( - SortOrder.ALPHABETICAL, - SortOrder.UPDATED, - SortOrder.NEWEST, - ) - override val availableStates: Set get() = emptySet() - override val availableContentRating: Set = emptySet() - - init { - paginator.firstPage = 1 - searchPaginator.firstPage = 1 - } - - override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - val url = buildString { - append("https://") - append(domain) - when (filter) { - is MangaListFilter.Search -> { - append("/search?q=") - append(filter.query.urlEncoded()) - append("&page=") - append(page.toString()) - } - - is MangaListFilter.Advanced -> { - - val tag = filter.tags.oneOrThrowIfMany() - if (filter.tags.isNotEmpty()) { - append("/genre/") - append(tag?.key.orEmpty()) - append("?page=") - append(page.toString()) - } else { - when (filter.sortOrder) { - SortOrder.UPDATED -> append("/latest") - SortOrder.NEWEST -> append("/new") - SortOrder.ALPHABETICAL -> append("/alphabet") - else -> append("/latest") - } - append("?page=") - append(page.toString()) - } - } - - null -> { - append("/latest?page=") - append(page.toString()) - } - } - } - val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.page-listing-item div.page-item-detail").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#manga-chapters-holder").attr("data-id") - val token = document.select("meta")[2].attr("content") - val url = "https://$domain/ajax" - val postData = "_token=$token&action=manga_get_chapters&manga=$mangaId" - val doc = webClient.httpPost(url, postData).parseHtml() - - val 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 - 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, - name = name, - number = i + 1f, - volume = 0, - branch = null, - uploadDate = parseChapterDate( - dateFormat, - dateText, - ), - scanlator = null, - source = source, - ) - } - } + MadaraParser(context, MangaParserSource.INSTAMANHWA, "www.xmanhwa.me", 15) { + override val sourceLocale: Locale = Locale.ENGLISH + override val selectPage = "img" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Kiara18.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Kiara18.kt new file mode 100644 index 00000000..7b080532 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Kiara18.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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +// need to login and pay for read +@MangaSourceParser("KIARA18", "Kiara18", "en", ContentType.HENTAI) +internal class Kiara18(context: MangaLoaderContext) : + MadaraParser(context, MangaParserSource.KIARA18, "18.kiara.cool") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MadaraDex.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MadaraDex.kt new file mode 100644 index 00000000..b315c993 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MadaraDex.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MADARADEX", "MadaraDex", "en", ContentType.HENTAI) +internal class MadaraDex(context: MangaLoaderContext) : + MadaraParser(context, MangaParserSource.MADARADEX, "madaradex.org") { + override val listUrl = "title/" + override val tagPrefix = "genre/" + override val postReq = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaHall.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaHall.kt index cd1e4f64..efa578ae 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaHall.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaHall.kt @@ -8,4 +8,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MANGAHALL", "MangaHall", "en", ContentType.HENTAI) internal class MangaHall(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.MANGAHALL, "mangahall.net", 24) + MadaraParser(context, MangaParserSource.MANGAHALL, "mangahall.org", 24) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt index af965a9b..82b39856 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ManyToon.kt @@ -1,13 +1,185 @@ package org.koitharu.kotatsu.parsers.site.madara.en +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import okhttp3.Headers +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +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.MangaParserSource +import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.madara.MadaraParser +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat @MangaSourceParser("MANYTOON", "ManyToon", "en", ContentType.HENTAI) internal class ManyToon(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.MANYTOON, "manytoon.com", 20) { override val listUrl = "comic/" + override val postReq = true + override val withoutAjax = true + + override val availableStates: Set = emptySet() + + override val availableContentRating: Set = emptySet() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val pages = page + 1 + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + if (pages > 1) { + append("/page/") + append(pages.toString()) + } + append("/?s=") + append(filter.query.urlEncoded()) + append("&post_type=wp-manga") + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append('/') + append(tagPrefix) + append(it.key) + append('/') + } + } else { + append('/') + append(listUrl) + } + + if (pages > 1) { + append("page/") + append(pages) + append('/') + } + + append("?m_orderby=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("new-manga") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + else -> append("latest") + } + } + + null -> { + append('/') + append(listUrl) + if (pages > 1) { + append("page/") + append(pages) + append("/?m_orderby=latest") + } + } + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + 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 chaptersDeferred = async { loadChapters(manga.url, doc) } + val desc = body.select(selectDesc).html() + val stateDiv = if (selectState.isEmpty()) { + body.selectFirst("div.post-content_item:contains(Status)")?.selectLast("div.summary-content") + } else { + body.selectFirst(selectState) + } + + + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + in abandoned -> MangaState.ABANDONED + in paused -> MangaState.PAUSED + else -> null + } + } + + val alt = + doc.body().select(".post-content_item:contains(Alt) .summary-content").firstOrNull()?.tableValue()?.text() + ?.trim() + + manga.copy( + tags = doc.body().select(selectGenre).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = alt, + state = state, + chapters = chaptersDeferred.await(), + ) + } + + 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 = "post_id=$mangaId&action=ajax_chap" + val headers = Headers.Builder().add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build() + val doc = makeRequest(url, postData.toRequestBody(), headers) + 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 + 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, + name = name, + number = i + 1f, + volume = 0, + branch = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + scanlator = null, + source = source, + ) + } + } + + private suspend fun makeRequest(url: String, payload: RequestBody, headers: Headers): Document { + var retryCount = 0 + val backoffDelay = 2000L // Initial delay (milliseconds) + val request = Request.Builder().url(url).post(payload).headers(headers).build() + while (true) { + try { + return context.httpClient.newCall(request).execute().parseHtml() + + } catch (e: Exception) { + // Log or handle the exception as needed + if (++retryCount <= 5) { + withContext(Dispatchers.Default) { + delay(backoffDelay) + } + } else { + throw e + } + } + } + } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Rio2MangaNet.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Rio2MangaNet.kt index 0c8ca56d..40bc68a4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Rio2MangaNet.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Rio2MangaNet.kt @@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("RIO2MANGANET", "ZinchanManga", "en") +@MangaSourceParser("RIO2MANGANET", "ZinchanManga.mobi", "en") internal class Rio2MangaNet(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.RIO2MANGANET, "zinchanmanga.mobi", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinChanManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinChanManga.kt index 8997cede..7a5c4978 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinChanManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinChanManga.kt @@ -6,6 +6,6 @@ import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("ZINCHANMANGA", "ZinChanManga", "en", ContentType.HENTAI) +@MangaSourceParser("ZINCHANMANGA", "ZinChanManga.com", "en", ContentType.HENTAI) internal class ZinChanManga(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.ZINCHANMANGA, "zinchanmanga.com", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinchanMangaNet.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinchanMangaNet.kt new file mode 100644 index 00000000..207f16fa --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/ZinchanMangaNet.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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("ZINCHANMANGA_NET", "ZinchanManga.net", "en") +internal class ZinchanMangaNet(context: MangaLoaderContext) : + MadaraParser(context, MangaParserSource.ZINCHANMANGA_NET, "zinchanmanga.net", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/Fbsquads.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/Fbsquads.kt index d25ac9d3..e971ec99 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/Fbsquads.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/Fbsquads.kt @@ -8,6 +8,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("FBSQUADS", "FbSquads", "pt", ContentType.HENTAI) internal class Fbsquads(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.FBSQUADS, "fbsscan.com") { + MadaraParser(context, MangaParserSource.FBSQUADS, "fbsquadx.com") { override val datePattern: String = "dd/MM/yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Jiangzaitoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Jiangzaitoon.kt index f70678a1..b384e4c2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Jiangzaitoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Jiangzaitoon.kt @@ -8,7 +8,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("JIANGZAITOON", "JiangzaiToon", "tr", ContentType.HENTAI) internal class Jiangzaitoon(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.JIANGZAITOON, "jiangzaitoon.dev") { + MadaraParser(context, MangaParserSource.JIANGZAITOON, "jiangzaitoon.pro") { override val datePattern = "d MMMM yyyy" override val postReq = true } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/StrayFansub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/StrayFansub.kt new file mode 100644 index 00000000..c17546e4 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/StrayFansub.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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("STRAYFANSUB", "StrayFansub", "tr") +internal class StrayFansub(context: MangaLoaderContext) : + MadaraParser(context, MangaParserSource.STRAYFANSUB, "strayfansub.com", 16) { + override val tagPrefix = "seri-turu/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Manjanoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Manjanoon.kt index 3d8cdb84..549285f4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Manjanoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Manjanoon.kt @@ -10,7 +10,7 @@ import org.koitharu.kotatsu.parsers.util.* @MangaSourceParser("MANJANOON", "Manjanoon", "ar") internal class Manjanoon(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.MANJANOON, "manjanoon.co", pageSize = 21, searchPageSize = 10) { + MangaReaderParser(context, MangaParserSource.MANJANOON, "noonscan.net", pageSize = 21, searchPageSize = 10) { override suspend fun getDetails(manga: Manga): Manga { val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt index 340004e2..111fb840 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Normoyun.kt @@ -10,7 +10,7 @@ import java.text.SimpleDateFormat @MangaSourceParser("NORMOYUN", "MaxLevelTeam", "ar") internal class Normoyun(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.NORMOYUN, "maxlevelteam.com", pageSize = 42, searchPageSize = 39) { + MangaReaderParser(context, MangaParserSource.NORMOYUN, "tatwt.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/en/DexHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/DexHentai.kt new file mode 100644 index 00000000..23eba63f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/DexHentai.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("DEXHENTAI", "DexHentai", "en", ContentType.HENTAI) +internal class DexHentai(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.DEXHENTAI, "dexhentai.com", 40, 36) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/EnryuManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/EnryuManga.kt new file mode 100644 index 00000000..c8fb10d8 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/EnryuManga.kt @@ -0,0 +1,12 @@ +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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("ENRYUMANGA", "EnryuManga", "en") +internal class EnryuManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.ENRYUMANGA, "enryumanga.net", pageSize = 20, searchPageSize = 10) { + override val isTagsExclusionSupported = false +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/MagusManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/MagusManga.kt index 71df860b..4a0daa61 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/MagusManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/MagusManga.kt @@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("MAGUSMANGA", "Recipeslik", "en") internal class MagusManga(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.MAGUSMANGA, "recipeslik.online", pageSize = 20, searchPageSize = 10) { + MangaReaderParser(context, MangaParserSource.MAGUSMANGA, "oocini.biz", pageSize = 20, searchPageSize = 10) { override val listUrl = "/series" } 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 index 949c229b..08173d52 100644 --- 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 @@ -9,4 +9,4 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @Broken @MangaSourceParser("MANHWA_FREAK", "ManhwaFreak", "en") internal class ManhwaFreak(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.MANHWA_FREAK, "manhwafreak.site", pageSize = 20, searchPageSize = 10) + MangaReaderParser(context, MangaParserSource.MANHWA_FREAK, "manhwafreak.xyz", pageSize = 30, searchPageSize = 42) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/SnowScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/SnowScans.kt new file mode 100644 index 00000000..77deba63 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/SnowScans.kt @@ -0,0 +1,12 @@ +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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("SNOWSCANS", "SnowScans", "en") +internal class SnowScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.SNOWSCANS, "snowscans.com", pageSize = 20, searchPageSize = 10) { + override val listUrl = "/series" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VarnaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VarnaScan.kt new file mode 100644 index 00000000..6a196fa1 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VarnaScan.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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("VARNASCAN", "VarnaScan", "en") +internal class VarnaScan(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.VARNASCAN, "varnascan.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VoidScansCo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VoidScansCo.kt new file mode 100644 index 00000000..3fd948e3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/VoidScansCo.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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("VOIDSCANS_CO", "VoidScans", "en") +internal class VoidScansCo(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.VOIDSCANS_CO, "voidscans.co", pageSize = 30, searchPageSize = 42) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/YdComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/YdComics.kt new file mode 100644 index 00000000..8eac84a7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/YdComics.kt @@ -0,0 +1,12 @@ +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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("YDCOMICS", "YdComics", "en") +internal class YdComics(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.YDCOMICS, "yd-comics.com", pageSize = 20, searchPageSize = 10) { + override val listUrl = "/index.php/series" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/JapScansFR.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/JapScansFR.kt new file mode 100644 index 00000000..5c7f1033 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/JapScansFR.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.fr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.util.Locale + +@MangaSourceParser("JAPSCANSFR", "JapScans.fr", "fr") +internal class JapScansFR(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.JAPSCANSFR, "japscans.fr", pageSize = 20, searchPageSize = 10) { + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/AlceaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/AlceaScan.kt index 2c76813d..bc760ac7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/AlceaScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/AlceaScan.kt @@ -10,6 +10,6 @@ import java.util.* @Broken @MangaSourceParser("ALCEASCAN", "AlceaScan", "id") internal class AlceaScan(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.ALCEASCAN, "alceascan.my.id", pageSize = 20, searchPageSize = 10) { + MangaReaderParser(context, MangaParserSource.ALCEASCAN, "alceacomic.my.id", pageSize = 20, searchPageSize = 10) { override val sourceLocale: Locale = Locale.ENGLISH } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaListOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaListOrg.kt new file mode 100644 index 00000000..127d575e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaListOrg.kt @@ -0,0 +1,15 @@ +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.MangaParserSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.util.* + +@MangaSourceParser("MANHWALIST_ORG", "ManhwaList.org", "id") +internal class ManhwaListOrg(context: MangaLoaderContext) : + MangaReaderParser(context, MangaParserSource.MANHWALIST_ORG, "manhwalist.org", pageSize = 20, searchPageSize = 10) { + override val sourceLocale: Locale = Locale.ENGLISH + override val listUrl = "/manhwa" + override val isTagsExclusionSupported = false +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwalistParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwalistParser.kt index 1ed808c3..f2553d5b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwalistParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwalistParser.kt @@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser import java.util.* -@MangaSourceParser("MANHWALIST", "ManhwaList", "id") +@MangaSourceParser("MANHWALIST", "ManhwaList.com", "id") internal class ManhwalistParser(context: MangaLoaderContext) : MangaReaderParser(context, MangaParserSource.MANHWALIST, "manhwalist.com", pageSize = 24, searchPageSize = 10) { override val sourceLocale: Locale = Locale.ENGLISH diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/StrayFansub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/StrayFansub.kt deleted file mode 100644 index b83bcf4b..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/StrayFansub.kt +++ /dev/null @@ -1,11 +0,0 @@ -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.MangaParserSource -import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser - -@MangaSourceParser("STRAYFANSUB", "StrayFansub", "tr", ContentType.HENTAI) -internal class StrayFansub(context: MangaLoaderContext) : - MangaReaderParser(context, MangaParserSource.STRAYFANSUB, "strayfansub.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt index c6041019..5784de83 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/en/ReadComicsOnline.kt @@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.mmrcms.MmrcmsParser -@MangaSourceParser("READCOMICSONLINE", "ReadComicsOnline", "en", ContentType.COMICS) +@MangaSourceParser("READCOMICSONLINE", "ReadComicsOnline.ru", "en", ContentType.COMICS) internal class ReadComicsOnline(context: MangaLoaderContext) : MmrcmsParser(context, MangaParserSource.READCOMICSONLINE, "readcomicsonline.ru") { override val selectState = "dt:contains(Status)" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt index 10b5930a..ad2c4cf3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt @@ -47,29 +47,41 @@ internal class NudeMoonParser( override suspend fun getList( offset: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, + filter: MangaListFilter?, ): List { val domain = domain - val url = when { - !query.isNullOrEmpty() -> { - if (!isAuthorized) { - throw AuthRequiredException(source) + + val url = + when (filter) { + is MangaListFilter.Search -> { + if (!isAuthorized) { + throw AuthRequiredException(source) + } + "https://$domain/search?stext=${filter.query.urlEncoded()}&rowstart=$offset" } - "https://$domain/search?stext=${query.urlEncoded()}&rowstart=$offset" - } - !tags.isNullOrEmpty() -> tags.joinToString( - separator = "_", - prefix = "https://$domain/tags/", - postfix = "&rowstart=$offset", - transform = { it.key.urlEncoded() }, - ) + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.joinToString( + separator = "_", + prefix = "https://$domain/tags/", + postfix = "&rowstart=$offset", + transform = { it.key.urlEncoded() }, + ) + } else { + val order = when (filter.sortOrder) { + SortOrder.POPULARITY -> "views" + SortOrder.NEWEST -> "date" + SortOrder.RATING -> "like" + else -> "like" + } + "https://$domain/all_manga?$order&rowstart=$offset" + } + } + + null -> "https://$domain/all_manga?views&rowstart=$offset" + } - else -> "https://$domain/all_manga?${getSortKey(sortOrder)}&rowstart=$offset" - } val doc = webClient.httpGet(url).parseHtml() return doc.body().select("table.news_pic2").mapNotNull { row -> val a = row.selectFirstOrThrow("a") @@ -176,11 +188,4 @@ internal class NudeMoonParser( } } } - - private fun getSortKey(sortOrder: SortOrder) = when (sortOrder) { - SortOrder.POPULARITY -> "views" - SortOrder.NEWEST -> "date" - SortOrder.RATING -> "like" - else -> "like" - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt index 4900d8b6..b5e3147c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt @@ -56,7 +56,8 @@ class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(context, MangaP MangaChapter( id = generateUid(manga.id), name = manga.title, - number = 1, + number = 1f, + volume = 0, url = manga.url, scanlator = null, uploadDate = date.tryParse(jsonDeferred.await().getString("add_date")), @@ -69,32 +70,34 @@ class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(context, MangaP override suspend fun getList( offset: Int, - query: String?, - tags: Set?, - tagsExclude: Set?, - sortOrder: SortOrder, + filter: MangaListFilter?, ): List { // Get all manga val json = allManga.get().toMutableList() - // Search - if (!query.isNullOrEmpty()) { - json.retainAll { item -> - item.getString("name").contains(query, ignoreCase = true) || - item.getStringOrNull("eng_name")?.contains(query, ignoreCase = true) == true || - item.getStringOrNull("orig_name")?.contains(query, ignoreCase = true) == true || - item.getStringOrNull("author")?.contains(query, ignoreCase = true) == true || - item.getStringOrNull("team")?.contains(query, ignoreCase = true) == true + when (filter) { + is MangaListFilter.Search -> { + json.retainAll { item -> + item.getString("name").contains(filter.query, ignoreCase = true) || + item.getStringOrNull("eng_name")?.contains(filter.query, ignoreCase = true) == true || + item.getStringOrNull("orig_name")?.contains(filter.query, ignoreCase = true) == true || + item.getStringOrNull("author")?.contains(filter.query, ignoreCase = true) == true || + item.getStringOrNull("team")?.contains(filter.query, ignoreCase = true) == true + } } - } - if (!tags.isNullOrEmpty()) { - val ids = tags.mapToSet { it.key } - json.retainAll { item -> - item.getJSONArray("tags") - .mapJSON { it.getAsString() } - .any { x -> x in ids } + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + val ids = filter.tags.mapToSet { it.key } + json.retainAll { item -> + item.getJSONArray("tags") + .mapJSON { it.getAsString() } + .any { x -> x in ids } + } + } } + + null -> {} } // Return to app