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