YugenMangas: Fix loading content (#9063)

* Fix loading content

* Move regex to companion object
This commit is contained in:
Chopper 2025-06-02 23:20:54 -03:00 committed by Draff
parent 2a2157d48b
commit db240799f0
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 48 additions and 159 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Yugen Mangás'
extClass = '.YugenMangas'
extVersionCode = 44
extVersionCode = 45
}
apply from: "$rootDir/common.gradle"

View File

@ -23,19 +23,13 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -93,59 +87,22 @@ class YugenMangas : HttpSource(), ConfigurableSource {
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val jsonContent = document.select("script")
.map(Element::data)
.firstOrNull(POPULAR_MANGA_REGEX::containsMatchIn)
val script = document.selectFirst("script:containsData(initialSeries)")?.data()
?: throw Exception("Não foi possivel encontrar a lista de mangás/manhwas")
val mangas = POPULAR_MANGA_REGEX.findAll(jsonContent)
.mapNotNull { result ->
result.groups.lastOrNull()?.value?.sanitizeJson()?.parseAs<JsonObject>()?.jsonObject
}
.map { element ->
val manga = element["children"]?.jsonArray
?.firstOrNull()?.jsonArray
?.firstOrNull { it is JsonObject }?.jsonObject
?.get("children")?.jsonArray
?.firstOrNull { it is JsonObject }?.jsonObject
SManga.create().apply {
title = manga!!.getValue("alt")
thumbnail_url = manga.getValue("src")
url = element.getValue("href")
}
}.toList()
return MangasPage(mangas, jsonContent.hasNextPage(response))
val json = POPULAR_MANGA_REGEX.find(script)?.groups?.get(1)?.value
?.replace(ESCAPE_QUOTATION_MARK_REGEX, "\"")
?: throw Exception("Erro ao analisar lista de mangás/manhwas")
val dto = json.parseAs<LibraryWrapper>()
return MangasPage(dto.mangas.map(MangaDetailsDto::toSManga), dto.hasNextPage())
}
// ================================ Latest =======================================
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/chapters?page=$page", headers)
GET("$baseUrl/series?page=$page&order=desc&sort=date", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
val jsonContent = document.select("script")
.map(Element::data)
.firstOrNull(LATEST_UPDATE_REGEX::containsMatchIn)
?: throw Exception("Não foi possivel encontrar a lista de mangás/manhwas")
val mangas = LATEST_UPDATE_REGEX.findAll(jsonContent)
.mapNotNull { result ->
result.groups.firstOrNull()?.value?.sanitizeJson()?.parseAs<JsonObject>()?.jsonObject
}
.map { element ->
val jsonString = element.toString()
SManga.create().apply {
this.title = jsonString.getFirstValueByKey("children")!!
thumbnail_url = jsonString.getFirstValueByKey("src")!!
url = element.getValue("href")
}
}.toList()
return MangasPage(mangas, jsonContent.hasNextPage())
}
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
// ================================ Search =======================================
@ -161,8 +118,17 @@ class YugenMangas : HttpSource(), ConfigurableSource {
// ================================ Details =======================================
override fun mangaDetailsParse(response: Response): SManga {
return getJsonFromResponse(response).parseAs<ContainerDto>().series.toSManga()
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val document = response.asJsoup()
title = document.selectFirst("h1")!!.text()
description = document.selectFirst("[property='og:description']")?.attr("content")
thumbnail_url = document.selectFirst("img")?.attr("srcset")
?.split(SRCSET_DELIMITER_REGEX)
?.map(String::trim)?.last(String::isNotBlank)
?.let { "$baseUrl$it" }
author = document.selectFirst("p:contains(Autor) ~ div")?.text()
artist = document.selectFirst("p:contains(Artista) ~ div")?.text()
genre = document.select("p:contains(Gêneros) ~ div div.inline-flex").joinToString { it.text() }
}
// ================================ Chapters =======================================
@ -172,9 +138,10 @@ class YugenMangas : HttpSource(), ConfigurableSource {
val chapters = mutableListOf<SChapter>()
do {
val response = client.newCall(chapterListRequest(manga, page++)).execute()
val chapterContainer = getJsonFromResponse(response).parseAs<ContainerDto>()
chapters += chapterContainer.toSChapterList()
} while (chapterContainer.hasNext())
val chapterGroup = chapterListParse(response).also {
chapters += it
}
} while (chapterGroup.isNotEmpty())
return Observable.just(chapters)
}
@ -187,22 +154,22 @@ class YugenMangas : HttpSource(), ConfigurableSource {
return GET(url, headers)
}
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return document.select("a.flex.bg-card[href*=series]").map { element ->
SChapter.create().apply {
name = element.selectFirst("p")!!.text()
setUrlWithoutDomain(element.absUrl("href"))
}
}
}
// ================================ Pages =======================================}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val script = document.select("script")
.map(Element::data)
.firstOrNull(PAGES_REGEX::containsMatchIn)
?: throw Exception("Páginas não encontradas")
val jsonContent = PAGES_REGEX.find(script)?.groups?.get(1)?.value?.sanitizeJson()
?: throw Exception("Erro ao obter as páginas")
return json.decodeFromString<List<String>>(jsonContent).mapIndexed { index, imageUrl ->
Page(index, baseUrl, "$BASE_MEDIA/$imageUrl")
return document.select("img[alt^=página]").mapIndexed { index, element ->
Page(index, imageUrl = element.absUrl("src"))
}
}
@ -237,57 +204,15 @@ class YugenMangas : HttpSource(), ConfigurableSource {
// ================================ Utils =======================================
private fun String.getFirstValueByKey(field: String) =
"""$field":"([^"]+)""".toRegex().find(this)?.groups?.get(1)?.value
private fun String.hasNextPage(): Boolean =
LATEST_PAGES_REGEX.findAll(this).lastOrNull()?.groups?.get(1)?.value?.toBoolean()?.not() ?: false
private fun String.hasNextPage(response: Response): Boolean {
val lastPage = POPULAR_PAGES_REGEX.findAll(this).mapNotNull {
it.groups[1]?.value?.toInt()
}.max() - 1
return response.request.url.queryParameter("page")
?.toInt()?.let { it < lastPage } ?: false
}
private fun String.sanitizeJson() =
this.replace("""\\{1}"""".toRegex(), "\"")
.replace("""\\{2,}""".toRegex(), """\\""")
.trimIndent()
private fun JsonObject.getValue(key: String): String =
this[key]!!.jsonPrimitive.content
private fun getJsonFromResponse(response: Response): String {
val document = response.asJsoup()
val script = document.select("script")
.map(Element::data)
.firstOrNull(MANGA_DETAILS_REGEX::containsMatchIn)
?: throw Exception("Dados não encontrado")
val jsonContent = MANGA_DETAILS_REGEX.find(script)
?.groups?.get(1)?.value
?: throw Exception("Erro ao obter JSON")
return jsonContent.sanitizeJson()
}
companion object {
private const val BASE_URL_PREF = "overrideBaseUrl"
private const val BASE_URL_PREF_TITLE = "Editar URL da fonte"
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
private const val RESTART_APP_MESSAGE = "Reinicie o aplicativo para aplicar as alterações"
private const val URL_PREF_SUMMARY = "Para uso temporário, se a extensão for atualizada, a alteração será perdida."
private const val BASE_MEDIA = "https://media.yugenweb.com"
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
private val POPULAR_MANGA_REGEX = """\d+\\",(\{\\"href\\":\\"\/series\/.*?\]\]\})""".toRegex()
private val LATEST_UPDATE_REGEX = """\{\\"href\\":\\"\/series\/\d+(.*?)\}\]\]\}\]\]\}\]\]\}""".toRegex()
private val LATEST_PAGES_REGEX = """aria-disabled\\":([^,]+)""".toRegex()
private val POPULAR_PAGES_REGEX = """series\?page=(\d+)""".toRegex()
private val MANGA_DETAILS_REGEX = """(\{\\"series\\":.*?"\})\],\[""".toRegex()
private val PAGES_REGEX = """images\\":(\[[^\]]+\])""".toRegex()
private val POPULAR_MANGA_REGEX = """(\{\\"initialSeries.+\"\})\]""".toRegex()
private val ESCAPE_QUOTATION_MARK_REGEX = """\\"""".toRegex()
private val SRCSET_DELIMITER_REGEX = """\d+w,?""".toRegex()
}
}

