From 72564f5449990b1eb4f1a80745f445eecb177ca0 Mon Sep 17 00:00:00 2001 From: devi Date: Mon, 14 Oct 2024 21:09:35 +0200 Subject: [PATCH] [MadaraParser] auto fix url manga on fix query ( withoutAjax [MangaPark] fix close #1152 close #1145 [AdultWebtoon] fix close #1139 [ComicExtra] change type close #1130 [liliana] fix and add ManhuaPlusorg, Raw1001, MangaSect, MangaKoma01, ManhuaGold --- .github/summary.yaml | 2 +- .../koitharu/kotatsu/parsers/model/Manga.kt | 1 + .../kotatsu/parsers/site/all/MangaPark.kt | 51 ++++--- .../kotatsu/parsers/site/en/ComicExtra.kt | 2 +- .../parsers/site/liliana/LilianaParser.kt | 140 +++++++++++------- .../parsers/site/liliana/en/MangaSect.kt | 10 ++ .../parsers/site/liliana/en/ManhuaGold.kt | 10 ++ .../parsers/site/liliana/en/ManhuaPlusOrg.kt | 10 ++ .../parsers/site/liliana/ja/MangaKoma01.kt | 10 ++ .../parsers/site/liliana/ja/Raw1001.kt | 10 ++ .../parsers/site/liliana/vi/DocTruyen5s.kt | 2 +- .../parsers/site/madara/MadaraParser.kt | 10 +- .../parsers/site/madara/en/AdultWebtoon.kt | 66 +++++++++ .../site/mangareader/MangaReaderParser.kt | 8 +- .../parsers/site/nepnep/NepnepParser.kt | 8 +- .../parsers/site/ru/multichan/ChanParser.kt | 2 +- .../parsers/site/ru/rulib/LibSocialParser.kt | 4 +- .../parsers/site/vi/BlogTruyenParser.kt | 6 +- .../kotatsu/parsers/site/vi/BlogTruyenVN.kt | 2 +- 19 files changed, 258 insertions(+), 96 deletions(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/MangaSect.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaGold.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaPlusOrg.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/MangaKoma01.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/Raw1001.kt diff --git a/.github/summary.yaml b/.github/summary.yaml index cffadbc6..713b196a 100644 --- a/.github/summary.yaml +++ b/.github/summary.yaml @@ -1 +1 @@ -total: 1122 \ No newline at end of file +total: 1127 \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt index 76919ca2..eb12e886 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt @@ -82,6 +82,7 @@ public class Manga( @InternalParsersApi public fun copy( + url: String = this.url, title: String = this.title, altTitle: String? = this.altTitle, publicUrl: String = this.publicUrl, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt index 07b7775e..7c6ba6c1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt @@ -236,18 +236,24 @@ internal class MangaPark(context: MangaLoaderContext) : val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val cal = Calendar.getInstance() return when { - WordSet("second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis - WordSet("minute", "minutes", "mins", "min").anyWordIn(date) -> cal.apply { - add( - Calendar.MINUTE, - -number, - ) - }.timeInMillis + WordSet("second") + .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + + WordSet("minute", "minutes", "mins", "min") + .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + + WordSet("hour", "hours") + .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + + WordSet("day", "days") + .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + + WordSet("month", "months") + .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + + WordSet("year") + .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis - WordSet("hour", "hours").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis - WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis - WordSet("month", "months").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis - WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis else -> 0 } } @@ -255,18 +261,23 @@ internal class MangaPark(context: MangaLoaderContext) : override suspend fun getPages(chapter: MangaChapter): List { val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() - val script = if (doc.selectFirst("script:containsData(comic-)") != null) { - doc.selectFirstOrThrow("script:containsData(comic-)").data() - .substringAfterLast("\"comic-") + val id = chapter.url.removeSuffix('/').substringAfterLast('/').substringBefore('-') + val s = doc.selectFirstOrThrow("script:containsData($id)").data() + + val script = if (s.contains("\"comic-")) { + s.substringAfterLast("\"comic-") } else { - doc.selectFirstOrThrow("script:containsData(manga-)").data() - .substringAfterLast("\"manga-") + s.substringAfterLast("\"manga-") } + return Regex("\"(https?:.+?)\"") .findAll(script) - .mapNotNullTo(ArrayList()) { - val url = it.groupValues.getOrNull(1) ?: return@mapNotNullTo null - if (url.contains("/comic/") || url.contains("/manga/") || url.contains("/image/mpup/")) { + .mapIndexedNotNullTo(ArrayList()) { i, it -> + val url = it.groupValues.getOrNull(1) ?: return@mapIndexedNotNullTo null + if (url.contains(".jpg") || url.contains(".jpeg") || url.contains(".jfif") || url.contains(".pjpeg") || + url.contains(".pjp") || url.contains(".png") || url.contains(".webp") || url.contains(".avif") || + url.contains(".gif") + ) { MangaPage( id = generateUid(url), url = url, @@ -274,7 +285,7 @@ internal class MangaPark(context: MangaLoaderContext) : source = source, ) } else { - return@mapNotNullTo null + return@mapIndexedNotNullTo null } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt index 1afd62f4..0a95edac 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt @@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat import java.util.* -@MangaSourceParser("COMICEXTRA", "ComicExtra", "en") +@MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS) internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.COMICEXTRA, 25) { override val configKeyDomain = ConfigKey.Domain("comixextra.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt index 84e7cea9..915d5e38 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.parsers.site.liliana +import androidx.collection.scatterSetOf import kotlinx.coroutines.coroutineScope import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -27,16 +28,26 @@ internal abstract class LilianaParser( override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.POPULARITY, + SortOrder.POPULARITY_MONTH, + SortOrder.POPULARITY_WEEK, + SortOrder.POPULARITY_TODAY, SortOrder.ALPHABETICAL, + SortOrder.ALPHABETICAL_DESC, SortOrder.NEWEST, - SortOrder.RATING_ASC, + SortOrder.NEWEST_ASC, + SortOrder.RATING, + ) + + override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions( + availableTags = getAvailableTags(), + availableStates = setOf(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), ) override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, + isTagsExclusionSupported = true, isSearchSupported = true, - isSearchWithFiltersSupported = true, ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { @@ -45,45 +56,51 @@ internal abstract class LilianaParser( append(domain) when { !filter.query.isNullOrEmpty() -> { - append("/search") - append("?keyword=") + append("/search/") + append(page) + append("/?keyword=") append(filter.query.urlEncoded()) } else -> { - append("/filter") + append("/filter/") + append(page) + + append("/?sort=") + when (order) { + SortOrder.UPDATED -> append("latest-updated") + SortOrder.POPULARITY -> append("views") + SortOrder.POPULARITY_MONTH -> append("views_month") + SortOrder.POPULARITY_WEEK -> append("views_week") + SortOrder.POPULARITY_TODAY -> append("views_day") + SortOrder.ALPHABETICAL -> append("az") + SortOrder.ALPHABETICAL_DESC -> append("za") + SortOrder.NEWEST -> append("new") + SortOrder.NEWEST_ASC -> append("old") + SortOrder.RATING -> append("score") + else -> append("latest-updated") + } + + append("&genres=") + filter.tags.joinTo(this, ",") { it.key } + + append("¬Genres=") + filter.tagsExclude.joinTo(this, ",") { it.key } + + if (filter.states.isNotEmpty()) { + append("&status=") + append( + when (filter.states.first()) { + MangaState.ONGOING -> "on-going" + MangaState.FINISHED -> "completed" + MangaState.PAUSED -> "on-hold" + MangaState.ABANDONED -> "canceled" + else -> "all" + }, + ) + } } } - append("/") - append(page) - append("/") - - when (order) { - SortOrder.UPDATED -> append("?sort=latest-updated") - SortOrder.POPULARITY -> append("?sort=views") - SortOrder.ALPHABETICAL -> append("?sort=az") - SortOrder.NEWEST -> append("?sort=new") - SortOrder.RATING_ASC -> append("?sort=score") - else -> append("?sort=default") - } - - filter.tags.forEach { tag -> - append("&genres=") - append(tag.key) - } - - if (filter.states.isNotEmpty()) { - append("&status=") - append( - when (filter.states.first()) { - MangaState.ONGOING -> "on-going" - MangaState.FINISHED -> "completed" - MangaState.PAUSED -> "on-hold" - MangaState.ABANDONED -> "canceled" - else -> "all" - }, - ) - } } val doc = webClient.httpGet(url).parseHtml() @@ -104,10 +121,30 @@ internal abstract class LilianaParser( author = null, state = null, source = source, - isNsfw = false, + isNsfw = isNsfwSource, ) } + @JvmField + protected val ongoing = scatterSetOf( + "on-going", "đang tiến hành", "進行中", + ) + + @JvmField + protected val finished = scatterSetOf( + "completed", "hoàn thành", "完了", + ) + + @JvmField + protected val abandoned = scatterSetOf( + "canceled", "đã huỷ bỏ", "キャンセル", + ) + + @JvmField + protected val paused = scatterSetOf( + "on-hold", "tạm dừng", "一時停止", + ) + override suspend fun getDetails(manga: Manga): Manga { val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() return manga.copy( @@ -123,27 +160,28 @@ internal abstract class LilianaParser( author = doc.selectFirst("div.y6x11p i.fas.fa-user + span.dt")?.text()?.takeUnless { it.equals("updating", true) }, - state = when (doc.selectFirst("div.y6x11p i.fas.fa-rss + span.dt")?.text()?.lowercase()) { - "on-going", "đang tiến hành", "進行中" -> MangaState.ONGOING - "completed", "hoàn thành", "完了" -> MangaState.FINISHED - "on-hold", "tạm dừng", "一時停止" -> MangaState.PAUSED - "canceled", "đã huỷ bỏ", "キャンセル" -> MangaState.ABANDONED + state = when (doc.selectFirst("div.y6x11p i.fas.fa-rss + span.dt")?.text()?.lowercase().orEmpty()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + in paused -> MangaState.PAUSED + in abandoned -> MangaState.ABANDONED else -> null }, chapters = doc.select("ul > li.chapter").mapChapters(reversed = true) { i, element -> val href = element.selectFirstOrThrow("a").attrAsRelativeUrl("href") MangaChapter( id = generateUid(href), - name = element.selectFirst("a")?.text().orEmpty(), + name = element.selectFirst("a")?.text() ?: "Chapter : ${i + 1f}", number = i + 1f, + volume = 0, url = href, scanlator = null, uploadDate = element.selectFirst("time[datetime]")?.attr("datetime")?.toLongOrNull()?.times(1000) ?: 0L, branch = null, source = source, - volume = 0, - ) + + ) }, ) } @@ -170,13 +208,10 @@ internal abstract class LilianaParser( throw Exception(responseJson.optString("msg")) } - val pageListHtml = responseJson.getString("html") - val pageListDoc = Jsoup.parse(pageListHtml) - - return pageListDoc.select("div.iv-card").mapIndexed { index, div -> - val img = div.selectFirst("img") - val url = img?.attr("data-src") ?: img?.attr("src") ?: throw Exception("Failed to get image url") + val pageListDoc = responseJson.getString("html").let(Jsoup::parse) + return pageListDoc.select("div").map { + val url = it.selectFirstOrThrow("img").attr("src") MangaPage( id = generateUid(url), url = url, @@ -196,9 +231,4 @@ internal abstract class LilianaParser( ) } } - - override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions( - availableTags = getAvailableTags(), - availableStates = setOf(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), - ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/MangaSect.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/MangaSect.kt new file mode 100644 index 00000000..55a0b62e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/MangaSect.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.liliana.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.liliana.LilianaParser + +@MangaSourceParser("MANGASECT", "MangaSect", "en") +internal class MangaSect(context: MangaLoaderContext) : + LilianaParser(context, MangaParserSource.MANGASECT, "mangasect.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaGold.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaGold.kt new file mode 100644 index 00000000..4a004003 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaGold.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.liliana.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.liliana.LilianaParser + +@MangaSourceParser("MANHUAGOLD", "ManhuaGold", "en") +internal class ManhuaGold(context: MangaLoaderContext) : + LilianaParser(context, MangaParserSource.MANHUAGOLD, "manhuagold.top") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaPlusOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaPlusOrg.kt new file mode 100644 index 00000000..ff4b7c2c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/en/ManhuaPlusOrg.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.liliana.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.liliana.LilianaParser + +@MangaSourceParser("MANHUAPLUSORG", "ManhuaPlus.org", "en") +internal class ManhuaPlusOrg(context: MangaLoaderContext) : + LilianaParser(context, MangaParserSource.MANHUAPLUSORG, "manhuaplus.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/MangaKoma01.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/MangaKoma01.kt new file mode 100644 index 00000000..a203ce12 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/MangaKoma01.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.liliana.ja + +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.liliana.LilianaParser + +@MangaSourceParser("MANGAKOMA01", "MangaKoma01", "ja") +internal class MangaKoma01(context: MangaLoaderContext) : + LilianaParser(context, MangaParserSource.MANGAKOMA01, "mangakoma01.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/Raw1001.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/Raw1001.kt new file mode 100644 index 00000000..e0691beb --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/ja/Raw1001.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.liliana.ja + +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.liliana.LilianaParser + +@MangaSourceParser("RAW1001", "Raw1001", "ja") +internal class Raw1001(context: MangaLoaderContext) : + LilianaParser(context, MangaParserSource.RAW1001, "raw1001.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/vi/DocTruyen5s.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/vi/DocTruyen5s.kt index 1e1949fa..01cf2834 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/vi/DocTruyen5s.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/vi/DocTruyen5s.kt @@ -9,4 +9,4 @@ import org.koitharu.kotatsu.parsers.Broken @Broken @MangaSourceParser("DOCTRUYEN5S", "DocTruyen5s", "vi") internal class DocTruyen5s(context: MangaLoaderContext) : - LilianaParser(context, MangaParserSource.DOCTRUYEN5S, "dongmoe.com") + LilianaParser(context, MangaParserSource.DOCTRUYEN5S, "dongmoe.com") 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 2f22f97a..97f14d82 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 @@ -233,7 +233,9 @@ internal abstract class MadaraParser( } append("/?s=") - append(filter.query?.urlEncoded()) + filter.query?.let { + append(filter.query.urlEncoded()) + } append("&post_type=wp-manga") @@ -536,10 +538,10 @@ internal abstract class MadaraParser( val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() + val href = doc.selectFirst("head meta[property='og:url']")?.attr("content")?.toRelativeUrl(domain) ?: manga.url val testCheckAsync = doc.select(selectTestAsync) - val chaptersDeferred = if (testCheckAsync.isNullOrEmpty()) { - async { loadChapters(manga.url, doc) } + async { loadChapters(href, doc) } } else { async { getChapters(manga, doc) } } @@ -561,6 +563,8 @@ internal abstract class MadaraParser( val alt = doc.body().select(selectAlt).firstOrNull()?.tableValue()?.text()?.trim() manga.copy( + url = href, + publicUrl = href.toAbsoluteUrl(domain), tags = doc.body().select(selectGenre).mapNotNullToSet { a -> MangaTag( key = a.attr("href").removeSuffix("/").substringAfterLast('/'), 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 2aaee8cc..47601c57 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 @@ -5,13 +5,17 @@ import okhttp3.Headers import okhttp3.Request import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.exception.AuthRequiredException +import org.koitharu.kotatsu.parsers.exception.ParseException 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.Base64 @MangaSourceParser("ADULT_WEBTOON", "AdultWebtoon", "en", ContentType.HENTAI) internal class AdultWebtoon(context: MangaLoaderContext) : @@ -150,6 +154,68 @@ internal class AdultWebtoon(context: MangaLoaderContext) : } } + 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) { + 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.replace("http:", "https:"), + preview = null, + source = source, + ) + } + } + } else { + + val chapterProtectorHtml = chapterProtector.attr("src") + .takeIf { it.startsWith("data:text/javascript;base64,") } + ?.substringAfter("data:text/javascript;base64,") + ?.let { + Base64.getDecoder().decode(it).decodeToString() + } + ?: chapterProtector.html() + + val password = chapterProtectorHtml.substringAfter("wpmangaprotectornonce='").substringBefore("';") + val chapterData = JSONObject( + chapterProtectorHtml.substringAfter("chapter_data='").substringBefore("';").replace("\\/", "/"), + ) + val unsaltedCiphertext = context.decodeBase64(chapterData.getString("ct")) + val salt = chapterData.getString("s").toString().decodeHex() + val ciphertext = "Salted__".toByteArray(Charsets.UTF_8) + salt + unsaltedCiphertext + + val rawImgArray = CryptoAES(context).decrypt(context.encodeBase64(ciphertext), password) + val imgArrayString = rawImgArray.filterNot { c -> c == '[' || c == ']' || c == '\\' || c == '"' } + + return imgArrayString.split(",").map { url -> + MangaPage( + id = generateUid(url), + url = url.replace("http:", "https:"), + preview = null, + source = source, + ) + } + + } + } + + private fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + + return chunked(2).map { it.toInt(16).toByte() }.toByteArray() + } + private suspend fun makeRequest(url: String, payload: RequestBody, headers: Headers): Document { var retryCount = 0 val backoffDelay = 2000L // Initial delay (milliseconds) 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 03fbca92..8c957f0b 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 @@ -245,18 +245,18 @@ internal abstract class MangaReaderParser( "En cours de publication", "Đang tiến hành", "Em lançamento", "em lançamento", "Em Lançamento", "Онгоінг", "Publishing", "Devam Ediyor", "Em Andamento", "In Corso", "Güncel", "Berjalan", "Продолжается", "Updating", "Lançando", "In Arrivo", "Emision", "En emision", "مستمر", "Curso", "En marcha", "Publicandose", "Publicando", "连载中", "Devam ediyor", "Devam Etmekte", - -> MangaState.ONGOING + -> MangaState.ONGOING "Completed", "Completo", "Complété", "Fini", "Achevé", "Terminé", "Terminé ⚫", "Tamamlandı", "Đã hoàn thành", "Hoàn Thành", "مكتملة", "Завершено", "Finished", "Finalizado", "Completata", "One-Shot", "Bitti", "Tamat", "Completado", "Concluído", "Concluido", "已完结", "Bitmiş", - -> MangaState.FINISHED + -> MangaState.FINISHED "Canceled", "Cancelled", "Cancelado", "cancellato", "Cancelados", "Dropped", "Discontinued", "abandonné", "Abandonné", - -> MangaState.ABANDONED + -> MangaState.ABANDONED "Hiatus", "On Hold", "Pausado", "En espera", "En pause", "En Pause", "En attente", - -> MangaState.PAUSED + -> MangaState.PAUSED else -> null } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt index e10176ab..90d99dea 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt @@ -186,17 +186,17 @@ internal abstract class NepnepParser( altTitle = null, state = when (doc.selectFirstOrThrow(".list-group-item:contains(Status:) a").text()) { "Ongoing (Scan)", "Ongoing (Publish)", - -> MangaState.ONGOING + -> MangaState.ONGOING "Complete (Scan)", "Complete (Publish)", - -> MangaState.FINISHED + -> MangaState.FINISHED "Cancelled (Scan)", "Cancelled (Publish)", "Discontinued (Scan)", "Discontinued (Publish)", - -> MangaState.ABANDONED + -> MangaState.ABANDONED "Hiatus (Scan)", "Hiatus (Publish)", - -> MangaState.PAUSED + -> MangaState.PAUSED else -> null }, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt index a8d9baf6..42083a4c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt @@ -216,7 +216,7 @@ internal abstract class ChanParser( when (order) { SortOrder.RATING, SortOrder.POPULARITY, - -> "favdesc" + -> "favdesc" SortOrder.ALPHABETICAL -> "abcasc" else -> "" // SortOrder.NEWEST diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt index 422e4f0d..2010bc87 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt @@ -95,7 +95,7 @@ internal abstract class LibSocialParser( SortOrder.NEWEST -> "created_at" SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC, - -> "rus_name" + -> "rus_name" else -> null }, @@ -108,7 +108,7 @@ internal abstract class LibSocialParser( SortOrder.RATING, SortOrder.NEWEST, SortOrder.ALPHABETICAL_DESC, - -> "desc" + -> "desc" SortOrder.ALPHABETICAL -> "asc" else -> null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt index fd16e375..c6bd3f4d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.vi -import androidx.collection.ArrayMap import org.json.JSONArray import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -53,7 +52,8 @@ internal class BlogTruyenParser(context: MangaLoaderContext) : filter.tags.isNotEmpty() -> { filter.tags.oneOrThrowIfMany()?.let { tag -> - val categoryAjax = "https://${domain}/ajax/Category/AjaxLoadMangaByCategory?id=${tag.key}&orderBy=5&p=$page" + val categoryAjax = + "https://${domain}/ajax/Category/AjaxLoadMangaByCategory?id=${tag.key}&orderBy=5&p=$page" val listContent = webClient.httpGet(categoryAjax).parseHtml().selectFirst("div.list") parseMangaList(listContent) ?: emptyList() } ?: emptyList() @@ -248,6 +248,6 @@ internal class BlogTruyenParser(context: MangaLoaderContext) : MangaTag("Soft Yuri", "36", source), MangaTag("Live action", "16", source), MangaTag("Tu chân - tu tiên", "66", source), - MangaTag("Ngôn tình", "65", source) + MangaTag("Ngôn tình", "65", source), ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt index 435efc07..423e84af 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt @@ -17,7 +17,7 @@ import org.koitharu.kotatsu.parsers.Broken @Broken @MangaSourceParser("BLOGTRUYENVN", "BlogTruyenVN", "vi") -internal class BlogTruyenVNParser(context: MangaLoaderContext) : +internal class BlogTruyenVN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.BLOGTRUYENVN, pageSize = 20) { override val configKeyDomain: ConfigKey.Domain