From cf4a208d087f5361a77b273854d66163c7ce7127 Mon Sep 17 00:00:00 2001
From: bapeey <90949336+bapeey@users.noreply.github.com>
Date: Mon, 4 Mar 2024 05:38:19 -0500
Subject: [PATCH] IkigaiMangas: Replace popular and latest endpoints +
 convention changes (#1689)

* Update

* Follow exactly site order

* Replace popular and latest endpoints
---
 src/es/ikigaimangas/build.gradle              |   2 +-
 .../extension/es/ikigaimangas/IkigaiMangas.kt | 109 ++++++++---------
 .../es/ikigaimangas/IkigaiMangasDto.kt        | 114 +++++++++++++-----
 .../es/ikigaimangas/IkigaiMangasFilters.kt    |   6 +-
 4 files changed, 141 insertions(+), 90 deletions(-)

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<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 }
 }
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<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,
 )
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<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
+}