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:
parent
73013e9d46
commit
d7f724243c
|
@ -13,8 +13,8 @@
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="yushukemangas.com"
|
android:host="new.yushukemangas.com"
|
||||||
android:pathPattern="/manga"
|
android:pathPattern="/manga/..*"
|
||||||
android:scheme="https" />
|
android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Yushuke Mangas'
|
extName = 'Yushuke Mangas'
|
||||||
extClass = '.YushukeMangas'
|
extClass = '.YushukeMangas'
|
||||||
extVersionCode = 1
|
extVersionCode = 2
|
||||||
isNsfw = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -11,81 +11,93 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
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.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class YushukeMangas : ParsedHttpSource() {
|
class YushukeMangas : ParsedHttpSource() {
|
||||||
|
|
||||||
override val name = "Yushuke Mangas"
|
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 lang = "pt-BR"
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val versionId = 2
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.rateLimit(3)
|
.rateLimit(1, 2)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
|
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 {
|
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
title = element.selectFirst("h3")!!.text()
|
title = element.selectFirst("h3")!!.text()
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||||
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
|
setUrlWithoutDomain(element.absUrl("href"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = null
|
override fun popularMangaNextPageSelector() = null
|
||||||
|
|
||||||
// ============================== Latest ===============================
|
// ============================== 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 {
|
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
||||||
title = element.selectFirst("h2")!!.text()
|
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
|
||||||
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = ".pagination-next"
|
override fun latestUpdatesNextPageSelector() = null
|
||||||
|
|
||||||
// ============================== Search ===============================
|
// ============================== Search ===============================
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
var url = "$baseUrl/search".toHttpUrl().newBuilder()
|
val urlFilterBuilder = filters.fold("$baseUrl/obras".toHttpUrl().newBuilder()) { urlBuilder, filter ->
|
||||||
.addQueryParameter("q", query)
|
|
||||||
.addQueryParameter("page", "1")
|
|
||||||
.build()
|
|
||||||
filters.forEach { filter ->
|
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is GenreFilter -> {
|
is RadioFilter -> {
|
||||||
val selected = filter.selected()
|
val selected = filter.selected()
|
||||||
if (selected == all) return@forEach
|
if (selected == all) return@fold urlBuilder
|
||||||
url = "$baseUrl/generos.php".toHttpUrl().newBuilder()
|
urlBuilder.addQueryParameter(filter.query, selected)
|
||||||
.addQueryParameter("genre", selected)
|
|
||||||
.addQueryParameter("search", query)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
else -> {}
|
is GenreFilter -> {
|
||||||
|
filter.state
|
||||||
|
.filter(GenreCheckBox::state)
|
||||||
|
.fold(urlBuilder) { builder, genre ->
|
||||||
|
builder.addQueryParameter(filter.query, genre.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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> {
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
if (query.startsWith(PREFIX_SEARCH)) {
|
if (query.startsWith(PREFIX_SEARCH)) {
|
||||||
val id = query.substringAfter(PREFIX_SEARCH)
|
val slug = query.substringAfter(PREFIX_SEARCH)
|
||||||
return client.newCall(GET("$baseUrl/manga?id=$id", headers))
|
return client.newCall(GET("$baseUrl/manga/$slug", headers))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
val manga = mangaDetailsParse(it.asJsoup())
|
val manga = mangaDetailsParse(it.asJsoup())
|
||||||
|
@ -95,12 +107,24 @@ class YushukeMangas : ParsedHttpSource() {
|
||||||
return super.fetchSearchManga(page, query, filters)
|
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 {
|
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")
|
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
|
override fun searchMangaNextPageSelector() = null
|
||||||
|
@ -108,71 +132,150 @@ class YushukeMangas : ParsedHttpSource() {
|
||||||
// ============================== Manga Details =========================
|
// ============================== Manga Details =========================
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
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()
|
title = details.selectFirst("h1")!!.text()
|
||||||
thumbnail_url = details.selectFirst(".manga-image img")?.absUrl("src")
|
thumbnail_url = details.selectFirst("img")?.absUrl("src")
|
||||||
genre = details.select(".manga-generos .genre-button").joinToString { it.text() }
|
genre = details.select(".genre-tag").joinToString { it.text() }
|
||||||
description = details.selectFirst("p.manga-sinopse")?.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())
|
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 ===============================
|
// ============================== Chapters ===============================
|
||||||
|
|
||||||
override fun chapterListSelector() = ".chapter-list .chapter-item a"
|
override fun chapterListSelector() = "a.chapter-item"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
name = element.selectFirst(".chapter-number")!!.text()
|
name = element.selectFirst(".chapter-number")!!.text()
|
||||||
setUrlWithoutDomain(element.absUrl("href"))
|
setUrlWithoutDomain(element.absUrl("href"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterListNextPageSelector() = latestUpdatesNextPageSelector()
|
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
val mangaId = manga.fetchMangaId()
|
||||||
val chapters = mutableListOf<SChapter>()
|
val chapters = mutableListOf<SChapter>()
|
||||||
var page = 1
|
var page = 1
|
||||||
do {
|
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)
|
chapters += document.select(chapterListSelector()).map(::chapterFromElement)
|
||||||
} while (document.selectFirst(chapterListNextPageSelector()) != null)
|
} while (dto.hasNext())
|
||||||
return Observable.just(chapters)
|
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
|
return client
|
||||||
.newCall(GET("$baseUrl${manga.url}&page=$page", headers))
|
.newCall(GET(url, headers))
|
||||||
.execute().asJsoup()
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Pages ===============================
|
// ============================== Pages ===============================
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
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"))
|
Page(index, imageUrl = imageUrl.absUrl("src"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
override fun imageUrlParse(document: Document) = ""
|
||||||
|
|
||||||
// ============================== Filters ===============================
|
// ============================== Filters =============================
|
||||||
|
|
||||||
open class GenreFilter(displayName: String, private val vals: Array<String>, state: Int = 0) :
|
override fun getFilterList(): FilterList {
|
||||||
Filter.Select<String>(displayName, vals, state) {
|
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]
|
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 all = "Todos"
|
||||||
|
|
||||||
private val genresList = arrayOf(
|
private val statusList = arrayOf(
|
||||||
all, "+18", "Abuso", "Adulto", "Amor Puro", "Artes Marciais",
|
all,
|
||||||
"Aventura", "Ação", "Comédia", "Crime", "Cultivação", "Drama",
|
"Em andamento",
|
||||||
"Fantasia", "Gap Girls", "Gore", "Harém", "Histórico", "Horror",
|
"Completo",
|
||||||
"Isekai", "Mistério", "Overpowered", "Psicológico", "Reencarnação",
|
"Cancelado",
|
||||||
"Romance", "Sistema", "Tragédia", "Viagem no Tempo", "Violência",
|
"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 {
|
companion object {
|
||||||
const val PREFIX_SEARCH = "id:"
|
const val PREFIX_SEARCH = "id:"
|
||||||
|
val SEARCH_URL_REGEX = "'([^']+)".toRegex()
|
||||||
|
val MANGA_ID_REGEX = """obra_id:\s+(\d+)""".toRegex()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ class YushukeMangasUrlActivity : Activity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val id = intent?.data?.getQueryParameter("id")
|
val pathSegment = intent?.data?.pathSegments
|
||||||
if (id != null) {
|
if (pathSegment != null && pathSegment.size > 1) {
|
||||||
val mainIntent = Intent().apply {
|
val mainIntent = Intent().apply {
|
||||||
action = "eu.kanade.tachiyomi.SEARCH"
|
action = "eu.kanade.tachiyomi.SEARCH"
|
||||||
putExtra("query", "${YushukeMangas.PREFIX_SEARCH}$id")
|
putExtra("query", "${YushukeMangas.PREFIX_SEARCH}${pathSegment[1]}")
|
||||||
putExtra("filter", packageName)
|
putExtra("filter", packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue