Remove deprecated Manga constructor and introduce authors field

master
palaks-1 1 year ago committed by Koitharu
parent 86e7c21e4d
commit f6145bc412

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import androidx.collection.ArrayMap import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.util.findById import org.koitharu.kotatsu.parsers.util.findById
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.parsers.util.nullIfEmpty
public data class Manga constructor( public data class Manga constructor(
@ -49,9 +50,9 @@ public data class Manga constructor(
*/ */
@JvmField public val state: MangaState?, @JvmField public val state: MangaState?,
/** /**
* Author of the manga, may be null * Authors of the manga
*/ */
@JvmField public val author: String?, @JvmField public val authors: Set<String>,
/** /**
* Large cover url (absolute), null if is no large cover * Large cover url (absolute), null if is no large cover
* @see coverUrl * @see coverUrl
@ -70,41 +71,12 @@ public data class Manga constructor(
*/ */
@JvmField public val source: MangaSource, @JvmField public val source: MangaSource,
) { ) {
/**
@Deprecated("Prefer constructor with contentRating instead of isNsfw") * Author of the manga, may be null
public constructor( */
id: Long, @Deprecated("Please use authors")
title: String, public val author: String?
altTitle: String?, get() = authors.firstOrNull()
url: String,
publicUrl: String,
rating: Float,
isNsfw: Boolean,
coverUrl: String?,
tags: Set<MangaTag>,
state: MangaState?,
author: String?,
largeCoverUrl: String? = null,
description: String? = null,
chapters: List<MangaChapter>? = null,
source: MangaSource,
) : this(
id = id,
title = title,
altTitle = altTitle?.nullIfEmpty(),
url = url,
publicUrl = publicUrl,
rating = rating,
contentRating = if (isNsfw) ContentRating.ADULT else null,
coverUrl = coverUrl?.nullIfEmpty(),
tags = tags,
state = state,
author = author?.nullIfEmpty(),
largeCoverUrl = largeCoverUrl?.nullIfEmpty(),
description = description?.nullIfEmpty(),
chapters = chapters,
source = source,
)
/** /**
* Return if manga has a specified rating * Return if manga has a specified rating

@ -214,6 +214,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
val attrs = details.selectFirst(".attr-main")?.select(".attr-item")?.associate { val attrs = details.selectFirst(".attr-main")?.select(".attr-item")?.associate {
it.child(0).text() to it.child(1) it.child(0).text() to it.child(1)
}.orEmpty() }.orEmpty()
val author = attrs["Authors:"]?.textOrNull()
return manga.copy( return manga.copy(
title = root.selectFirst("h3.item-title")?.text() ?: manga.title, title = root.selectFirst("h3.item-title")?.text() ?: manga.title,
contentRating = if (root.selectFirst("alert")?.getElementsContainingOwnText("NSFW").isNullOrEmpty()) { contentRating = if (root.selectFirst("alert")?.getElementsContainingOwnText("NSFW").isNullOrEmpty()) {
@ -233,7 +234,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
"Hiatus" -> MangaState.PAUSED "Hiatus" -> MangaState.PAUSED
else -> manga.state else -> manga.state
}, },
author = attrs["Authors:"]?.textOrNull() ?: manga.author, authors = author?.let { setOf(it) } ?: manga.authors,
chapters = root.selectFirst(".episode-list") chapters = root.selectFirst(".episode-list")
?.selectFirst(".main") ?.selectFirst(".main")
?.children() ?.children()
@ -337,13 +338,13 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
url = href, url = href,
publicUrl = a.absUrl("href"), publicUrl = a.absUrl("href"),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirst("img[src]")?.absUrl("src"), coverUrl = div.selectFirst("img[src]")?.absUrl("src"),
largeCoverUrl = null, largeCoverUrl = null,
description = null, description = null,
tags = div.selectFirst(".item-genre")?.parseTags().orEmpty(), tags = div.selectFirst(".item-genre")?.parseTags().orEmpty(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -168,7 +168,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
4 -> MangaState.PAUSED 4 -> MangaState.PAUSED
else -> null else -> null
}, },
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -182,6 +182,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
val alt = comic.getJSONArray("md_titles").asTypedList<JSONObject>().joinToString("\n") { val alt = comic.getJSONArray("md_titles").asTypedList<JSONObject>().joinToString("\n") {
it.getStringOrNull("title").orEmpty() it.getStringOrNull("title").orEmpty()
} }
val author = jo.getJSONArray("artists").optJSONObject(0)?.getStringOrNull("name")
return manga.copy( return manga.copy(
altTitle = alt.nullIfEmpty(), altTitle = alt.nullIfEmpty(),
contentRating = if (jo.getBooleanOrDefault("matureContent", false) contentRating = if (jo.getBooleanOrDefault("matureContent", false)
@ -200,7 +201,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
author = jo.getJSONArray("artists").optJSONObject(0)?.getStringOrNull("name"), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = getChapters(comic.getString("hid")), chapters = getChapters(comic.getString("hid")),
) )
} }

@ -168,6 +168,8 @@ internal class ExHentaiParser(
val href = a.attrAsRelativeUrl("href") val href = a.attrAsRelativeUrl("href")
val tagsDiv = gLink.nextElementSibling() ?: gLink.parseFailed("tags div not found") val tagsDiv = gLink.nextElementSibling() ?: gLink.parseFailed("tags div not found")
val rawTitle = gLink.text() val rawTitle = gLink.text()
val author = tagsDiv.getElementsContainingOwnText("artist:").first()
?.nextElementSibling()?.textOrNull()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = rawTitle.cleanupTitle(), title = rawTitle.cleanupTitle(),
@ -182,8 +184,7 @@ internal class ExHentaiParser(
rawTitle.contains("(ongoing)", ignoreCase = true) -> MangaState.ONGOING rawTitle.contains("(ongoing)", ignoreCase = true) -> MangaState.ONGOING
else -> null else -> null
}, },
author = tagsDiv.getElementsContainingOwnText("artist:").first() authors = author?.let { setOf(it) } ?: emptySet(),
?.nextElementSibling()?.textOrNull(),
source = source, source = source,
) )
} }

@ -524,9 +524,9 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
doc.selectFirstOrThrow("h1 > a") doc.selectFirstOrThrow("h1 > a")
.attrAsRelativeUrl("href") .attrAsRelativeUrl("href")
.toAbsoluteUrl(domain), .toAbsoluteUrl(domain),
author = null, authors = emptySet(),
tags = emptySet(), tags = emptySet(),
isNsfw = true, contentRating = ContentRating.ADULT,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
altTitle = null, altTitle = null,
state = null, state = null,
@ -542,6 +542,10 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
.parseRaw() .parseRaw()
.substringAfter("var galleryinfo = ") .substringAfter("var galleryinfo = ")
.let(::JSONObject) .let(::JSONObject)
val author =
json.optJSONArray("artists")
?.mapJSON { it.getString("artist").toCamelCase() }
?.joinToString()
return manga.copy( return manga.copy(
title = json.getString("title"), title = json.getString("title"),
@ -554,10 +558,7 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp" "https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
}, },
author = authors = author?.let { setOf(it) } ?: emptySet(),
json.optJSONArray("artists")
?.mapJSON { it.getString("artist").toCamelCase() }
?.joinToString(),
publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain), publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain),
tags = tags =
buildSet { buildSet {

@ -125,10 +125,10 @@ internal class ImHentai(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -161,6 +161,7 @@ internal class ImHentai(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val author = doc.selectFirst("li:contains(Artists) a.tag")?.ownTextOrNull()
manga.copy( manga.copy(
tags = doc.body().select("li:contains(Tags) a.tag").mapNotNullToSet { tags = doc.body().select("li:contains(Tags) a.tag").mapNotNullToSet {
val href = it.attr("href").substringAfterLast("tag/").substringBeforeLast('/') val href = it.attr("href").substringAfterLast("tag/").substringBeforeLast('/')
@ -170,7 +171,7 @@ internal class ImHentai(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
author = doc.selectFirst("li:contains(Artists) a.tag")?.ownTextOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = manga.id, id = manga.id,
@ -202,10 +203,10 @@ internal class ImHentai(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = false, contentRating = null,
) )
} }
} }

@ -121,6 +121,8 @@ internal abstract class LineWebtoonsParser(
makeRequest("/lineWebtoon/webtoon/challengeTitleInfo.json?v=2&titleNo=${titleNo}") makeRequest("/lineWebtoon/webtoon/challengeTitleInfo.json?v=2&titleNo=${titleNo}")
.getJSONObject("titleInfo") .getJSONObject("titleInfo")
.let { jo -> .let { jo ->
val isNsfwSource = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource)
val author = jo.getStringOrNull("writingAuthorName")
Manga( Manga(
id = generateUid(titleNo), id = generateUid(titleNo),
title = jo.getString("title"), title = jo.getString("title"),
@ -128,11 +130,11 @@ internal abstract class LineWebtoonsParser(
url = "$titleNo", url = "$titleNo",
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=${titleNo}", publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=${titleNo}",
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain), largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
tags = setOf(parseTag(jo.getJSONObject("genreInfo"))), tags = setOf(parseTag(jo.getJSONObject("genreInfo"))),
author = jo.getStringOrNull("writingAuthorName"), authors = author?.let { setOf(it) } ?: emptySet(),
description = jo.getString("synopsis"), description = jo.getString("synopsis"),
// I don't think the API provides this info // I don't think the API provides this info
state = null, state = null,
@ -150,6 +152,7 @@ internal abstract class LineWebtoonsParser(
.getJSONArray("titleList") .getJSONArray("titleList")
.mapJSON { jo -> .mapJSON { jo ->
val titleNo = jo.getLong("titleNo") val titleNo = jo.getLong("titleNo")
val author = jo.getStringOrNull("writingAuthorName")
Manga( Manga(
id = generateUid(titleNo), id = generateUid(titleNo),
@ -158,11 +161,11 @@ internal abstract class LineWebtoonsParser(
url = titleNo.toString(), url = titleNo.toString(),
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
largeCoverUrl = null, largeCoverUrl = null,
tags = emptySet(), tags = emptySet(),
author = jo.getStringOrNull("writingAuthorName"), authors = author?.let { setOf(it) } ?: emptySet(),
description = null, description = null,
state = null, state = null,
source = source, source = source,
@ -194,6 +197,8 @@ internal abstract class LineWebtoonsParser(
.getJSONArray("titles") .getJSONArray("titles")
.mapJSON { jo -> .mapJSON { jo ->
val titleNo = jo.getLong("titleNo") val titleNo = jo.getLong("titleNo")
val isNsfwSource = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource)
val author = jo.getStringOrNull("writingAuthorName")
Manga( Manga(
id = generateUid(titleNo), id = generateUid(titleNo),
@ -202,11 +207,11 @@ internal abstract class LineWebtoonsParser(
url = titleNo.toString(), url = titleNo.toString(),
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo", publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain), largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
tags = setOfNotNull(genres[jo.getString("representGenre")]), tags = setOfNotNull(genres[jo.getString("representGenre")]),
author = jo.getStringOrNull("writingAuthorName"), authors = author?.let { setOf(it) } ?: emptySet(),
description = jo.getString("synopsis"), description = jo.getString("synopsis"),
// I don't think the API provides this info // I don't think the API provides this info
state = null, state = null,

@ -287,6 +287,9 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
?.let { ?.let {
"https://uploads.$domain/covers/$id/$it" "https://uploads.$domain/covers/$id/$it"
} }
val author = (relations["author"] ?: relations["artist"])
?.getJSONObject("attributes")
?.getStringOrNull("name")
return Manga( return Manga(
id = generateUid(id), id = generateUid(id),
title = requireNotNull(attrs.getJSONObject("title").selectByLocale()) { title = requireNotNull(attrs.getJSONObject("title").selectByLocale()) {
@ -322,9 +325,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
"cancelled" -> MangaState.ABANDONED "cancelled" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = (relations["author"] ?: relations["artist"]) authors = author?.let { setOf(it) } ?: emptySet(),
?.getJSONObject("attributes")
?.getStringOrNull("name"),
chapters = chapters, chapters = chapters,
source = source, source = source,
) )

@ -165,7 +165,7 @@ internal abstract class MangaFireParser(
source = source, source = source,
altTitle = null, altTitle = null,
largeCoverUrl = null, largeCoverUrl = null,
author = null, authors = emptySet(),
contentRating = null, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
@ -179,6 +179,8 @@ internal abstract class MangaFireParser(
val availableTags = tags.get() val availableTags = tags.get()
var isAdult = false var isAdult = false
var isSuggestive = false var isSuggestive = false
val author = document.select("div.meta a[href*=/author/]")
.joinToString { it.ownText() }.nullIfEmpty()
return manga.copy( return manga.copy(
title = document.selectFirstOrThrow(".info > h1").ownText(), title = document.selectFirstOrThrow(".info > h1").ownText(),
@ -211,8 +213,7 @@ internal abstract class MangaFireParser(
else -> null else -> null
} }
}, },
author = document.select("div.meta a[href*=/author/]") authors = author?.let { setOf(it) } ?: emptySet(),
.joinToString { it.ownText() }.nullIfEmpty(),
description = document.selectFirstOrThrow("#synopsis div.modal-content").html(), description = document.selectFirstOrThrow("#synopsis div.modal-content").html(),
chapters = getChapters(manga.url, document), chapters = getChapters(manga.url, document),
) )
@ -332,7 +333,7 @@ internal abstract class MangaFireParser(
source = source, source = source,
altTitle = null, altTitle = null,
largeCoverUrl = null, largeCoverUrl = null,
author = null, authors = emptySet(),
contentRating = null, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
@ -355,7 +356,7 @@ internal abstract class MangaFireParser(
source = source, source = source,
altTitle = null, altTitle = null,
largeCoverUrl = null, largeCoverUrl = null,
author = null, authors = emptySet(),
contentRating = null, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,

@ -160,10 +160,10 @@ internal class MangaPark(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.selectFirst("span.text-yellow-500")?.text()?.toFloatOrNull()?.div(10F) ?: RATING_UNKNOWN, rating = div.selectFirst("span.text-yellow-500")?.text()?.toFloatOrNull()?.div(10F) ?: RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -193,9 +193,10 @@ internal class MangaPark(context: MangaLoaderContext) :
val tags = selectTag.mapNotNullToSet { tagMap[it.text()] } val tags = selectTag.mapNotNullToSet { tagMap[it.text()] }
val nsfw = tags.any { t -> t.key == "hentai" || t.key == "adult" } val nsfw = tags.any { t -> t.key == "hentai" || t.key == "adult" }
val dateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale) val dateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale)
val author = doc.selectFirst("div[q:key=tz_4]")?.textOrNull()
manga.copy( manga.copy(
altTitle = doc.selectFirst("div[q:key=tz_2]")?.textOrNull(), altTitle = doc.selectFirst("div[q:key=tz_2]")?.textOrNull(),
author = doc.selectFirst("div[q:key=tz_4]")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.selectFirst("react-island[q:key=0a_9]")?.html(), description = doc.selectFirst("react-island[q:key=0a_9]")?.html(),
state = when (doc.selectFirst("span[q:key=Yn_5]")?.text()?.lowercase()) { state = when (doc.selectFirst("span[q:key=Yn_5]")?.text()?.lowercase()) {
"ongoing" -> MangaState.ONGOING "ongoing" -> MangaState.ONGOING

@ -122,8 +122,8 @@ internal abstract class MangaPlusParser(
title = name, title = name,
coverUrl = it.getString("portraitImageUrl"), coverUrl = it.getString("portraitImageUrl"),
altTitle = null, altTitle = null,
author = author, authors = setOf(author),
isNsfw = false, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
source = source, source = source,
@ -143,13 +143,14 @@ internal abstract class MangaPlusParser(
} }
val hiatus = json.getStringOrNull("nonAppearanceInfo")?.contains("on a hiatus") == true val hiatus = json.getStringOrNull("nonAppearanceInfo")?.contains("on a hiatus") == true
val author = title.getString("author")
.split("/").joinToString(transform = String::trim)
return manga.copy( return manga.copy(
title = title.getString("name"), title = title.getString("name"),
publicUrl = "/titles/${title.getInt("titleId")}".toAbsoluteUrl(domain), publicUrl = "/titles/${title.getInt("titleId")}".toAbsoluteUrl(domain),
coverUrl = title.getString("portraitImageUrl"), coverUrl = title.getString("portraitImageUrl"),
author = title.getString("author") authors = setOf(author),
.split("/").joinToString(transform = String::trim),
description = buildString { description = buildString {
json.getString("overview").let(::append) json.getString("overview").let(::append)
json.getStringOrNull("viewingPeriodDescription") json.getStringOrNull("viewingPeriodDescription")

@ -132,8 +132,8 @@ internal class MangaReaderToParser(context: MangaLoaderContext) :
coverUrl = thumb.attr("src"), coverUrl = thumb.attr("src"),
source = source, source = source,
altTitle = null, altTitle = null,
author = null, authors = emptySet(),
isNsfw = false, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
tags = emptySet(), tags = emptySet(),
@ -155,7 +155,7 @@ internal class MangaReaderToParser(context: MangaLoaderContext) :
coverUrl = thumb.attrAsAbsoluteUrlOrNull("src"), coverUrl = thumb.attrAsAbsoluteUrlOrNull("src"),
source = source, source = source,
altTitle = null, altTitle = null,
author = null, authors = emptySet(),
contentRating = null, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
@ -169,6 +169,8 @@ internal class MangaReaderToParser(context: MangaLoaderContext) :
val availableTags = tags.get() val availableTags = tags.get()
var isAdult = false var isAdult = false
var isSuggestive = false var isSuggestive = false
val author = document.select("div.anisc-info a[href*=/author/]")
.joinToString { it.ownText().replace(", ", " ") }.nullIfEmpty()
return manga.copy( return manga.copy(
title = document.selectFirst("h2.manga-name")!!.ownText(), title = document.selectFirst("h2.manga-name")!!.ownText(),
@ -201,8 +203,7 @@ internal class MangaReaderToParser(context: MangaLoaderContext) :
else -> null else -> null
} }
}, },
author = document.select("div.anisc-info a[href*=/author/]") authors = author?.let { setOf(it) } ?: emptySet(),
.joinToString { it.ownText().replace(", ", " ") }.nullIfEmpty(),
description = document.select("div.description").html(), description = document.select("div.description").html(),
chapters = parseChapters(document), chapters = parseChapters(document),
source = source, source = source,

@ -116,8 +116,8 @@ internal abstract class NineMangaParser(
altTitle = null, altTitle = null,
coverUrl = node.selectFirst("img")?.src(), coverUrl = node.selectFirst("img")?.src(),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
author = null, authors = emptySet(),
isNsfw = false, contentRating = null,
tags = emptySet(), tags = emptySet(),
state = null, state = null,
source = source, source = source,
@ -135,11 +135,12 @@ internal abstract class NineMangaParser(
val tagMap = getOrCreateTagMap() val tagMap = getOrCreateTagMap()
val selectTag = infoRoot.getElementsByAttributeValue("itemprop", "genre").first()?.select("a") val selectTag = infoRoot.getElementsByAttributeValue("itemprop", "genre").first()?.select("a")
val tags = selectTag?.mapNotNullToSet { tagMap[it.text()] } val tags = selectTag?.mapNotNullToSet { tagMap[it.text()] }
val author = infoRoot.getElementsByAttributeValue("itemprop", "author").first()?.textOrNull()
return manga.copy( return manga.copy(
title = root.selectFirst("h1[itemprop=name]")?.textOrNull()?.removeSuffix("Manga")?.trimEnd() title = root.selectFirst("h1[itemprop=name]")?.textOrNull()?.removeSuffix("Manga")?.trimEnd()
?: manga.title, ?: manga.title,
tags = tags.orEmpty(), tags = tags.orEmpty(),
author = infoRoot.getElementsByAttributeValue("itemprop", "author").first()?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
state = parseStatus(infoRoot.select("li a.red").text()), state = parseStatus(infoRoot.select("li a.red").text()),
description = infoRoot.getElementsByAttributeValue("itemprop", "description").first()?.html() description = infoRoot.getElementsByAttributeValue("itemprop", "description").first()?.html()
?.substringAfter("</b>"), ?.substringAfter("</b>"),

@ -206,7 +206,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
cover == null -> null cover == null -> null
else -> "https://${cdnHost.get()}/$cover" else -> "https://${cdnHost.get()}/$cover"
}, },
author = null, authors = emptySet(),
contentRating = ContentRating.ADULT, contentRating = ContentRating.ADULT,
url = id, url = id,
publicUrl = "/hchapter/$id".toAbsoluteUrl(domain), publicUrl = "/hchapter/$id".toAbsoluteUrl(domain),
@ -264,12 +264,13 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
type = it.getStringOrNull("tagType"), type = it.getStringOrNull("tagType"),
) )
} }
val author = tags?.filter { it.type == "artist" }?.joinToString { it.name.toCamelCase() }?.nullIfEmpty()
return manga.copy( return manga.copy(
title = name.replace(shortenTitleRegex, "").trim(), title = name.replace(shortenTitleRegex, "").trim(),
altTitle = name, altTitle = name,
coverUrl = cover.first, coverUrl = cover.first,
largeCoverUrl = cover.second, largeCoverUrl = cover.second,
author = tags?.filter { it.type == "artist" }?.joinToString { it.name.toCamelCase() }?.nullIfEmpty(), authors = author?.let { setOf(it) } ?: emptySet(),
contentRating = ContentRating.ADULT, contentRating = ContentRating.ADULT,
tags = tags?.mapToSet { tags = tags?.mapToSet {
MangaTag( MangaTag(

@ -123,6 +123,8 @@ internal abstract class WebtoonsParser(
val chapters = chaptersDeferred.await() val chapters = chaptersDeferred.await()
makeRequest("/lineWebtoon/webtoon/titleInfo.json?titleNo=${titleNo}&anyServiceStatus=false").getJSONObject("titleInfo") makeRequest("/lineWebtoon/webtoon/titleInfo.json?titleNo=${titleNo}&anyServiceStatus=false").getJSONObject("titleInfo")
.let { jo -> .let { jo ->
val isNsfwSource = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource)
val author = jo.getStringOrNull("writingAuthorName")
MangaWebtoon( MangaWebtoon(
Manga( Manga(
id = generateUid(titleNo), id = generateUid(titleNo),
@ -131,11 +133,11 @@ internal abstract class WebtoonsParser(
url = "$titleNo", url = "$titleNo",
publicUrl = "https://$domain/$languageCode/originals/a/list?title_no=${titleNo}", publicUrl = "https://$domain/$languageCode/originals/a/list?title_no=${titleNo}",
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain), largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
tags = setOf(parseTag(jo.getJSONObject("genreInfo"))), tags = setOf(parseTag(jo.getJSONObject("genreInfo"))),
author = jo.getStringOrNull("writingAuthorName"), authors = author?.let { setOf(it) } ?: emptySet(),
description = jo.getString("synopsis"), description = jo.getString("synopsis"),
// I don't think the API provides this info, // I don't think the API provides this info,
state = null, state = null,
@ -158,6 +160,8 @@ internal abstract class WebtoonsParser(
makeRequest("/lineWebtoon/webtoon/titleList.json?").getJSONObject("titleList").getJSONArray("titles") makeRequest("/lineWebtoon/webtoon/titleList.json?").getJSONObject("titleList").getJSONArray("titles")
.mapJSON { jo -> .mapJSON { jo ->
val titleNo = jo.getLong("titleNo") val titleNo = jo.getLong("titleNo")
val isNsfwSource = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource)
val author = jo.getStringOrNull("writingAuthorName")
MangaWebtoon( MangaWebtoon(
Manga( Manga(
id = generateUid(titleNo), id = generateUid(titleNo),
@ -166,8 +170,8 @@ internal abstract class WebtoonsParser(
title = jo.getString("title"), title = jo.getString("title"),
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
altTitle = null, altTitle = null,
author = jo.getStringOrNull("writingAuthorName"), authors = author?.let { setOf(it) } ?: emptySet(),
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource), contentRating = if (isNsfwSource) ContentRating.ADULT else null,
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f, rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
tags = setOfNotNull(allGenreCache.get()[jo.getString("representGenre")]), tags = setOfNotNull(allGenreCache.get()[jo.getString("representGenre")]),
description = jo.getString("synopsis"), description = jo.getString("synopsis"),
@ -196,6 +200,7 @@ internal abstract class WebtoonsParser(
makeRequest("/lineWebtoon/webtoon/searchWebtoon?query=${filter.query.urlEncoded()}").getJSONObject("webtoonSearch") makeRequest("/lineWebtoon/webtoon/searchWebtoon?query=${filter.query.urlEncoded()}").getJSONObject("webtoonSearch")
.getJSONArray("titleList").mapJSON { jo -> .getJSONArray("titleList").mapJSON { jo ->
val titleNo = jo.getLong("titleNo") val titleNo = jo.getLong("titleNo")
val author = jo.getStringOrNull("writingAuthorName")
MangaWebtoon( MangaWebtoon(
Manga( Manga(
id = generateUid(titleNo), id = generateUid(titleNo),
@ -204,11 +209,11 @@ internal abstract class WebtoonsParser(
url = titleNo.toString(), url = titleNo.toString(),
publicUrl = "https://$domain/$languageCode/originals/a/list?title_no=$titleNo", publicUrl = "https://$domain/$languageCode/originals/a/list?title_no=$titleNo",
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain), coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
largeCoverUrl = null, largeCoverUrl = null,
tags = emptySet(), tags = emptySet(),
author = jo.getStringOrNull("writingAuthorName"), authors = author?.let { setOf(it) } ?: emptySet(),
description = null, description = null,
state = null, state = null,
source = source, source = source,

@ -110,10 +110,10 @@ internal abstract class AnimeBootstrapParser(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -72,10 +72,10 @@ internal class PapScan(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -112,7 +112,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = cover, coverUrl = cover,
tags = emptySet(), tags = emptySet(),
state = when (j.getString("status")) { state = when (j.getString("status")) {
@ -122,7 +122,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
"droped" -> MangaState.ABANDONED "droped" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -74,11 +74,11 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").src(), coverUrl = div.selectFirstOrThrow("img").src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -108,7 +108,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").src()?.replace("thumbnail_", ""), coverUrl = div.selectFirstOrThrow("img").src()?.replace("thumbnail_", ""),
tags = emptySet(), tags = emptySet(),
state = when (div.selectFirst(".status")?.text()) { state = when (div.selectFirst(".status")?.text()) {
@ -117,7 +117,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
"متوقف" -> MangaState.ABANDONED "متوقف" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -90,8 +90,8 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context,
coverUrl = jo.getString("poster").removePrefix("/cdn") coverUrl = jo.getString("poster").removePrefix("/cdn")
.toAbsoluteUrl(getDomain("cdn")) + "?width=200&height=280", .toAbsoluteUrl(getDomain("cdn")) + "?width=200&height=280",
altTitle = title.optJSONArray("alt")?.optString(0)?.nullIfEmpty(), altTitle = title.optJSONArray("alt")?.optString(0)?.nullIfEmpty(),
author = null, authors = emptySet(),
isNsfw = false, contentRating = null,
rating = jo.getDouble("rating").toFloat() / 10f, rating = jo.getDouble("rating").toFloat() / 10f,
url = href, url = href,
publicUrl = "https://${domain}/$href", publicUrl = "https://${domain}/$href",
@ -237,8 +237,8 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context,
coverUrl = jo.getString("poster").removePrefix("/cdn") coverUrl = jo.getString("poster").removePrefix("/cdn")
.toAbsoluteUrl(getDomain("cdn")) + "?width=200&height=280", .toAbsoluteUrl(getDomain("cdn")) + "?width=200&height=280",
altTitle = title.getString("en").nullIfEmpty(), altTitle = title.getString("en").nullIfEmpty(),
author = null, authors = emptySet(),
isNsfw = false, contentRating = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
url = href, url = href,
publicUrl = "https://${domain}/$href", publicUrl = "https://${domain}/$href",

@ -99,11 +99,11 @@ internal abstract class CupFoxParser(
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = li.selectFirst(selectMangasCover)?.src(), coverUrl = li.selectFirst(selectMangasCover)?.src(),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -122,6 +122,7 @@ internal abstract class CupFoxParser(
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val author = doc.selectFirst(selectMangaDetailsAuthor)?.text()?.substringAfter("")?.nullIfEmpty()
return manga.copy( return manga.copy(
altTitle = doc.selectFirst(selectMangaDetailsAltTitle)?.text()?.substringAfter("")?.nullIfEmpty(), altTitle = doc.selectFirst(selectMangaDetailsAltTitle)?.text()?.substringAfter("")?.nullIfEmpty(),
tags = doc.select(selectMangaDetailsTags).mapToSet { a -> tags = doc.select(selectMangaDetailsTags).mapToSet { a ->
@ -131,7 +132,7 @@ internal abstract class CupFoxParser(
source = source, source = source,
) )
}, },
author = doc.selectFirst(selectMangaDetailsAuthor)?.text()?.substringAfter("")?.nullIfEmpty(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.selectFirst(selectMangaDescription)?.html(), description = doc.selectFirst(selectMangaDescription)?.html(),
chapters = doc.select(selectMangaChapters) chapters = doc.select(selectMangaChapters)
.mapChapters { i, li -> .mapChapters { i, li ->
@ -164,11 +165,11 @@ internal abstract class CupFoxParser(
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = li.selectFirst(selectMangasCover)?.src(), coverUrl = li.selectFirst(selectMangasCover)?.src(),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -116,7 +116,7 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = a.selectFirst("div.block label.ml-1")?.text()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, rating = a.selectFirst("div.block label.ml-1")?.text()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = when (a.selectLast("span.status")?.text()) { state = when (a.selectLast("span.status")?.text()) {
"Ongoing" -> MangaState.ONGOING "Ongoing" -> MangaState.ONGOING
"Completed" -> MangaState.FINISHED "Completed" -> MangaState.FINISHED
@ -126,7 +126,7 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -159,10 +159,11 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
val tagMap = getOrCreateTagMap() val tagMap = getOrCreateTagMap()
val selectTag = doc.select("div[class^=space] > div.flex > button.text-white") val selectTag = doc.select("div[class^=space] > div.flex > button.text-white")
val tags = selectTag.mapNotNullToSet { tagMap[it.text()] } val tags = selectTag.mapNotNullToSet { tagMap[it.text()] }
val author = doc.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.text().orEmpty()
return manga.copy( return manga.copy(
description = doc.selectFirst("span.font-medium.text-sm")?.text().orEmpty(), description = doc.selectFirst("span.font-medium.text-sm")?.text().orEmpty(),
tags = tags, tags = tags,
author = doc.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.text().orEmpty(), authors = setOf(author),
chapters = doc.select("div.scrollbar-thumb-themecolor > div.group").mapChapters(reversed = true) { i, div -> chapters = doc.select("div.scrollbar-thumb-themecolor > div.group").mapChapters(reversed = true) { i, div ->
val a = div.selectLastOrThrow("a") val a = div.selectLastOrThrow("a")
val urlRelative = "/series/" + a.attrAsRelativeUrl("href") val urlRelative = "/series/" + a.attrAsRelativeUrl("href")

@ -78,14 +78,14 @@ internal class BeeToon(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.selectFirst(".counter")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = div.selectFirst(".counter")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = when (div.selectLastOrThrow(".status span").text()) { state = when (div.selectLastOrThrow(".status span").text()) {
"Ongoing" -> MangaState.ONGOING "Ongoing" -> MangaState.ONGOING
"Completed" -> MangaState.FINISHED "Completed" -> MangaState.FINISHED
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -103,6 +103,7 @@ internal class BeeToon(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val author = doc.selectFirst(".info .author a")?.text()
return manga.copy( return manga.copy(
description = doc.getElementById("desc")?.text().orEmpty(), description = doc.getElementById("desc")?.text().orEmpty(),
rating = doc.selectFirst(".counter")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = doc.selectFirst(".counter")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
@ -113,7 +114,7 @@ internal class BeeToon(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
author = doc.selectFirst(".info .author a")?.text(), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = doc.select(".items-chapters a").mapChapters(reversed = true) { i, a -> chapters = doc.select(".items-chapters a").mapChapters(reversed = true) { i, a ->
val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain)
MangaChapter( MangaChapter(

@ -39,10 +39,10 @@ internal class CloneMangaParser(context: MangaLoaderContext) :
title = item.selectFirst("h3")?.text() ?: return@mapNotNull null, title = item.selectFirst("h3")?.text() ?: return@mapNotNull null,
coverUrl = "https://$domain/$cover", coverUrl = "https://$domain/$cover",
altTitle = null, altTitle = null,
author = "Dan Kim", authors = setOf("Dan Kim"),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
url = href, url = href,
isNsfw = false, contentRating = null,
tags = emptySet(), tags = emptySet(),
state = null, state = null,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),

@ -74,7 +74,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
tags = div.select("div.egb-details a").mapToSet { a -> tags = div.select("div.egb-details a").mapToSet { a ->
MangaTag( MangaTag(
@ -84,7 +84,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
) )
}, },
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -103,6 +103,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val author = doc.selectFirst("table.full-table tr:contains(Author:) td:nth-child(2)")?.textOrNull()
return manga.copy( return manga.copy(
altTitle = doc.selectFirstOrThrow("div.anime-top h1.title").textOrNull(), altTitle = doc.selectFirstOrThrow("div.anime-top h1.title").textOrNull(),
state = when (doc.selectFirstOrThrow("ul.anime-genres li.status a").text()) { state = when (doc.selectFirstOrThrow("ul.anime-genres li.status a").text()) {
@ -117,7 +118,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
source = source, source = source,
) )
}, },
author = doc.selectFirst("table.full-table tr:contains(Author:) td:nth-child(2)")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.selectFirstOrThrow("div.detail-desc-content p").html(), description = doc.selectFirstOrThrow("div.detail-desc-content p").html(),
chapters = doc.select("ul.basic-list li").let { elements -> chapters = doc.select("ul.basic-list li").let { elements ->
elements.mapChapters { i, li -> elements.mapChapters { i, li ->

@ -90,11 +90,11 @@ internal class DynastyScans(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -111,7 +111,7 @@ internal class DynastyScans(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = null, coverUrl = null,
tags = div.select("span.tags a").mapToSet { a -> tags = div.select("span.tags a").mapToSet { a ->
MangaTag( MangaTag(
@ -121,7 +121,7 @@ internal class DynastyScans(context: MangaLoaderContext) :
) )
}, },
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -128,6 +128,7 @@ internal class FlameComics(context: MangaLoaderContext) :
private fun parseManga(jo: JSONObject): Manga { private fun parseManga(jo: JSONObject): Manga {
val seriesId = jo.getLong("series_id") val seriesId = jo.getLong("series_id")
val cover = jo.getStringOrNull("cover") val cover = jo.getStringOrNull("cover")
val author = jo.getStringOrNull("author")
return Manga( return Manga(
id = generateUid(seriesId), id = generateUid(seriesId),
title = jo.getString("title"), title = jo.getString("title"),
@ -137,7 +138,7 @@ internal class FlameComics(context: MangaLoaderContext) :
url = seriesId.toString(), url = seriesId.toString(),
publicUrl = "https://${domain}/series/$seriesId", publicUrl = "https://${domain}/series/$seriesId",
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = if (cover != null) { coverUrl = if (cover != null) {
imageUrl(seriesId, cover, 256) imageUrl(seriesId, cover, 256)
} else { } else {
@ -153,7 +154,7 @@ internal class FlameComics(context: MangaLoaderContext) :
"Ongoing" -> MangaState.ONGOING "Ongoing" -> MangaState.ONGOING
else -> null else -> null
}, },
author = jo.getStringOrNull("author"), authors = author?.let { setOf(it) } ?: emptySet(),
largeCoverUrl = if (cover != null) { largeCoverUrl = if (cover != null) {
imageUrl(seriesId, cover, 640) imageUrl(seriesId, cover, 640)
} else { } else {

@ -91,7 +91,7 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = cover, coverUrl = cover,
tags = emptySet(), tags = emptySet(),
state = when (j.getString("status")) { state = when (j.getString("status")) {
@ -101,7 +101,7 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
"droped" -> MangaState.ABANDONED "droped" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -77,6 +77,7 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
return doc.select("li.novel-item").map { div -> return doc.select("li.novel-item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val author = div.selectFirstOrThrow("h6").text().removePrefix("Author(S): ").nullIfEmpty()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = div.selectFirstOrThrow("h4").text(), title = div.selectFirstOrThrow("h4").text(),
@ -84,11 +85,11 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").src(), coverUrl = div.selectFirstOrThrow("img").src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = div.selectFirstOrThrow("h6").text().removePrefix("Author(S): ").nullIfEmpty(), authors = author?.let { setOf(it) } ?: emptySet(),
source = source, source = source,
) )
} }
@ -108,6 +109,7 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val chaptersDeferred = async { loadChapters(manga.url) } val chaptersDeferred = async { loadChapters(manga.url) }
val author = doc.selectFirstOrThrow(".author").textOrNull()
manga.copy( manga.copy(
altTitle = doc.selectFirstOrThrow(".alternative-title").textOrNull(), altTitle = doc.selectFirstOrThrow(".alternative-title").textOrNull(),
state = when (doc.selectFirstOrThrow(".header-stats span:contains(Status) strong").text()) { state = when (doc.selectFirstOrThrow(".header-stats span:contains(Status) strong").text()) {
@ -122,7 +124,7 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
source = source, source = source,
) )
}, },
author = doc.selectFirstOrThrow(".author").textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.selectFirstOrThrow(".description").html(), description = doc.selectFirstOrThrow(".description").html(),
chapters = chaptersDeferred.await(), chapters = chaptersDeferred.await(),
) )

@ -86,10 +86,10 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -99,10 +99,11 @@ internal class MangaKawaiiEn(context: MangaLoaderContext) :
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val firstChapter = doc.selectFirst("tr[class*='volume-'] a")?.attr("href") val firstChapter = doc.selectFirst("tr[class*='volume-'] a")?.attr("href")
val chaptersDeferred = async { loadChapters(firstChapter) } val chaptersDeferred = async { loadChapters(firstChapter) }
val author = doc.select("a[href*=author]").textOrNull()
manga.copy( manga.copy(
description = doc.selectFirst("dd.text-justify.text-break")?.html(), description = doc.selectFirst("dd.text-justify.text-break")?.html(),
altTitle = doc.select("span[itemprop*=alternativeHeadline]").joinToString { ", " }.nullIfEmpty(), altTitle = doc.select("span[itemprop*=alternativeHeadline]").joinToString { ", " }.nullIfEmpty(),
author = doc.select("a[href*=author]").textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (doc.selectFirst("span.badge.bg-success.text-uppercase")?.text()) { state = when (doc.selectFirst("span.badge.bg-success.text-uppercase")?.text()) {
"Ongoing" -> MangaState.ONGOING "Ongoing" -> MangaState.ONGOING
"" -> MangaState.FINISHED "" -> MangaState.FINISHED

@ -110,6 +110,9 @@ internal class MangaTownParser(context: MangaLoaderContext) :
val views = li.select("p.view") val views = li.select("p.view")
val status = views.firstNotNullOfOrNull { it.ownText().takeIf { x -> x.startsWith("Status:") } } val status = views.firstNotNullOfOrNull { it.ownText().takeIf { x -> x.startsWith("Status:") } }
?.substringAfter(':')?.trim()?.lowercase(Locale.ROOT) ?.substringAfter(':')?.trim()?.lowercase(Locale.ROOT)
val author = views.firstNotNullOfOrNull { it.text().takeIf { x -> x.startsWith("Author:") } }
?.substringAfter(':')
?.trim()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = a.attr("title"), title = a.attr("title"),
@ -118,9 +121,7 @@ internal class MangaTownParser(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = li.selectFirst("p.score")?.selectFirst("b") rating = li.selectFirst("p.score")?.selectFirst("b")
?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, ?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
author = views.firstNotNullOfOrNull { it.text().takeIf { x -> x.startsWith("Author:") } } authors = author?.let { setOf(it) } ?: emptySet(),
?.substringAfter(':')
?.trim(),
state = when (status) { state = when (status) {
"ongoing" -> MangaState.ONGOING "ongoing" -> MangaState.ONGOING
"completed" -> MangaState.FINISHED "completed" -> MangaState.FINISHED
@ -134,7 +135,7 @@ internal class MangaTownParser(context: MangaLoaderContext) :
) )
}.orEmpty(), }.orEmpty(),
url = href, url = href,
isNsfw = false, contentRating = null,
publicUrl = href.toAbsoluteUrl(a.host ?: domain), publicUrl = href.toAbsoluteUrl(a.host ?: domain),
) )
} }

@ -109,10 +109,10 @@ internal class Mangaowl(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.select("span").last()?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = div.select("span").last()?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = false, contentRating = null,
) )
} }
} }

@ -129,11 +129,11 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
url = absUrl.toRelativeUrl(domain), url = absUrl.toRelativeUrl(domain),
publicUrl = absUrl, publicUrl = absUrl,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = true, contentRating = ContentRating.ADULT,
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg"), coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg"),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
largeCoverUrl = null, largeCoverUrl = null,
description = null, description = null,
source = MangaParserSource.MANHWA18, source = MangaParserSource.MANHWA18,
@ -166,7 +166,7 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
return manga.copy( return manga.copy(
altTitle = cardInfoElement?.selectFirst("b:contains(Other names)")?.parent()?.ownTextOrNull() altTitle = cardInfoElement?.selectFirst("b:contains(Other names)")?.parent()?.ownTextOrNull()
?.removePrefix(": "), ?.removePrefix(": "),
author = author, authors = author?.let { setOf(it) } ?: emptySet(),
description = docs.selectFirst(".series-summary .summary-content")?.html(), description = docs.selectFirst(".series-summary .summary-content")?.html(),
tags = tags.orEmpty(), tags = tags.orEmpty(),
state = state, state = state,

@ -129,11 +129,11 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
url = absUrl.toRelativeUrl(domain), url = absUrl.toRelativeUrl(domain),
publicUrl = absUrl, publicUrl = absUrl,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = true, contentRating = ContentRating.ADULT,
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg"), coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg"),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
largeCoverUrl = null, largeCoverUrl = null,
description = null, description = null,
source = MangaParserSource.MANHWA18, source = MangaParserSource.MANHWA18,
@ -166,7 +166,7 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
return manga.copy( return manga.copy(
altTitle = cardInfoElement?.selectFirst("b:contains(Other names)")?.parent()?.ownTextOrNull() altTitle = cardInfoElement?.selectFirst("b:contains(Other names)")?.parent()?.ownTextOrNull()
?.removePrefix(": "), ?.removePrefix(": "),
author = author, authors = author?.let { setOf(it) } ?: emptySet(),
description = docs.selectFirst(".series-summary .summary-content")?.html(), description = docs.selectFirst(".series-summary .summary-content")?.html(),
tags = tags.orEmpty(), tags = tags.orEmpty(),
state = state, state = state,

@ -88,10 +88,10 @@ internal class ManhwasMen(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -74,10 +74,10 @@ internal class MyComicList(context: MangaLoaderContext) : PagedMangaParser(conte
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
title = div.selectFirst("h3 a")?.text().orEmpty(), title = div.selectFirst("h3 a")?.text().orEmpty(),
altTitle = null, altTitle = null,
author = null, authors = emptySet(),
tags = emptySet(), tags = emptySet(),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = img?.attrAsAbsoluteUrlOrNull("data-src"), coverUrl = img?.attrAsAbsoluteUrlOrNull("data-src"),
state = null, state = null,
source = source, source = source,
@ -87,6 +87,7 @@ internal class MyComicList(context: MangaLoaderContext) : PagedMangaParser(conte
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val author = doc.selectFirst("td:contains(Author:) + td")?.textOrNull()
return manga.copy( return manga.copy(
tags = doc.select("td:contains(Genres:) + td a").mapToSet { a -> tags = doc.select("td:contains(Genres:) + td a").mapToSet { a ->
MangaTag( MangaTag(
@ -95,7 +96,7 @@ internal class MyComicList(context: MangaLoaderContext) : PagedMangaParser(conte
source = source, source = source,
) )
}, },
author = doc.selectFirst("td:contains(Author:) + td")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (doc.selectFirst("td:contains(Status:) + td a")?.text()?.lowercase()) { state = when (doc.selectFirst("td:contains(Status:) + td a")?.text()?.lowercase()) {
"ongoing" -> MangaState.ONGOING "ongoing" -> MangaState.ONGOING
"completed" -> MangaState.FINISHED "completed" -> MangaState.FINISHED

@ -49,11 +49,11 @@ internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(con
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").src(), coverUrl = div.selectFirstOrThrow("img").src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -62,6 +62,7 @@ internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(con
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("dd MMM, yy", Locale.ENGLISH) val dateFormat = SimpleDateFormat("dd MMM, yy", Locale.ENGLISH)
val author = doc.selectLast(".author span")?.textOrNull()
return manga.copy( return manga.copy(
state = when (doc.select(".status span").last()?.text()) { state = when (doc.select(".status span").last()?.text()) {
"Ongoing" -> MangaState.ONGOING "Ongoing" -> MangaState.ONGOING
@ -69,7 +70,7 @@ internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(con
else -> null else -> null
}, },
tags = emptySet(), tags = emptySet(),
author = doc.selectLast(".author span")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.selectFirstOrThrow(".summary").html(), description = doc.selectFirstOrThrow(".summary").html(),
chapters = doc.select(".chap-section .chap") chapters = doc.select(".chap-section .chap")
.mapChapters(reversed = true) { i, div -> .mapChapters(reversed = true) { i, div ->

@ -99,10 +99,10 @@ internal class Pururin(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -133,6 +133,7 @@ internal class Pururin(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val author = doc.selectFirst("a[itemprop=author]")?.text()
manga.copy( manga.copy(
description = doc.selectFirst("p.mb-2")?.text().orEmpty(), description = doc.selectFirst("p.mb-2")?.text().orEmpty(),
rating = doc.selectFirst("td span.rating")?.attr("content")?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = doc.selectFirst("td span.rating")?.attr("content")?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
@ -144,7 +145,7 @@ internal class Pururin(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
author = doc.selectFirst("a[itemprop=author]")?.text(), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = manga.id, id = manga.id,
@ -175,10 +176,10 @@ internal class Pururin(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = false, contentRating = null,
) )
} }
} }

@ -110,10 +110,10 @@ internal class VyManga(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -179,6 +179,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
.attrAsAbsoluteUrl("href") .attrAsAbsoluteUrl("href")
.toHttpUrl() .toHttpUrl()
.pathSegments[1] .pathSegments[1]
val author = document.select("div:contains(author) a").eachText().joinToString().nullIfEmpty()
Manga( Manga(
id = generateUid(mangaId), id = generateUid(mangaId),
url = mangaId, url = mangaId,
@ -210,7 +211,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
"Hiatus" -> PAUSED "Hiatus" -> PAUSED
else -> null else -> null
}, },
author = document.select("div:contains(author) a").eachText().joinToString().nullIfEmpty(), authors = author?.let { setOf(it) } ?: emptySet(),
largeCoverUrl = null, largeCoverUrl = null,
chapters = null, chapters = null,
source = source, source = source,
@ -226,6 +227,8 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
val sectionLeft = document.select("section[x-data] > section")[0] val sectionLeft = document.select("section[x-data] > section")[0]
val sectionRight = document.select("section[x-data] > section")[1] val sectionRight = document.select("section[x-data] > section")[1]
val author = sectionLeft.select("ul > li:has(strong:contains(Author)) > span > a")
.eachText().joinToString()
manga.copy( manga.copy(
title = sectionRight.selectFirstOrThrow("h1").text(), title = sectionRight.selectFirstOrThrow("h1").text(),
@ -253,8 +256,7 @@ internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, M
"Hiatus" -> PAUSED "Hiatus" -> PAUSED
else -> null else -> null
}, },
author = sectionLeft.select("ul > li:has(strong:contains(Author)) > span > a") authors = setOf(author),
.eachText().joinToString(),
description = Element("div").also { desc -> description = Element("div").also { desc ->
sectionRight.selectFirst("li:has(strong:contains(Description)) > p")?.let { sectionRight.selectFirst("li:has(strong:contains(Description)) > p")?.let {
desc.appendChild(it) desc.appendChild(it)

@ -47,10 +47,10 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
altTitle = it.getStringOrNull("alternativeName"), altTitle = it.getStringOrNull("alternativeName"),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -151,6 +151,7 @@ internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaPars
val doc = webClient.httpGet(url, getRequestHeaders()).parseHtml() val doc = webClient.httpGet(url, getRequestHeaders()).parseHtml()
val items = doc.body().select("div.element") val items = doc.body().select("div.element")
return items.mapNotNull { item -> return items.mapNotNull { item ->
val isNsfwSource = item.select("i").hasClass("fas fa-heartbeat fa-2x")
val href = val href =
item.selectFirst("a")?.attrAsRelativeUrlOrNull("href")?.substringAfter(' ') ?: return@mapNotNull null item.selectFirst("a")?.attrAsRelativeUrlOrNull("href")?.substringAfter(' ') ?: return@mapNotNull null
Manga( Manga(
@ -158,10 +159,10 @@ internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaPars
title = item.selectFirst("h4.text-truncate")?.text() ?: return@mapNotNull null, title = item.selectFirst("h4.text-truncate")?.text() ?: return@mapNotNull null,
coverUrl = item.select("style").toString().substringAfter("('").substringBeforeLast("')"), coverUrl = item.select("style").toString().substringAfter("('").substringBeforeLast("')"),
altTitle = null, altTitle = null,
author = null, authors = emptySet(),
rating = item.selectFirst("span.score")?.text()?.toFloatOrNull()?.div(10F) ?: RATING_UNKNOWN, rating = item.selectFirst("span.score")?.text()?.toFloatOrNull()?.div(10F) ?: RATING_UNKNOWN,
url = href, url = href,
isNsfw = item.select("i").hasClass("fas fa-heartbeat fa-2x"), contentRating = if (isNsfwSource) ContentRating.ADULT else null,
tags = emptySet(), tags = emptySet(),
state = null, state = null,
publicUrl = href.toAbsoluteUrl(doc.host ?: domain), publicUrl = href.toAbsoluteUrl(doc.host ?: domain),
@ -174,6 +175,7 @@ internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaPars
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val contents = doc.body().selectFirstOrThrow("section.element-header-content") val contents = doc.body().selectFirstOrThrow("section.element-header-content")
val author = contents.selectFirst("h5.card-title")?.attr("title")?.substringAfter(", ")
return manga.copy( return manga.copy(
description = contents.selectFirst("p.element-description")?.html(), description = contents.selectFirst("p.element-description")?.html(),
tags = contents.select("h6 a").mapToSet { a -> tags = contents.select("h6 a").mapToSet { a ->
@ -185,7 +187,7 @@ internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaPars
}, },
largeCoverUrl = contents.selectFirst(".book-thumbnail")?.attrAsAbsoluteUrlOrNull("src"), largeCoverUrl = contents.selectFirst(".book-thumbnail")?.attrAsAbsoluteUrlOrNull("src"),
state = parseStatus(contents.select("span.book-status").text().orEmpty()), state = parseStatus(contents.select("span.book-status").text().orEmpty()),
author = contents.selectFirst("h5.card-title")?.attr("title")?.substringAfter(", "), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = if (doc.select("div.chapters").isEmpty()) { chapters = if (doc.select("div.chapters").isEmpty()) {
doc.select(oneShotChapterListSelector).mapChapters(reversed = true) { _, item -> doc.select(oneShotChapterListSelector).mapChapters(reversed = true) { _, item ->
oneShotChapterFromElement(item) oneShotChapterFromElement(item)

@ -147,10 +147,10 @@ internal abstract class FmreaderParser(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -190,7 +190,7 @@ internal abstract class FmreaderParser(
} }
} }
val alt = doc.body().selectFirst(selectAlt)?.text()?.replace("Other names", "")?.nullIfEmpty() val alt = doc.body().selectFirst(selectAlt)?.text()?.replace("Other names", "")?.nullIfEmpty()
val auth = doc.body().selectFirst(selectAut)?.textOrNull() val author = doc.body().selectFirst(selectAut)?.textOrNull()
manga.copy( manga.copy(
tags = doc.body().select(selectTag).mapToSet { a -> tags = doc.body().select(selectTag).mapToSet { a ->
MangaTag( MangaTag(
@ -201,7 +201,7 @@ internal abstract class FmreaderParser(
}, },
description = desc, description = desc,
altTitle = alt, altTitle = alt,
author = auth, authors = author?.let { setOf(it) } ?: emptySet(),
state = state, state = state,
chapters = chaptersDeferred.await(), chapters = chaptersDeferred.await(),
) )

@ -35,10 +35,10 @@ internal class Klz9(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -93,10 +93,10 @@ internal abstract class FoolSlideParser(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
@ -126,7 +126,7 @@ internal abstract class FoolSlideParser(
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl,
description = desc?.nullIfEmpty(), description = desc?.nullIfEmpty(),
author = author?.nullIfEmpty(), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = chapters, chapters = chapters,
) )
} }

@ -36,7 +36,7 @@ internal class Seinagi(context: MangaLoaderContext) :
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl,
description = desc?.nullIfEmpty(), description = desc?.nullIfEmpty(),
author = author?.nullIfEmpty(), authors = author?.nullIfEmpty()?.let { setOf(it) } ?: emptySet(),
chapters = chapters, chapters = chapters,
) )
} }

@ -35,7 +35,7 @@ internal class Pzykosis666hFansub(context: MangaLoaderContext) :
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl,
description = desc?.nullIfEmpty(), description = desc?.nullIfEmpty(),
author = author?.nullIfEmpty(), authors = author?.nullIfEmpty()?.let { setOf(it) } ?: emptySet(),
chapters = chapters, chapters = chapters,
) )
} }

@ -35,7 +35,7 @@ internal class SeinagiAdulto(context: MangaLoaderContext) :
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src(),// for manga result on search coverUrl = doc.selectFirst(".thumbnail img")?.src(),// for manga result on search
description = desc?.nullIfEmpty(), description = desc?.nullIfEmpty(),
author = author?.nullIfEmpty(), authors = author?.nullIfEmpty()?.let { setOf(it) } ?: emptySet(),
chapters = chapters, chapters = chapters,
) )
} }

@ -111,6 +111,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
return root.select(".manga[data-manga]").map { div -> return root.select(".manga[data-manga]").map { div ->
val header = div.selectFirstOrThrow(".manga_header") val header = div.selectFirstOrThrow(".manga_header")
val href = header.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = header.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val isNsfwSource = div.selectFirst(".badge-adult_content") != null
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = div.selectFirst("h1")?.text().orEmpty(), title = div.selectFirst("h1")?.text().orEmpty(),
@ -123,7 +124,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
?.toFloatOrNull() ?.toFloatOrNull()
?.div(10f) ?.div(10f)
?: RATING_UNKNOWN, ?: RATING_UNKNOWN,
isNsfw = div.selectFirst(".badge-adult_content") != null, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = div.selectFirst("img")?.src().assertNotNull("src"), coverUrl = div.selectFirst("img")?.src().assertNotNull("src"),
tags = div.selectFirst(".component-manga-categories") tags = div.selectFirst(".component-manga-categories")
.assertNotNull("tags") .assertNotNull("tags")
@ -136,7 +137,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
) )
}.orEmpty(), }.orEmpty(),
state = null, state = null,
author = null, authors = emptySet(),
description = div.selectFirst(".manga_synopsis")?.html().assertNotNull("description"), description = div.selectFirst(".manga_synopsis")?.html().assertNotNull("description"),
source = source, source = source,
) )
@ -147,6 +148,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
val mangaUrl = manga.url.toAbsoluteUrl(domain) val mangaUrl = manga.url.toAbsoluteUrl(domain)
val root = webClient.httpGet(mangaUrl).parseHtml() val root = webClient.httpGet(mangaUrl).parseHtml()
.requireElementById("container_manga_show") .requireElementById("container_manga_show")
val author = root.selectFirst(".datas_more-authors-people")?.textOrNull()
return manga.copy( return manga.copy(
altTitle = root.selectFirst(".component-manga-title_alt")?.textOrNull(), altTitle = root.selectFirst(".component-manga-title_alt")?.textOrNull(),
description = root.selectFirst(".datas_synopsis")?.html().assertNotNull("description") description = root.selectFirst(".datas_synopsis")?.html().assertNotNull("description")
@ -158,7 +160,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
"En pause" -> MangaState.PAUSED "En pause" -> MangaState.PAUSED
else -> null else -> null
}, },
author = root.selectFirst(".datas_more-authors-people")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = run { chapters = run {
val input = root.selectFirst("input[name=\"limit\"]") ?: return@run parseChapters(root) val input = root.selectFirst("input[name=\"limit\"]") ?: return@run parseChapters(root)
val max = input.attr("max").toInt() val max = input.attr("max").toInt()

@ -74,10 +74,10 @@ internal class FuryoSociety(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = false, contentRating = null,
) )
} }
} }

@ -136,11 +136,11 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
url = urlManga, url = urlManga,
publicUrl = urlManga, publicUrl = urlManga,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = "https://api.$domain/" + j.getString("cover"), coverUrl = "https://api.$domain/" + j.getString("cover"),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -157,11 +157,11 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
url = urlManga, url = urlManga,
publicUrl = urlManga, publicUrl = urlManga,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = null, coverUrl = null,
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -170,6 +170,7 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH) val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH)
val author = root.select("div.serieAdd p:contains(Auteur:) strong").textOrNull()
return manga.copy( return manga.copy(
tags = root.select("div.serieGenre span").mapToSet { span -> tags = root.select("div.serieGenre span").mapToSet { span ->
MangaTag( MangaTag(
@ -179,7 +180,7 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
) )
}, },
coverUrl = root.selectFirst("div.serieImg img")?.attrAsAbsoluteUrlOrNull("src"), coverUrl = root.selectFirst("div.serieImg img")?.attrAsAbsoluteUrlOrNull("src"),
author = root.select("div.serieAdd p:contains(Auteur:) strong").textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = root.selectFirst("div.serieDescription div")?.html(), description = root.selectFirst("div.serieDescription div")?.html(),
chapters = root.select("div.chapterList a") chapters = root.select("div.chapterList a")
.mapChapters(reversed = true) { i, a -> .mapChapters(reversed = true) { i, a ->

@ -72,11 +72,11 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context,
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = div.selectFirstOrThrow(".item__rating").ownText().toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, rating = div.selectFirstOrThrow(".item__rating").ownText().toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -85,6 +85,9 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context,
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE) val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE)
val author = root.select("ul.pmovie__list li:contains(Artist(s):)").text()
.replace("Artist(s):", "")
.nullIfEmpty()
return manga.copy( return manga.copy(
altTitle = root.select("ul.pmovie__list li:contains(Nom Alternatif:)").text() altTitle = root.select("ul.pmovie__list li:contains(Nom Alternatif:)").text()
.replace("Nom Alternatif:", "").nullIfEmpty(), .replace("Nom Alternatif:", "").nullIfEmpty(),
@ -101,9 +104,7 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context,
source = source, source = source,
) )
}, },
author = root.select("ul.pmovie__list li:contains(Artist(s):)").text() authors = author?.let { setOf(it) } ?: emptySet(),
.replace("Artist(s):", "")
.nullIfEmpty(),
description = root.selectFirst("div.pmovie__text")?.html(), description = root.selectFirst("div.pmovie__text")?.html(),
chapters = root.select("ul li div.chapter") chapters = root.select("ul li div.chapter")
.mapChapters(reversed = true) { i, div -> .mapChapters(reversed = true) { i, div ->

@ -111,11 +111,11 @@ internal class LugnicaScans(context: MangaLoaderContext) :
url = urlManga.toRelativeUrl(domain), url = urlManga.toRelativeUrl(domain),
publicUrl = urlManga.toAbsoluteUrl(domain), publicUrl = urlManga.toAbsoluteUrl(domain),
rating = j.getFloatOrDefault("manga_rate", RATING_UNKNOWN).div(5f), rating = j.getFloatOrDefault("manga_rate", RATING_UNKNOWN).div(5f),
isNsfw = false, contentRating = null,
coverUrl = img, coverUrl = img,
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -132,7 +132,7 @@ internal class LugnicaScans(context: MangaLoaderContext) :
url = urlManga.toRelativeUrl(domain), url = urlManga.toRelativeUrl(domain),
publicUrl = urlManga.toAbsoluteUrl(domain), publicUrl = urlManga.toAbsoluteUrl(domain),
rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = img, coverUrl = img,
tags = setOf(), tags = setOf(),
state = when (j.getString("status")) { state = when (j.getString("status")) {
@ -141,7 +141,7 @@ internal class LugnicaScans(context: MangaLoaderContext) :
"3" -> MangaState.ABANDONED "3" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -161,6 +161,7 @@ internal class LugnicaScans(context: MangaLoaderContext) :
} }
val slug = manga.url.substringAfterLast("/") val slug = manga.url.substringAfterLast("/")
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE) val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE)
val author = jsonManga.getStringOrNull("author")
return manga.copy( return manga.copy(
state = when (jsonManga.getString("status")) { state = when (jsonManga.getString("status")) {
"0" -> MangaState.ONGOING "0" -> MangaState.ONGOING
@ -168,7 +169,7 @@ internal class LugnicaScans(context: MangaLoaderContext) :
"3" -> MangaState.ABANDONED "3" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = jsonManga.getStringOrNull("author"), authors = author?.let { setOf(it) } ?: emptySet(),
description = jsonManga.getStringOrNull("description"), description = jsonManga.getStringOrNull("description"),
chapters = chapters.mapChapters { i, it -> chapters = chapters.mapChapters { i, it ->
val id = it.substringAfter("\"chapter\":").substringBefore(",") val id = it.substringAfter("\"chapter\":").substringBefore(",")

@ -85,10 +85,10 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -98,10 +98,11 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val firstChapter = doc.selectFirst("tr[class*='volume-'] a")?.attr("href") val firstChapter = doc.selectFirst("tr[class*='volume-'] a")?.attr("href")
val chaptersDeferred = async { loadChapters(firstChapter) } val chaptersDeferred = async { loadChapters(firstChapter) }
val author = doc.select("a[href*=author]").text()
manga.copy( manga.copy(
description = doc.selectFirst("dd.text-justify.text-break")?.html(), description = doc.selectFirst("dd.text-justify.text-break")?.html(),
altTitle = doc.select("span[itemprop*=alternativeHeadline]").joinToString { ", " }.nullIfEmpty(), altTitle = doc.select("span[itemprop*=alternativeHeadline]").joinToString { ", " }.nullIfEmpty(),
author = doc.select("a[href*=author]").text(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (doc.selectFirst("span.badge.bg-success.text-uppercase")?.text()) { state = when (doc.selectFirst("span.badge.bg-success.text-uppercase")?.text()) {
"En Cours" -> MangaState.ONGOING "En Cours" -> MangaState.ONGOING
"Terminé" -> MangaState.FINISHED "Terminé" -> MangaState.FINISHED

@ -68,17 +68,18 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
val slug = jo.getString("slug") ?: throw ParseException("Missing Slug", url) val slug = jo.getString("slug") ?: throw ParseException("Missing Slug", url)
val url = "https://$domain/m/$slug" val url = "https://$domain/m/$slug"
val img = "https://$domainCdn/uploads/manga/$slug/cover/cover_thumb.jpg" val img = "https://$domainCdn/uploads/manga/$slug/cover/cover_thumb.jpg"
val isNsfwSource = when (jo.getIntOrDefault("caution", 0)) {
0 -> false
2 -> true
else -> false
}
Manga( Manga(
id = generateUid(url), id = generateUid(url),
title = jo.getString("name").orEmpty(), title = jo.getString("name").orEmpty(),
coverUrl = img, coverUrl = img,
altTitle = jo.getString("otherNames").orEmpty(), altTitle = jo.getString("otherNames").orEmpty(),
author = null, authors = emptySet(),
isNsfw = when (jo.getIntOrDefault("caution", 0)) { contentRating = if (isNsfwSource) ContentRating.ADULT else null,
0 -> false
2 -> true
else -> false
},
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
url = url, url = url,
description = jo.getString("summary_old").orEmpty(), description = jo.getString("summary_old").orEmpty(),
@ -107,8 +108,8 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
val doc = webClient.httpGet("https://$domain/?page=$page").parseHtml() val doc = webClient.httpGet("https://$domain/?page=$page").parseHtml()
return doc.select("div.row div.col_home").map { div -> return doc.select("div.row div.col_home").map { div ->
val href = div.selectFirstOrThrow("h4 a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("h4 a").attrAsRelativeUrl("href")
val isNsfw = div.selectFirst("img[data-adult]")?.attr("data-adult")?.isNotEmpty() == true val isNsfwSource = div.selectFirst("img[data-adult]")?.attr("data-adult")?.isNotEmpty() == true
val img = if (isNsfw) { val img = if (isNsfwSource) {
div.selectFirst("img")?.attr("data-adult") div.selectFirst("img")?.attr("data-adult")
} else { } else {
div.selectFirst("img")?.attr("data-src")?.replace(" ", "") div.selectFirst("img")?.attr("data-src")?.replace(" ", "")
@ -120,12 +121,12 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfw, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = img, coverUrl = img,
description = null, description = null,
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -170,8 +171,8 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
return doc.select("div.p-2 div.col").map { div -> return doc.select("div.p-2 div.col").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val isNsfw = div.selectFirst("img[data-adult]")?.attr("data-adult")?.isNotEmpty() == true val isNsfwSource = div.selectFirst("img[data-adult]")?.attr("data-adult")?.isNotEmpty() == true
val img = if (isNsfw) { val img = if (isNsfwSource) {
div.selectFirst("img")?.attr("data-adult") div.selectFirst("img")?.attr("data-adult")
} else { } else {
div.selectFirst("img")?.attr("data-src")?.replace(" ", "") div.selectFirst("img")?.attr("data-src")?.replace(" ", "")
@ -183,7 +184,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = div.getElementById("avgrating")?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = div.getElementById("avgrating")?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
isNsfw = isNsfw, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = img, coverUrl = img,
description = div.selectFirst(".mangalist_item_description")?.text().orEmpty(), description = div.selectFirst(".mangalist_item_description")?.text().orEmpty(),
tags = div.select("div.mb-1 a").mapToSet { tags = div.select("div.mb-1 a").mapToSet {
@ -195,7 +196,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
) )
}, },
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -228,6 +229,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
val doc = webClient.httpGet(mangaUrl).parseHtml() val doc = webClient.httpGet(mangaUrl).parseHtml()
val maxPageChapterSelect = doc.select("ul.pagination a.page-link") val maxPageChapterSelect = doc.select("ul.pagination a.page-link")
var maxPageChapter = 1 var maxPageChapter = 1
val author = doc.selectFirst("div.show_details span[itemprop=author]")?.text().orEmpty()
if (!maxPageChapterSelect.isNullOrEmpty()) { if (!maxPageChapterSelect.isNullOrEmpty()) {
maxPageChapterSelect.map { maxPageChapterSelect.map {
val i = it.attr("href").substringAfterLast("=").toInt() val i = it.attr("href").substringAfterLast("=").toInt()
@ -243,7 +245,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
"Abandonné" -> MangaState.ABANDONED "Abandonné" -> MangaState.ABANDONED
else -> null else -> null
}, },
author = doc.selectFirst("div.show_details span[itemprop=author]")?.text().orEmpty(), authors = setOf(author),
description = doc.selectFirst("dd[itemprop=description]")?.text(), description = doc.selectFirst("dd[itemprop=description]")?.text(),
rating = doc.getElementById("avgrating")?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, rating = doc.getElementById("avgrating")?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
tags = doc.select("ul.list-unstyled li a.category").mapToSet { tags = doc.select("ul.list-unstyled li a.category").mapToSet {

@ -87,10 +87,10 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
rating = div.selectFirstOrThrow("div.rating i").ownText().toFloatOrNull()?.div(10f) rating = div.selectFirstOrThrow("div.rating i").ownText().toFloatOrNull()?.div(10f)
?: RATING_UNKNOWN, ?: RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -114,7 +114,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
val chaptersDeferred = getChapters(doc) val chaptersDeferred = getChapters(doc)
val desc = doc.selectFirstOrThrow("div.desc").html().nullIfEmpty() val desc = doc.selectFirstOrThrow("div.desc").html().nullIfEmpty()
val alt = doc.body().select("div.infox span.alter").textOrNull() val alt = doc.body().select("div.infox span.alter").textOrNull()
val aut = doc.select("div.spe span")[2].text().replace("Auteur:", "").nullIfEmpty() val author = doc.select("div.spe span")[2].text().replace("Auteur:", "").nullIfEmpty()
manga.copy( manga.copy(
tags = doc.select("div.spe span:contains(Genres) a").mapToSet { a -> tags = doc.select("div.spe span:contains(Genres) a").mapToSet { a ->
MangaTag( MangaTag(
@ -125,7 +125,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
}, },
description = desc, description = desc,
altTitle = alt, altTitle = alt,
author = aut, authors = author?.let { setOf(it) } ?: emptySet(),
state = when (doc.selectFirstOrThrow("div.spe span:contains(Statut:)").textOrNull() state = when (doc.selectFirstOrThrow("div.spe span:contains(Statut:)").textOrNull()
?.substringAfterLast(':')) { ?.substringAfterLast(':')) {
" En cours" -> MangaState.ONGOING " En cours" -> MangaState.ONGOING

@ -88,11 +88,11 @@ internal class ScantradUnion(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = article.selectFirst("img.attachment-thumbnail")?.attrAsAbsoluteUrl("src"), coverUrl = article.selectFirst("img.attachment-thumbnail")?.attrAsAbsoluteUrl("src"),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -108,11 +108,11 @@ internal class ScantradUnion(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = article.selectFirst("img")?.attrAsAbsoluteUrl("src"), coverUrl = article.selectFirst("img")?.attrAsAbsoluteUrl("src"),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -122,6 +122,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml().requireElementById("main") val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml().requireElementById("main")
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE) val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE)
val author = root.select("div.project-details a[href*=auteur]").textOrNull()
return manga.copy( return manga.copy(
altTitle = root.select(".divider2:contains(Noms associés :)").firstOrNull()?.textOrNull(), altTitle = root.select(".divider2:contains(Noms associés :)").firstOrNull()?.textOrNull(),
@ -137,7 +138,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
author = root.select("div.project-details a[href*=auteur]").textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = root.selectFirst("p.sContent")?.html(), description = root.selectFirst("p.sContent")?.html(),
chapters = root.select("div.chapter-list li") chapters = root.select("div.chapter-list li")
.mapChapters(reversed = true) { i, li -> .mapChapters(reversed = true) { i, li ->

@ -152,10 +152,10 @@ internal abstract class FuzzyDoodleParser(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -181,6 +181,7 @@ internal abstract class FuzzyDoodleParser(
} }
} }
} }
val author = doc.selectFirst(selectAuthor)?.textOrNull()
manga.copy( manga.copy(
altTitle = doc.selectLast(selectAltTitle)?.textOrNull(), altTitle = doc.selectLast(selectAltTitle)?.textOrNull(),
@ -191,7 +192,7 @@ internal abstract class FuzzyDoodleParser(
in paused -> MangaState.PAUSED in paused -> MangaState.PAUSED
else -> null else -> null
}, },
author = doc.selectFirst(selectAuthor)?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.select(selectDescription).html(), description = doc.select(selectDescription).html(),
tags = doc.select(selectTagManga).mapToSet { tags = doc.select(selectTagManga).mapToSet {
val key = it.attr("href").substringAfterLast('=') val key = it.attr("href").substringAfterLast('=')

@ -113,11 +113,11 @@ internal abstract class GalleryAdultsParser(
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = div.selectFirst(selectGalleryImg)?.src(), coverUrl = div.selectFirst(selectGalleryImg)?.src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -165,10 +165,11 @@ internal abstract class GalleryAdultsParser(
val branch = doc.select(selectLanguageChapter).joinToString(separator = " / ") { val branch = doc.select(selectLanguageChapter).joinToString(separator = " / ") {
it.html().substringBefore("<") it.html().substringBefore("<")
} }
val author = doc.selectFirst(selectAuthor)?.html()?.substringBefore("<span")
return manga.copy( return manga.copy(
tags = tag.orEmpty(), tags = tag.orEmpty(),
title = doc.selectFirst(selectTitle)?.textOrNull()?.cleanupTitle() ?: manga.title, title = doc.selectFirst(selectTitle)?.textOrNull()?.cleanupTitle() ?: manga.title,
author = doc.selectFirst(selectAuthor)?.html()?.substringBefore("<span"), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = manga.id, id = manga.id,

@ -43,11 +43,11 @@ internal class DoujinDesuUk(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = div.selectLastOrThrow(selectGalleryImg).src(), coverUrl = div.selectLastOrThrow(selectGalleryImg).src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -126,9 +126,10 @@ internal class HentaiEra(context: MangaLoaderContext) :
val branch = doc.select(selectLanguageChapter).joinToString(separator = " / ") { val branch = doc.select(selectLanguageChapter).joinToString(separator = " / ") {
it.text() it.text()
} }
val author = doc.selectFirst(selectAuthor)?.text()
return manga.copy( return manga.copy(
tags = tag.orEmpty(), tags = tag.orEmpty(),
author = doc.selectFirst(selectAuthor)?.text(), authors = author?.let { setOf(it) } ?: emptySet(),
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = manga.id, id = manga.id,

@ -94,11 +94,11 @@ internal class NHentaiParser(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = div.selectFirstOrThrow(selectGalleryImg).src(), coverUrl = div.selectFirstOrThrow(selectGalleryImg).src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -88,7 +88,7 @@ internal class NHentaiToParser(context: MangaLoaderContext) :
coverUrl = div.selectFirstOrThrow(selectGalleryImg).src(), coverUrl = div.selectFirstOrThrow(selectGalleryImg).src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
largeCoverUrl = null, largeCoverUrl = null,
description = null, description = null,
chapters = null, chapters = null,

@ -101,7 +101,7 @@ internal class NHentaiXxxParser(context: MangaLoaderContext) :
coverUrl = div.selectFirstOrThrow(selectGalleryImg).src(), coverUrl = div.selectFirstOrThrow(selectGalleryImg).src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
largeCoverUrl = null, largeCoverUrl = null,
description = null, description = null,
chapters = null, chapters = null,

@ -84,8 +84,8 @@ internal abstract class GattsuParser(
tags = emptySet(), tags = emptySet(),
description = null, description = null,
state = null, state = null,
author = null, authors = emptySet(),
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
source = source, source = source,
) )
} }
@ -111,12 +111,12 @@ internal abstract class GattsuParser(
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val urlChapter = doc.selectFirstOrThrow("ul.post-fotos li a, ul.paginaPostBotoes a").attrAsAbsoluteUrl("href") val urlChapter = doc.selectFirstOrThrow("ul.post-fotos li a, ul.paginaPostBotoes a").attrAsAbsoluteUrl("href")
val author = doc.selectFirst(".post-itens li:contains(Autor) a, .paginaPostInfo li:contains(Artista) a")?.text()
return manga.copy( return manga.copy(
description = doc.selectFirst("div.post-texto")?.html(), description = doc.selectFirst("div.post-texto")?.html(),
tags = doc.selectFirst(".post-itens li:contains(Tags), .paginaPostInfo li:contains(Categorias)") tags = doc.selectFirst(".post-itens li:contains(Tags), .paginaPostInfo li:contains(Categorias)")
?.parseTags().orEmpty(), ?.parseTags().orEmpty(),
author = doc.selectFirst(".post-itens li:contains(Autor) a, .paginaPostInfo li:contains(Artista) a") authors = author?.let { setOf(it) } ?: emptySet(),
?.text(),
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = manga.id, id = manga.id,

@ -3,10 +3,7 @@ package org.koitharu.kotatsu.parsers.site.gattsu.pt
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -35,8 +32,8 @@ internal class MundoHentaiOficial(context: MangaLoaderContext) :
tags = emptySet(), tags = emptySet(),
description = null, description = null,
state = null, state = null,
author = null, authors = emptySet(),
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
source = source, source = source,
) )
} }

@ -60,6 +60,7 @@ internal abstract class GuyaParser(
private fun addManga(j: JSONObject, name: String): Manga { private fun addManga(j: JSONObject, name: String): Manga {
val url = "https://$domain/read/manga/" + j.getString("slug") val url = "https://$domain/read/manga/" + j.getString("slug")
val apiUrl = "https://$domain/api/series/" + j.getString("slug") val apiUrl = "https://$domain/api/series/" + j.getString("slug")
val author = j.getString("author")
return Manga( return Manga(
id = generateUid(apiUrl), id = generateUid(apiUrl),
url = apiUrl, url = apiUrl,
@ -71,8 +72,8 @@ internal abstract class GuyaParser(
tags = emptySet(), tags = emptySet(),
description = j.getString("description"), description = j.getString("description"),
state = null, state = null,
author = j.getString("author"), authors = author?.let { setOf(it) } ?: emptySet(),
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
source = source, source = source,
) )
} }

@ -129,7 +129,7 @@ internal abstract class HeanCms(
publicUrl = publicUrl.toAbsoluteUrl(domain), publicUrl = publicUrl.toAbsoluteUrl(domain),
description = it.getString("description"), description = it.getString("description"),
rating = it.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f, rating = it.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
coverUrl = cover, coverUrl = cover,
tags = emptySet(), tags = emptySet(),
state = when (it.getString("status")) { state = when (it.getString("status")) {
@ -139,7 +139,7 @@ internal abstract class HeanCms(
"Hiatus" -> MangaState.PAUSED "Hiatus" -> MangaState.PAUSED
else -> null else -> null
}, },
author = null, authors = emptySet(),
source = source, source = source,
) )
} }

@ -70,10 +70,10 @@ internal abstract class HeanCmsAlt(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -100,6 +100,8 @@ internal abstract class HotComicsParser(
} }
val tags = li.select(".etc span").mapNotNullToSet { tagMap[it.text()] } val tags = li.select(".etc span").mapNotNullToSet { tagMap[it.text()] }
val isNsfwSource = a.selectFirst(".ico-18plus") != null
val author = li.selectFirst(".writer")?.text().orEmpty()
Manga( Manga(
id = generateUid(url), id = generateUid(url),
@ -111,14 +113,14 @@ internal abstract class HotComicsParser(
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
description = li.selectFirst("p[itemprop*=description]")?.text().orEmpty(), description = li.selectFirst("p[itemprop*=description]")?.text().orEmpty(),
tags = tags, tags = tags,
author = li.selectFirst(".writer")?.text().orEmpty(), authors = setOf(author),
state = if (doc.selectFirst(".ico_fin") != null) { state = if (doc.selectFirst(".ico_fin") != null) {
MangaState.FINISHED MangaState.FINISHED
} else { } else {
MangaState.ONGOING MangaState.ONGOING
}, },
source = source, source = source,
isNsfw = a.selectFirst(".ico-18plus") != null, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -120,11 +120,11 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = true, contentRating = ContentRating.ADULT,
coverUrl = it.selectFirst(".thumbnail > img")?.src(), coverUrl = it.selectFirst(".thumbnail > img")?.src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
largeCoverUrl = null, largeCoverUrl = null,
description = null, description = null,
source = source, source = source,
@ -141,8 +141,9 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
"Publishing" -> MangaState.ONGOING "Publishing" -> MangaState.ONGOING
else -> null else -> null
} }
val author = metadataEl?.selectFirst("tr:contains(Author)")?.selectLast("td")?.text()
return manga.copy( return manga.copy(
author = metadataEl?.selectFirst("tr:contains(Author)")?.selectLast("td")?.text(), authors = author?.let { setOf(it) } ?: emptySet(),
description = docs.selectFirst(".wrapper > .metadata > .pb-2")?.selectFirst("p")?.html(), description = docs.selectFirst(".wrapper > .metadata > .pb-2")?.selectFirst("p")?.html(),
state = state, state = state,
rating = metadataEl?.selectFirst(".rating-prc")?.ownText()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, rating = metadataEl?.selectFirst(".rating-prc")?.ownText()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,

@ -71,10 +71,10 @@ internal class HentaiCrot(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -95,10 +95,11 @@ internal class HentaiCrot(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val author = doc.selectFirst("div.entry-content ul li:contains(Artists :) em")?.textOrNull()
return manga.copy( return manga.copy(
description = doc.selectFirst("div.entry-content p")?.html(), description = doc.selectFirst("div.entry-content p")?.html(),
altTitle = doc.selectFirst("div.entry-content ul li:contains(Alternative Name(s) :) em")?.textOrNull(), altTitle = doc.selectFirst("div.entry-content ul li:contains(Alternative Name(s) :) em")?.textOrNull(),
author = doc.selectFirst("div.entry-content ul li:contains(Artists :) em")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
state = null, state = null,
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(

@ -71,10 +71,10 @@ internal class PixHentai(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -95,10 +95,11 @@ internal class PixHentai(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val author = doc.selectFirst("div.entry-content ul li:contains(Artists :) em")?.textOrNull()
return manga.copy( return manga.copy(
description = doc.selectFirst("div.entry-content p")?.html(), description = doc.selectFirst("div.entry-content p")?.html(),
altTitle = doc.selectFirst("div.entry-content ul li:contains(Alternative Name(s) :) em")?.textOrNull(), altTitle = doc.selectFirst("div.entry-content ul li:contains(Alternative Name(s) :) em")?.textOrNull(),
author = doc.selectFirst("div.entry-content ul li:contains(Artists :) em")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
state = null, state = null,
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(

@ -101,6 +101,8 @@ internal abstract class IkenParser(
protected open fun parseMangaList(json: JSONObject): List<Manga> { protected open fun parseMangaList(json: JSONObject): List<Manga> {
return json.getJSONArray("posts").mapJSON { return json.getJSONArray("posts").mapJSON {
val url = "/series/${it.getString("slug")}" val url = "/series/${it.getString("slug")}"
val isNsfwSource = it.getBooleanOrDefault("hot", false)
val author = it.getString("author")
Manga( Manga(
id = it.getLong("id"), id = it.getLong("id"),
url = url, url = url,
@ -111,7 +113,7 @@ internal abstract class IkenParser(
description = it.getString("postContent"), description = it.getString("postContent"),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = it.getString("author"), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (it.getString("seriesStatus")) { state = when (it.getString("seriesStatus")) {
"ONGOING" -> MangaState.ONGOING "ONGOING" -> MangaState.ONGOING
"COMPLETED" -> MangaState.FINISHED "COMPLETED" -> MangaState.FINISHED
@ -120,7 +122,7 @@ internal abstract class IkenParser(
else -> null else -> null
}, },
source = source, source = source,
isNsfw = it.getBooleanOrDefault("hot", false), contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -84,15 +84,16 @@ internal class NicovideoSeigaParser(context: MangaLoaderContext) :
val href = val href =
item.selectFirst(".comic_icon > div > a")?.attrAsRelativeUrlOrNull("href") ?: return@mapNotNull null item.selectFirst(".comic_icon > div > a")?.attrAsRelativeUrlOrNull("href") ?: return@mapNotNull null
val statusText = item.selectFirst(".mg_description_header > .mg_icon > .content_status > span")?.text() val statusText = item.selectFirst(".mg_description_header > .mg_icon > .content_status > span")?.text()
val author = item.selectFirst(".mg_description_header > .mg_author > a")?.text()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = item.selectFirst(".mg_body > .title > a")?.text() ?: return@mapNotNull null, title = item.selectFirst(".mg_body > .title > a")?.text() ?: return@mapNotNull null,
coverUrl = item.selectFirst(".comic_icon > div > a > img")?.attrAsAbsoluteUrl("src"), coverUrl = item.selectFirst(".comic_icon > div > a > img")?.attrAsAbsoluteUrl("src"),
altTitle = null, altTitle = null,
author = item.selectFirst(".mg_description_header > .mg_author > a")?.text(), authors = author?.let { setOf(it) } ?: emptySet(),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
url = href, url = href,
isNsfw = false, contentRating = null,
tags = item.getElementsByAttributeValueContaining("href", "?category=").mapToSet { a -> tags = item.getElementsByAttributeValueContaining("href", "?category=").mapToSet { a ->
MangaTag( MangaTag(
key = a.attr("href").substringAfterLast('='), key = a.attr("href").substringAfterLast('='),
@ -193,11 +194,11 @@ internal class NicovideoSeigaParser(context: MangaLoaderContext) :
title = item.selectFirst(".search_result__item__info > .search_result__item__info--title > a") title = item.selectFirst(".search_result__item__info > .search_result__item__info--title > a")
?.textOrNull() ?: return@mapNotNull null, ?.textOrNull() ?: return@mapNotNull null,
altTitle = null, altTitle = null,
author = null, authors = emptySet(),
tags = emptySet(), tags = emptySet(),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
isNsfw = false, contentRating = null,
source = source, source = source,
coverUrl = item.selectFirst(".search_result__item__thumbnail > a > img") coverUrl = item.selectFirst(".search_result__item__thumbnail > a > img")
?.attrAsAbsoluteUrl("data-original"), ?.attrAsAbsoluteUrl("data-original"),

@ -138,10 +138,10 @@ internal abstract class KeyoappParser(
source = source, source = source,
) )
}, },
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }

@ -115,11 +115,11 @@ internal abstract class LikeMangaParser(
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, contentRating = null,
coverUrl = div.selectFirstOrThrow("img").src(), coverUrl = div.selectFirstOrThrow("img").src(),
tags = emptySet(), tags = emptySet(),
state = null, state = null,
author = null, authors = emptySet(),
source = source, source = source,
) )
} }
@ -149,6 +149,7 @@ internal abstract class LikeMangaParser(
} }
} }
} }
val author = doc.selectLast("li.author p")?.textOrNull()
return manga.copy( return manga.copy(
altTitle = doc.selectFirst(".list-info li.othername h2")?.textOrNull(), altTitle = doc.selectFirst(".list-info li.othername h2")?.textOrNull(),
tags = doc.select("li.kind a").mapToSet { a -> tags = doc.select("li.kind a").mapToSet { a ->
@ -158,7 +159,7 @@ internal abstract class LikeMangaParser(
source = source, source = source,
) )
}, },
author = doc.selectLast("li.author p")?.textOrNull(), authors = author?.let { setOf(it) } ?: emptySet(),
description = doc.requireElementById("summary_shortened").html(), description = doc.requireElementById("summary_shortened").html(),
chapters = run { chapters = run {
if (maxPageChapter == 1) { if (maxPageChapter == 1) {

@ -119,10 +119,10 @@ internal abstract class LilianaParser(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
@ -148,6 +148,9 @@ internal abstract class LilianaParser(
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val author = doc.selectFirst("div.y6x11p i.fas.fa-user + span.dt")?.textOrNull()?.takeUnless {
it.equals("updating", true)
}
return manga.copy( return manga.copy(
description = doc.selectFirst("div#syn-target")?.html(), description = doc.selectFirst("div#syn-target")?.html(),
largeCoverUrl = doc.selectFirst(".a1 > figure img")?.src(), largeCoverUrl = doc.selectFirst(".a1 > figure img")?.src(),
@ -158,9 +161,7 @@ internal abstract class LilianaParser(
source = source, source = source,
) )
}, },
author = doc.selectFirst("div.y6x11p i.fas.fa-user + span.dt")?.textOrNull()?.takeUnless { authors = author?.let { setOf(it) } ?: emptySet(),
it.equals("updating", true)
},
state = when (doc.selectFirst("div.y6x11p i.fas.fa-rss + span.dt")?.text()?.lowercase().orEmpty()) { state = when (doc.selectFirst("div.y6x11p i.fas.fa-rss + span.dt")?.text()?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED in finished -> MangaState.FINISHED

@ -460,6 +460,7 @@ internal abstract class MadaraParser(
}.map { div -> }.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -476,7 +477,7 @@ internal abstract class MadaraParser(
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when ( state = when (
summary?.selectFirst(".mg_status") summary?.selectFirst(".mg_status")
?.selectFirst(".summary-content") ?.selectFirst(".summary-content")
@ -491,7 +492,7 @@ internal abstract class MadaraParser(
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -660,12 +661,12 @@ internal abstract class MadaraParser(
publicUrl = href.toAbsoluteUrl(a.host ?: domain), publicUrl = href.toAbsoluteUrl(a.host ?: domain),
altTitle = null, altTitle = null,
title = div.selectFirstOrThrow(".widget-title").text(), title = div.selectFirstOrThrow(".widget-title").text(),
author = null, authors = emptySet(),
coverUrl = div.selectFirst("img")?.src(), coverUrl = div.selectFirst("img")?.src(),
tags = emptySet(), tags = emptySet(),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
state = null, state = null,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
source = source, source = source,
) )
} }

@ -74,10 +74,10 @@ internal class Manga18Fx(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.selectFirst("div.item-rate span")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, rating = div.selectFirst("div.item-rate span")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -80,10 +80,10 @@ internal class Manhwa18Cc(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.selectFirst(".item-rate span")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, rating = div.selectFirst(".item-rate span")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -3,10 +3,7 @@ package org.koitharu.kotatsu.parsers.site.madara.en
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -20,6 +17,7 @@ internal class FireScans(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("h3 a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("h3 a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -36,7 +34,7 @@ internal class FireScans(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when ( state = when (
summary?.selectFirst(".mg_status") summary?.selectFirst(".mg_status")
?.selectFirst(".summary-content") ?.selectFirst(".summary-content")
@ -50,7 +48,7 @@ internal class FireScans(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -102,6 +102,7 @@ internal class Hentai4Free(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -118,7 +119,7 @@ internal class Hentai4Free(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -126,7 +127,7 @@ internal class Hentai4Free(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -77,6 +77,7 @@ internal class IsekaiScan(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -92,7 +93,7 @@ internal class IsekaiScan(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase()) { ?.lowercase()) {
"ongoing" -> MangaState.ONGOING "ongoing" -> MangaState.ONGOING
@ -100,7 +101,7 @@ internal class IsekaiScan(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -99,6 +99,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -115,7 +116,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -123,7 +124,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -75,6 +75,7 @@ internal class MangaDass(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -90,7 +91,7 @@ internal class MangaDass(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -98,7 +99,7 @@ internal class MangaDass(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -68,6 +68,7 @@ internal class MangaDna(context: MangaLoaderContext) :
return doc.select("div.home-item").map { div -> return doc.select("div.home-item").map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".hcontent") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".hcontent")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -83,7 +84,7 @@ internal class MangaDna(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -91,7 +92,7 @@ internal class MangaDna(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -82,6 +82,7 @@ internal class MangaPure(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -97,7 +98,7 @@ internal class MangaPure(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase()) { ?.lowercase()) {
"ongoing" -> MangaState.ONGOING "ongoing" -> MangaState.ONGOING
@ -105,7 +106,7 @@ internal class MangaPure(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -71,6 +71,7 @@ internal class Manhwaz(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -86,7 +87,7 @@ internal class Manhwaz(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -94,7 +95,7 @@ internal class Manhwaz(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -3,10 +3,7 @@ package org.koitharu.kotatsu.parsers.site.madara.en
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -21,6 +18,7 @@ internal class ShibaManga(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -37,7 +35,7 @@ internal class ShibaManga(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when ( state = when (
summary?.selectFirst(".mg_status") summary?.selectFirst(".mg_status")
?.selectFirst(".summary-content") ?.selectFirst(".summary-content")
@ -52,7 +50,7 @@ internal class ShibaManga(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -2,10 +2,7 @@ package org.koitharu.kotatsu.parsers.site.madara.es
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@ -67,10 +64,10 @@ internal class DragonTranslationParser(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -20,6 +20,7 @@ internal class MangasNoSekai(context: MangaLoaderContext) :
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val body = doc.body() val body = doc.body()
val chaptersDeferred = async { loadChapters(manga.url, doc) } val chaptersDeferred = async { loadChapters(manga.url, doc) }
val author = doc.selectFirst("section#section-sinopsis div.d-flex:has(div:contains(Autor)) p a")?.textOrNull()
manga.copy( manga.copy(
tags = doc.body().select("#section-sinopsis a[href*=genre]").mapToSet { a -> tags = doc.body().select("#section-sinopsis a[href*=genre]").mapToSet { a ->
MangaTag( MangaTag(
@ -28,8 +29,7 @@ internal class MangasNoSekai(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
author = doc.selectFirst("section#section-sinopsis div.d-flex:has(div:contains(Autor)) p a") authors = author?.let { setOf(it) } ?: emptySet(),
?.textOrNull(),
description = body.selectFirst("#section-sinopsis p")?.text().orEmpty(), description = body.selectFirst("#section-sinopsis p")?.text().orEmpty(),
altTitle = doc.selectFirst("section#section-sinopsis div.d-flex:has(div:contains(Otros nombres)) p") altTitle = doc.selectFirst("section#section-sinopsis div.d-flex:has(div:contains(Otros nombres)) p")
?.textOrNull(), ?.textOrNull(),

@ -75,10 +75,10 @@ internal class TmoManga(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -66,6 +66,7 @@ internal class ManhwaHub(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -82,7 +83,7 @@ internal class ManhwaHub(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -90,7 +91,7 @@ internal class ManhwaHub(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -4,10 +4,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@ -25,6 +22,7 @@ internal class MangaFenxi(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -41,7 +39,7 @@ internal class MangaFenxi(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when ( state = when (
summary?.selectFirst(".mg_status") summary?.selectFirst(".mg_status")
?.selectFirst(".summary-content") ?.selectFirst(".summary-content")
@ -56,7 +54,7 @@ internal class MangaFenxi(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -72,6 +72,7 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
}.map { div -> }.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found") val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
Manga( Manga(
id = generateUid(href), id = generateUid(href),
url = href, url = href,
@ -87,7 +88,7 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
source = source, source = source,
) )
}.orEmpty(), }.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(), authors = author?.let { setOf(it) } ?: emptySet(),
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText() state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
?.lowercase().orEmpty()) { ?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -95,7 +96,7 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
else -> null else -> null
}, },
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -130,10 +130,10 @@ internal abstract class MadthemeParser(
source = source, source = source,
) )
}, },
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

@ -112,10 +112,10 @@ internal abstract class Manga18Parser(
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }
@ -166,7 +166,7 @@ internal abstract class Manga18Parser(
}, },
description = desc?.nullIfEmpty(), description = desc?.nullIfEmpty(),
altTitle = alt, altTitle = alt,
author = author, authors = author?.let { setOf(it) } ?: emptySet(),
state = state, state = state,
chapters = chaptersDeferred.await(), chapters = chaptersDeferred.await(),
) )

@ -3,10 +3,7 @@ package org.koitharu.kotatsu.parsers.site.manga18.en
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -28,10 +25,10 @@ internal class Hentai3zCc(context: MangaLoaderContext) :
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
author = null, authors = emptySet(),
state = null, state = null,
source = source, source = source,
isNsfw = isNsfwSource, contentRating = if (isNsfwSource) ContentRating.ADULT else null,
) )
} }
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save