From 314b8f3848e8e6972072cc14c155e62aed53bd4c Mon Sep 17 00:00:00 2001 From: Chopper <156493704+choppeh@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:09:14 -0300 Subject: [PATCH] FenixManhwas: Redesign (#10336) --- src/pt/fenixmanhwas/build.gradle | 4 +- .../extension/pt/fenixmanhwas/FenixManhwas.kt | 150 +++++++++++++++++- 2 files changed, 143 insertions(+), 11 deletions(-) diff --git a/src/pt/fenixmanhwas/build.gradle b/src/pt/fenixmanhwas/build.gradle index d09dd6194..3c7b43bfd 100644 --- a/src/pt/fenixmanhwas/build.gradle +++ b/src/pt/fenixmanhwas/build.gradle @@ -1,9 +1,7 @@ ext { extName = 'Fênix Manhwas' extClass = '.FenixManhwas' - themePkg = 'madara' - baseUrl = 'https://fenixscan.xyz' - overrideVersionCode = 2 + extVersionCode = 45 isNsfw = false } diff --git a/src/pt/fenixmanhwas/src/eu/kanade/tachiyomi/extension/pt/fenixmanhwas/FenixManhwas.kt b/src/pt/fenixmanhwas/src/eu/kanade/tachiyomi/extension/pt/fenixmanhwas/FenixManhwas.kt index 5c9c84452..b4dc448ff 100644 --- a/src/pt/fenixmanhwas/src/eu/kanade/tachiyomi/extension/pt/fenixmanhwas/FenixManhwas.kt +++ b/src/pt/fenixmanhwas/src/eu/kanade/tachiyomi/extension/pt/fenixmanhwas/FenixManhwas.kt @@ -1,12 +1,146 @@ package eu.kanade.tachiyomi.extension.pt.fenixmanhwas -import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +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 eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import java.util.Calendar -class FenixManhwas : Madara( - "Fênix Manhwas", - "https://fenixscan.xyz", - "pt-BR", -) { - override val versionId = 2 - override val useNewChapterEndpoint = true +class FenixManhwas : HttpSource() { + + override val name: String = "Fênix Manhwas" + + override val baseUrl: String = "https://fenixscan.xyz" + + override val lang: String = "pt-BR" + + override val supportsLatest: Boolean = true + + override val versionId: Int = 3 + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimit(3) + .build() + + // ========================== Popular ========================== + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select(".obra-slider a.obra-slide-item").map { element -> + SManga.create().apply { + title = element.selectFirst(".obra-slide-title")!!.text() + thumbnail_url = element.selectFirst("img")?.absUrl("src") + setUrlWithoutDomain(element.absUrl("href")) + } + } + return MangasPage(mangas, hasNextPage = false) + } + + // ========================== Latest ========================== + + override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) + + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select("#lancamentos-container .lancamento-bloco").map { element -> + SManga.create().apply { + title = element.selectFirst(".lancamento-titulo")!!.text() + thumbnail_url = element.selectFirst("img")?.absUrl("src") + setUrlWithoutDomain(element.selectFirst("a.lancamento-thumb-link")!!.absUrl("href")) + } + } + return MangasPage(mangas, hasNextPage = false) + } + + // ========================== Search ========================== + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return Observable.zip(fetchPopularManga(page), fetchLatestUpdates(page)) { popularPage, latestPage -> + val distinctMangas = (popularPage.mangas + latestPage.mangas) + .distinctBy(SManga::url) + .filter { it.title.contains(query, ignoreCase = true) } + MangasPage(distinctMangas, hasNextPage = false) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + throw UnsupportedOperationException() + + override fun searchMangaParse(response: Response): MangasPage = + throw UnsupportedOperationException() + + // ========================== Details ========================== + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + return SManga.create().apply { + title = document.selectFirst("h1.obra-title")!!.text() + thumbnail_url = document.selectFirst("img.obra-thumb")?.absUrl("src") + description = document.selectFirst(".obra-excerpt")?.text() + genre = document.select(".obra-genero").joinToString { it.text() } + author = document.selectFirst(".info-pill:has(.mdi-account)")?.text() + status = when (document.selectFirst(".info-pill:has(.mdi-information)")?.text()?.lowercase()) { + "andamento" -> SManga.ONGOING + else -> SManga.UNKNOWN + } + } + } + + // ========================== Chapters ========================== + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + return document.select(".chapters-grid > .chapter-item").map { element -> + SChapter.create().apply { + name = element.selectFirst(".chapter-title-text")!!.text() + chapter_number = element.attr("data-num").toFloat() + element.selectFirst("small")?.text()?.let(::parseRelativeDate)?.let { + date_upload = it + } + setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) + } + } + } + + private fun parseRelativeDate(date: String): Long { + val number = DATE_NUMBER_REGEX.find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + date.contains("segundo", ignoreCase = true) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + date.contains("minuto", ignoreCase = true) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + date.contains("hora", ignoreCase = true) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + date.contains("dia", ignoreCase = true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + date.contains("semana", ignoreCase = true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + date.contains("mes", ignoreCase = true) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + date.contains("ano", ignoreCase = true) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + // ========================== Pages ========================== + + override fun pageListParse(response: Response): List { + val document = response.asJsoup() + return document.select(".chapter-content img.lozad").mapIndexed { index, element -> + Page(index, imageUrl = element.absUrl("data-src")) + } + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() + + companion object { + private val DATE_NUMBER_REGEX = """(\d+)""".toRegex() + } }