IkigaiMangas: Replace popular and latest endpoints + convention changes (#1689)

* Update

* Follow exactly site order

* Replace popular and latest endpoints
This commit is contained in:
bapeey 2024-03-04 05:38:19 -05:00 committed by Draff
parent 5344c62b6b
commit cf4a208d08
4 changed files with 141 additions and 90 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Ikigai Mangas'
extClass = '.IkigaiMangas'
extVersionCode = 1
extVersionCode = 2
isNsfw = true
}

View File

@ -18,6 +18,7 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import kotlin.concurrent.thread
class IkigaiMangas : HttpSource() {
@ -30,12 +31,13 @@ class IkigaiMangas : HttpSource() {
override val supportsLatest: Boolean = true
override val client = super.client.newBuilder()
override val client = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 1, 2)
.rateLimitHost(apiBaseUrl.toHttpUrl(), 2, 1)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
private val json: Json by injectLazy()
@ -45,18 +47,26 @@ class IkigaiMangas : HttpSource() {
}
override fun popularMangaRequest(page: Int): Request {
val apiUrl = "$apiBaseUrl/api/swf/series?page=$page&column=view_count&direction=desc"
val apiUrl = "$apiBaseUrl/api/swf/series/ranking-list?type=total_ranking&series_type=comic"
return GET(apiUrl, headers)
}
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
override fun popularMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<PayloadSeriesDto>(response.body.string())
val mangaList = result.data.map { it.toSManga() }
return MangasPage(mangaList, false)
}
override fun latestUpdatesRequest(page: Int): Request {
val apiUrl = "$apiBaseUrl/api/swf/series?page=$page&column=last_chapter_date&direction=desc"
val apiUrl = "$apiBaseUrl/api/swf/new-chapters?page=$page"
return GET(apiUrl, headers)
}
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
override fun latestUpdatesParse(response: Response): MangasPage {
val result = json.decodeFromString<PayloadLatestDto>(response.body.string())
val mangaList = result.data.filter { it.type == "comic" }.map { it.toSManga() }
return MangasPage(mangaList, result.hasNextPage())
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val sortByFilter = filters.firstInstanceOrNull<SortByFilter>()
@ -66,6 +76,7 @@ class IkigaiMangas : HttpSource() {
if (query.isNotEmpty()) apiUrl.addQueryParameter("search", query)
apiUrl.addQueryParameter("page", page.toString())
apiUrl.addQueryParameter("type", "comic")
val genres = filters.firstInstanceOrNull<GenreFilter>()?.state.orEmpty()
.filter(Genre::state)
@ -82,23 +93,14 @@ class IkigaiMangas : HttpSource() {
apiUrl.addQueryParameter("column", sortByFilter?.selected ?: "name")
apiUrl.addQueryParameter("direction", if (sortByFilter?.state?.ascending == true) "asc" else "desc")
apiUrl.addQueryParameter("type", "comic")
return GET(apiUrl.build(), headers)
}
override fun searchMangaParse(response: Response): MangasPage {
runCatching { fetchFilters() }
val result = json.decodeFromString<PayloadSeriesDto>(response.body.string())
val mangaList = result.data.filter { it.type == "comic" }.map {
SManga.create().apply {
url = "/series/comic-${it.slug}#${it.id}"
title = it.name
thumbnail_url = it.cover
}
}
val hasNextPage = result.currentPage < result.lastPage
return MangasPage(mangaList, hasNextPage)
val mangaList = result.data.filter { it.type == "comic" }.map { it.toSManga() }
return MangasPage(mangaList, result.hasNextPage())
}
override fun mangaDetailsParse(response: Response): SManga {
@ -109,13 +111,7 @@ class IkigaiMangas : HttpSource() {
val apiUrl = "$apiBaseUrl/api/swf/series/$slug".toHttpUrl()
val newResponse = client.newCall(GET(url = apiUrl, headers = headers)).execute()
val result = json.decodeFromString<PayloadSeriesDetailsDto>(newResponse.body.string())
return SManga.create().apply {
title = result.series.name
thumbnail_url = result.series.cover
description = result.series.summary
status = parseStatus(result.series.status?.id)
genre = result.series.genres?.joinToString { it.name.trim() }
}
return result.series.toSMangaDetails()
}
override fun getChapterUrl(chapter: SChapter): String = pageViewerUrl + chapter.url
@ -127,14 +123,7 @@ class IkigaiMangas : HttpSource() {
override fun chapterListParse(response: Response): List<SChapter> {
val result = json.decodeFromString<PayloadChaptersDto>(response.body.string())
return result.data.map {
SChapter.create().apply {
url = "/capitulo/${it.id}"
name = "Capítulo ${it.name}"
date_upload = runCatching { dateFormat.parse(it.date)?.time }
.getOrNull() ?: 0L
}
}.reversed()
return result.data.map { it.toSChapter(dateFormat) }.reversed()
}
override fun pageListRequest(chapter: SChapter): Request {
@ -150,33 +139,14 @@ class IkigaiMangas : HttpSource() {
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
private fun parseStatus(statusId: Long?) = when (statusId) {
906397890812182531, 911437469204086787 -> SManga.ONGOING
906409397258190851 -> SManga.ON_HIATUS
906409532796731395, 911793517664960513 -> SManga.COMPLETED
906426661911756802, 906428048651190273, 911793767845265410, 911793856861798402 -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
data class SortProperty(val name: String, val value: String) {
override fun toString(): String = name
}
private fun getSortProperties(): List<SortProperty> = listOf(
SortProperty("Nombre", "name"),
SortProperty("Creado en", "created_at"),
SortProperty("Actualización más reciente", "last_chapter_date"),
SortProperty("Número de favoritos", "bookmark_count"),
SortProperty("Número de valoración", "rating_count"),
SortProperty("Número de vistas", "view_count"),
)
override fun getFilterList(): FilterList {
fetchFilters()
val filters = mutableListOf<Filter<*>>(
SortByFilter("Ordenar por", getSortProperties()),
)
filters += if (genresList.isNotEmpty() || statusesList.isNotEmpty()) {
filters += if (filtersState == FiltersState.FETCHED) {
listOf(
StatusFilter("Estados", getStatusFilters()),
GenreFilter("Géneros", getGenreFilters()),
@ -190,27 +160,44 @@ class IkigaiMangas : HttpSource() {
return FilterList(filters)
}
private fun getSortProperties(): List<SortProperty> = listOf(
SortProperty("Nombre", "name"),
SortProperty("Creado en", "created_at"),
SortProperty("Actualización más reciente", "last_chapter_date"),
SortProperty("Número de favoritos", "bookmark_count"),
SortProperty("Número de valoración", "rating_count"),
SortProperty("Número de vistas", "view_count"),
)
private fun getGenreFilters(): List<Genre> = genresList.map { Genre(it.first, it.second) }
private fun getStatusFilters(): List<Status> = statusesList.map { Status(it.first, it.second) }
private var genresList: List<Pair<String, Long>> = emptyList()
private var statusesList: List<Pair<String, Long>> = emptyList()
private var fetchFiltersAttempts = 0
private var fetchFiltersFailed = false
private var filtersState = FiltersState.NOT_FETCHED
private fun fetchFilters() {
if (fetchFiltersAttempts <= 3 && ((genresList.isEmpty() && statusesList.isEmpty()) || fetchFiltersFailed)) {
val filters = runCatching {
if (filtersState != FiltersState.NOT_FETCHED || fetchFiltersAttempts >= 3) return
filtersState = FiltersState.FETCHING
fetchFiltersAttempts++
thread {
try {
val response = client.newCall(GET("$apiBaseUrl/api/swf/filter-options", headers)).execute()
json.decodeFromString<PayloadFiltersDto>(response.body.string())
}
val filters = json.decodeFromString<PayloadFiltersDto>(response.body.string())
fetchFiltersFailed = filters.isFailure
genresList = filters.getOrNull()?.data?.genres?.map { it.name.trim() to it.id } ?: emptyList()
statusesList = filters.getOrNull()?.data?.statuses?.map { it.name.trim() to it.id } ?: emptyList()
genresList = filters.data.genres.map { it.name.trim() to it.id }
statusesList = filters.data.statuses.map { it.name.trim() to it.id }
filtersState = FiltersState.FETCHED
} catch (e: Throwable) {
filtersState = FiltersState.NOT_FETCHED
}
}
}
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().firstOrNull()
private enum class FiltersState { NOT_FETCHED, FETCHING, FETCHED }
}

View File

@ -1,73 +1,133 @@
package eu.kanade.tachiyomi.extension.es.ikigaimangas
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.text.SimpleDateFormat
@Serializable
data class PayloadSeriesDto(
val data: List<SeriesDto>,
@SerialName("current_page")val currentPage: Int = 0,
@SerialName("last_page") val lastPage: Int = 0,
)
class PayloadLatestDto(
val data: List<LatestDto>,
@SerialName("current_page") private val currentPage: Int = 0,
@SerialName("last_page") private val lastPage: Int = 0,
) {
fun hasNextPage() = currentPage < lastPage
}
@Serializable
data class SeriesDto(
val id: Long,
val name: String,
val slug: String,
val cover: String? = null,
class LatestDto(
@SerialName("series_id") private val id: Long,
@SerialName("series_name") private val name: String,
@SerialName("series_slug") private val slug: String,
private val thumbnail: String? = null,
val type: String? = null,
val summary: String? = null,
val status: SeriesStatusDto? = null,
val genres: List<FilterDto>? = null,
)
) {
fun toSManga() = SManga.create().apply {
url = "/series/comic-$slug#$id"
title = name
thumbnail_url = thumbnail
}
}
@Serializable
data class PayloadSeriesDetailsDto(
class PayloadSeriesDto(
val data: List<SeriesDto>,
@SerialName("current_page") private val currentPage: Int = 0,
@SerialName("last_page") private val lastPage: Int = 0,
) {
fun hasNextPage() = currentPage < lastPage
}
@Serializable
class SeriesDto(
private val id: Long,
private val name: String,
private val slug: String,
private val cover: String? = null,
val type: String? = null,
private val summary: String? = null,
private val status: SeriesStatusDto? = null,
private val genres: List<FilterDto>? = null,
) {
fun toSManga() = SManga.create().apply {
url = "/series/comic-$slug#$id"
title = name
thumbnail_url = cover
}
fun toSMangaDetails() = SManga.create().apply {
title = name
thumbnail_url = cover
description = summary
status = parseStatus(this@SeriesDto.status?.id)
genre = genres?.joinToString { it.name.trim() }
}
private fun parseStatus(statusId: Long?) = when (statusId) {
906397890812182531, 911437469204086787 -> SManga.ONGOING
906409397258190851 -> SManga.ON_HIATUS
906409532796731395, 911793517664960513 -> SManga.COMPLETED
906426661911756802, 906428048651190273, 911793767845265410, 911793856861798402 -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
}
@Serializable
class PayloadSeriesDetailsDto(
val series: SeriesDto,
)
@Serializable
data class PayloadChaptersDto(
class PayloadChaptersDto(
var data: List<ChapterDto>,
)
@Serializable
data class ChapterDto(
val id: Long,
val name: String,
class ChapterDto(
private val id: Long,
private val name: String,
@SerialName("published_at") val date: String,
)
) {
fun toSChapter(dateFormat: SimpleDateFormat) = SChapter.create().apply {
url = "/capitulo/$id"
name = "Capítulo ${this@ChapterDto.name}"
date_upload = try {
dateFormat.parse(date)?.time ?: 0L
} catch (e: Exception) {
0L
}
}
}
@Serializable
data class PayloadPagesDto(
class PayloadPagesDto(
val chapter: PageDto,
)
@Serializable
data class PageDto(
class PageDto(
val pages: List<String>,
)
@Serializable
data class SeriesStatusDto(
class SeriesStatusDto(
val id: Long,
val name: String,
)
@Serializable
data class PayloadFiltersDto(
class PayloadFiltersDto(
val data: GenresStatusesDto,
)
@Serializable
data class GenresStatusesDto(
class GenresStatusesDto(
val genres: List<FilterDto>,
val statuses: List<FilterDto>,
)
@Serializable
data class FilterDto(
class FilterDto(
val id: Long,
val name: String,
)

View File

@ -8,7 +8,7 @@ class GenreFilter(title: String, genres: List<Genre>) : Filter.Group<Genre>(titl
class Status(title: String, val id: Long) : Filter.CheckBox(title)
class StatusFilter(title: String, statuses: List<Status>) : Filter.Group<Status>(title, statuses)
class SortByFilter(title: String, private val sortProperties: List<IkigaiMangas.SortProperty>) : Filter.Sort(
class SortByFilter(title: String, private val sortProperties: List<SortProperty>) : Filter.Sort(
title,
sortProperties.map { it.name }.toTypedArray(),
Selection(0, ascending = true),
@ -16,3 +16,7 @@ class SortByFilter(title: String, private val sortProperties: List<IkigaiMangas.
val selected: String
get() = sortProperties[state!!.index].value
}
class SortProperty(val name: String, val value: String) {
override fun toString(): String = name
}