[InovaScanManga] Remake parser (Close #1988)
parent
33a0a68ac7
commit
e7316b5cd0
@ -0,0 +1,271 @@
|
||||
package org.koitharu.kotatsu.parsers.site.fr
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import org.json.JSONObject
|
||||
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.SinglePageMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("INOVASCANMANGA", "InovaScanManga", "fr", type = ContentType.HENTAI)
|
||||
internal class InovaScanManga(context: MangaLoaderContext) :
|
||||
SinglePageMangaParser(context, MangaParserSource.INOVASCANMANGA) {
|
||||
override val configKeyDomain = ConfigKey.Domain("inovascanmanga.com")
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isMultipleTagsSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
)
|
||||
|
||||
private var genreCache: Set<MangaTag>? = null
|
||||
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions {
|
||||
return MangaListFilterOptions(
|
||||
availableTags = fetchAvailableGenres(),
|
||||
availableStates = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.PAUSED,
|
||||
),
|
||||
availableContentTypes = EnumSet.of(
|
||||
ContentType.MANGA,
|
||||
ContentType.MANHWA,
|
||||
ContentType.MANHUA,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchAvailableGenres(): Set<MangaTag> {
|
||||
genreCache?.let { return it }
|
||||
|
||||
val url = buildApiUrl(
|
||||
search = "",
|
||||
sort = "trending",
|
||||
status = "all",
|
||||
genre = "all",
|
||||
type = "all",
|
||||
)
|
||||
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
val genresArray = json.getJSONArray("availableGenres")
|
||||
val genres = HashSet<MangaTag>(genresArray.length())
|
||||
|
||||
for (i in 0 until genresArray.length()) {
|
||||
val genreName = genresArray.getString(i)
|
||||
genres.add(
|
||||
MangaTag(
|
||||
key = genreName.toTitleCase(sourceLocale),
|
||||
title = genreName.toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
)
|
||||
}
|
||||
genreCache = genres
|
||||
return genres
|
||||
}
|
||||
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
|
||||
val url = buildApiUrl(
|
||||
search = filter.query.orEmpty(),
|
||||
sort = when (order) {
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.NEWEST -> "new"
|
||||
else -> "popular"
|
||||
},
|
||||
status = filter.states.oneOrThrowIfMany()?.let {
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
MangaState.PAUSED -> "hiatus"
|
||||
else -> "all"
|
||||
}
|
||||
} ?: "all",
|
||||
genre = filter.tags.oneOrThrowIfMany()?.key ?: "all",
|
||||
type = filter.types.oneOrThrowIfMany()?.let {
|
||||
when (it) {
|
||||
ContentType.MANGA -> "Manga"
|
||||
ContentType.MANHWA -> "Manhwa"
|
||||
ContentType.MANHUA -> "Manhua"
|
||||
else -> "all"
|
||||
}
|
||||
} ?: "all",
|
||||
)
|
||||
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
|
||||
return json.getJSONArray("manga").mapJSON { jo ->
|
||||
parseMangaFromList(jo)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val json = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseJson()
|
||||
val mangaJson = json.getJSONObject("manga")
|
||||
val mangaId = mangaJson.getInt("id")
|
||||
val chaptersUrl = "https://$domain/api/manga/$mangaId/chapters"
|
||||
val chaptersJson = webClient.httpGet(chaptersUrl).parseJson()
|
||||
val allChapters = parseAllChapters(chaptersJson, mangaId, mangaJson.optString("team_name").nullIfEmpty())
|
||||
|
||||
return parseMangaDetails(mangaJson, allChapters)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val json = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseJson()
|
||||
return json.getJSONArray("pages").mapJSON { jo ->
|
||||
val url = jo.getString("url").toAbsoluteUrl(domain)
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildApiUrl(
|
||||
search: String,
|
||||
sort: String,
|
||||
status: String,
|
||||
genre: String,
|
||||
type: String,
|
||||
): String = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/manga/discover?")
|
||||
append("search=").append(search.urlEncoded())
|
||||
append("&sort=").append(sort)
|
||||
append("&status=").append(status)
|
||||
append("&genre=").append(genre.urlEncoded())
|
||||
append("&type=").append(type)
|
||||
append("&year=").append("all")
|
||||
}
|
||||
|
||||
private fun parseMangaFromList(jo: JSONObject): Manga {
|
||||
val mangaId = jo.getInt("id")
|
||||
val mangaUrl = "/api/manga/$mangaId"
|
||||
val genres = jo.getJSONArray("genres")
|
||||
|
||||
return Manga(
|
||||
id = generateUid(mangaUrl),
|
||||
url = mangaUrl,
|
||||
publicUrl = "https://$domain/manga/$mangaId",
|
||||
coverUrl = jo.getStringOrNull("cover_url")?.toAbsoluteUrl(domain),
|
||||
title = jo.getString("title"),
|
||||
altTitles = emptySet(),
|
||||
rating = jo.optDouble("rating", 0.0).let { if (it > 0) it.toFloat() / 10f else RATING_UNKNOWN },
|
||||
tags = parseGenreTags(genres),
|
||||
authors = emptySet(),
|
||||
state = parseStatus(jo.getString("status")),
|
||||
source = source,
|
||||
contentRating = if (genres.toString().contains("Adulte") || genres.toString().contains("Mature") || isNsfwSource) {
|
||||
ContentRating.ADULT
|
||||
} else null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseMangaDetails(mangaJson: JSONObject, chapters: List<MangaChapter>): Manga {
|
||||
val mangaId = mangaJson.getInt("id")
|
||||
val mangaUrl = "/api/manga/$mangaId"
|
||||
val genres = mangaJson.getJSONArray("genres")
|
||||
|
||||
val authors = parseStringArray(mangaJson.optJSONArray("authors"))
|
||||
val artists = parseStringArray(mangaJson.optJSONArray("artists"))
|
||||
|
||||
return Manga(
|
||||
id = generateUid(mangaUrl),
|
||||
url = mangaUrl,
|
||||
publicUrl = "https://$domain/manga/$mangaId",
|
||||
coverUrl = mangaJson.getStringOrNull("cover_url")?.toAbsoluteUrl(domain),
|
||||
title = mangaJson.getString("title"),
|
||||
altTitles = parseStringArray(mangaJson.optJSONArray("alternative_titles")),
|
||||
rating = mangaJson.optDouble("rating", 0.0).let { if (it > 0) it.toFloat() / 10f else RATING_UNKNOWN },
|
||||
tags = parseGenreTags(genres),
|
||||
authors = authors + artists,
|
||||
state = parseStatus(mangaJson.getString("status")),
|
||||
source = source,
|
||||
contentRating = if (genres.toString().contains("Adulte") || genres.toString().contains("Mature")) {
|
||||
ContentRating.ADULT
|
||||
} else null,
|
||||
description = mangaJson.getString("description"),
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseAllChapters(json: JSONObject, mangaId: Int, teamName: String?): List<MangaChapter> {
|
||||
return json.getJSONArray("chapters").mapJSON { jo ->
|
||||
val chapterId = jo.getInt("id")
|
||||
val chapterUrl = "/api/manga/$mangaId/chapters/$chapterId/pages"
|
||||
val numberStr = jo.getString("number")
|
||||
|
||||
val chapterNumber = numberStr.substringBefore('.').substringBefore(' ').toFloatOrNull() ?: 0f
|
||||
|
||||
MangaChapter(
|
||||
id = generateUid(chapterUrl),
|
||||
title = jo.optString("title").nullIfEmpty(),
|
||||
number = chapterNumber,
|
||||
volume = 0,
|
||||
url = chapterUrl,
|
||||
scanlator = teamName,
|
||||
uploadDate = parseDate(jo.getString("date")),
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
}.reversed()
|
||||
}
|
||||
|
||||
private fun parseGenreTags(genresArray: org.json.JSONArray): Set<MangaTag> {
|
||||
val tags = HashSet<MangaTag>(genresArray.length())
|
||||
for (i in 0 until genresArray.length()) {
|
||||
val genreName = genresArray.getString(i)
|
||||
tags.add(
|
||||
MangaTag(
|
||||
key = genreName.toTitleCase(),
|
||||
title = genreName.toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
private fun parseStringArray(array: org.json.JSONArray?): Set<String> {
|
||||
if (array == null) return emptySet()
|
||||
val result = HashSet<String>(array.length())
|
||||
for (i in 0 until array.length()) {
|
||||
result.add(array.getString(i))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String): MangaState? = when (status) {
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun parseDate(dateStr: String): Long {
|
||||
return SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH).parse(dateStr)
|
||||
?.time ?: 0L
|
||||
}
|
||||
|
||||
override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? {
|
||||
val mangaId = link.pathSegments.lastOrNull()?.toIntOrNull() ?: return null
|
||||
val apiUrl = "/api/manga/$mangaId"
|
||||
return resolver.resolveManga(this, url = apiUrl, id = generateUid(apiUrl))
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.fr
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken("Source change template")
|
||||
@MangaSourceParser("INOVASCANMANGA", "InovaScanManga", "fr")
|
||||
internal class InovaScanManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.INOVASCANMANGA, "inovascanmanga.com") {
|
||||
override val datePattern = "d MMMM yyyy"
|
||||
}
|
||||
Loading…
Reference in New Issue