@ -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)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
|
@ -1,9 +1,9 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'EternalMangas'
|
extName = 'EternalMangas'
|
||||||
extClass = '.EternalMangasFactory'
|
extClass = '.EternalMangas'
|
||||||
themePkg = 'mangaesp'
|
themePkg = 'iken'
|
||||||
baseUrl = 'https://eternalmangas.com'
|
baseUrl = 'https://eternalmangas.com'
|
||||||
overrideVersionCode = 5
|
overrideVersionCode = 0
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@ -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",
|
||||||
|
)
|