IkigaiMangas: Replace popular and latest endpoints + convention changes (#1689)
* Update * Follow exactly site order * Replace popular and latest endpoints
This commit is contained in:
parent
5344c62b6b
commit
cf4a208d08
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Ikigai Mangas'
|
||||
extClass = '.IkigaiMangas'
|
||||
extVersionCode = 1
|
||||
extVersionCode = 2
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user