Skip to content

Commit

Permalink
YushukeMangas: Fix content loading and bump versionID (#6986)
Browse files Browse the repository at this point in the history
* Fix content loading and bump versionID

* Remove mutableList instance

Co-authored-by: AwkwardPeak7 <[email protected]>

* Remove use function

Co-authored-by: AwkwardPeak7 <[email protected]>

* Remove unused code

---------

Co-authored-by: AwkwardPeak7 <[email protected]>
  • Loading branch information
choppeh and AwkwardPeak7 authored Jan 7, 2025
1 parent f71f8ab commit 1456587
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 60 deletions.
4 changes: 2 additions & 2 deletions src/pt/yushukemangas/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
3 changes: 1 addition & 2 deletions src/pt/yushukemangas/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
ext {
extName = 'Yushuke Mangas'
extClass = '.YushukeMangas'
extVersionCode = 1
isNsfw = true
extVersionCode = 2
}

apply from: "$rootDir/common.gradle"
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
is GenreFilter -> {
filter.state
.filter(GenreCheckBox::state)
.fold(urlBuilder) { builder, genre ->
builder.addQueryParameter(filter.query, genre.id)
}
}
else -> {}
else -> urlBuilder
}
}
return GET(url, headers)

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())
Expand All @@ -95,84 +107,175 @@ 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

// ============================== 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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down

0 comments on commit 1456587

Please sign in to comment.