diff --git a/src/es/ikigaimangas/build.gradle b/src/es/ikigaimangas/build.gradle index f3e289e70..c2b395106 100644 --- a/src/es/ikigaimangas/build.gradle +++ b/src/es/ikigaimangas/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Ikigai Mangas' extClass = '.IkigaiMangas' - extVersionCode = 1 + extVersionCode = 2 isNsfw = true } diff --git a/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangas.kt b/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangas.kt index ab744b1fb..3f94f73b1 100644 --- a/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangas.kt +++ b/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangas.kt @@ -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(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(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() @@ -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()?.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(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(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 { val result = json.decodeFromString(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 = 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>( 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 = 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 = genresList.map { Genre(it.first, it.second) } private fun getStatusFilters(): List = statusesList.map { Status(it.first, it.second) } private var genresList: List> = emptyList() private var statusesList: List> = 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(response.body.string()) - } + val filters = json.decodeFromString(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 List<*>.firstInstanceOrNull(): R? = filterIsInstance().firstOrNull() + + private enum class FiltersState { NOT_FETCHED, FETCHING, FETCHED } } diff --git a/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasDto.kt b/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasDto.kt index 41899f36e..57edc0e0f 100644 --- a/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasDto.kt +++ b/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasDto.kt @@ -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, - @SerialName("current_page")val currentPage: Int = 0, - @SerialName("last_page") val lastPage: Int = 0, -) +class PayloadLatestDto( + val data: List, + @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? = 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, + @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? = 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, ) @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, ) @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, val statuses: List, ) @Serializable -data class FilterDto( +class FilterDto( val id: Long, val name: String, ) diff --git a/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasFilters.kt b/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasFilters.kt index e34f90192..961f25c98 100644 --- a/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasFilters.kt +++ b/src/es/ikigaimangas/src/eu/kanade/tachiyomi/extension/es/ikigaimangas/IkigaiMangasFilters.kt @@ -8,7 +8,7 @@ class GenreFilter(title: String, genres: List) : Filter.Group(titl class Status(title: String, val id: Long) : Filter.CheckBox(title) class StatusFilter(title: String, statuses: List) : Filter.Group(title, statuses) -class SortByFilter(title: String, private val sortProperties: List) : Filter.Sort( +class SortByFilter(title: String, private val sortProperties: List) : Filter.Sort( title, sortProperties.map { it.name }.toTypedArray(), Selection(0, ascending = true), @@ -16,3 +16,7 @@ class SortByFilter(title: String, private val sortProperties: List