From 7503c2d20e2689b117c6207b478ad9ebc4af064d Mon Sep 17 00:00:00 2001 From: Chopper <156493704+choppeh@users.noreply.github.com> Date: Wed, 7 May 2025 12:26:34 -0300 Subject: [PATCH] ApeComics, Atemporal, Crystal Comics: Migrate to Mangathemesia (#8741) --- src/pt/apecomics/build.gradle | 4 +- .../extension/pt/apecomics/ApeComics.kt | 11 +- src/pt/atemporal/build.gradle | 4 +- .../extension/pt/atemporal/Atemporal.kt | 19 +- src/pt/crystalcomics/AndroidManifest.xml | 22 -- src/pt/crystalcomics/build.gradle | 5 +- .../pt/crystalcomics/CrystalComics.kt | 257 +----------------- .../crystalcomics/CrystalComicsUrlActivity.kt | 37 --- 8 files changed, 34 insertions(+), 325 deletions(-) delete mode 100644 src/pt/crystalcomics/AndroidManifest.xml delete mode 100644 src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComicsUrlActivity.kt diff --git a/src/pt/apecomics/build.gradle b/src/pt/apecomics/build.gradle index de061a2eb..ed6693e0a 100644 --- a/src/pt/apecomics/build.gradle +++ b/src/pt/apecomics/build.gradle @@ -1,9 +1,9 @@ ext { extName = 'ApeComics' extClass = '.ApeComics' - themePkg = 'madara' + themePkg = 'mangathemesia' baseUrl = 'https://apecomics.net' - overrideVersionCode = 0 + overrideVersionCode = 13 isNsfw = false } diff --git a/src/pt/apecomics/src/eu/kanade/tachiyomi/extension/pt/apecomics/ApeComics.kt b/src/pt/apecomics/src/eu/kanade/tachiyomi/extension/pt/apecomics/ApeComics.kt index 9f187b383..99b9db070 100644 --- a/src/pt/apecomics/src/eu/kanade/tachiyomi/extension/pt/apecomics/ApeComics.kt +++ b/src/pt/apecomics/src/eu/kanade/tachiyomi/extension/pt/apecomics/ApeComics.kt @@ -1,14 +1,17 @@ package eu.kanade.tachiyomi.extension.pt.apecomics -import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit import java.text.SimpleDateFormat import java.util.Locale -class ApeComics : Madara( +class ApeComics : MangaThemesia( "ApeComics", "https://apecomics.net", "pt-BR", - SimpleDateFormat("MMMM dd, yyyy", Locale("pt", "BR")), + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("pt", "BR")), ) { - override val useNewChapterEndpoint = true + override val client = super.client.newBuilder() + .rateLimit(2) + .build() } diff --git a/src/pt/atemporal/build.gradle b/src/pt/atemporal/build.gradle index f267224cf..67ea55713 100644 --- a/src/pt/atemporal/build.gradle +++ b/src/pt/atemporal/build.gradle @@ -1,9 +1,9 @@ ext { extName = 'Atemporal' extClass = '.Atemporal' - themePkg = 'madara' + themePkg = 'mangathemesia' baseUrl = 'https://atemporal.cloud' - overrideVersionCode = 1 + overrideVersionCode = 14 isNsfw = false } diff --git a/src/pt/atemporal/src/eu/kanade/tachiyomi/extension/pt/atemporal/Atemporal.kt b/src/pt/atemporal/src/eu/kanade/tachiyomi/extension/pt/atemporal/Atemporal.kt index 89fa6f63b..d204d0758 100644 --- a/src/pt/atemporal/src/eu/kanade/tachiyomi/extension/pt/atemporal/Atemporal.kt +++ b/src/pt/atemporal/src/eu/kanade/tachiyomi/extension/pt/atemporal/Atemporal.kt @@ -1,24 +1,17 @@ package eu.kanade.tachiyomi.extension.pt.atemporal -import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor -import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.HttpUrl.Companion.toHttpUrl +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit import java.text.SimpleDateFormat import java.util.Locale -class Atemporal : Madara( +class Atemporal : MangaThemesia( "Atemporal", "https://atemporal.cloud", "pt-BR", - dateFormat = SimpleDateFormat("d 'de' MMMM 'de' yyyy", Locale("pt", "BR")), + dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale("pt", "BR")), ) { - // Cookie required for search - private val cookieInterceptor = CookieInterceptor(baseUrl.toHttpUrl().host, "visited" to "true") - - override val client = network.cloudflareClient.newBuilder() - .addNetworkInterceptor(cookieInterceptor) + override val client = super.client.newBuilder() + .rateLimit(2) .build() - - override val useNewChapterEndpoint = true - override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" } diff --git a/src/pt/crystalcomics/AndroidManifest.xml b/src/pt/crystalcomics/AndroidManifest.xml deleted file mode 100644 index bcbed1536..000000000 --- a/src/pt/crystalcomics/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/pt/crystalcomics/build.gradle b/src/pt/crystalcomics/build.gradle index 37c4c76e4..54048cac7 100644 --- a/src/pt/crystalcomics/build.gradle +++ b/src/pt/crystalcomics/build.gradle @@ -1,7 +1,10 @@ ext { extName = 'Crystal Comics' extClass = '.CrystalComics' - extVersionCode = 39 + themePkg = 'mangathemesia' + baseUrl = 'https://atemporal.cloud' + overrideVersionCode = 10 + isNsfw = false } apply from: "$rootDir/common.gradle" diff --git a/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComics.kt b/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComics.kt index 42fb11bee..7c846a19b 100644 --- a/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComics.kt +++ b/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComics.kt @@ -1,251 +1,20 @@ package eu.kanade.tachiyomi.extension.pt.crystalcomics -import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia 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 eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable +import java.text.SimpleDateFormat +import java.util.Locale -// Etoshore -class CrystalComics : ParsedHttpSource() { +class CrystalComics : MangaThemesia( + "Crystal Comics", + "https://crystalcomics.com", + "pt-BR", + dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale("pt", "BR")), +) { + // Migrate from Etoshore to MangaThemesia + override val versionId = 2 - override val name = "Crystal Comics" - - override val baseUrl = "https://crystalcomics.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(1, 2) + override val client = super.client.newBuilder() + .rateLimit(2) .build() - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") - - // ============================== Popular ============================== - - open val popularFilter = FilterList( - SelectionList("", listOf(Tag(value = "views", query = "sort"))), - ) - - override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter) - override fun popularMangaParse(response: Response) = searchMangaParse(response) - - override fun popularMangaSelector() = throw UnsupportedOperationException() - override fun popularMangaNextPageSelector() = throw UnsupportedOperationException() - override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException() - - // ============================== Latest =============================== - - open val latestFilter = FilterList( - SelectionList("", listOf(Tag(value = "date", query = "sort"))), - ) - - override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter) - override fun latestUpdatesParse(response: Response) = searchMangaParse(response) - - override fun latestUpdatesSelector() = throw UnsupportedOperationException() - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() - - // ============================== Search =============================== - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/page/$page".toHttpUrl().newBuilder() - .addQueryParameter("s", query) - - filters.forEach { filter -> - when (filter) { - is SelectionList -> { - val selected = filter.selected() - url.addQueryParameter(selected.query, selected.value) - } - else -> {} - } - } - - return GET(url.build(), headers) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - if (query.startsWith(PREFIX_SEARCH)) { - val slug = query.substringAfter(PREFIX_SEARCH) - return fetchMangaDetails(SManga.create().apply { url = "/manga/$slug/" }) - .map { manga -> MangasPage(listOf(manga), false) } - } - return super.fetchSearchManga(page, query, filters) - } - - override fun searchMangaSelector() = ".search-posts .chapter-box .poster a" - - override fun searchMangaNextPageSelector() = ".navigation .naviright:has(a)" - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - title = element.attr("title") - thumbnail_url = element.selectFirst("img")?.let(::imageFromElement) - setUrlWithoutDomain(element.absUrl("href")) - } - - override fun searchMangaParse(response: Response): MangasPage { - if (filterList.isEmpty()) { - filterParse(response) - } - return super.searchMangaParse(response) - } - - // ============================== Details =============================== - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.selectFirst("h1")!!.text() - description = document.selectFirst(".excerpt p")?.text() - document.selectFirst(".details-right-con img")?.let { thumbnail_url = imageFromElement(it) } - genre = document.select("div.meta-item span.meta-title:contains(Genres) + span a") - .joinToString { it.text() } - author = document.selectFirst("div.meta-item span.meta-title:contains(Author) + span a") - ?.text() - document.selectFirst(".status")?.text()?.let { - status = it.toMangaStatus() - } - - setUrlWithoutDomain(document.location()) - } - - protected open fun imageFromElement(element: Element): String? { - return when { - element.hasAttr("data-src") -> element.attr("abs:data-src") - element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src") - element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage() - element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc") - else -> element.attr("abs:src") - } - } - - protected open fun String.getSrcSetImage(): String? { - return this.split(" ") - .filter(URL_REGEX::matches) - .maxOfOrNull(String::toString) - } - - protected val completedStatusList: Array = arrayOf( - "Finished", - "Completo", - ) - - protected open val ongoingStatusList: Array = arrayOf( - "Publishing", - "Ativo", - ) - - protected val hiatusStatusList: Array = arrayOf( - "on hiatus", - ) - - protected val canceledStatusList: Array = arrayOf( - "Canceled", - "Discontinued", - ) - - open fun String.toMangaStatus(): Int { - return when { - containsIn(completedStatusList) -> SManga.COMPLETED - containsIn(ongoingStatusList) -> SManga.ONGOING - containsIn(hiatusStatusList) -> SManga.ON_HIATUS - containsIn(canceledStatusList) -> SManga.CANCELLED - else -> SManga.UNKNOWN - } - } - - // ============================== Chapters ============================ - - override fun chapterListSelector() = ".chapter-list li a" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - name = element.selectFirst(".title")!!.text() - setUrlWithoutDomain(element.absUrl("href")) - } - - // ============================== Pages =============================== - - override fun pageListParse(document: Document): List { - return document.select(".chapter-images .chapter-item > img").mapIndexed { index, element -> - Page(index, document.location(), imageFromElement(element)) - } - } - - override fun imageUrlParse(document: Document) = "" - - // ============================= Filters ============================== - - private var filterList = emptyList>>() - - override fun getFilterList(): FilterList { - val filters = mutableListOf>() - - filters += if (filterList.isNotEmpty()) { - filterList.map { SelectionList(it.first, it.second) } - } else { - listOf(Filter.Header("Aperte 'Redefinir' para tentar mostrar os filtros")) - } - - return FilterList(filters) - } - - protected open fun parseSelection(document: Document, selector: String): Pair>? { - val selectorFilter = "#filter-form $selector .select-item-head .text" - return document.selectFirst(selectorFilter)?.text()?.let { displayName -> - displayName to document.select("#filter-form $selector li").map { element -> - element.selectFirst("input")!!.let { input -> - Tag( - name = element.selectFirst(".text")!!.text(), - value = input.attr("value"), - query = input.attr("name"), - ) - } - } - } - } - - open val filterListSelector: List = listOf( - ".filter-genre", - ".filter-status", - ".filter-type", - ".filter-year", - ".filter-sort", - ) - - open fun filterParse(response: Response) { - val document = Jsoup.parseBodyFragment(response.peekBody(Long.MAX_VALUE).string()) - filterList = filterListSelector.mapNotNull { selector -> parseSelection(document, selector) } - } - - protected data class Tag(val name: String = "", val value: String = "", val query: String = "") - - private open class SelectionList(displayName: String, private val vals: List, state: Int = 0) : - Filter.Select(displayName, vals.map { it.name }.toTypedArray(), state) { - fun selected() = vals[state] - } - - // ============================= Utils ============================== - - private fun String.containsIn(array: Array): Boolean { - return this.lowercase() in array.map { it.lowercase() } - } - - companion object { - const val PREFIX_SEARCH = "id:" - val URL_REGEX = """^(https?://[^\s/$.?#].[^\s]*)${'$'}""".toRegex() - } } diff --git a/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComicsUrlActivity.kt b/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComicsUrlActivity.kt deleted file mode 100644 index 17f8d87ca..000000000 --- a/src/pt/crystalcomics/src/eu/kanade/tachiyomi/extension/pt/crystalcomics/CrystalComicsUrlActivity.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.crystalcomics - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class CrystalComicsUrlActivity : Activity() { - - private val tag = javaClass.simpleName - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val item = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${CrystalComics.PREFIX_SEARCH}$item") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e(tag, e.toString()) - } - } else { - Log.e(tag, "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -}