YugenMangas: Fix loading content (#9063)
* Fix loading content * Move regex to companion object
This commit is contained in:
parent
2a2157d48b
commit
db240799f0
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Yugen Mangás'
|
||||
extClass = '.YugenMangas'
|
||||
extVersionCode = 44
|
||||
extVersionCode = 45
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user