EternalMangas: Move to es and change theme (#9243)

move to ES
This commit is contained in:
kanoou 2025-06-14 03:50:22 -05:00 committed by Draff
parent 66f2a0ed6e
commit dd595cca72
Signed by: Draff
GPG Key ID: E8A89F3211677653
9 changed files with 13 additions and 186 deletions

View File

@ -1,168 +0,0 @@
package eu.kanade.tachiyomi.extension.all.eternalmangas
import eu.kanade.tachiyomi.multisrc.mangaesp.MangaEsp
import eu.kanade.tachiyomi.multisrc.mangaesp.SeriesDto
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import rx.Observable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
open class EternalMangas(
lang: String,
private val internalLang: String,
) : MangaEsp(
"EternalMangas",
"https://eternalmangas.com",
lang,
) {
override val useApiSearch = true
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return super.fetchSearchManga(page, "", createSortFilter("views", false))
}
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return super.fetchSearchManga(page, "", createSortFilter("updated_at", false))
}
override fun List<SeriesDto>.additionalParse(): List<SeriesDto> {
return this.filter { it.language == internalLang }.toMutableList()
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/comics", headers)
}
private val dataUrl = "https://raw.githubusercontent.com/bapeey/extensions-tools/refs/heads/main/keiyoushi/eternalmangas/values.txt"
override fun searchMangaParse(
response: Response,
page: Int,
query: String,
filters: FilterList,
): MangasPage {
val (apiComicsUrl, jsonHeaders, useApi, scriptSelector, comicsRegex) = client.newCall(GET(dataUrl)).execute().body.string().split("\n")
val apiSearch = useApi == "1"
comicsList = if (apiSearch) {
val headersJson = json.parseToJsonElement(jsonHeaders).jsonObject
val apiHeaders = headersBuilder()
headersJson.forEach { (key, jsonElement) ->
var value = jsonElement.jsonPrimitive.contentOrNull.orEmpty()
if (value.startsWith("1-")) {
val match = value.substringAfter("-").toRegex().find(response.body.string())
value = match?.groupValues?.get(1).orEmpty()
} else {
value = value.substringAfter("-")
}
apiHeaders.add(key, value)
}
val apiResponse = client.newCall(GET(apiComicsUrl, apiHeaders.build())).execute()
json.decodeFromString<List<SeriesDto>>(apiResponse.body.string()).toMutableList()
} else {
val script = response.asJsoup().select(scriptSelector).joinToString { it.data() }
val jsonString = comicsRegex.toRegex().find(script)?.groupValues?.get(1)
?: throw Exception(intl["comics_list_error"])
val unescapedJson = jsonString.unescape()
json.decodeFromString<List<SeriesDto>>(unescapedJson).toMutableList()
}
return parseComicsList(page, query, filters)
}
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val body = jsRedirect(response)
MANGA_DETAILS_REGEX.find(body)?.groupValues?.get(1)?.let {
val unescapedJson = it.unescape()
return json.decodeFromString<SeriesDto>(unescapedJson).toSMangaDetails()
}
val document = Jsoup.parse(body)
with(document.selectFirst("div#info")!!) {
title = select("div:has(p.font-bold:contains(Títuto)) > p.text-sm").text()
author = select("div:has(p.font-bold:contains(Autor)) > p.text-sm").text()
artist = select("div:has(p.font-bold:contains(Artista)) > p.text-sm").text()
genre = select("div:has(p.font-bold:contains(Género)) > p.text-sm > span").joinToString { it.ownText() }
}
description = document.select("div#sinopsis p").text()
thumbnail_url = document.selectFirst("div.contenedor img.object-cover")?.imgAttr()
}
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
override fun chapterListParse(response: Response): List<SChapter> {
val body = jsRedirect(response)
MANGA_DETAILS_REGEX.find(body)?.groupValues?.get(1)?.let {
val unescapedJson = it.unescape()
val series = json.decodeFromString<SeriesDto>(unescapedJson)
return series.chapters.map { chapter -> chapter.toSChapter(seriesPath, series.slug) }
}
val document = Jsoup.parse(body)
return document.select("div.contenedor > div.grid > div > a").map {
SChapter.create().apply {
name = it.selectFirst("span.text-sm")!!.text()
date_upload = try {
it.selectFirst("span.chapter-date")?.attr("data-date")?.let { date ->
dateFormat.parse(date)?.time
} ?: 0
} catch (e: ParseException) {
0
}
setUrlWithoutDomain(it.selectFirst("a")!!.attr("href"))
}
}
}
override fun pageListParse(response: Response): List<Page> {
val doc = Jsoup.parse(jsRedirect(response))
return doc.select("main > img").mapIndexed { i, img ->
Page(i, imageUrl = img.imgAttr())
}
}
private fun jsRedirect(response: Response): String {
var body = response.body.string()
val document = Jsoup.parse(body)
document.selectFirst("body > form[method=post], body > div[hidden] > form[method=post]")?.let {
val action = it.attr("action")
val inputs = it.select("input")
val form = FormBody.Builder()
inputs.forEach { input ->
form.add(input.attr("name"), input.attr("value"))
}
body = client.newCall(POST(action, headers, form.build())).execute().body.string()
}
return body
}
private fun createSortFilter(value: String, ascending: Boolean = false): FilterList {
val sortProperties = getSortProperties()
val index = sortProperties.indexOfFirst { it.value == value }.takeIf { it >= 0 } ?: 0
return FilterList(
SortByFilter("", sortProperties).apply {
state = Filter.Sort.Selection(index, ascending)
},
)
}
}

View File

@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.extension.all.eternalmangas
import eu.kanade.tachiyomi.source.SourceFactory
class EternalMangasFactory : SourceFactory {
override fun createSources() = listOf(
EternalMangasES(),
EternalMangasEN(),
EternalMangasPTBR(),
)
}
class EternalMangasES : EternalMangas("es", "es")
class EternalMangasEN : EternalMangas("en", "en")
class EternalMangasPTBR : EternalMangas("pt-BR", "pt")

View File

@ -1,9 +1,9 @@
ext {
extName = 'EternalMangas'
extClass = '.EternalMangasFactory'
themePkg = 'mangaesp'
extClass = '.EternalMangas'
themePkg = 'iken'
baseUrl = 'https://eternalmangas.com'
overrideVersionCode = 5
overrideVersionCode = 0
isNsfw = true
}

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.extension.es.eternalmangas
import eu.kanade.tachiyomi.multisrc.iken.Iken
class EternalMangas : Iken(
"EternalMangas",
"es",
"https://eternalmangas.com",
"https://api.eternalmangas.com",
)