From 5df1445e29ee3c915f0262734a3ee33f5c1bfcf5 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 12 Jan 2025 18:16:30 +0200 Subject: [PATCH] Migrate models to data class and update Manga class --- .../koitharu/kotatsu/parsers/model/Favicon.kt | 26 +- .../koitharu/kotatsu/parsers/model/Manga.kt | 261 ++++++++++++------ .../kotatsu/parsers/model/MangaChapter.kt | 50 +--- .../kotatsu/parsers/model/MangaPage.kt | 29 +- .../kotatsu/parsers/model/MangaTag.kt | 29 +- .../kotatsu/parsers/site/all/BatoToParser.kt | 6 +- .../parsers/site/all/ComickFunParser.kt | 8 +- .../parsers/site/all/MangaFireParser.kt | 21 +- .../kotatsu/parsers/site/all/MangaPark.kt | 2 +- .../parsers/site/all/MangaReaderToParser.kt | 15 +- .../site/all/NineNineNineHentaiParser.kt | 4 +- .../kotatsu/parsers/site/ar/FlixScans.kt | 2 +- .../kotatsu/parsers/site/en/FlixScansOrg.kt | 2 +- .../kotatsu/parsers/site/fr/FuryoSociety.kt | 6 +- .../kotatsu/parsers/site/fr/ScansMangasMe.kt | 7 +- .../parsers/site/ja/NicovideoSeigaParser.kt | 6 +- .../parsers/site/madara/MadaraParser.kt | 6 +- .../parsers/site/madtheme/MadthemeParser.kt | 6 +- .../parsers/site/mangabox/MangaboxParser.kt | 5 +- .../parsers/site/mangabox/en/Mangairo.kt | 7 +- .../site/mangabox/en/MangakakalotTv.kt | 5 +- .../site/mangareader/MangaReaderParser.kt | 23 +- .../parsers/site/mangareader/ar/Normoyun.kt | 8 +- .../parsers/site/mangareader/es/TuManhwas.kt | 11 +- .../parsers/site/mangareader/id/Komikcast.kt | 9 +- .../kotatsu/parsers/site/pt/BrMangas.kt | 12 +- .../kotatsu/parsers/site/pt/LerManga.kt | 6 +- .../parsers/site/ru/grouple/GroupleParser.kt | 15 +- .../parsers/site/vi/BlogTruyenParser.kt | 10 +- .../kotatsu/parsers/site/vi/BlogTruyenVN.kt | 10 +- .../parsers/site/vi/CuuTruyenParser.kt | 60 ++-- .../parsers/site/wpcomics/vi/MeHentaiVN.kt | 70 +++-- .../parsers/site/zmanga/ZMangaParser.kt | 10 +- .../kotatsu/parsers/util/LinkResolver.kt | 4 +- .../kotatsu/parsers/MangaParserTest.kt | 9 +- 35 files changed, 406 insertions(+), 354 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Favicon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Favicon.kt index 5b110162..28d7c717 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Favicon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Favicon.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.model import okhttp3.HttpUrl.Companion.toHttpUrl -public class Favicon( +public data class Favicon( @JvmField public val url: String, @JvmField public val size: Int, @JvmField internal val rel: String?, @@ -20,30 +20,6 @@ public class Favicon( return relWeightOf(rel).compareTo(relWeightOf(other.rel)) } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Favicon - - if (url != other.url) return false - if (size != other.size) return false - if (rel != other.rel) return false - - return true - } - - override fun hashCode(): Int { - var result = url.hashCode() - result = 31 * result + size - result = 31 * result + rel.hashCode() - return result - } - - override fun toString(): String { - return "Favicon(size=$size, type='$type', rel='$rel', url='$url')" - } - private fun relWeightOf(rel: String?) = when (rel) { "apple-touch-icon" -> 1 // Prefer apple-touch-icon because it has a better quality "mask-icon" -> -1 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 f7786f53..d7a9b8dd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt @@ -1,10 +1,11 @@ package org.koitharu.kotatsu.parsers.model import androidx.collection.ArrayMap -import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.util.findById +import org.koitharu.kotatsu.parsers.util.nullIfEmpty -public class Manga( +@ExposedCopyVisibility +public data class Manga private constructor( /** * Unique identifier for manga */ @@ -34,12 +35,12 @@ public class Manga( /** * Indicates that manga may contain sensitive information (18+, NSFW) */ - @JvmField public val isNsfw: Boolean, + @JvmField public val contentRating: ContentRating?, /** * Absolute link to the cover * @see largeCoverUrl */ - @JvmField public val coverUrl: String, + @JvmField public val coverUrl: String?, /** * Tags (genres) of the manga */ @@ -78,15 +79,17 @@ public class Manga( public val hasRating: Boolean get() = rating > 0f && rating <= 1f + public val isNsfw: Boolean + get() = contentRating == ContentRating.ADULT + public fun getChapters(branch: String?): List { return chapters?.filter { x -> x.branch == branch }.orEmpty() } public fun findChapterById(id: Long): MangaChapter? = chapters?.findById(id) - public fun requireChapterById(id: Long): MangaChapter = requireNotNull(findChapterById(id)) { - "Chapter with id $id not found" - } + public fun requireChapterById(id: Long): MangaChapter = findChapterById(id) + ?: throw NoSuchElementException("Chapter with id $id not found") public fun getBranches(): Map { if (chapters.isNullOrEmpty()) { @@ -100,85 +103,173 @@ public class Manga( return result } - @InternalParsersApi - public fun copy( - url: String = this.url, - title: String = this.title, - altTitle: String? = this.altTitle, - publicUrl: String = this.publicUrl, - rating: Float = this.rating, - isNsfw: Boolean = this.isNsfw, - coverUrl: String = this.coverUrl, - tags: Set = this.tags, - state: MangaState? = this.state, - author: String? = this.author, - largeCoverUrl: String? = this.largeCoverUrl, - description: String? = this.description, - chapters: List? = this.chapters, - source: MangaSource = this.source, - ): Manga = Manga( - id = id, - title = title, - altTitle = altTitle, - url = url, - publicUrl = publicUrl, - rating = rating, - isNsfw = isNsfw, - coverUrl = coverUrl, - tags = tags, - state = state, - author = author, - largeCoverUrl = largeCoverUrl, - description = description, - chapters = chapters, - source = source, - ) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Manga - - if (id != other.id) return false - if (title != other.title) return false - if (altTitle != other.altTitle) return false - if (url != other.url) return false - if (publicUrl != other.publicUrl) return false - if (rating != other.rating) return false - if (isNsfw != other.isNsfw) return false - if (coverUrl != other.coverUrl) return false - if (tags != other.tags) return false - if (state != other.state) return false - if (author != other.author) return false - if (largeCoverUrl != other.largeCoverUrl) return false - if (description != other.description) return false - if (chapters != other.chapters) return false - if (source != other.source) return false + public companion object { - return true - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + title.hashCode() - result = 31 * result + (altTitle?.hashCode() ?: 0) - result = 31 * result + url.hashCode() - result = 31 * result + publicUrl.hashCode() - result = 31 * result + rating.hashCode() - result = 31 * result + isNsfw.hashCode() - result = 31 * result + coverUrl.hashCode() - result = 31 * result + tags.hashCode() - result = 31 * result + (state?.hashCode() ?: 0) - result = 31 * result + (author?.hashCode() ?: 0) - result = 31 * result + (largeCoverUrl?.hashCode() ?: 0) - result = 31 * result + (description?.hashCode() ?: 0) - result = 31 * result + (chapters?.hashCode() ?: 0) - result = 31 * result + source.hashCode() - return result - } + @Deprecated("") + public operator fun invoke( + /** + * Unique identifier for manga + */ + id: Long, + /** + * Manga title, human-readable + */ + title: String, + /** + * Alternative title (for example on other language), may be null + */ + altTitle: String?, + /** + * Relative url to manga (**without** a domain) or any other uri. + * Used principally in parsers + */ + url: String, + /** + * Absolute url to manga, must be ready to open in browser + */ + publicUrl: String, + /** + * Normalized manga rating, must be in range of 0..1 or [RATING_UNKNOWN] if rating s unknown + * @see hasRating + */ + rating: Float, + /** + * Indicates that manga may contain sensitive information (18+, NSFW) + */ + isNsfw: Boolean, + /** + * Absolute link to the cover + * @see largeCoverUrl + */ + coverUrl: String?, + /** + * Tags (genres) of the manga + */ + tags: Set, + /** + * Manga status (ongoing, finished) or null if unknown + */ + state: MangaState?, + /** + * Author of the manga, may be null + */ + author: String?, + /** + * Large cover url (absolute), null if is no large cover + * @see coverUrl + */ + largeCoverUrl: String? = null, + /** + * Manga description, may be html or null + */ + description: String? = null, + /** + * List of chapters + */ + chapters: List? = null, + /** + * Manga source + */ + source: MangaSource, + ): Manga = invoke( + id = id, + title = title, + altTitle = altTitle, + url = url, + publicUrl = publicUrl, + rating = rating, + contentRating = if (isNsfw) ContentRating.ADULT else ContentRating.SAFE, + coverUrl = coverUrl, + tags = tags, + state = state, + author = author, + largeCoverUrl = largeCoverUrl, + description = description, + chapters = chapters, + source = source, + ) - override fun toString(): String { - return "Manga($id - \"$title\" [$url] - $source)" + public operator fun invoke( + /** + * Unique identifier for manga + */ + id: Long, + /** + * Manga title, human-readable + */ + title: String, + /** + * Alternative title (for example on other language), may be null + */ + altTitle: String?, + /** + * Relative url to manga (**without** a domain) or any other uri. + * Used principally in parsers + */ + url: String, + /** + * Absolute url to manga, must be ready to open in browser + */ + publicUrl: String, + /** + * Normalized manga rating, must be in range of 0..1 or [RATING_UNKNOWN] if rating s unknown + * @see hasRating + */ + rating: Float, + /** + * Indicates that manga may contain sensitive information (18+, NSFW) + */ + contentRating: ContentRating?, + /** + * Absolute link to the cover + * @see largeCoverUrl + */ + coverUrl: String?, + /** + * Tags (genres) of the manga + */ + tags: Set, + /** + * Manga status (ongoing, finished) or null if unknown + */ + state: MangaState?, + /** + * Author of the manga, may be null + */ + author: String?, + /** + * Large cover url (absolute), null if is no large cover + * @see coverUrl + */ + largeCoverUrl: String? = null, + /** + * Manga description, may be html or null + */ + description: String? = null, + /** + * List of chapters + */ + chapters: List? = null, + /** + * Manga source + */ + source: MangaSource, + ): Manga = Manga( + id = id, + title = title, + altTitle = altTitle?.nullIfEmpty(), + url = url, + publicUrl = publicUrl, + rating = rating, + contentRating = contentRating, + coverUrl = coverUrl?.nullIfEmpty(), + tags = tags, + state = state, + author = author?.nullIfEmpty(), + largeCoverUrl = largeCoverUrl?.nullIfEmpty(), + description = description?.nullIfEmpty(), + chapters = chapters, + source = source, + ) } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaChapter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaChapter.kt index 74a74bdf..b512b35f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaChapter.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaChapter.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.model import org.koitharu.kotatsu.parsers.util.formatSimple -public class MangaChapter( +public data class MangaChapter( /** * An unique id of chapter */ @@ -51,52 +51,4 @@ public class MangaChapter( } else { null } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MangaChapter - - if (id != other.id) return false - if (name != other.name) return false - if (number != other.number) return false - if (volume != other.volume) return false - if (url != other.url) return false - if (scanlator != other.scanlator) return false - if (uploadDate != other.uploadDate) return false - if (branch != other.branch) return false - if (source != other.source) return false - - return true - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + number.hashCode() - result = 31 * result + volume - result = 31 * result + url.hashCode() - result = 31 * result + (scanlator?.hashCode() ?: 0) - result = 31 * result + uploadDate.hashCode() - result = 31 * result + (branch?.hashCode() ?: 0) - result = 31 * result + source.hashCode() - return result - } - - override fun toString(): String { - return "MangaChapter($id - #$number [$url] - $source)" - } - - internal fun copy(volume: Int, number: Float) = MangaChapter( - id = id, - name = name, - number = number, - volume = volume, - url = url, - scanlator = scanlator, - uploadDate = uploadDate, - branch = branch, - source = source, - ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaPage.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaPage.kt index 5ac7ccf6..259541ce 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaPage.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaPage.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.model import org.koitharu.kotatsu.parsers.MangaParser -public class MangaPage( +public data class MangaPage( /** * Unique identifier for page */ @@ -19,29 +19,4 @@ public class MangaPage( */ @JvmField public val preview: String?, @JvmField public val source: MangaSource, -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MangaPage - - if (id != other.id) return false - if (url != other.url) return false - if (preview != other.preview) return false - return source == other.source - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + (preview?.hashCode() ?: 0) - result = 31 * result + source.hashCode() - return result - } - - override fun toString(): String { - return "MangaPage($id [$url] - $source)" - } -} +) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaTag.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaTag.kt index a694b4c8..69e96977 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaTag.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaTag.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.model import org.koitharu.kotatsu.parsers.MangaParser -public class MangaTag( +public data class MangaTag( /** * User-readable tag title, should be in Title case */ @@ -13,29 +13,4 @@ public class MangaTag( */ @JvmField public val key: String, @JvmField public val source: MangaSource, -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MangaTag - - if (title != other.title) return false - if (key != other.key) return false - if (source != other.source) return false - - return true - } - - override fun hashCode(): Int { - var result = title.hashCode() - result = 31 * result + key.hashCode() - result = 31 * result + source.hashCode() - return result - } - - override fun toString(): String { - return "MangaTag($key \"$title\" - $source)" - } -} +) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt index 6b8a1829..da5e4c29 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt @@ -214,7 +214,11 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( }.orEmpty() return manga.copy( title = root.selectFirst("h3.item-title")?.text() ?: manga.title, - isNsfw = !root.selectFirst("alert")?.getElementsContainingOwnText("NSFW").isNullOrEmpty(), + contentRating = if (root.selectFirst("alert")?.getElementsContainingOwnText("NSFW").isNullOrEmpty()) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, largeCoverUrl = details.selectFirst("img[src]")?.absUrl("src"), description = details.getElementById("limit-height-body-summary") ?.selectFirst(".limit-html") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt index aa5fc273..e957c459 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt @@ -183,7 +183,13 @@ internal class ComickFunParser(context: MangaLoaderContext) : comic.getJSONArray("md_titles").mapJSON { alt += it.getString("title") + " - " } return manga.copy( altTitle = alt.ifEmpty { comic.getStringOrNull("title") }, - isNsfw = jo.getBooleanOrDefault("matureContent", false) || comic.getBooleanOrDefault("hentai", false), + contentRating = if (jo.getBooleanOrDefault("matureContent", false) + || comic.getBooleanOrDefault("hentai", false) + ) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, description = comic.getStringOrNull("parsed") ?: comic.getStringOrNull("desc"), tags = manga.tags + comic.getJSONArray("md_comic_md_genres").mapJSONToSet { val g = it.getJSONObject("md_genres") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt index 1fb32323..6468a974 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt @@ -166,7 +166,7 @@ internal abstract class MangaFireParser( altTitle = null, largeCoverUrl = null, author = null, - isNsfw = false, + contentRating = null, rating = RATING_UNKNOWN, state = null, tags = emptySet(), @@ -177,7 +177,8 @@ internal abstract class MangaFireParser( override suspend fun getDetails(manga: Manga): Manga { val document = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val availableTags = tags.get() - var isNsfw = false + var isAdult = false + var isSuggestive = false return manga.copy( title = document.selectFirstOrThrow(".info > h1").ownText(), @@ -188,12 +189,18 @@ internal abstract class MangaFireParser( .attrAsAbsoluteUrl("src"), tags = document.select("div.meta a[href*=/genre/]").mapNotNullToSet { val tag = it.ownText() - if (tag == "Hentai" || tag == "Ecchi") { - isNsfw = true + if (tag == "Hentai") { + isAdult = true + } else if (tag == "Ecchi") { + isSuggestive = true } availableTags[tag.toTitleCase(sourceLocale)] }, - isNsfw = isNsfw, + contentRating = when { + isAdult -> ContentRating.ADULT + isSuggestive -> ContentRating.SUGGESTIVE + else -> ContentRating.SAFE + }, state = document.selectFirst(".info > p")?.ownText()?.let { when (it.lowercase()) { "releasing" -> MangaState.ONGOING @@ -326,7 +333,7 @@ internal abstract class MangaFireParser( altTitle = null, largeCoverUrl = null, author = null, - isNsfw = false, + contentRating = null, rating = RATING_UNKNOWN, state = null, tags = emptySet(), @@ -349,7 +356,7 @@ internal abstract class MangaFireParser( altTitle = null, largeCoverUrl = null, author = null, - isNsfw = false, + contentRating = null, rating = RATING_UNKNOWN, state = null, tags = emptySet(), 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 fa5b9d5f..b15dde56 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 @@ -190,7 +190,7 @@ internal class MangaPark(context: MangaLoaderContext) : else -> null }, tags = tags, - isNsfw = nsfw, + contentRating = if (nsfw) ContentRating.ADULT else ContentRating.SAFE, chapters = doc.body().select("div.group.flex div.px-2").mapChapters(reversed = true) { i, div -> val a = div.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt index 0c21a3c6..38f65e2b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt @@ -167,7 +167,8 @@ internal class MangaReaderToParser(context: MangaLoaderContext) : override suspend fun getDetails(manga: Manga): Manga { val document = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val availableTags = tags.get() - var isNsfw = false + var isAdult = false + var isSuggestive = false return manga.copy( title = document.selectFirst("h2.manga-name")!!.ownText(), @@ -177,12 +178,18 @@ internal class MangaReaderToParser(context: MangaLoaderContext) : coverUrl = document.selectFirst(".manga-poster > img")!!.attr("src"), tags = document.select("div.genres > a[href*=/genre/]").mapNotNullToSet { val tag = it.ownText() - if (tag == "Hentai" || tag == "Ecchi") { - isNsfw = true + if (tag == "Hentai") { + isAdult = true + } else if (tag == "Ecchi") { + isSuggestive = true } availableTags[tag] }, - isNsfw = isNsfw, + contentRating = when { + isAdult -> ContentRating.ADULT + isSuggestive -> ContentRating.SUGGESTIVE + else -> ContentRating.SAFE + }, state = document.selectFirst("div.anisc-info .item:contains(status:) > .name") ?.text()?.let { when (it) { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt index f923aebd..8c136ed4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt @@ -207,7 +207,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) : else -> "https://${cdnHost.get()}/$cover" }, author = null, - isNsfw = true, + contentRating = ContentRating.ADULT, url = id, publicUrl = "/hchapter/$id".toAbsoluteUrl(domain), tags = emptySet(), @@ -270,7 +270,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) : coverUrl = cover.first, largeCoverUrl = cover.second, author = tags?.filter { it.type == "artist" }?.joinToString { it.name.toCamelCase() }, - isNsfw = true, + contentRating = ContentRating.ADULT, tags = tags?.mapToSet { MangaTag( title = it.name.toCamelCase(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt index 94d1a46c..f0e92ddb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt @@ -173,7 +173,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context ) }, rating = rating?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, - isNsfw = nsfw, + contentRating = if (nsfw) ContentRating.ADULT else ContentRating.SAFE, chapters = chaptersDeferred.await(), ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt index e9412273..7f2c8966 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt @@ -152,7 +152,7 @@ internal class FlixScansOrg(context: MangaLoaderContext) : ) }, rating = rating?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, - isNsfw = nsfw, + contentRating = if (nsfw) ContentRating.ADULT else ContentRating.SAFE, chapters = chaptersDeferred.await(), ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt index 4b219dea..2fac73ac 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt @@ -89,7 +89,11 @@ internal class FuryoSociety(context: MangaLoaderContext) : manga.copy( description = doc.selectFirst("div.fs-comic-description")?.html().orEmpty(), chapters = chaptersDeferred, - isNsfw = doc.selectFirst(".adult-text") != null, + contentRating = if (doc.selectFirst(".adult-text") != null) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt index f9a7a771..fa4c032e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt @@ -112,9 +112,9 @@ internal class ScansMangasMe(context: MangaLoaderContext) : val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val chaptersDeferred = getChapters(doc) - val desc = doc.selectFirstOrThrow("div.desc").html() - val alt = doc.body().select("div.infox span.alter").text() - val aut = doc.select("div.spe span")[2].text().replace("Auteur:", "") + val desc = doc.selectFirstOrThrow("div.desc").html().nullIfEmpty() + val alt = doc.body().select("div.infox span.alter").text().nullIfEmpty() + val aut = doc.select("div.spe span")[2].text().replace("Auteur:", "").nullIfEmpty() manga.copy( tags = doc.select("div.spe span:contains(Genres) a").mapToSet { a -> MangaTag( @@ -133,7 +133,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) : else -> null }, chapters = chaptersDeferred, - isNsfw = manga.isNsfw, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt index 5f83e0b5..439c2f58 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt @@ -126,7 +126,11 @@ internal class NicovideoSeigaParser(context: MangaLoaderContext) : STATUS_FINISHED -> MangaState.FINISHED else -> null }, - isNsfw = contents.select(".icon_adult").isNotEmpty(), + contentRating = if (contents.select(".icon_adult").isNotEmpty()) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, chapters = contents.select("#episode_list > ul > li").mapChapters { i, li -> val href = li.selectFirst("div > div.description > div.title > a") ?.attrAsRelativeUrl("href") ?: li.parseFailed() 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 a3f28488..5a61e581 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 @@ -577,7 +577,11 @@ internal abstract class MadaraParser( altTitle = alt, state = state, chapters = chaptersDeferred.await(), - isNsfw = doc.selectFirst(".adult-confirm") != null, + contentRating = if (doc.selectFirst(".adult-confirm") != null) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt index 7ffcfa54..1526edff 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt @@ -190,7 +190,11 @@ internal abstract class MadthemeParser( altTitle = alt.orEmpty(), state = state, chapters = chaptersDeferred.await(), - isNsfw = nsfw || manga.isNsfw, + contentRating = if (nsfw || manga.isNsfw) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt index 023ed43c..b008e266 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt @@ -174,8 +174,8 @@ internal abstract class MangaboxParser( else -> null } } - val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") - val aut = doc.body().select(selectAut).eachText().joinToString() + val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "").nullIfEmpty() + val aut = doc.body().select(selectAut).eachText().joinToString().nullIfEmpty() manga.copy( tags = doc.body().select(selectTag).mapToSet { a -> MangaTag( @@ -189,7 +189,6 @@ internal abstract class MangaboxParser( author = aut, state = state, chapters = chaptersDeferred.await(), - isNsfw = manga.isNsfw, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt index a41df4ae..fb19b4c4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt @@ -126,7 +126,7 @@ internal class Mangairo(context: MangaLoaderContext) : val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirst(selectDesc)?.html() + val desc = doc.selectFirst(selectDesc)?.html()?.nullIfEmpty() val stateDiv = doc.select(selectState).text() val state = stateDiv.let { when (it) { @@ -136,8 +136,8 @@ internal class Mangairo(context: MangaLoaderContext) : } } - val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") - val aut = doc.body().select(selectAut).eachText().joinToString() + val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "").nullIfEmpty() + val aut = doc.body().select(selectAut).eachText().joinToString().nullIfEmpty() manga.copy( tags = doc.body().select(selectTag).mapToSet { a -> MangaTag( @@ -152,7 +152,6 @@ internal class Mangairo(context: MangaLoaderContext) : author = aut, state = state, chapters = chaptersDeferred.await(), - isNsfw = manga.isNsfw, ) } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt index b3125d25..3ec07a94 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt @@ -108,8 +108,8 @@ internal class MangakakalotTv(context: MangaLoaderContext) : else -> null } } - val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") - val aut = doc.body().select(selectAut).eachText().joinToString() + val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "").nullIfEmpty() + val aut = doc.body().select(selectAut).eachText().joinToString().nullIfEmpty() manga.copy( tags = doc.body().select(selectTag).mapToSet { a -> MangaTag( @@ -123,7 +123,6 @@ internal class MangakakalotTv(context: MangaLoaderContext) : author = aut, state = state, chapters = chaptersDeferred.await(), - isNsfw = manga.isNsfw, ) } 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 3f681b89..262e43ba 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 @@ -262,11 +262,11 @@ internal abstract class MangaReaderParser( } } - val author = tableMode?.selectFirst(".infotable td:contains(Author)")?.lastElementSibling()?.text() - ?: docs.selectFirst(".tsinfo div:contains(Author)")?.lastElementChild()?.text() - ?: docs.selectFirst(".tsinfo div:contains(Auteur)")?.lastElementChild()?.text() - ?: docs.selectFirst(".tsinfo div:contains(Artist)")?.lastElementChild()?.text() - ?: docs.selectFirst(".tsinfo div:contains(Durum)")?.lastElementChild()?.text() + val author = tableMode?.selectFirst(".infotable td:contains(Author)")?.lastElementSibling()?.textOrNull() + ?: docs.selectFirst(".tsinfo div:contains(Author)")?.lastElementChild()?.textOrNull() + ?: docs.selectFirst(".tsinfo div:contains(Auteur)")?.lastElementChild()?.textOrNull() + ?: docs.selectFirst(".tsinfo div:contains(Artist)")?.lastElementChild()?.textOrNull() + ?: docs.selectFirst(".tsinfo div:contains(Durum)")?.lastElementChild()?.textOrNull() val nsfw = docs.selectFirst(".restrictcontainer") != null || docs.selectFirst(".info-right .alr") != null @@ -276,7 +276,11 @@ internal abstract class MangaReaderParser( description = docs.selectFirst(detailsDescriptionSelector)?.text(), state = mangaState, author = author, - isNsfw = manga.isNsfw || nsfw, + contentRating = if (manga.isNsfw || nsfw) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, tags = tags, chapters = chapters, ) @@ -348,14 +352,13 @@ internal abstract class MangaReaderParser( protected open suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } - val tagMap = ArrayMap() val url = listUrl.toAbsoluteUrl(domain) val tagElements = webClient.httpGet(url).parseHtml().select("ul.genrez > li") + val tagMap = ArrayMap(tagElements.size) for (el in tagElements) { - if (el.text().isEmpty()) continue tagMap[el.text()] = MangaTag( - title = el.text(), - key = el.selectFirst("input")?.attr("value") ?: continue, + title = el.textOrNull()?.toTitleCase(sourceLocale) ?: continue, + key = el.selectFirst("input")?.attrOrNull("value") ?: continue, source = source, ) } 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 890876af..3604c37b 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 @@ -101,7 +101,7 @@ internal class Normoyun(context: MangaLoaderContext) : } else { MangaState.ONGOING } - val author = docs.selectFirst("span.author i")?.text() + val author = docs.selectFirst("span.author i")?.textOrNull() val nsfw = docs.selectFirst(".restrictcontainer") != null || docs.selectFirst(".info-right .alr") != null @@ -111,7 +111,11 @@ internal class Normoyun(context: MangaLoaderContext) : description = docs.selectFirst("span.desc")?.html(), state = mangaState, author = author, - isNsfw = manga.isNsfw || nsfw, + contentRating = if (manga.isNsfw || nsfw) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, tags = emptySet(), chapters = chapters, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt index db4de19f..94c6c776 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt @@ -91,14 +91,17 @@ internal class TuManhwas(context: MangaLoaderContext) : || docs.selectFirst(".postbody .alr") != null return manga.copy( - description = docs.selectFirst("div.entry-content")?.text(), + description = docs.selectFirst("div.entry-content")?.html(), state = mangaState, - author = null, - isNsfw = manga.isNsfw || nsfw, + contentRating = if (manga.isNsfw || nsfw) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, tags = docs.select(".wd-full .mgen > a").mapToSet { a -> MangaTag( key = a.attr("href").substringAfterLast('='), - title = a.text().toTitleCase(), + title = a.text().toTitleCase(sourceLocale), source = source, ) }, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt index 47ee4918..82f7e247 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt @@ -113,7 +113,8 @@ internal class Komikcast(context: MangaLoaderContext) : } else { MangaState.FINISHED } - val author = docs.selectFirst(".komik_info-content-meta span:contains(Author)")?.lastElementChild()?.text() + val author = docs.selectFirst(".komik_info-content-meta span:contains(Author)") + ?.lastElementChild()?.textOrNull() val nsfw = docs.selectFirst(".restrictcontainer") != null || docs.selectFirst(".info-right .alr") != null || docs.selectFirst( ".postbody .alr", @@ -123,7 +124,11 @@ internal class Komikcast(context: MangaLoaderContext) : description = docs.selectFirst("div.komik_info-description-sinopsis")?.text(), state = mangaState, author = author, - isNsfw = manga.isNsfw || nsfw, + contentRating = if (manga.isNsfw || nsfw) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, tags = tags, chapters = chapters, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt index b58c3e13..058edde1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt @@ -113,7 +113,7 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, return doc.select(".genres_page a").mapToSet { a -> MangaTag( key = a.attr("href").removeSuffix('/').substringAfterLast('/'), - title = a.text(), + title = a.text().toTitleCase(sourceLocale), source = source, ) } @@ -127,13 +127,17 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, tags = doc.select("div.serie-infos li:contains(Categorias:) a").mapToSet { a -> MangaTag( key = a.attr("href").removeSuffix('/').substringAfterLast('/'), - title = a.text(), + title = a.text().toTitleCase(sourceLocale), source = source, ) }, - author = doc.select("div.serie-infos li:contains(Autor:)").text().replace("Autor:", ""), + author = doc.select("div.serie-infos li:contains(Autor:)").text().replace("Autor:", "").nullIfEmpty(), description = doc.select(".serie-texto p").text(), - isNsfw = doc.select("div.serie-infos li:contains(Categorias:)").text().contains("Hentai"), + contentRating = if (doc.select("div.serie-infos li:contains(Categorias:)").text().contains("Hentai")) { + ContentRating.ADULT + } else { + manga.contentRating + }, chapters = doc.select(".capitulos li a") .mapChapters { i, a -> val url = a.attrAsRelativeUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt index 0bba5038..cf91ec60 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt @@ -124,7 +124,11 @@ internal class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, source = source, ) }.orEmpty(), - isNsfw = doc.select("ul.genre-list li").text().contains("Adulto"), + contentRating = if (doc.select("ul.genre-list li").text().contains("Adulto")) { + ContentRating.ADULT + } else { + manga.contentRating + }, chapters = doc.select("div.manga-chapters div.single-chapter").mapChapters(reversed = true) { i, div -> val a = div.selectFirstOrThrow("a") val href = a.attrAsAbsoluteUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt index 6d5767bc..a98a31b0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt @@ -140,11 +140,12 @@ internal abstract class GroupleParser( source = newSource, title = doc.metaValue("name") ?: manga.title, altTitle = root.selectFirst(".all-names-popover")?.select(".name")?.joinToString { it.text() } + ?.nullIfEmpty() ?: manga.altTitle, publicUrl = response.request.url.toString(), description = root.selectFirst("div.manga-description")?.html(), - largeCoverUrl = coverImg?.attr("data-full"), - coverUrl = coverImg?.attr("data-thumb") ?: manga.coverUrl, + largeCoverUrl = coverImg?.attrAsAbsoluteUrlOrNull("data-full"), + coverUrl = coverImg?.attrAsAbsoluteUrlOrNull("data-thumb") ?: manga.coverUrl, tags = root.selectFirstOrThrow("div.subject-meta") .getElementsByAttributeValueContaining("href", "/list/genre/").mapTo(manga.tags.toMutableSet()) { a -> MangaTag( @@ -153,8 +154,14 @@ internal abstract class GroupleParser( source = source, ) }, - author = root.selectFirst("a.person-link")?.text() ?: manga.author, - isNsfw = manga.isNsfw || root.select(".alert-warning").any { it.ownText().contains(NSFW_ALERT) }, + author = root.selectFirst("a.person-link")?.textOrNull() ?: manga.author, + contentRating = if (manga.isNsfw || root.select(".alert-warning") + .any { it.ownText().contains(NSFW_ALERT) } + ) { + ContentRating.ADULT + } else { + manga.contentRating + }, chapters = chaptersList?.select("a.chapter-link") ?.flatMapChapters(reversed = true) { a -> val tr = a.selectFirstParent("tr") ?: return@flatMapChapters emptyList() 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 1e4e18ac..eb7b86a0 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 @@ -128,13 +128,17 @@ internal class BlogTruyenParser(context: MangaLoaderContext) : return manga.copy( tags = tags, - author = descriptionElement.selectFirst("p:contains(Tác giả) > a")?.text(), + author = descriptionElement.selectFirst("p:contains(Tác giả) > a")?.textOrNull(), description = doc.selectFirst(".detail .content")?.html(), chapters = parseChapterList(doc), - largeCoverUrl = doc.selectLast("div.thumbnail > img")?.src().orEmpty(), + largeCoverUrl = doc.selectLast("div.thumbnail > img")?.src(), state = state, rating = rating ?: RATING_UNKNOWN, - isNsfw = doc.getElementById("warningCategory") != null, + contentRating = if (doc.getElementById("warningCategory") != null) { + ContentRating.ADULT + } else { + manga.contentRating + }, ) } 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 dc98f7d2..74df4cc7 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 @@ -180,13 +180,17 @@ internal class BlogTruyenVN(context: MangaLoaderContext) : return manga.copy( tags = tags ?: emptySet(), - author = descriptionElement.selectFirst("p:contains(Tác giả) > a")?.text(), + author = descriptionElement.selectFirst("p:contains(Tác giả) > a")?.textOrNull(), description = doc.selectFirst(".detail .content")?.html(), chapters = parseChapterList(doc), - largeCoverUrl = doc.selectLast("div.thumbnail > img")?.src().orEmpty(), + largeCoverUrl = doc.selectLast("div.thumbnail > img")?.src(), state = state, rating = rating ?: RATING_UNKNOWN, - isNsfw = doc.getElementById("warningCategory") != null, + contentRating = if (doc.getElementById("warningCategory") != null) { + ContentRating.ADULT + } else { + manga.contentRating + }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt index bac559ab..7ede5846 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt @@ -127,37 +127,41 @@ internal class CuuTruyenParser(context: MangaLoaderContext) : } override suspend fun getDetails(manga: Manga): Manga = coroutineScope { - val url = "https://" + domain + manga.url - val chapters = async { - webClient.httpGet("$url/chapters").parseJson().getJSONArray("data") - } - val json = webClient.httpGet(url).parseJson().getJSONObject("data") - val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("GMT+7") - } - val tags = json.optJSONArray("tags")?.mapJSONToSet { jo -> - MangaTag( - title = jo.getString("name").toTitleCase(sourceLocale), - key = jo.getString("slug"), - source = source, - ) - }.orEmpty() + val url = "https://" + domain + manga.url + val chapters = async { + webClient.httpGet("$url/chapters").parseJson().getJSONArray("data") + } + val json = webClient.httpGet(url).parseJson().getJSONObject("data") + val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ROOT).apply { + timeZone = TimeZone.getTimeZone("GMT+7") + } + val tags = json.optJSONArray("tags")?.mapJSONToSet { jo -> + MangaTag( + title = jo.getString("name").toTitleCase(sourceLocale), + key = jo.getString("slug"), + source = source, + ) + }.orEmpty() - // Testing: Add custom manga status using available tags - val state = when { - tags.any { it.key == "da-hoan-thanh" } -> MangaState.FINISHED - tags.any { it.key == "dang-tien-hanh" } -> MangaState.ONGOING - else -> null - } + // Testing: Add custom manga status using available tags + val state = when { + tags.any { it.key == "da-hoan-thanh" } -> MangaState.FINISHED + tags.any { it.key == "dang-tien-hanh" } -> MangaState.ONGOING + else -> null + } - // Remove old manga status from "tags" - val newTags = tags.filter { it.key != "da-hoan-thanh" && it.key != "dang-tien-hanh" }.toSet() + // Remove old manga status from "tags" + val newTags = tags.filter { it.key != "da-hoan-thanh" && it.key != "dang-tien-hanh" }.toSet() - manga.copy( - title = json.getStringOrNull("name") ?: manga.title, - isNsfw = json.getBooleanOrDefault("is_nsfw", manga.isNsfw), - author = json.optJSONObject("author")?.getStringOrNull("name")?.substringBefore(','), - description = json.getString("full_description"), + manga.copy( + title = json.getStringOrNull("name") ?: manga.title, + contentRating = if (json.getBooleanOrDefault("is_nsfw", manga.isNsfw)) { + ContentRating.ADULT + } else { + ContentRating.SAFE + }, + author = json.optJSONObject("author")?.getStringOrNull("name")?.substringBefore(',')?.nullIfEmpty(), + description = json.getStringOrNull("full_description"), tags = newTags, state = state, chapters = chapters.await().mapJSON { jo -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/MeHentaiVN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/MeHentaiVN.kt index 74116179..301502eb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/MeHentaiVN.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/MeHentaiVN.kt @@ -1,49 +1,47 @@ package org.koitharu.kotatsu.parsers.site.wpcomics.vi -import kotlinx.coroutines.async import androidx.collection.ArraySet +import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.model.MangaParserSource -import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.util.* import java.util.* @MangaSourceParser("MEHENTAIVN", "MeHentaiVN", "vi", ContentType.HENTAI) internal class MeHentaiVN(context: MangaLoaderContext) : WpComicsParser(context, MangaParserSource.MEHENTAIVN, "www.mehentaivn.xyz", 44) { - + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("www.mehentaivn.xyz", "www.hentaivnx.autos") - override suspend fun getFilterOptions() = MangaListFilterOptions( + override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), ) - override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val chaptersDeferred = async { getChapters(doc) } - val tagMap = getOrCreateTagMap() val tagsElement = doc.select("li.kind p.col-xs-8 a") - val mangaTags = tagsElement.mapNotNullToSet { - val tagTitle = it.text() - if (tagTitle.isNotEmpty()) - MangaTag( - title = tagTitle, - key = tagsElement.attr("href").substringAfterLast('/').trim(), - source = source - ) - else null - } + val mangaTags = tagsElement.mapNotNullToSet { + val tagTitle = it.text() + if (tagTitle.isNotEmpty()) + MangaTag( + title = tagTitle.toTitleCase(sourceLocale), + key = tagsElement.attr("href").substringAfterLast('/').trim(), + source = source, + ) + else null + } manga.copy( - description = doc.selectFirst(selectDesc)?.html().orEmpty(), - altTitle = doc.selectFirst("h2.other-name")?.text().orEmpty(), - author = doc.body().select(selectAut).text(), + description = doc.selectFirst(selectDesc)?.html(), + altTitle = doc.selectFirst("h2.other-name")?.textOrNull(), + author = doc.body().selectFirst(selectAut)?.textOrNull(), state = doc.selectFirst(selectState)?.let { when (it.text()) { in ongoing -> MangaState.ONGOING @@ -54,21 +52,21 @@ internal class MeHentaiVN(context: MangaLoaderContext) : tags = mangaTags, rating = doc.selectFirst("div.star input")?.attr("value")?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, chapters = chaptersDeferred.await(), - isNsfw = true + contentRating = ContentRating.ADULT, ) } - - private suspend fun fetchTags(): Set { - val doc = webClient.httpGet("https://$domain/").parseHtml() - val tagItems = doc.select("ul.dropdown-menu.megamenu li a") - val tagSet = ArraySet(tagItems.size) - for (item in tagItems) { - val title = item.attr("data-title").trim() - val key = item.attr("href").substringAfterLast('/').trim() - if (key.isNotEmpty() && title.isNotEmpty()) { - tagSet.add(MangaTag(title = title, key = key, source = source)) - } - } - return tagSet - } -} \ No newline at end of file + + private suspend fun fetchTags(): Set { + val doc = webClient.httpGet("https://$domain/").parseHtml() + val tagItems = doc.select("ul.dropdown-menu.megamenu li a") + val tagSet = ArraySet(tagItems.size) + for (item in tagItems) { + val title = item.attr("data-title").toTitleCase(sourceLocale) + val key = item.attr("href").substringAfterLast('/').trim() + if (key.isNotEmpty() && title.isNotEmpty()) { + tagSet.add(MangaTag(title = title, key = key, source = source)) + } + } + return tagSet + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt index 9419d628..161fe7fb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt @@ -220,9 +220,9 @@ internal abstract class ZMangaParser( } } - val alt = doc.body().select(selectAlt).text() + val alt = doc.body().selectFirst(selectAlt)?.textOrNull() - val aut = doc.body().select(selectAut).text() + val aut = doc.body().selectFirst(selectAut)?.textOrNull() manga.copy( tags = doc.body().select(selectTag).mapToSet { a -> @@ -237,7 +237,11 @@ internal abstract class ZMangaParser( author = aut, state = state, chapters = chaptersDeferred.await(), - isNsfw = manga.isNsfw || doc.getElementById("adt-warning") != null, + contentRating = if (doc.getElementById("adt-warning") != null) { + ContentRating.ADULT + } else { + manga.contentRating + }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt index daafaae4..3160fe50 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt @@ -50,7 +50,7 @@ public class LinkResolver internal constructor( url = url, publicUrl = link.toString(), rating = RATING_UNKNOWN, - isNsfw = false, + contentRating = null, coverUrl = "", tags = emptySet(), state = null, @@ -94,7 +94,7 @@ public class LinkResolver internal constructor( author = seed.author ?: resolved.author, tags = seed.tags + resolved.tags, state = seed.state ?: resolved.state, - coverUrl = seed.coverUrl.ifEmpty { resolved.coverUrl }, + coverUrl = seed.coverUrl ?: resolved.coverUrl, largeCoverUrl = seed.largeCoverUrl ?: resolved.largeCoverUrl, altTitle = seed.altTitle ?: resolved.altTitle, ) diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt index 31880af8..96950060 100644 --- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt +++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt @@ -248,8 +248,8 @@ internal class MangaParserTest { for (item in list) { assert(item.url.isNotEmpty()) { "Url is empty" } assert(!item.url.isUrlAbsolute()) { "Url looks like absolute: ${item.url}" } - if (item.coverUrl.isNotEmpty()) { // TODO nullable cover - assert(item.coverUrl.isUrlAbsolute()) { "Cover url is not absolute: ${item.coverUrl}" } + item.coverUrl?.let { + assert(it.isUrlAbsolute()) { "Cover url is not absolute: ${item.coverUrl}" } } assert(item.title.isNotEmpty()) { "Title for ${item.publicUrl} is empty" } assert(item.publicUrl.isUrlAbsolute()) @@ -258,7 +258,10 @@ internal class MangaParserTest { checkImageRequest(testItem.coverUrl, testItem.source) } - private suspend fun checkImageRequest(url: String, source: MangaSource) { + private suspend fun checkImageRequest(url: String?, source: MangaSource) { + if (url == null) { + return + } context.doRequest(url, source).use { assert(it.isSuccessful) { "Request failed: ${it.code}(${it.message}): $url" } assert(it.mimeType?.startsWith("image/") == true) {