AnimeSama: Add source (#2080)

Co-authored-by: Draken <131387159+dragonx943@users.noreply.github.com>
master
Naga 9 months ago committed by GitHub
parent 7f39798a8c
commit cbd74dd7df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1 +1 @@
total: 1248
total: 1249

@ -0,0 +1,321 @@
package org.koitharu.kotatsu.parsers.site.fr
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.model.ContentRating
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.MangaTag
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.parsers.util.parseRaw
import org.koitharu.kotatsu.parsers.util.toRelativeUrl
import org.koitharu.kotatsu.parsers.util.urlDecode
import org.koitharu.kotatsu.parsers.util.urlEncoded
import java.util.EnumSet
@MangaSourceParser("ANIMESAMA", "AnimeSama", "fr")
internal class AnimeSama(context: MangaLoaderContext) :
PagedMangaParser(context, source = MangaParserSource.ANIMESAMA, 96) {
override val configKeyDomain = ConfigKey.Domain("anime-sama.fr")
private val baseUrl = "https://$domain"
private val cdnUrl = "$baseUrl/s2/scans/"
override fun getRequestHeaders() = Headers.Builder()
.add("Referer", baseUrl)
.build()
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.ALPHABETICAL,
)
override val filterCapabilities = MangaListFilterCapabilities(
isSearchSupported = true,
isSearchWithFiltersSupported = true,
isMultipleTagsSupported = true,
)
override suspend fun getFilterOptions(): MangaListFilterOptions {
val doc = webClient.httpGet("$baseUrl/catalogue").parseHtml()
val genres = doc.select("#list_genres label").mapNotNull { labelElement ->
val input = labelElement.selectFirst("input[name=genre[]]") ?: return@mapNotNull null
val labelText = labelElement.ownText()
val value = input.attr("value")
MangaTag(
key = value,
title = labelText,
source = source,
)
}.toSet()
return MangaListFilterOptions(
availableTags = genres,
)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildListUrl(page, order, filter)
val doc = webClient.httpGet(url).parseHtml()
return if (url.toString() == "$baseUrl/") {
parseHomePageScans(doc)
} else {
parseCataloguePage(doc)
}
}
private fun buildListUrl(page: Int, order: SortOrder, filter: MangaListFilter) = when {
filter.query.isNullOrEmpty().not() || filter.tags.isNotEmpty() -> {
"$baseUrl/catalogue".toHttpUrl().newBuilder()
.addQueryParameter("type[0]", "Scans")
.apply {
filter.query?.let { addQueryParameter("search", it) }
filter.tags.forEach { tag ->
addQueryParameter("genre[]", tag.key)
}
}
.addQueryParameter("page", page.toString())
.build()
}
order == SortOrder.UPDATED && page == 1 -> baseUrl.toHttpUrl()
else -> "$baseUrl/catalogue".toHttpUrl().newBuilder()
.addQueryParameter("type[0]", "Scans")
.addQueryParameter("page", page.toString())
.build()
}
private fun parseCataloguePage(doc: Document): List<Manga> {
return doc.select("#list_catalog > div").mapNotNull { element ->
val a = element.selectFirst("a") ?: return@mapNotNull null
val title = element.selectFirst("h1")?.text() ?: return@mapNotNull null
val cover = element.selectFirst("img")?.attr("src") ?: return@mapNotNull null
val href = a.attr("href").removeSuffix("/")
createManga(
href = href,
title = title,
cover = cover
)
}
}
private fun parseHomePageScans(doc: Document): List<Manga> {
val mangaList = mutableListOf<Manga>()
val seenUrls = mutableSetOf<String>()
doc.select("#containerAjoutsScans > div").forEach { element ->
parseHomePageManga(element)?.let { manga ->
if (seenUrls.add(manga.url)) {
mangaList.add(manga)
}
}
}
val selectors = listOf("#containerSorties", "#containerClassiques", "#containerPepites")
doc.select(selectors.joinToString(", ") { "$it > div" }).forEach { element ->
parseHorizontalManga(element)?.let { manga ->
if (manga.url.contains("/scan") && seenUrls.add(manga.url)) {
mangaList.add(manga)
}
}
}
return mangaList
}
private fun parseHorizontalManga(element: Element): Manga? {
val a = element.selectFirst("a") ?: return null
val title = element.selectFirst("h1")?.text() ?: return null
val cover = element.selectFirst("img")?.attr("src") ?: return null
val href = a.attr("href")
val types = element.select("p").getOrNull(2)?.text().orEmpty()
if (!types.contains("Scans")) return null
return createManga(href, title, cover)
}
private fun parseHomePageManga(element: Element): Manga? {
val a = element.selectFirst("a") ?: return null
val title = element.selectFirst("h1")?.text() ?: return null
val cover = element.selectFirst("img")?.attr("src") ?: return null
val href = a.attr("href").removeSuffix("/scan/vf/")
return createManga(href, title, cover)
}
private fun createManga(href: String, title: String, cover: String) = Manga(
id = generateUid(href),
title = normalizeTitle(title),
altTitles = emptySet(),
url = href.toRelativeUrl(domain),
publicUrl = href,
rating = RATING_UNKNOWN,
contentRating = ContentRating.SAFE,
coverUrl = cover,
largeCoverUrl = null,
tags = emptySet(),
state = null,
authors = emptySet(),
description = null,
chapters = null,
source = source,
)
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.publicUrl.toHttpUrl()).parseHtml()
val description = doc.selectFirst("#sousBlocMiddle > div h2:contains(Synopsis)+p")?.text()
val genreText = doc.select("#sousBlocMiddle > div h2:contains(Genres)+a").text()
val title = doc.selectFirst("#titreOeuvre")?.text() ?: manga.title
val cover = doc.selectFirst("#coverOeuvre")?.attr("src")
val genres = genreText.split("-", ",")
.mapNotNull { genre ->
genre.trim().takeIf { it.isNotEmpty() }?.let {
MangaTag(key = it, title = it, source = source)
}
}
.toSet()
val chapters = parseChapters(manga.url, title)
return manga.copy(
title = normalizeTitle(title),
description = description,
tags = genres,
chapters = chapters,
coverUrl = cover ?: manga.coverUrl,
)
}
private suspend fun parseChapters(mangaUrl: String, mangaTitle: String): List<MangaChapter> {
return try {
val subDoc = webClient.httpGet("$baseUrl$mangaUrl/scan/vf").parseHtml()
parseChapterListFromJs(subDoc).ifEmpty {
parseChaptersFromJsonApi(mangaUrl, mangaTitle)
}
} catch (_: Exception) {
parseChaptersFromJsonApi(mangaUrl, mangaTitle)
}
}
private suspend fun parseChapterListFromJs(doc: Document): List<MangaChapter> {
val title = doc.selectFirst("#titreOeuvre")?.text().orEmpty()
val chapterUrlBuilder = doc.baseUri().toHttpUrl()
.newBuilder()
.query(null)
.addPathSegment("episodes.js")
.addQueryParameter("title", title)
val jsContent = webClient.httpGet(chapterUrlBuilder.build()).parseRaw()
val episodeNumbers = Regex("""eps(\d+)""").findAll(jsContent)
.mapNotNull { it.groupValues[1].toIntOrNull() }
.distinct()
.sorted()
.toList()
return episodeNumbers.mapIndexed { index, number ->
val chapterId = index + 1
val url = chapterUrlBuilder
.addQueryParameter("id", chapterId.toString())
.build()
.toString()
.removePrefix("https://$domain")
MangaChapter(
id = generateUid(url),
title = "Chapitre $number",
number = number.toFloat(),
volume = 0,
url = url,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
)
}
}
private suspend fun parseChaptersFromJsonApi(
mangaUrl: String,
mangaTitle: String
): List<MangaChapter> {
val chapterInfoUrl =
"$baseUrl/s2/scans/get_nb_chap_et_img.php?oeuvre=${mangaTitle.urlEncoded()}"
return try {
val chapterInfo = webClient.httpGet(chapterInfoUrl).parseJson()
chapterInfo.keys().asSequence()
.mapNotNull { it.toIntOrNull() }
.sorted()
.map { chapterNumber ->
val dataUrl = "$mangaUrl#${mangaTitle.urlEncoded()}#$chapterNumber"
MangaChapter(
id = generateUid(dataUrl),
title = "Chapitre $chapterNumber",
number = chapterNumber.toFloat(),
volume = 0,
url = dataUrl,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
)
}.toList()
} catch (_: Exception) {
emptyList()
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val encodedTitle = extractEncodedTitle(chapter.url) ?: return emptyList()
val chapterInfoUrl = "$baseUrl/s2/scans/get_nb_chap_et_img.php?oeuvre=$encodedTitle"
val decodedTitle = encodedTitle.urlDecode()
return try {
val chapterInfo = webClient.httpGet(chapterInfoUrl).parseJson()
val chapterKey = chapter.number.toInt().toString()
val pageCount = chapterInfo.optInt(chapterKey)
if (pageCount == 0) return emptyList()
(1..pageCount).map { pageIndex ->
MangaPage(
id = generateUid("${chapter.id}_$pageIndex"),
url = "$cdnUrl$decodedTitle/$chapterKey/$pageIndex.jpg",
preview = null,
source = source,
)
}
} catch (_: Exception) {
emptyList()
}
}
private fun normalizeTitle(title: String) = title.replace("", "'")
private fun extractEncodedTitle(url: String): String? {
return if ('#' in url) {
url.split('#').getOrNull(1)
} else {
"$baseUrl$url".toHttpUrl().queryParameter("title")
}
}
}
Loading…
Cancel
Save