YushukeMangas: Fix content loading and bump versionID (#6986)

* Fix content loading and bump versionID

* Remove mutableList instance

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Remove use function

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Remove unused code

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
Chopper 2025-01-07 15:24:02 -03:00 committed by Draff
parent 73013e9d46
commit d7f724243c
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
4 changed files with 162 additions and 60 deletions

View File

@ -13,8 +13,8 @@
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="yushukemangas.com"
android:pathPattern="/manga"
android:host="new.yushukemangas.com"
android:pathPattern="/manga/..*"
android:scheme="https" />
</intent-filter>
</activity>

View File

@ -1,8 +1,7 @@
ext {
extName = 'Yushuke Mangas'
extClass = '.YushukeMangas'
extVersionCode = 1
isNsfw = true
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

View File

@ -11,81 +11,93 @@ 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 kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
class YushukeMangas : ParsedHttpSource() {
override val name = "Yushuke Mangas"
override val baseUrl = "https://yushukemangas.com"
override val baseUrl = "https://new.yushukemangas.com"
override val lang = "pt-BR"
override val supportsLatest = true
override val versionId = 2
override val client = network.cloudflareClient.newBuilder()
.rateLimit(3)
.rateLimit(1, 2)
.build()
private val json: Json by injectLazy()
// ============================== Popular ===============================
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
override fun popularMangaSelector() = ".popular-manga-widget .popular-manga-item"
override fun popularMangaSelector() = "#semanal a.top-item"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
title = element.selectFirst("h3")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
setUrlWithoutDomain(element.absUrl("href"))
}
override fun popularMangaNextPageSelector() = null
// ============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/obras", headers)
override fun latestUpdatesSelector() = ".manga-list .manga-item"
override fun latestUpdatesSelector() = ".obras-grid .manga-card a"
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
title = element.selectFirst("h2")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
}
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = ".pagination-next"
override fun latestUpdatesNextPageSelector() = null
// ============================== Search ===============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
var url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("page", "1")
.build()
filters.forEach { filter ->
val urlFilterBuilder = filters.fold("$baseUrl/obras".toHttpUrl().newBuilder()) { urlBuilder, filter ->
when (filter) {
is GenreFilter -> {
is RadioFilter -> {
val selected = filter.selected()
if (selected == all) return@forEach
url = "$baseUrl/generos.php".toHttpUrl().newBuilder()
.addQueryParameter("genre", selected)
.addQueryParameter("search", query)
.build()
if (selected == all) return@fold urlBuilder
urlBuilder.addQueryParameter(filter.query, selected)
}
else -> {}
is GenreFilter -> {
filter.state
.filter(GenreCheckBox::state)
.fold(urlBuilder) { builder, genre ->
builder.addQueryParameter(filter.query, genre.id)
}
}
return GET(url, headers)
else -> urlBuilder
}
}
val url = when {
query.isBlank() -> urlFilterBuilder
else -> baseUrl.toHttpUrl().newBuilder().addQueryParameter("search", query)
}
return GET(url.build(), headers)
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(PREFIX_SEARCH)) {
val id = query.substringAfter(PREFIX_SEARCH)
return client.newCall(GET("$baseUrl/manga?id=$id", headers))
val slug = query.substringAfter(PREFIX_SEARCH)
return client.newCall(GET("$baseUrl/manga/$slug", headers))
.asObservableSuccess()
.map {
val manga = mangaDetailsParse(it.asJsoup())
@ -95,12 +107,24 @@ class YushukeMangas : ParsedHttpSource() {
return super.fetchSearchManga(page, query, filters)
}
override fun searchMangaSelector() = "${latestUpdatesSelector()}, a.search-item"
override fun searchMangaSelector() = ".search-result-item"
override fun searchMangaParse(response: Response): MangasPage {
return if (response.request.url.queryParameter("search").isNullOrBlank()) {
latestUpdatesParse(response)
} else {
super.searchMangaParse(response)
}
}
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
title = element.selectFirst("h3, .search-title")!!.text()
title = element.selectFirst(".search-result-title")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
setUrlWithoutDomain((element.selectFirst("a") ?: element).absUrl("href"))
setUrlWithoutDomain(
element.attr("onclick").let {
SEARCH_URL_REGEX.find(it)?.groups?.get(1)?.value!!
},
)
}
override fun searchMangaNextPageSelector() = null
@ -108,71 +132,150 @@ class YushukeMangas : ParsedHttpSource() {
// ============================== Manga Details =========================
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val details = document.selectFirst(".manga-header")!!
val details = document.selectFirst(".manga-banner .container")!!
title = details.selectFirst("h1")!!.text()
thumbnail_url = details.selectFirst(".manga-image img")?.absUrl("src")
genre = details.select(".manga-generos .genre-button").joinToString { it.text() }
description = details.selectFirst("p.manga-sinopse")?.text()
thumbnail_url = details.selectFirst("img")?.absUrl("src")
genre = details.select(".genre-tag").joinToString { it.text() }
description = details.selectFirst(".sinopse p")?.text()
details.selectFirst(".manga-meta > div")?.ownText()?.let {
status = when (it.lowercase()) {
"em andamento" -> SManga.ONGOING
"completo" -> SManga.COMPLETED
"cancelado" -> SManga.CANCELLED
"hiato" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
setUrlWithoutDomain(document.location())
}
private fun SManga.fetchMangaId(): String {
val document = client.newCall(mangaDetailsRequest(this)).execute().asJsoup()
return document.select("script")
.map(Element::data)
.firstOrNull(MANGA_ID_REGEX::containsMatchIn)
?.let { MANGA_ID_REGEX.find(it)?.groups?.get(1)?.value }
?: throw Exception("Manga ID não encontrado")
}
// ============================== Chapters ===============================
override fun chapterListSelector() = ".chapter-list .chapter-item a"
override fun chapterListSelector() = "a.chapter-item"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
name = element.selectFirst(".chapter-number")!!.text()
setUrlWithoutDomain(element.absUrl("href"))
}
private fun chapterListNextPageSelector() = latestUpdatesNextPageSelector()
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val mangaId = manga.fetchMangaId()
val chapters = mutableListOf<SChapter>()
var page = 1
do {
val document = fetchChapterListPage(manga, page++)
val dto = fetchChapterListPage(mangaId, page++).parseAs<ChaptersDto>()
val document = Jsoup.parseBodyFragment(dto.chapters, baseUrl)
chapters += document.select(chapterListSelector()).map(::chapterFromElement)
} while (document.selectFirst(chapterListNextPageSelector()) != null)
} while (dto.hasNext())
return Observable.just(chapters)
}
private fun fetchChapterListPage(manga: SManga, page: Int): Document {
private fun fetchChapterListPage(mangaId: String, page: Int): Response {
val url = "$baseUrl/ajax/load_more_chapters.php?order=DESC".toHttpUrl().newBuilder()
.addQueryParameter("manga_id", mangaId)
.addQueryParameter("page", page.toString())
.build()
return client
.newCall(GET("$baseUrl${manga.url}&page=$page", headers))
.execute().asJsoup()
.newCall(GET(url, headers))
.execute()
}
// ============================== Pages ===============================
override fun pageListParse(document: Document): List<Page> {
return document.select(".chapter-images img").mapIndexed { index, imageUrl ->
return document.select(".manga-container .manga-image").mapIndexed { index, imageUrl ->
Page(index, imageUrl = imageUrl.absUrl("src"))
}
}
override fun imageUrlParse(document: Document) = ""
// ============================== Filters ===============================
// ============================== Filters =============================
open class GenreFilter(displayName: String, private val vals: Array<String>, state: Int = 0) :
Filter.Select<String>(displayName, vals, state) {
override fun getFilterList(): FilterList {
return FilterList(
RadioFilter("Status", "status", statusList),
RadioFilter("Tipo", "tipo", typeList),
GenreFilter("Gêneros", "tags[]", genresList),
)
}
class RadioFilter(
displayName: String,
val query: String,
private val vals: Array<String>,
state: Int = 0,
) : Filter.Select<String>(displayName, vals, state) {
fun selected() = vals[state]
}
override fun getFilterList() = FilterList(GenreFilter("Gêneros", genresList))
protected class GenreFilter(
title: String,
val query: String,
genres: List<String>,
) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it) })
class GenreCheckBox(name: String, val id: String = name) : Filter.CheckBox(name)
private val all = "Todos"
private val genresList = arrayOf(
all, "+18", "Abuso", "Adulto", "Amor Puro", "Artes Marciais",
"Aventura", "Ação", "Comédia", "Crime", "Cultivação", "Drama",
"Fantasia", "Gap Girls", "Gore", "Harém", "Histórico", "Horror",
"Isekai", "Mistério", "Overpowered", "Psicológico", "Reencarnação",
"Romance", "Sistema", "Tragédia", "Viagem no Tempo", "Violência",
private val statusList = arrayOf(
all,
"Em andamento",
"Completo",
"Cancelado",
"Hiato",
)
private val typeList = arrayOf(
all,
"Mangá",
"Manhwa",
"Manhua",
"Comics",
)
private var genresList: List<String> = listOf(
"Ação", "Artes Marciais", "Aventura",
"Comédia",
"Drama",
"Escolar",
"Esporte",
"Fantasia",
"Harém", "Histórico",
"Isekai",
"Josei",
"Mistério",
"Reencarnação", "Regressão", "Romance",
"Sci-fi", "Seinen", "Shoujo", "Shounen", "Slice of Life", "Sobrenatural", "Super Poderes",
"Terror",
"Vingança",
)
// ============================== Utilities ===========================
private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromStream(body.byteStream())
}
@Serializable
class ChaptersDto(val chapters: String, private val remaining: Int) {
fun hasNext() = remaining > 0
}
companion object {
const val PREFIX_SEARCH = "id:"
val SEARCH_URL_REGEX = "'([^']+)".toRegex()
val MANGA_ID_REGEX = """obra_id:\s+(\d+)""".toRegex()
}
}

View File

@ -13,11 +13,11 @@ class YushukeMangasUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val id = intent?.data?.getQueryParameter("id")
if (id != null) {
val pathSegment = intent?.data?.pathSegments
if (pathSegment != null && pathSegment.size > 1) {
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${YushukeMangas.PREFIX_SEARCH}$id")
putExtra("query", "${YushukeMangas.PREFIX_SEARCH}${pathSegment[1]}")
putExtra("filter", packageName)
}