Update OlympusScanlation (#18480)
* Update OlympusScanlation * Add ratelimit * More popular
This commit is contained in:
parent
8da1a49fcb
commit
f9f00c4e60
|
@ -7,7 +7,7 @@ ext {
|
||||||
extName = 'Olympus Scanlation'
|
extName = 'Olympus Scanlation'
|
||||||
pkgNameSuffix = 'es.olympusscanlation'
|
pkgNameSuffix = 'es.olympusscanlation'
|
||||||
extClass = '.OlympusScanlation'
|
extClass = '.OlympusScanlation'
|
||||||
extVersionCode = 4
|
extVersionCode = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package eu.kanade.tachiyomi.extension.es.olympusscanlation
|
package eu.kanade.tachiyomi.extension.es.olympusscanlation
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
@ -15,29 +17,41 @@ import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
class OlympusScanlation : HttpSource() {
|
class OlympusScanlation : HttpSource() {
|
||||||
|
|
||||||
|
override val versionId = 2
|
||||||
|
|
||||||
override val baseUrl: String = "https://olympusv2.gg"
|
override val baseUrl: String = "https://olympusv2.gg"
|
||||||
private val apiBaseUrl: String = "https://dashboard.olympusv2.gg"
|
private val apiBaseUrl: String = "https://dashboard.olympusv2.gg"
|
||||||
|
|
||||||
override val lang: String = "es"
|
override val lang: String = "es"
|
||||||
override val name: String = "Olympus Scanlation"
|
override val name: String = "Olympus Scanlation"
|
||||||
override val versionId = 2
|
|
||||||
override val supportsLatest: Boolean = true
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.US)
|
|
||||||
|
|
||||||
// Search
|
override val supportsLatest: Boolean = true
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
val apiUrl = "$apiBaseUrl/api/search".toHttpUrl().newBuilder()
|
override val client = super.client.newBuilder()
|
||||||
.addQueryParameter("name", query)
|
.rateLimitHost(baseUrl.toHttpUrl(), 1, 2)
|
||||||
|
.rateLimitHost(apiBaseUrl.toHttpUrl(), 2, 1)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
val apiUrl = "$apiBaseUrl/api/series?page=1&direction=asc&type=comic".toHttpUrl().newBuilder()
|
||||||
.build()
|
.build()
|
||||||
return GET(apiUrl, headers)
|
return GET(apiUrl, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val result = json.decodeFromString<PayloadMangaDto>(response.body.string())
|
runCatching { fetchFilters() }
|
||||||
val mangaList = result.data.map {
|
val result = json.decodeFromString<PayloadSeriesDto>(response.body.string())
|
||||||
|
val resultMangaList = json.decodeFromString<List<MangaDto>>(result.data.recommended_series)
|
||||||
|
val mangaList = resultMangaList.filter { it.type == "comic" }.map {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = "/series/comic-${it.slug}"
|
url = "/series/comic-${it.slug}"
|
||||||
title = it.name
|
title = it.name
|
||||||
|
@ -47,28 +61,95 @@ class OlympusScanlation : HttpSource() {
|
||||||
return MangasPage(mangaList, hasNextPage = false)
|
return MangasPage(mangaList, hasNextPage = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(1)
|
val apiUrl = "$apiBaseUrl/api/sf/new-chapters?page=$page".toHttpUrl().newBuilder()
|
||||||
|
.build()
|
||||||
|
return GET(apiUrl, headers)
|
||||||
|
}
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val result = json.decodeFromString<PayloadHomeDto>(response.body.string())
|
runCatching { fetchFilters() }
|
||||||
val mangaList = result.data.new_chapters.map {
|
val result = json.decodeFromString<NewChaptersDto>(response.body.string())
|
||||||
|
val mangaList = result.data.filter { it.type == "comic" }.map {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = "/series/comic-${it.slug}"
|
url = "/series/comic-${it.slug}"
|
||||||
title = it.name
|
title = it.name
|
||||||
thumbnail_url = it.cover
|
thumbnail_url = it.cover
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MangasPage(mangaList, hasNextPage = false)
|
val hasNextPage = result.current_page < result.last_page
|
||||||
|
return MangasPage(mangaList, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (query.isNotEmpty()) {
|
||||||
|
val apiUrl = "$apiBaseUrl/api/search".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("name", query)
|
||||||
|
.build()
|
||||||
|
return GET(apiUrl, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = "$apiBaseUrl/api/series".toHttpUrl().newBuilder()
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is SortFilter -> {
|
||||||
|
if (filter.state?.ascending == true) {
|
||||||
|
url.addQueryParameter("direction", "desc")
|
||||||
|
} else {
|
||||||
|
url.addQueryParameter("direction", "asc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is GenreFilter -> {
|
||||||
|
if (filter.toUriPart() != 9999) {
|
||||||
|
url.addQueryParameter("genres", filter.toUriPart().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is StatusFilter -> {
|
||||||
|
if (filter.toUriPart() != 9999) {
|
||||||
|
url.addQueryParameter("status", filter.toUriPart().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.addQueryParameter("type", "comic")
|
||||||
|
url.addQueryParameter("page", page.toString())
|
||||||
|
return GET(url.build().toString(), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
runCatching { fetchFilters() }
|
||||||
|
if (response.request.url.toString().startsWith("$apiBaseUrl/api/search")) {
|
||||||
|
val result = json.decodeFromString<PayloadMangaDto>(response.body.string())
|
||||||
|
val mangaList = result.data.filter { it.type == "comic" }.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
url = "/series/comic-${it.slug}"
|
||||||
|
title = it.name
|
||||||
|
thumbnail_url = it.cover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MangasPage(mangaList, hasNextPage = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = json.decodeFromString<PayloadSeriesDto>(response.body.string())
|
||||||
|
val mangaList = result.data.series.data.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
url = "/series/comic-${it.slug}"
|
||||||
|
title = it.name
|
||||||
|
thumbnail_url = it.cover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasNextPage = result.data.series.current_page < result.data.series.last_page
|
||||||
|
return MangasPage(mangaList, hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
val slug = response.request.url
|
val slug = response.request.url
|
||||||
.toString()
|
.toString()
|
||||||
.substringAfter("/series/comic-")
|
.substringAfter("/series/comic-")
|
||||||
.substringBefore("/chapters")
|
.substringBefore("/chapters")
|
||||||
val urla = "$apiBaseUrl/api/series/$slug?type=comic"
|
val apiUrl = "$apiBaseUrl/api/series/$slug?type=comic"
|
||||||
val newRequest = GET(url = urla, headers = headers)
|
val newRequest = GET(url = apiUrl, headers = headers)
|
||||||
val newResponse = client.newCall(newRequest).execute()
|
val newResponse = client.newCall(newRequest).execute()
|
||||||
val result = json.decodeFromString<MangaDetailDto>(newResponse.body.string())
|
val result = json.decodeFromString<MangaDetailDto>(newResponse.body.string())
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
|
@ -76,12 +157,13 @@ class OlympusScanlation : HttpSource() {
|
||||||
title = result.data.name
|
title = result.data.name
|
||||||
thumbnail_url = result.data.cover
|
thumbnail_url = result.data.cover
|
||||||
description = result.data.summary
|
description = result.data.summary
|
||||||
|
status = parseStatus(result.data.status?.id)
|
||||||
|
genre = result.data.genres?.joinToString { it.name.trim() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = throw Exception("Not used")
|
override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url
|
||||||
|
|
||||||
// Chapters
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
return paginatedChapterListRequest(
|
return paginatedChapterListRequest(
|
||||||
manga.url
|
manga.url
|
||||||
|
@ -124,9 +206,6 @@ class OlympusScanlation : HttpSource() {
|
||||||
.getOrNull() ?: 0L
|
.getOrNull() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url
|
|
||||||
|
|
||||||
// Pages
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
val id = chapter.url
|
val id = chapter.url
|
||||||
.substringAfter("/capitulo/")
|
.substringAfter("/capitulo/")
|
||||||
|
@ -139,28 +218,94 @@ class OlympusScanlation : HttpSource() {
|
||||||
return GET("$apiBaseUrl/api/series/$slug/chapters/$id?type=comic")
|
return GET("$apiBaseUrl/api/series/$slug/chapters/$id?type=comic")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> =
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
json.decodeFromString<PayloadPagesDto>(response.body.string()).chapter.pages.mapIndexed { i, img ->
|
return json.decodeFromString<PayloadPagesDto>(response.body.string()).chapter.pages.mapIndexed { i, img ->
|
||||||
Page(i, "", img)
|
Page(i, "", img)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Popular
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val result = json.decodeFromString<PayloadHomeDto>(response.body.string())
|
|
||||||
val resultMangaList = json.decodeFromString<List<MangaDto>>(result.data.popular_comics)
|
|
||||||
val mangaList = resultMangaList.map {
|
|
||||||
SManga.create().apply {
|
|
||||||
url = "/series/comic-${it.slug}"
|
|
||||||
title = it.name
|
|
||||||
thumbnail_url = it.cover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MangasPage(mangaList, hasNextPage = false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun imageUrlParse(response: Response): String = throw Exception("Not used")
|
||||||
val apiUrl = "$apiBaseUrl/api/home".toHttpUrl().newBuilder()
|
|
||||||
.build()
|
private fun parseStatus(statusId: Int?) = when (statusId) {
|
||||||
return GET(apiUrl, headers)
|
1 -> SManga.ONGOING
|
||||||
|
3 -> SManga.ON_HIATUS
|
||||||
|
4 -> SManga.COMPLETED
|
||||||
|
5 -> SManga.CANCELLED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SortFilter : Filter.Sort(
|
||||||
|
"Ordenar",
|
||||||
|
arrayOf("Alfabético"),
|
||||||
|
Selection(0, false),
|
||||||
|
)
|
||||||
|
|
||||||
|
private class GenreFilter(genres: List<Pair<String, Int>>) : UriPartFilter(
|
||||||
|
"Género",
|
||||||
|
arrayOf(
|
||||||
|
Pair("Todos", 9999),
|
||||||
|
*genres.toTypedArray(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private class StatusFilter(statuses: List<Pair<String, Int>>) : UriPartFilter(
|
||||||
|
"Estado",
|
||||||
|
arrayOf(
|
||||||
|
Pair("Todos", 9999),
|
||||||
|
*statuses.toTypedArray(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList {
|
||||||
|
val filters = mutableListOf<Filter<*>>(
|
||||||
|
Filter.Header("Los filtros no funcionan en la búsqueda por texto"),
|
||||||
|
Filter.Separator(),
|
||||||
|
SortFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (genresList.isNotEmpty() || statusesList.isNotEmpty()) {
|
||||||
|
filters += listOf(
|
||||||
|
Filter.Separator(),
|
||||||
|
Filter.Header("Filtrar por género"),
|
||||||
|
GenreFilter(genresList),
|
||||||
|
)
|
||||||
|
|
||||||
|
filters += listOf(
|
||||||
|
Filter.Separator(),
|
||||||
|
Filter.Header("Filtrar por estado"),
|
||||||
|
StatusFilter(statusesList),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
filters += listOf(
|
||||||
|
Filter.Separator(),
|
||||||
|
Filter.Header("Presione 'Reiniciar' para intentar cargar los filtros"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilterList(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var genresList: List<Pair<String, Int>> = emptyList()
|
||||||
|
private var statusesList: List<Pair<String, Int>> = emptyList()
|
||||||
|
private var fetchFiltersAttemps = 0
|
||||||
|
private var fetchFiltersFailed = false
|
||||||
|
|
||||||
|
private fun fetchFilters() {
|
||||||
|
if (fetchFiltersAttemps <= 3 && ((genresList.isEmpty() && statusesList.isEmpty()) || fetchFiltersFailed)) {
|
||||||
|
val filters = runCatching {
|
||||||
|
val response = client.newCall(GET("$apiBaseUrl/api/genres-statuses", headers)).execute()
|
||||||
|
json.decodeFromString<GenresStatusesDto>(response.body.string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFiltersFailed = filters.isFailure
|
||||||
|
genresList = filters.getOrNull()?.genres?.map { it.name.trim() to it.id } ?: emptyList()
|
||||||
|
statusesList = filters.getOrNull()?.statuses?.map { it.name.trim() to it.id } ?: emptyList()
|
||||||
|
fetchFiltersAttemps++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, Int>>) :
|
||||||
|
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||||
|
fun toUriPart() = vals[state].second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,24 @@ import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ChapterDto(
|
data class PayloadSeriesDto(val data: PayloadSeriesDataDto)
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
@Serializable
|
||||||
@SerialName("published_at") val date: String,
|
data class PayloadSeriesDataDto(
|
||||||
|
val series: SeriesDto,
|
||||||
|
val recommended_series: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SeriesDto(
|
||||||
|
val current_page: Int,
|
||||||
|
val data: List<MangaDto>,
|
||||||
|
val last_page: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PayloadMangaDto(val data: List<MangaDto>)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MangaDto(
|
data class MangaDto(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
@ -18,6 +30,24 @@ data class MangaDto(
|
||||||
val cover: String? = null,
|
val cover: String? = null,
|
||||||
val type: String? = null,
|
val type: String? = null,
|
||||||
val summary: String? = null,
|
val summary: String? = null,
|
||||||
|
val status: MangaStatusDto? = null,
|
||||||
|
val genres: List<FilterDto>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NewChaptersDto(
|
||||||
|
val data: List<LatestMangaDto>,
|
||||||
|
val current_page: Int,
|
||||||
|
val last_page: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LatestMangaDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val slug: String,
|
||||||
|
val cover: String? = null,
|
||||||
|
val type: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -25,28 +55,39 @@ data class MangaDetailDto(
|
||||||
var data: MangaDto,
|
var data: MangaDto,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MetaDto(val total: Int)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PageDto(val pages: List<String>)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PayloadChapterDto(var data: List<ChapterDto>, val meta: MetaDto)
|
data class PayloadChapterDto(var data: List<ChapterDto>, val meta: MetaDto)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PayloadMangaDto(val data: List<MangaDto>)
|
data class ChapterDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
@SerialName("published_at") val date: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MetaDto(val total: Int)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PayloadPagesDto(val chapter: PageDto)
|
data class PayloadPagesDto(val chapter: PageDto)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class HomeDto(
|
data class PageDto(val pages: List<String>)
|
||||||
val popular_comics: String,
|
|
||||||
val new_chapters: List<MangaDto>,
|
@Serializable
|
||||||
|
data class MangaStatusDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PayloadHomeDto(
|
data class GenresStatusesDto(
|
||||||
val data: HomeDto,
|
val genres: List<FilterDto>,
|
||||||
|
val statuses: List<FilterDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FilterDto(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue