210 lines
6.9 KiB
Kotlin
210 lines
6.9 KiB
Kotlin
package eu.kanade.tachiyomi.multisrc.mangadventure
|
|
|
|
import android.os.Build.VERSION
|
|
import eu.kanade.tachiyomi.AppInfo
|
|
import eu.kanade.tachiyomi.network.GET
|
|
import eu.kanade.tachiyomi.source.model.FilterList
|
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
import eu.kanade.tachiyomi.source.model.Page
|
|
import eu.kanade.tachiyomi.source.model.SChapter
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
import kotlinx.serialization.json.Json
|
|
import kotlinx.serialization.json.decodeFromJsonElement
|
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
import okhttp3.Response
|
|
import uy.kohesive.injekt.injectLazy
|
|
|
|
/** MangAdventure base source. */
|
|
abstract class MangAdventure(
|
|
override val name: String,
|
|
override val baseUrl: String,
|
|
override val lang: String = "en",
|
|
) : HttpSource() {
|
|
/** The site's manga categories. */
|
|
protected open val categories = DEFAULT_CATEGORIES
|
|
|
|
/** The site's manga status names. */
|
|
protected open val statuses = arrayOf("Any", "Completed", "Ongoing", "Hiatus", "Cancelled")
|
|
|
|
/** The site's sort order labels that correspond to [SortOrder.values]. */
|
|
protected open val orders = arrayOf(
|
|
"Title",
|
|
"Views",
|
|
"Latest upload",
|
|
"Chapter count",
|
|
)
|
|
|
|
/** A user agent representing Tachiyomi. */
|
|
private val userAgent =
|
|
"Mozilla/5.0 (Android ${VERSION.RELEASE}; Mobile) Tachiyomi/${AppInfo.getVersionName()}"
|
|
|
|
/** The URL of the site's API. */
|
|
private val apiUrl by lazy { "$baseUrl/api/v2" }
|
|
|
|
/** The JSON parser of the class. */
|
|
private val json by injectLazy<Json>()
|
|
|
|
override val versionId = 3
|
|
|
|
override val supportsLatest = true
|
|
|
|
override fun headersBuilder() =
|
|
super.headersBuilder().set("User-Agent", userAgent)
|
|
|
|
override fun latestUpdatesRequest(page: Int) =
|
|
GET("$apiUrl/series?page=$page&sort=-latest_upload", headers)
|
|
|
|
override fun popularMangaRequest(page: Int) =
|
|
GET("$apiUrl/series?page=$page&sort=-views", headers)
|
|
|
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
|
apiUrl.toHttpUrl().newBuilder().addEncodedPathSegment("series").run {
|
|
if (query.startsWith(SLUG_QUERY)) {
|
|
addQueryParameter("slug", query.substringAfter(SLUG_QUERY))
|
|
} else {
|
|
addQueryParameter("page", page.toString())
|
|
addQueryParameter("title", query)
|
|
filters.filterIsInstance<UriFilter>().forEach {
|
|
addQueryParameter(it.param, it.toString())
|
|
}
|
|
}
|
|
|
|
GET(build(), headers)
|
|
}
|
|
|
|
override fun mangaDetailsRequest(manga: SManga) =
|
|
GET("$apiUrl/series/${manga.url}", headers)
|
|
|
|
override fun chapterListRequest(manga: SManga) =
|
|
GET("$apiUrl/series/${manga.url}/chapters?date_format=timestamp", headers)
|
|
|
|
override fun pageListRequest(chapter: SChapter) =
|
|
GET("$apiUrl/chapters/${chapter.url}/pages?track=true", headers)
|
|
|
|
override fun latestUpdatesParse(response: Response) =
|
|
response.decode<Paginator<Series>>().let {
|
|
MangasPage(it.map(::mangaFromJSON), !it.last)
|
|
}
|
|
|
|
override fun searchMangaParse(response: Response) =
|
|
latestUpdatesParse(response)
|
|
|
|
override fun popularMangaParse(response: Response) =
|
|
latestUpdatesParse(response)
|
|
|
|
override fun chapterListParse(response: Response) =
|
|
response.decode<Results<Chapter>>().map { chapter ->
|
|
SChapter.create().apply {
|
|
url = chapter.id.toString()
|
|
name = buildString {
|
|
append(chapter.fullTitle)
|
|
if (chapter.final) append(" [END]")
|
|
}
|
|
chapter_number = chapter.number
|
|
date_upload = chapter.published.toLong()
|
|
scanlator = chapter.groups.joinToString()
|
|
}
|
|
}
|
|
|
|
override fun mangaDetailsParse(response: Response) =
|
|
response.decode<Series>().let(::mangaFromJSON)
|
|
|
|
override fun pageListParse(response: Response) =
|
|
response.decode<Results<MAPage>>().map { page ->
|
|
Page(page.number, imageUrl = page.image)
|
|
}
|
|
|
|
override fun imageUrlParse(response: Response) =
|
|
throw UnsupportedOperationException()
|
|
|
|
override fun getMangaUrl(manga: SManga) = "$baseUrl/reader/${manga.url}"
|
|
|
|
override fun getChapterUrl(chapter: SChapter) = "$apiUrl/chapters/${chapter.url}/read"
|
|
|
|
override fun getFilterList() =
|
|
FilterList(
|
|
Author(),
|
|
Artist(),
|
|
Status(statuses),
|
|
SortOrder(orders),
|
|
CategoryList(categories),
|
|
)
|
|
|
|
/** Decodes the JSON response as an object. */
|
|
private inline fun <reified T> Response.decode() =
|
|
json.decodeFromJsonElement<T>(json.parseToJsonElement(body.string()))
|
|
|
|
/** Converts a [Series] object to an [SManga]. */
|
|
private fun mangaFromJSON(series: Series) =
|
|
SManga.create().apply {
|
|
url = series.slug
|
|
title = series.title
|
|
thumbnail_url = series.cover
|
|
description = buildString {
|
|
series.description?.let(::append)
|
|
series.aliases.let {
|
|
if (!it.isNullOrEmpty()) {
|
|
it.joinTo(this, "\n", "\n\nAlternative titles:\n")
|
|
}
|
|
}
|
|
}
|
|
author = series.authors?.joinToString()
|
|
artist = series.artists?.joinToString()
|
|
genre = series.categories?.joinToString()
|
|
status = if (series.licensed == true) {
|
|
SManga.LICENSED
|
|
} else {
|
|
when (series.status) {
|
|
"completed" -> SManga.COMPLETED
|
|
"ongoing" -> SManga.ONGOING
|
|
"hiatus" -> SManga.ON_HIATUS
|
|
"canceled" -> SManga.CANCELLED
|
|
else -> SManga.UNKNOWN
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
/** Manga categories from MangAdventure `categories.xml` fixture. */
|
|
val DEFAULT_CATEGORIES = listOf(
|
|
"4-Koma",
|
|
"Action",
|
|
"Adventure",
|
|
"Comedy",
|
|
"Doujinshi",
|
|
"Drama",
|
|
"Ecchi",
|
|
"Fantasy",
|
|
"Gender Bender",
|
|
"Harem",
|
|
"Hentai",
|
|
"Historical",
|
|
"Horror",
|
|
"Josei",
|
|
"Martial Arts",
|
|
"Mecha",
|
|
"Mystery",
|
|
"Psychological",
|
|
"Romance",
|
|
"School Life",
|
|
"Sci-Fi",
|
|
"Seinen",
|
|
"Shoujo",
|
|
"Shoujo Ai",
|
|
"Shounen",
|
|
"Shounen Ai",
|
|
"Slice of Life",
|
|
"Smut",
|
|
"Sports",
|
|
"Supernatural",
|
|
"Tragedy",
|
|
"Yaoi",
|
|
"Yuri",
|
|
)
|
|
|
|
/** Query to search by manga slug. */
|
|
internal const val SLUG_QUERY = "slug:"
|
|
}
|
|
}
|