RaijinScans: Fixes (#2070)

master
Naga 9 months ago committed by GitHub
parent 6fb4cc608c
commit a4824a3582
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,12 +1,294 @@
package org.koitharu.kotatsu.parsers.site.madara.fr
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaPage
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.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.mapChapters
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import org.koitharu.kotatsu.parsers.util.selectLast
import org.koitharu.kotatsu.parsers.util.src
import org.koitharu.kotatsu.parsers.util.textOrNull
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.parsers.util.urlEncoded
import java.text.SimpleDateFormat
import java.util.Base64
import java.util.Calendar
import java.util.EnumSet
import java.util.Locale
@MangaSourceParser("RAIJINSCANS", "RaijinScans", "fr")
internal class RaijinScans(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.RAIJINSCANS, "raijinscans.net") {
MadaraParser(context, MangaParserSource.RAIJINSCANS, "raijinscan.co", 21) {
override val datePattern = "dd/MM/yyyy"
override val withoutAjax = true
override val listUrl = ""
override val tagPrefix = "genre/"
override val selectBodyPage = "div.protected-image-data"
override val selectChapter = "ul.scroll-sm li.item"
override val selectDate = "span:nth-of-type(2)"
override val selectPage = "div.protected-image-data"
override val selectGenre = "div.genre-list div.genre-link"
override val selectDesc = "div.description-content"
override val selectState = "div.stat-item:has(span:contains(État du titre)) span.manga"
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
isMultipleTagsSupported = true,
isYearSupported = true,
isSearchWithFiltersSupported = true,
)
private lateinit var tagMap: Map<String, String>
override suspend fun getFilterOptions(): MangaListFilterOptions {
val availableTags = fetchAvailableTags()
tagMap = availableTags.associateBy({ it.title }, { it.key })
return MangaListFilterOptions(
availableTags = availableTags,
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
),
)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://$domain")
if (page > 0) append("/page/${page + 1}")
append("?post_type=wp-manga&s=")
filter.query?.let { append(it.urlEncoded()) }
if (filter.year != YEAR_UNKNOWN) append("&release[]=${filter.year}")
if (!filter.tags.isEmpty()) {
append("&genre_mode=and")
filter.tags.forEach { append("&genre[]=${it.key}") }
}
filter.states.forEach {
val status = when (it) {
MangaState.ONGOING -> "on-going"
MangaState.FINISHED -> "end"
else -> ""
}
if (status.isNotEmpty()) append("&status[]=$status")
}
val sortOrder = when (order) {
SortOrder.POPULARITY -> "most_viewed"
SortOrder.UPDATED -> "recently_added"
SortOrder.ALPHABETICAL -> "title_az"
else -> "recently_added"
}
if (sortOrder.isNotEmpty()) append("&sort=$sortOrder")
}
val doc = webClient.httpGet(url).parseHtml()
return parseMangaList(doc)
}
override fun parseMangaList(doc: Document): List<Manga> {
val elements = doc.select("div.original.card-lg div.unit")
return elements.map { element ->
val linkElement =
element.selectFirst("a.c-title") ?: element.selectFirst("div.info > a") ?: element.selectFirst("a")
?: error("link not found")
val href = linkElement.attrAsRelativeUrl("href")
val title = linkElement.text()
val cover = element.selectLast("div.poster-image-wrapper > img")?.src()
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(domain),
coverUrl = cover,
title = title,
altTitles = emptySet(),
rating = RATING_UNKNOWN,
tags = emptySet(),
authors = emptySet(),
state = null,
source = source,
contentRating = ContentRating.SAFE,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val title = doc.selectFirst("h1.serie-title")?.text() ?: manga.title
val cover = doc.selectFirst("img.cover")?.src() ?: manga.coverUrl
val author = doc.selectFirst("div.stat-item:has(span:contains(Auteur)) span.stat-value")?.text()
val description = doc.selectFirst(selectDesc)?.text()
val genres = doc.select(selectGenre).mapNotNullToSet { a ->
val href = a.attr("href")
val genreSlug = href.substringAfter("/manga-genre/").substringBefore("/").toTitleCase()
val genreId = tagMap[genreSlug]
if (genreId != null) {
MangaTag(
key = genreId,
title = a.text(),
source = source,
)
} else {
error("Error: Genre '$genreSlug' from detail page not found in filter options map.")
}
}
val state = doc.selectFirst(selectState)?.text()?.lowercase()?.let { stateText ->
when {
"en cours" in stateText -> MangaState.ONGOING
"terminé" in stateText -> MangaState.FINISHED
else -> null
}
}
val rating = doc.select(".vote-count").textOrNull()?.toFloat()?.div(10f) ?: RATING_UNKNOWN
return manga.copy(
title = title,
coverUrl = cover,
authors = setOfNotNull(author),
description = description,
tags = genres,
state = state,
chapters = getChapters(manga, doc),
rating = rating,
)
}
override suspend fun getChapters(manga: Manga, doc: Document): List<MangaChapter> {
return doc.select(selectChapter).mapChapters(reversed = true) { i, element ->
val link = element.selectFirstOrThrow("a")
val href = link.attrAsRelativeUrl("href")
val name = link.attr("title").trim()
val dateText = link.selectFirst(selectDate)?.text()
MangaChapter(
id = generateUid(href),
title = name,
number = i + 1f,
volume = 0,
url = href,
uploadDate = parseRelativeDate(dateText ?: ""),
source = source,
scanlator = null,
branch = null,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
return doc.select(selectPage).map { element ->
val encodedUrl = element.attr("data-src")
val imageUrl = String(Base64.getDecoder().decode(encodedUrl))
MangaPage(
id = generateUid(imageUrl),
url = imageUrl,
preview = null,
source = source,
)
}
}
override suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/?post_type=wp-manga&s=").parseHtml()
return doc.select("ul.dropdown-menu.c1 li input[type=checkbox][name='genre[]']").mapNotNullToSet { input ->
val value = input.attr("value")
val label = input.nextElementSibling()?.text()?.trim()
if (value.isNotEmpty() && !label.isNullOrEmpty()) {
MangaTag(
key = value,
title = label.toTitleCase(),
source = source,
)
} else {
null
}
}
}
private fun parseRelativeDate(date: String): Long {
val lcDate = date.lowercase(Locale.FRENCH).trim()
val cal = Calendar.getInstance()
val number = """(\d+)""".toRegex().find(lcDate)?.value?.toIntOrNull()
return when {
"aujourd'hui" in lcDate -> cal.timeInMillis
"hier" in lcDate -> cal.apply { add(Calendar.DAY_OF_MONTH, -1) }.timeInMillis
number != null -> when {
("h" in lcDate || "heure" in lcDate) && "chapitre" !in lcDate -> cal.apply {
add(
Calendar.HOUR_OF_DAY,
-number,
)
}.timeInMillis
"min" in lcDate -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
"jour" in lcDate || lcDate.endsWith("j") -> cal.apply {
add(
Calendar.DAY_OF_MONTH,
-number,
)
}.timeInMillis
"semaine" in lcDate -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
"mois" in lcDate || (lcDate.endsWith("m") && "min" !in lcDate) -> cal.apply {
add(
Calendar.MONTH,
-number,
)
}.timeInMillis
"an" in lcDate -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0L
}
else -> parseChapterDate(SimpleDateFormat(datePattern, sourceLocale), date)
}
}
}

Loading…
Cancel
Save