Refactor HentaiVN (#17391)
* Refactor HentaiVN * better cookie handling
This commit is contained in:
parent
9f542d8234
commit
28850cf51a
@ -5,8 +5,12 @@ ext {
|
|||||||
extName = 'HentaiVN'
|
extName = 'HentaiVN'
|
||||||
pkgNameSuffix = 'vi.hentaivn'
|
pkgNameSuffix = 'vi.hentaivn'
|
||||||
extClass = '.HentaiVN'
|
extClass = '.HentaiVN'
|
||||||
extVersionCode = 28
|
extVersionCode = 29
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":lib-randomua"))
|
||||||
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.vi.hentaivn
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class CookieInterceptor(
|
||||||
|
private val domain: String,
|
||||||
|
private val key: String,
|
||||||
|
private val value: String,
|
||||||
|
) : Interceptor {
|
||||||
|
|
||||||
|
init {
|
||||||
|
val url = "https://$domain/"
|
||||||
|
val cookie = "$key=$value; Domain=$domain; Path=/"
|
||||||
|
setCookie(url, cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
if (!request.url.host.endsWith(domain)) return chain.proceed(request)
|
||||||
|
|
||||||
|
val cookie = "$key=$value"
|
||||||
|
val cookieList = request.header("Cookie")?.split("; ") ?: emptyList()
|
||||||
|
if (cookie in cookieList) return chain.proceed(request)
|
||||||
|
|
||||||
|
setCookie("https://$domain/", "$cookie; Domain=$domain; Path=/")
|
||||||
|
val prefix = "$key="
|
||||||
|
val newCookie = buildList(cookieList.size + 1) {
|
||||||
|
cookieList.filterNotTo(this) { it.startsWith(prefix) }
|
||||||
|
add(cookie)
|
||||||
|
}.joinToString("; ")
|
||||||
|
val newRequest = request.newBuilder().header("Cookie", newCookie).build()
|
||||||
|
return chain.proceed(newRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCookie(url: String, value: String) {
|
||||||
|
try {
|
||||||
|
CookieManager.getInstance().setCookie(url, value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Probably running on Tachidesk
|
||||||
|
Log.e("HentaiVN", "failed to set cookie", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,19 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.hentaivn
|
package eu.kanade.tachiyomi.extension.vi.hentaivn
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||||
|
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
|
||||||
|
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
|
||||||
|
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
@ -11,7 +22,6 @@ 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 okhttp3.CookieJar
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
@ -21,41 +31,45 @@ import okhttp3.Response
|
|||||||
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 java.text.ParseException
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class HentaiVN : ParsedHttpSource() {
|
class HentaiVN : ParsedHttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override val baseUrl = "https://hentaivn.tv"
|
|
||||||
override val lang = "vi"
|
|
||||||
override val name = "HentaiVN"
|
override val name = "HentaiVN"
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
|
private val defaultBaseUrl = "https://hentaivn.tv"
|
||||||
|
override val baseUrl = preferences.getString(PREF_KEY_BASE_URL, defaultBaseUrl)!!
|
||||||
|
|
||||||
|
private val domain = baseUrl.toHttpUrl().host
|
||||||
private val searchUrl = "$baseUrl/forum/search-plus.php"
|
private val searchUrl = "$baseUrl/forum/search-plus.php"
|
||||||
private val searchByAuthorUrl = "$baseUrl/tim-kiem-tac-gia.html"
|
private val searchByAuthorUrl = "$baseUrl/tim-kiem-tac-gia.html"
|
||||||
private val searchAllURL = "$baseUrl/tim-kiem-truyen.html"
|
private val searchAllURL = "$baseUrl/tim-kiem-truyen.html"
|
||||||
private val searchClient = network.cloudflareClient
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val lang = "vi"
|
||||||
.cookieJar(CookieJar.NO_COOKIES)
|
|
||||||
.addInterceptor { chain ->
|
override val supportsLatest = true
|
||||||
val originalRequest = chain.request()
|
|
||||||
when {
|
override val client: OkHttpClient by lazy {
|
||||||
originalRequest.url.toString().startsWith(searchUrl) -> {
|
network.cloudflareClient.newBuilder()
|
||||||
searchClient.newCall(originalRequest).execute()
|
.addNetworkInterceptor(CookieInterceptor(domain, "view1", "1"))
|
||||||
}
|
.addNetworkInterceptor(CookieInterceptor(domain, "view4", "1"))
|
||||||
else -> chain.proceed(originalRequest)
|
.setRandomUserAgent(
|
||||||
}
|
preferences.getPrefUAType(),
|
||||||
}
|
preferences.getPrefCustomUA(),
|
||||||
|
)
|
||||||
.rateLimit(1)
|
.rateLimit(1)
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
.add("Referer", baseUrl)
|
.add("Referer", "$baseUrl/")
|
||||||
.add("Cookie", "view1=1; view4=1") // bypass "captcha" and get popular manga
|
|
||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)
|
|
||||||
|
|
||||||
// latestUpdates
|
// latestUpdates
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
@ -63,7 +77,7 @@ class HentaiVN : ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item"
|
override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item"
|
||||||
override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)"
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
element.select(".box-description a").first()!!.let {
|
element.select(".box-description a").first()!!.let {
|
||||||
@ -74,112 +88,20 @@ class HentaiVN : ParsedHttpSource() {
|
|||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)"
|
||||||
|
|
||||||
// Popular
|
// Popular
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET("$baseUrl/danh-sach.html?page=$page", headers)
|
return GET("$baseUrl/danh-sach.html?page=$page", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
|
||||||
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
|
||||||
override fun popularMangaSelector() = latestUpdatesSelector()
|
override fun popularMangaSelector() = latestUpdatesSelector()
|
||||||
|
|
||||||
// Chapter
|
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||||
override fun chapterListSelector() = "table.listing > tbody > tr"
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
|
||||||
if (element.select("a").isEmpty()) throw Exception(element.select("h2").html())
|
|
||||||
val chapter = SChapter.create()
|
|
||||||
element.select("a").first()!!.let {
|
|
||||||
chapter.name = it.select("h2").text()
|
|
||||||
chapter.setUrlWithoutDomain(it.attr("href"))
|
|
||||||
}
|
|
||||||
chapter.date_upload = parseDate(element.select("td:nth-child(2)").text().trim())
|
|
||||||
return chapter
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||||
val mangaId = manga.url.substringAfterLast("/").substringBefore('-')
|
|
||||||
return GET("$baseUrl/list-showchapter.php?idchapshow=$mangaId", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseDate(dateString: String): Long {
|
|
||||||
return try {
|
|
||||||
dateFormat.parse(dateString)?.time ?: 0L
|
|
||||||
} catch (e: ParseException) {
|
|
||||||
return 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
|
||||||
|
|
||||||
// Detail
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
|
||||||
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 =
|
|
||||||
parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text())
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseStatus(status: String?) = when {
|
|
||||||
status == null -> SManga.UNKNOWN
|
|
||||||
status.contains("Đang tiến hành") -> SManga.ONGOING
|
|
||||||
status.contains("Đã hoàn thành") -> SManga.COMPLETED
|
|
||||||
status.contains("Tạm ngưng") -> SManga.ON_HIATUS
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pages
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
return document.select("#image > img").mapIndexed { i, e ->
|
|
||||||
Page(i, imageUrl = e.attr("abs:src"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
if (document.select("p").toString()
|
|
||||||
.contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên")
|
|
||||||
) {
|
|
||||||
throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm")
|
|
||||||
}
|
|
||||||
|
|
||||||
val mangas = document.select(searchMangaSelector()).map { element ->
|
|
||||||
searchMangaFromElement(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasNextPage = searchMangaNextPageSelector().let { selector ->
|
|
||||||
document.select(selector).first()
|
|
||||||
} != null
|
|
||||||
|
|
||||||
return MangasPage(mangas, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
element.select(".search-des > a, .box-description 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")
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)"
|
|
||||||
|
|
||||||
private fun searchMangaByIdRequest(id: String) = GET("$searchAllURL?key=$id", headers)
|
|
||||||
private fun searchMangaByIdParse(response: Response, ids: String): MangasPage {
|
|
||||||
val details = mangaDetailsParse(response)
|
|
||||||
details.url = "/$ids-doc-truyen-id.html"
|
|
||||||
return MangasPage(listOf(details), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchSearchManga(
|
override fun fetchSearchManga(
|
||||||
page: Int,
|
page: Int,
|
||||||
query: String,
|
query: String,
|
||||||
@ -230,10 +152,6 @@ class HentaiVN : ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val PREFIX_ID_SEARCH = "id:"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = "$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=".toHttpUrlOrNull()!!
|
val url = "$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=".toHttpUrlOrNull()!!
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
@ -256,9 +174,161 @@ class HentaiVN : ParsedHttpSource() {
|
|||||||
return GET(url.toString(), headers)
|
return GET(url.toString(), headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
if (document.select("p").toString()
|
||||||
|
.contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên")
|
||||||
|
) {
|
||||||
|
throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm")
|
||||||
|
}
|
||||||
|
|
||||||
|
val mangas = document.select(searchMangaSelector()).map { element ->
|
||||||
|
searchMangaFromElement(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasNextPage = searchMangaNextPageSelector().let { selector ->
|
||||||
|
document.select(selector).first()
|
||||||
|
} != null
|
||||||
|
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() =
|
override fun searchMangaSelector() =
|
||||||
".search-ul .search-li, .main > .block-left > .block-item > ul > li.item"
|
".search-ul .search-li, .main > .block-left > .block-item > ul > li.item"
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
element.select(".search-des > a, .box-description 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")
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)"
|
||||||
|
|
||||||
|
private fun searchMangaByIdRequest(id: String) = GET("$searchAllURL?key=$id", headers)
|
||||||
|
private fun searchMangaByIdParse(response: Response, ids: String): MangasPage {
|
||||||
|
val details = mangaDetailsParse(response)
|
||||||
|
details.url = "/$ids-doc-truyen-id.html"
|
||||||
|
return MangasPage(listOf(details), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detail
|
||||||
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
|
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 =
|
||||||
|
parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text())
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapter
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
val mangaId = manga.url.substringAfterLast("/").substringBefore('-')
|
||||||
|
return GET("$baseUrl/list-showchapter.php?idchapshow=$mangaId", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListSelector() = "table.listing > tbody > tr"
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
|
if (element.select("a").isEmpty()) throw Exception(element.select("h2").html())
|
||||||
|
val chapter = SChapter.create()
|
||||||
|
element.select("a").first()!!.let {
|
||||||
|
chapter.name = it.select("h2").text()
|
||||||
|
chapter.setUrlWithoutDomain(it.attr("href"))
|
||||||
|
}
|
||||||
|
chapter.date_upload = parseDate(element.select("td:nth-child(2)").text().trim())
|
||||||
|
return chapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
|
return document.select("#image > img").mapIndexed { i, e ->
|
||||||
|
Page(i, imageUrl = imageFromElement(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document) = ""
|
||||||
|
|
||||||
|
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)
|
||||||
|
|
||||||
|
private fun parseDate(dateString: String): Long {
|
||||||
|
return kotlin.runCatching {
|
||||||
|
dateFormat.parse(dateString)?.time
|
||||||
|
}.getOrNull() ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStatus(status: String?) = when {
|
||||||
|
status == null -> SManga.UNKNOWN
|
||||||
|
status.contains("Đang tiến hành") -> SManga.ONGOING
|
||||||
|
status.contains("Đã hoàn thành") -> SManga.COMPLETED
|
||||||
|
status.contains("Tạm ngưng") -> SManga.ON_HIATUS
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun imageFromElement(element: Element): String? {
|
||||||
|
return when {
|
||||||
|
element.hasAttr("data-src") -> element.attr("abs:data-src")
|
||||||
|
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
|
||||||
|
element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ")
|
||||||
|
else -> element.attr("abs:src")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
EditTextPreference(screen.context).apply {
|
||||||
|
key = PREF_KEY_BASE_URL
|
||||||
|
title = TITLE_BASE_URL
|
||||||
|
summary = SUMMARY_BASE_URL
|
||||||
|
|
||||||
|
setDefaultValue(defaultBaseUrl)
|
||||||
|
dialogTitle = TITLE_BASE_URL
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, _ ->
|
||||||
|
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_KEY_RANDOM_UA
|
||||||
|
title = TITLE_RANDOM_UA
|
||||||
|
entries = ENTRIES_RANDOM_UA
|
||||||
|
entryValues = VALUES_RANDOM_UA
|
||||||
|
summary = "%s"
|
||||||
|
setDefaultValue("off")
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, _ ->
|
||||||
|
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
EditTextPreference(screen.context).apply {
|
||||||
|
key = PREF_KEY_CUSTOM_UA
|
||||||
|
title = TITLE_CUSTOM_UA
|
||||||
|
summary = SUMMARY_CUSTOM_UA
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
try {
|
||||||
|
Headers.Builder().add("User-Agent", newValue as String).build()
|
||||||
|
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||||
|
true
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Toast.makeText(screen.context, "Chuỗi đại diện người dùng không hợp lệ: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
}
|
||||||
|
|
||||||
private class Alls : Filter.Text("Tìm tất cả")
|
private class Alls : Filter.Text("Tìm tất cả")
|
||||||
private class Author : Filter.Text("Tác giả")
|
private class Author : Filter.Text("Tác giả")
|
||||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||||
@ -280,11 +350,11 @@ class HentaiVN : ParsedHttpSource() {
|
|||||||
Author(),
|
Author(),
|
||||||
TextField("Doujinshi", "dou"),
|
TextField("Doujinshi", "dou"),
|
||||||
TextField("Nhân vật", "char"),
|
TextField("Nhân vật", "char"),
|
||||||
GenreList(getGenreList()),
|
|
||||||
GroupList(getGroupList()),
|
GroupList(getGroupList()),
|
||||||
|
GenreList(getGenreList()),
|
||||||
)
|
)
|
||||||
|
|
||||||
// jQuery.makeArray($('#container > div > div > div.box-box.textbox > form > ul:nth-child(7) > li').map((i, e) => `Genre("${e.textContent}", "${e.children[0].value}")`)).join(',\n')
|
// console.log(jQuery.makeArray($('ul.ul-search > li').map((i, e) => `Genre("${e.textContent}", "${e.children[0].value}")`)).join(',\n'))
|
||||||
// https://hentaivn.autos/forum/search-plus.php
|
// https://hentaivn.autos/forum/search-plus.php
|
||||||
private fun getGenreList() = listOf(
|
private fun getGenreList() = listOf(
|
||||||
Genre("3D Hentai", "3"),
|
Genre("3D Hentai", "3"),
|
||||||
@ -514,4 +584,23 @@ class HentaiVN : ParsedHttpSource() {
|
|||||||
TransGroup("Depressed Lolicons Squad - DLS", "52"),
|
TransGroup("Depressed Lolicons Squad - DLS", "52"),
|
||||||
TransGroup("Heaven Of The Fuck", "53"),
|
TransGroup("Heaven Of The Fuck", "53"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PREFIX_ID_SEARCH = "id:"
|
||||||
|
|
||||||
|
const val RESTART_TACHIYOMI = "Khởi động lại Tachiyomi để áp dụng thay đổi."
|
||||||
|
|
||||||
|
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."
|
||||||
|
|
||||||
|
const val PREF_KEY_RANDOM_UA = "pref_key_random_ua_"
|
||||||
|
const val TITLE_RANDOM_UA = "Chuỗi đại diện người dùng ngẫu nhiên"
|
||||||
|
val ENTRIES_RANDOM_UA = arrayOf("Tắt", "Máy tính", "Di động")
|
||||||
|
val VALUES_RANDOM_UA = arrayOf("off", "desktop", "mobile")
|
||||||
|
|
||||||
|
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua_"
|
||||||
|
const val TITLE_CUSTOM_UA = "Chuỗi đại diện người dùng tuỳ chỉnh"
|
||||||
|
const val SUMMARY_CUSTOM_UA = "Để trống để dùng chuỗi đại diện người dùng mặc định của ứng dụng. Cài đặt này bị vô hiệu nếu chuỗi đại diện người dùng ngẫu nhiên được bật."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user