mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-25 11:42:47 +01:00
parent
5e1d31f2ba
commit
d265f4bd77
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'HeavenManga'
|
||||
extClass = '.HeavenManga'
|
||||
extVersionCode = 6
|
||||
extVersionCode = 7
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -9,12 +9,17 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class HeavenManga : ParsedHttpSource() {
|
||||
|
||||
@ -24,81 +29,20 @@ class HeavenManga : ParsedHttpSource() {
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
// latest is broken on the site, it's the same as popular so turning it off
|
||||
override val supportsLatest = false
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
override fun headersBuilder(): Headers.Builder {
|
||||
return Headers.Builder()
|
||||
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/75")
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = "div.page-item-detail"
|
||||
|
||||
override fun latestUpdatesSelector() = "#container .ultimos_epis .not"
|
||||
|
||||
override fun searchMangaSelector() = "div.c-tabs-item__content, ${popularMangaSelector()}"
|
||||
|
||||
override fun chapterListSelector() = "div.listing-chapters_wrap tr"
|
||||
|
||||
override fun popularMangaNextPageSelector() = "ul.pagination a[rel=next]"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/top?orderby=views&page=$page", headers)
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/", headers)
|
||||
override fun popularMangaSelector() = "div.page-item-detail"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val searchUrl = "$baseUrl/buscar?query=$query"
|
||||
|
||||
// Filter
|
||||
val pageParameter = if (page > 1) "?page=$page" else ""
|
||||
|
||||
if (query.isBlank()) {
|
||||
val ext = ".html"
|
||||
var name: String
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is GenreFilter -> {
|
||||
if (filter.toUriPart().isNotBlank() && filter.state != 0) {
|
||||
name = filter.toUriPart()
|
||||
return GET("$baseUrl/genero/$name$ext$pageParameter", headers)
|
||||
}
|
||||
}
|
||||
is AlphabeticoFilter -> {
|
||||
if (filter.toUriPart().isNotBlank() && filter.state != 0) {
|
||||
name = filter.toUriPart()
|
||||
return GET("$baseUrl/letra/$name$ext$pageParameter", headers)
|
||||
}
|
||||
}
|
||||
is ListaCompletasFilter -> {
|
||||
if (filter.toUriPart().isNotBlank() && filter.state != 0) {
|
||||
name = filter.toUriPart()
|
||||
return GET("$baseUrl/$name$pageParameter", headers)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GET(searchUrl, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
return if (response.request.url.toString().contains("query=")) {
|
||||
super.searchMangaParse(response)
|
||||
} else {
|
||||
popularMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
// get contents of a url
|
||||
private fun getUrlContents(url: String): Document = client.newCall(GET(url, headers)).execute().asJsoup()
|
||||
override fun popularMangaNextPageSelector() = "ul.pagination a[rel=next]"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
@ -108,16 +52,93 @@ class HeavenManga : ParsedHttpSource() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val mangas = document.selectFirst(latestUpdatesWrapperSelector())!!
|
||||
.select(latestUpdatesSelector())
|
||||
.map { element ->
|
||||
latestUpdatesFromElement(element)
|
||||
}.distinctBy { it.url }
|
||||
|
||||
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
private fun latestUpdatesWrapperSelector() = ".container #loop-content "
|
||||
|
||||
override fun latestUpdatesSelector() = "span.list-group-item:not(:has(> div.row:containsOwn(Novela)))"
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? = null
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
||||
element.select("a").let {
|
||||
val latestChapter = getUrlContents(it.attr("href"))
|
||||
val url = latestChapter.select(".rpwe-clearfix:last-child a")
|
||||
setUrlWithoutDomain(url.attr("href"))
|
||||
title = it.select("span span").text()
|
||||
thumbnail_url = it.select("img").attr("src")
|
||||
with(element.selectFirst("a")!!) {
|
||||
val mangaUrl = attr("href").substringBeforeLast("/")
|
||||
setUrlWithoutDomain(mangaUrl)
|
||||
title = select(".captitle").text()
|
||||
thumbnail_url = mangaUrl.replace("/manga/", "/uploads/manga/") + "/cover/cover_250x350.jpg"
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = baseUrl.toHttpUrl().newBuilder()
|
||||
if (query.isNotBlank()) {
|
||||
if (query.length < 3) throw Exception("La búsqueda debe tener al menos 3 caracteres")
|
||||
url.addPathSegment("buscar")
|
||||
.addQueryParameter("query", query)
|
||||
} else {
|
||||
val ext = ".html"
|
||||
var name: String
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is GenreFilter -> {
|
||||
if (filter.toUriPart().isNotBlank() && filter.state != 0) {
|
||||
name = filter.toUriPart()
|
||||
url.addPathSegment("genero")
|
||||
.addPathSegment(name + ext)
|
||||
}
|
||||
}
|
||||
is AlphabeticoFilter -> {
|
||||
if (filter.toUriPart().isNotBlank() && filter.state != 0) {
|
||||
name = filter.toUriPart()
|
||||
url.addPathSegment("letra")
|
||||
.addPathSegment("manga$ext")
|
||||
.addQueryParameter("alpha", name)
|
||||
}
|
||||
}
|
||||
is ListaCompletasFilter -> {
|
||||
if (filter.toUriPart().isNotBlank() && filter.state != 0) {
|
||||
name = filter.toUriPart()
|
||||
url.addPathSegment(name)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) url.addQueryParameter("page", page.toString())
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
return if (response.request.url.pathSegments.contains("buscar")) {
|
||||
super.searchMangaParse(response)
|
||||
} else {
|
||||
popularMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "div.c-tabs-item__content"
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||
element.select("h4 a").let {
|
||||
title = it.text()
|
||||
@ -126,16 +147,6 @@ class HeavenManga : ParsedHttpSource() {
|
||||
thumbnail_url = element.select("img").attr("abs:data-src")
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
return SChapter.create().apply {
|
||||
element.select("a").let {
|
||||
name = it.text()
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
}
|
||||
scanlator = element.select("span.pull-right").text()
|
||||
}
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
document.select("div.tab-summary").let { info ->
|
||||
genre = info.select("div.genres-content a").joinToString { it.text() }
|
||||
@ -144,27 +155,60 @@ class HeavenManga : ParsedHttpSource() {
|
||||
description = document.select("div.description-summary p").text()
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
val mangaUrl = (baseUrl + manga.url).toHttpUrl().newBuilder()
|
||||
.addQueryParameter("columns[0][data]", "number")
|
||||
.addQueryParameter("columns[0][orderable]", "true")
|
||||
.addQueryParameter("columns[1][data]", "created_at")
|
||||
.addQueryParameter("columns[1][searchable]", "true")
|
||||
.addQueryParameter("order[0][column]", "1")
|
||||
.addQueryParameter("order[0][dir]", "desc")
|
||||
.addQueryParameter("start", "0")
|
||||
.addQueryParameter("length", CHAPTER_LIST_LIMIT.toString())
|
||||
|
||||
val headers = headersBuilder()
|
||||
.add("X-Requested-With", "XMLHttpRequest")
|
||||
.build()
|
||||
|
||||
return GET(mangaUrl.build(), headers)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val mangaUrl = response.request.url.toString().substringBefore("?").removeSuffix("/")
|
||||
val result = json.decodeFromString<PayloadChaptersDto>(response.body.string())
|
||||
return result.data.map {
|
||||
SChapter.create().apply {
|
||||
name = "Capítulo: ${it.slug}"
|
||||
setUrlWithoutDomain("$mangaUrl/${it.slug}#${it.id}")
|
||||
date_upload = runCatching { dateFormat.parse(it.createdAt)?.time ?: 0 }.getOrDefault(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = throw UnsupportedOperationException()
|
||||
|
||||
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException()
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
return getUrlContents(baseUrl + chapter.url).select("a[id=leer]").attr("abs:href")
|
||||
.let { GET(it, headers) }
|
||||
val chapterId = chapter.url.substringAfterLast("#")
|
||||
if (chapterId.isBlank()) throw Exception("Error al obtener el id del capítulo. Actualice la lista")
|
||||
val url = "$baseUrl/manga/leer/$chapterId"
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select("script:containsData(pUrl)").first()!!.data()
|
||||
.substringAfter("pUrl=[").substringBefore("\"},];").split("\"},")
|
||||
.mapIndexed { i, string -> Page(i, "", string.substringAfterLast("\"")) }
|
||||
val data = document.select("script:containsData(pUrl)").first()!!.data()
|
||||
val jsonString = PAGES_REGEX.find(data)!!.groupValues[1].removeTrailingComma()
|
||||
val pages = json.decodeFromString<List<PageDto>>(jsonString)
|
||||
return pages.mapIndexed { i, dto -> Page(i, "", dto.imgURL) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Array.from(document.querySelectorAll('.categorias a')).map(a => `Pair("${a.textContent}", "${a.getAttribute('href')}")`).join(',\n')
|
||||
* on https://heavenmanga.com/top/
|
||||
* */
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||
|
||||
private class GenreFilter : UriPartFilter(
|
||||
"Géneros",
|
||||
arrayOf(
|
||||
Pair("Todo", ""),
|
||||
Pair("<Seleccionar>", ""),
|
||||
Pair("Accion", "accion"),
|
||||
Pair("Adulto", "adulto"),
|
||||
Pair("Aventura", "aventura"),
|
||||
@ -258,7 +302,8 @@ class HeavenManga : ParsedHttpSource() {
|
||||
private class AlphabeticoFilter : UriPartFilter(
|
||||
"Alfabético",
|
||||
arrayOf(
|
||||
Pair("Todo", ""),
|
||||
Pair("<Seleccionar>", ""),
|
||||
Pair("Other", "Other"),
|
||||
Pair("A", "a"),
|
||||
Pair("B", "b"),
|
||||
Pair("C", "c"),
|
||||
@ -296,7 +341,7 @@ class HeavenManga : ParsedHttpSource() {
|
||||
private class ListaCompletasFilter : UriPartFilter(
|
||||
"Lista Completa",
|
||||
arrayOf(
|
||||
Pair("Todo", ""),
|
||||
Pair("<Seleccionar>", ""),
|
||||
Pair("Lista Comis", "comic"),
|
||||
Pair("Lista Novelas", "novela"),
|
||||
Pair("Lista Adulto", "adulto"),
|
||||
@ -317,4 +362,13 @@ class HeavenManga : ParsedHttpSource() {
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private fun String.removeTrailingComma() = replace(TRAILING_COMMA_REGEX, "$1")
|
||||
|
||||
companion object {
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
val PAGES_REGEX = """pUrl\s*=\s*(\[.*\])\s*;""".toRegex()
|
||||
val TRAILING_COMMA_REGEX = """,\s*(\}|\])""".toRegex()
|
||||
private const val CHAPTER_LIST_LIMIT = 10000
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package eu.kanade.tachiyomi.extension.es.heavenmanga
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PayloadChaptersDto(
|
||||
val data: List<ChapterDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ChapterDto(
|
||||
val id: Int,
|
||||
val slug: String,
|
||||
@SerialName("created_at") val createdAt: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PageDto(
|
||||
val imgURL: String,
|
||||
)
|
Loading…
Reference in New Issue
Block a user