View File

@ -1,10 +1,18 @@
package eu.kanade.tachiyomi.extension.pt.yugenmangas
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.util.Calendar
@Serializable
class LibraryWrapper(
@SerialName("initialSeries")
val mangas: List<MangaDetailsDto>,
val currentPage: Int = 0,
val totalPages: Int = 0,
) {
fun hasNextPage() = currentPage < totalPages
}
@Serializable
class MangaDetailsDto(
@ -36,50 +44,6 @@ class MangaDetailsDto(
}
}
@Serializable
class ContainerDto(
val chapters: List<ChapterDto>,
val currentPage: Int,
val series: MangaDetailsDto,
val totalPages: Int,
) {
fun hasNext() = currentPage < totalPages
fun toSChapterList() = chapters.map { it.toSChapter(series.code) }
}
@Serializable
class ChapterDto(
val code: String,
val name: String,
@SerialName("upload_date")
val date: String,
) {
fun toSChapter(mangaCode: String): SChapter = SChapter.create().apply {
name = this@ChapterDto.name
date_upload = parseDate()
url = "/series/$mangaCode/$code"
}
private fun parseDate(): Long {
return try {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0L
Calendar.getInstance().let {
when {
date.contains("dia") -> it.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
date.contains("mês", "meses") -> it.apply { add(Calendar.MONTH, -number) }.timeInMillis
date.contains("ano") -> it.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0L
}
}
} catch (_: Exception) { 0L }
}
private fun String.contains(vararg elements: String): Boolean {
return elements.any { this.contains(it, true) }
}
}
@Serializable
class SearchDto(
val query: String,