From 1ec8554fe2138c1ecf92b4f3cfbc0845513bef6e Mon Sep 17 00:00:00 2001 From: manti <133025162+manti-X@users.noreply.github.com> Date: Mon, 29 Sep 2025 14:42:48 +0200 Subject: [PATCH] EZmanga: from HeanCms to Iken (#10755) theme switch --- src/en/ezmanga/build.gradle | 4 +- .../tachiyomi/extension/en/ezmanga/Dto.kt | 9 ++ .../tachiyomi/extension/en/ezmanga/EZmanga.kt | 128 +++++++++++++++++- 3 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/Dto.kt diff --git a/src/en/ezmanga/build.gradle b/src/en/ezmanga/build.gradle index 3e2950595..2f33ec17a 100644 --- a/src/en/ezmanga/build.gradle +++ b/src/en/ezmanga/build.gradle @@ -1,9 +1,9 @@ ext { extName = 'EZmanga' extClass = '.EZmanga' - themePkg = 'heancms' + themePkg = 'iken' baseUrl = 'https://ezmanga.org' - overrideVersionCode = 21 + overrideVersionCode = 41 isNsfw = false } diff --git a/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/Dto.kt b/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/Dto.kt new file mode 100644 index 000000000..1f609f5be --- /dev/null +++ b/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/Dto.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.ezmanga + +import kotlinx.serialization.Serializable + +@Serializable +class GenreDto(val id: Int, val name: String) + +@Serializable +class PageDto(val url: String, val order: Int) diff --git a/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/EZmanga.kt b/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/EZmanga.kt index 4235beeb2..1be6d644c 100644 --- a/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/EZmanga.kt +++ b/src/en/ezmanga/src/eu/kanade/tachiyomi/extension/en/ezmanga/EZmanga.kt @@ -1,14 +1,130 @@ package eu.kanade.tachiyomi.extension.en.ezmanga -import eu.kanade.tachiyomi.multisrc.heancms.HeanCms +import eu.kanade.tachiyomi.multisrc.iken.GenreFilter +import eu.kanade.tachiyomi.multisrc.iken.Iken +import eu.kanade.tachiyomi.multisrc.iken.SelectFilter +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import keiyoushi.utils.parseAs +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import rx.schedulers.Schedulers -class EZmanga : HeanCms( +class EZmanga : Iken( "EZmanga", - "https://ezmanga.org", "en", + "https://ezmanga.org", + "https://vapi.ezmanga.org", ) { - // Migrated from Keyoapp to HeanCms - override val versionId = 3 + // Migrated from HeanCms to Iken + override val versionId = 4 - override val useNewChapterEndpoint = true + override val client = super.client.newBuilder() + .rateLimit(3) + .build() + + override fun popularMangaRequest(page: Int): Request { + val url = "$apiUrl/api/query".toHttpUrl().newBuilder().apply { + addQueryParameter("page", page.toString()) + addQueryParameter("perPage", "18") + addQueryParameter("orderBy", "totalViews") + }.build() + return GET(url, headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + return searchMangaParse(response) + } + + override fun latestUpdatesRequest(page: Int): Request { + val url = "$apiUrl/api/query".toHttpUrl().newBuilder().apply { + addQueryParameter("page", page.toString()) + addQueryParameter("perPage", "18") + addQueryParameter("orderBy", "updatedAt") + }.build() + return GET(url, headers) + } + + override fun pageListRequest(chapter: SChapter): Request { + return GET(baseUrl + chapter.url, headersBuilder().add("rsc", "1").build()) + } + + override fun pageListParse(response: Response): List { + return response.body.string().lines() + .mapNotNull { line -> + val jsonStartIndex = line.indexOf('{').takeIf { it != -1 } ?: return@mapNotNull null + val jsonString = line.substring(jsonStartIndex) + try { + jsonString.parseAs().takeIf { it.url.isNotEmpty() } + } catch (e: Exception) { + null + } + } + .sortedBy { it.order } + .mapIndexed { i, p -> Page(i, imageUrl = p.url) } + } + + private var genresList: List> = emptyList() + private var fetchGenresAttempts = 0 + + private fun fetchGenres() { + try { + val response = client.newCall(GET("$apiUrl/api/genres", headers)).execute() + genresList = response.parseAs>() + .map { Pair(it.name, it.id.toString()) } + } catch (e: Throwable) { + } finally { + fetchGenresAttempts++ + } + } + + override fun getFilterList(): FilterList { + if (genresList.isEmpty() && fetchGenresAttempts < 3) { + Observable.fromCallable { fetchGenres() } + .subscribeOn(Schedulers.io()) + .subscribe() + } + + val filters = mutableListOf>( + SortFilter(), + StatusFilter(), + ) + + if (genresList.isNotEmpty()) { + filters.add(GenreFilter(genresList)) + } else { + filters.add(Filter.Header("Press 'Reset' to attempt to load genres")) + } + return FilterList(filters) + } + + private class SortFilter : SelectFilter( + "Sort", + "orderBy", + listOf( + Pair("Popularity", "totalViews"), + Pair("Latest", "updatedAt"), + Pair("Created at", "createdAt"), + Pair("Z-A", "postTitle"), + ), + ) + + private class StatusFilter : SelectFilter( + "Status", + "seriesStatus", + listOf( + Pair("All", ""), + Pair("Ongoing", "ONGOING"), + Pair("Hiatus", "HIATUS"), + Pair("Completed", "COMPLETED"), + Pair("Dropped", "DROPPED"), + ), + ) }