Update OlympusScanlation (#18480)

* Update OlympusScanlation

* Add ratelimit

* More popular
This commit is contained in:
bapeey 2023-10-11 19:02:28 -05:00 committed by GitHub
parent 8da1a49fcb
commit f9f00c4e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 246 additions and 60 deletions

View File

@ -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"

View File

@ -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
} }
} }

View File

@ -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,
) )