From db240799f012fe2d24f165169feb3b05a9b109aa Mon Sep 17 00:00:00 2001 From: Chopper <156493704+choppeh@users.noreply.github.com> Date: Mon, 2 Jun 2025 23:20:54 -0300 Subject: [PATCH] YugenMangas: Fix loading content (#9063) * Fix loading content * Move regex to companion object --- src/pt/yugenmangas/build.gradle | 2 +- .../extension/pt/yugenmangas/YugenMangas.kt | 149 +++++------------- .../pt/yugenmangas/YugenMangasDto.kt | 56 ++----- 3 files changed, 48 insertions(+), 159 deletions(-) diff --git a/src/pt/yugenmangas/build.gradle b/src/pt/yugenmangas/build.gradle index f31611c23..59c71918e 100644 --- a/src/pt/yugenmangas/build.gradle +++ b/src/pt/yugenmangas/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Yugen Mangás' extClass = '.YugenMangas' - extVersionCode = 44 + extVersionCode = 45 } apply from: "$rootDir/common.gradle" diff --git a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt index aed028b58..89c62c1a0 100644 --- a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt +++ b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt @@ -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 - } - .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() + 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 - } - .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().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() do { val response = client.newCall(chapterListRequest(manga, page++)).execute() - val chapterContainer = getJsonFromResponse(response).parseAs() - 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 { + 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 { 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>(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() } } diff --git a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt index ae9df71bf..74174a858 100644 --- a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt +++ b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt @@ -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, + 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, - 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,