HentaiVN: Support mobile site and add Cloudflare bypass toggle (#17428)
* HentaiVN: Support mobile site * mobile: Parse title from cover alt before making a request * mobile: Fix nextPageSelector * Fix covers not loading in advanced search
This commit is contained in:
parent
fc9a363934
commit
ad65245dc0
|
@ -5,7 +5,7 @@ ext {
|
|||
extName = 'HentaiVN'
|
||||
pkgNameSuffix = 'vi.hentaivn'
|
||||
extClass = '.HentaiVN'
|
||||
extVersionCode = 29
|
||||
extVersionCode = 30
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.widget.Toast
|
|||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
|
||||
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
|
||||
|
@ -24,7 +25,6 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
|||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
@ -57,7 +57,13 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient by lazy {
|
||||
network.cloudflareClient.newBuilder()
|
||||
val baseClient = if (preferences.getBoolean(PREF_KEY_ENABLE_CLOUDFLARE_BYPASS, true)) {
|
||||
network.cloudflareClient
|
||||
} else {
|
||||
network.client
|
||||
}
|
||||
|
||||
baseClient.newBuilder()
|
||||
.addNetworkInterceptor(CookieInterceptor(domain, "view1", "1"))
|
||||
.addNetworkInterceptor(CookieInterceptor(domain, "view4", "1"))
|
||||
.setRandomUserAgent(
|
||||
|
@ -76,19 +82,19 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
return GET("$baseUrl/chap-moi.html?page=$page", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item"
|
||||
override fun latestUpdatesSelector() = ".block-item ul li.item"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select(".box-description a").first()!!.let {
|
||||
element.select(".box-description a, .box-description-2 a").first()!!.let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text().trim()
|
||||
}
|
||||
manga.thumbnail_url = element.select(".box-cover a img").attr("data-src")
|
||||
manga.thumbnail_url = imageFromElement(element.selectFirst(".box-cover a img, .box-cover-2 a img"))
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)"
|
||||
override fun latestUpdatesNextPageSelector() = ".pagination *:contains(Next)"
|
||||
|
||||
// Popular
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
|
@ -153,25 +159,34 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=".toHttpUrlOrNull()!!
|
||||
.newBuilder()
|
||||
val url = searchUrl.toHttpUrl().newBuilder().apply {
|
||||
addQueryParameter("name", query)
|
||||
addQueryParameter("dou", "")
|
||||
addQueryParameter("char", "")
|
||||
addQueryParameter("search", "")
|
||||
|
||||
if (page > 1) {
|
||||
addQueryParameter("page", page.toString())
|
||||
}
|
||||
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||
is TextField -> setQueryParameter(filter.key, filter.state)
|
||||
is GenreList ->
|
||||
filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id }
|
||||
.forEach { url.addQueryParameter("tag[]", it) }
|
||||
.forEach { addQueryParameter("tag[]", it) }
|
||||
is GroupList -> {
|
||||
val group = getGroupList()[filter.state]
|
||||
url.addQueryParameter("group", group.id)
|
||||
addQueryParameter("group", group.id)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
return GET(url.toString(), headers)
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
|
@ -194,19 +209,19 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
}
|
||||
|
||||
override fun searchMangaSelector() =
|
||||
".search-ul .search-li, .main > .block-left > .block-item > ul > li.item"
|
||||
".search-ul .search-li, ${latestUpdatesSelector()}"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select(".search-des > a, .box-description a").first()!!.let {
|
||||
element.select(".search-des a, .box-description a, .box-description-2 a").first()!!.let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text().trim()
|
||||
}
|
||||
manga.thumbnail_url = element.select("div.search-img img").attr("abs:src")
|
||||
manga.thumbnail_url = imageFromElement(element.selectFirst("div.search-img img, .box-cover a img, .box-cover-2 a img"))
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)"
|
||||
override fun searchMangaNextPageSelector() = ".pagination *:contains(Cuối), .pagination *:contains(Next)"
|
||||
|
||||
private fun searchMangaByIdRequest(id: String) = GET("$searchAllURL?key=$id", headers)
|
||||
private fun searchMangaByIdParse(response: Response, ids: String): MangasPage {
|
||||
|
@ -216,18 +231,53 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
}
|
||||
|
||||
// Detail
|
||||
private val genreUrlRegex = Regex("""\"(list-info-theloai-mobile\.php?.+)\"""")
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
if (document.toString().contains("document.cookie = \"mobile=1")) { // Desktop version
|
||||
val infoElement = document.select(".main > .page-left > .left-info > .page-info")
|
||||
val manga = SManga.create()
|
||||
manga.title = document.selectFirst(".breadcrumb2 li:last-child span")!!.text()
|
||||
manga.author = infoElement.select("p:contains(Tác giả:) a").text()
|
||||
manga.description = infoElement.select(":root > p:contains(Nội dung:) + p").text()
|
||||
manga.genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() }
|
||||
manga.thumbnail_url =
|
||||
document.select(".main > .page-right > .right-info > .page-ava > img").attr("src")
|
||||
manga.status =
|
||||
return SManga.create().apply {
|
||||
title = document.selectFirst(".breadcrumb2 li:last-child span")!!.text()
|
||||
author = infoElement.select("p:contains(Tác giả:) a").text()
|
||||
description = infoElement.select(":root > p:contains(Nội dung:) + p").text()
|
||||
genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() }
|
||||
thumbnail_url =
|
||||
imageFromElement(document.selectFirst(".main > .page-right > .right-info > .page-ava > img"))
|
||||
status =
|
||||
parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text())
|
||||
return manga
|
||||
}
|
||||
} else { // Mobile version
|
||||
val id = document.location().substringAfterLast("/").substringBefore("-")
|
||||
val documentText = document.toString()
|
||||
|
||||
return SManga.create().apply {
|
||||
val thumbnailElem = document.selectFirst(".content-images-1 img.cover-1")
|
||||
thumbnail_url = imageFromElement(thumbnailElem)
|
||||
|
||||
title = thumbnailElem?.attr("alt")?.substringBeforeLast(" Cover")?.trim() ?: client
|
||||
.newCall(GET("$baseUrl/list-info-ten-mobile.php?id_anime=$id"))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
.select("h3")
|
||||
.text()
|
||||
|
||||
val genreUrl = genreUrlRegex.find(documentText)?.groupValues?.get(1)
|
||||
genre = client
|
||||
.newCall(GET("$baseUrl/$genreUrl"))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
.select("a.tag")
|
||||
.joinToString { it.text() }
|
||||
|
||||
val infoElement = client
|
||||
.newCall(GET("$baseUrl/list-info-all-mobile.php?id_anime=$id"))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
author = infoElement.select("p:contains(Tác giả:) a").text()
|
||||
status = parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text())
|
||||
description = infoElement.select("p:contains(Nội dung:) + p").text()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chapter
|
||||
|
@ -274,16 +324,32 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
private fun imageFromElement(element: Element): String? {
|
||||
private fun imageFromElement(element: Element?): String? {
|
||||
if (element == null) return null
|
||||
|
||||
return when {
|
||||
element.hasAttr("data-src") -> element.attr("abs:data-src")
|
||||
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
|
||||
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
|
||||
element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ")
|
||||
else -> element.attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = PREF_KEY_ENABLE_CLOUDFLARE_BYPASS
|
||||
title = TITLE_ENABLE_CLOUDFLARE_BYPASS
|
||||
summary = SUMMARY_ENABLE_CLOUDFLARE_BYPASS
|
||||
|
||||
setDefaultValue(true)
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
EditTextPreference(screen.context).apply {
|
||||
key = PREF_KEY_BASE_URL
|
||||
title = TITLE_BASE_URL
|
||||
|
@ -590,6 +656,10 @@ class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
|||
|
||||
const val RESTART_TACHIYOMI = "Khởi động lại Tachiyomi để áp dụng thay đổi."
|
||||
|
||||
const val PREF_KEY_ENABLE_CLOUDFLARE_BYPASS = "enable_cloudflare"
|
||||
const val TITLE_ENABLE_CLOUDFLARE_BYPASS = "Kích hoạt bỏ qua Cloudflare"
|
||||
const val SUMMARY_ENABLE_CLOUDFLARE_BYPASS = "Nếu bật khi không cần thiết, có thể gây lỗi \"Bỏ qua Cloudflare thất bại\" giả."
|
||||
|
||||
const val PREF_KEY_BASE_URL = "override_base_url_${BuildConfig.VERSION_CODE}"
|
||||
const val TITLE_BASE_URL = "Thay đổi tên miền"
|
||||
const val SUMMARY_BASE_URL = "Thay đổi này là tạm thời và sẽ bị xoá khi cập nhật tiện ích mở rộng."
|
||||
|
|
Loading…
Reference in New Issue