Fix Manhuagui manga thumbnail, added ability to parse more manga details and view R18+ manga. (#3556)

* Fix Manhuagui manga thumbnail, added ability to parse more manga details and view R18+ manga.

* Fixed bugs and irregular codes. Move the logic to send Post and Get request for disguise into mangaDetailsRequest(), they will be send less frequently now.

* compileOnly

Co-authored-by: snakedoc83 <mikepeterson83@gmail.com>
This commit is contained in:
Oldwangtouchtouchdoge 2020-06-19 09:32:18 +08:00 committed by GitHub
parent 8444b4c008
commit 60e16cf119
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 275 additions and 23 deletions

View File

@ -1,5 +1,6 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.72' ext.kotlin_version = '1.3.72'
ext.coroutines_version = '1.3.5'
repositories { repositories {
google() google()
maven { url 'https://plugins.gradle.org/m2/' } maven { url 'https://plugins.gradle.org/m2/' }

View File

@ -5,12 +5,15 @@ ext {
appName = 'Tachiyomi: ManHuaGui' appName = 'Tachiyomi: ManHuaGui'
pkgNameSuffix = 'zh.manhuagui' pkgNameSuffix = 'zh.manhuagui'
extClass = '.Manhuagui' extClass = '.Manhuagui'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
} }
dependencies { dependencies {
implementation project(':lib-ratelimit')
compileOnly project(':duktape-stub') compileOnly project(':duktape-stub')
compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,48 +1,146 @@
package eu.kanade.tachiyomi.extension.zh.manhuagui package eu.kanade.tachiyomi.extension.zh.manhuagui
import android.util.Log import android.app.Application
import android.content.SharedPreferences
import android.support.v7.preference.CheckBoxPreference
import android.support.v7.preference.PreferenceScreen
import com.google.gson.Gson import com.google.gson.Gson
import com.squareup.duktape.Duktape import com.squareup.duktape.Duktape
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter 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 java.io.IOException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
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 uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Manhuagui : ParsedHttpSource() { class Manhuagui : ConfigurableSource, ParsedHttpSource() {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val name = "漫画柜" override val name = "漫画柜"
override val baseUrl = "https://www.manhuagui.com" override val baseUrl =
if (preferences.getBoolean(SHOW_ZH_HANT_WEBSITE_PREF, false))
"https://tw.manhuagui.com"
else
"https://www.manhuagui.com"
override val lang = "zh" override val lang = "zh"
override val supportsLatest = true override val supportsLatest = true
val imageServer = arrayOf("https://i.hamreus.com")
private val imageServer = arrayOf("https://i.hamreus.com")
private val gson = Gson() private val gson = Gson()
private val baseHttpUrl: HttpUrl = HttpUrl.parse(baseUrl)!!
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/view_p$page.html", headers) // Add rate limit to fix manga thumbnail load failure
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/update_p$page.html", headers) private val rateLimitInterceptor = RateLimitInterceptor(5, 1, TimeUnit.SECONDS)
override val client: OkHttpClient =
if (getShowR18())
network.client.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)
.addNetworkInterceptor(AddCookieHeaderInterceptor(baseHttpUrl))
.build()
else
network.client.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)
.build()
// Add R18 verification cookie
class AddCookieHeaderInterceptor(private val baseHttpUrl: HttpUrl) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
if (chain.request().url().host() == baseHttpUrl.host()) {
val originalCookies = chain.request().header("Cookie") ?: ""
if (originalCookies != "") {
return chain.proceed(chain.request().newBuilder()
.header("Cookie", "$originalCookies; isAdult=1")
.build()
)
}
}
return chain.proceed(chain.request())
}
}
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/list/view_p$page.html", headers)
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/list/update_p$page.html", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
GET("$baseUrl/s/${query}_p$page.html", headers) GET("$baseUrl/s/${query}_p$page.html", headers)
override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) override fun mangaDetailsRequest(manga: SManga): Request {
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) var bid = Regex("""\d+/?$""").find(manga.url)?.value
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) if (bid != null) {
bid = bid.removeSuffix("/")
// Send a get request to https://www.manhuagui.com/tools/vote.ashx?act=get&bid=$bid
// and a post request to https://www.manhuagui.com/tools/submit_ajax.ashx?action=user_check_login
// to simulate what web page javascript do and get "country" cookie.
// Send requests using coroutine in another (IO) thread.
GlobalScope.launch {
withContext(Dispatchers.IO) {
// Delay 1 second to wait main manga details request complete
delay(1000L)
client.newCall(POST("$baseUrl/tools/submit_ajax.ashx?action=user_check_login", headersBuilder()
.set("Referer", manga.url)
.set("X-Requested-With", "XMLHttpRequest")
.build()
)).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) = e.printStackTrace()
override fun onResponse(call: Call, response: Response) = response.close()
})
client.newCall(GET("$baseUrl/tools/vote.ashx?act=get&bid=$bid", headersBuilder()
.set("Referer", manga.url)
.set("X-Requested-With", "XMLHttpRequest").build()
)).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) = e.printStackTrace()
override fun onResponse(call: Call, response: Response) = response.close()
})
}
}
}
return GET(baseUrl + manga.url, headers)
}
override fun popularMangaSelector() = "ul#contList > li" override fun popularMangaSelector() = "ul#contList > li"
override fun latestUpdatesSelector() = popularMangaSelector() override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = "div.book-result > ul > li" override fun searchMangaSelector() = "div.book-result > ul > li"
override fun chapterListSelector() = "ul > li > a.status0" override fun chapterListSelector() = "ul > li > a.status0"
override fun searchMangaNextPageSelector() = "a.prev" override fun searchMangaNextPageSelector() = "span.current + a" // "a.prev" contain 2~4 elements: first, previous, next and last page, "span.current + a" is a better choice.
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", baseUrl) .set("Referer", baseUrl)
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4086.0 Safari/537.36")
override fun popularMangaFromElement(element: Element) = mangaFromElement(element) override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
@ -51,7 +149,13 @@ class Manhuagui : ParsedHttpSource() {
element.select("a.bcover").first().let { element.select("a.bcover").first().let {
manga.url = it.attr("href") manga.url = it.attr("href")
manga.title = it.attr("title").trim() manga.title = it.attr("title").trim()
manga.thumbnail_url = it.select("img").first().attr("src")
// Fix thumbnail lazy load
val thumbnailElement = it.select("img").first()
manga.thumbnail_url = if (thumbnailElement.hasAttr("src"))
thumbnailElement.attr("abs:src")
else
thumbnailElement.attr("abs:data-src")
} }
return manga return manga
} }
@ -59,26 +163,74 @@ class Manhuagui : ParsedHttpSource() {
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("div.book-cover > a.bcover > img").first().attr("src")
element.select("div.book-detail").first().let { element.select("div.book-detail").first().let {
manga.url = it.select("dl > dt > a").first().attr("href") manga.url = it.select("dl > dt > a").first().attr("href")
manga.title = it.select("dl > dt > a").first().attr("title").trim() manga.title = it.select("dl > dt > a").first().attr("title").trim()
manga.thumbnail_url = element.select("div.book-cover > a.bcover > img").first().attr("abs:src")
} }
return manga return manga
} }
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element) = throw Exception("Not used")
val chapter = SChapter.create() override fun chapterListParse(response: Response): List<SChapter> {
chapter.url = element.attr("href") val document = response.asJsoup()
chapter.name = element.attr("title").trim() val chapters = mutableListOf<SChapter>()
return chapter
// Try to get R18 manga hidden chapter list
val hiddenEncryptedChapterList = document.select("#__VIEWSTATE").first()
if (hiddenEncryptedChapterList != null) {
if (getShowR18()) {
// Hidden chapter list is LZString encoded
val decodedHiddenChapterList = Duktape.create().use {
it.evaluate(jsDecodeFunc +
"""LZString.decompressFromBase64('${hiddenEncryptedChapterList.`val`()}');""") as String
} }
val hiddenChapterList = Jsoup.parse(decodedHiddenChapterList, response.request().url().toString())
if (hiddenChapterList != null) {
// Replace R18 warning with actual chapter list
document.select("#erroraudit_show").first().replaceWith(hiddenChapterList)
// Remove hidden chapter list element
document.select("#__VIEWSTATE").first().remove()
}
} else {
// "You need to enable R18 switch and restart Tachiyomi to read this manga"
error("您需要打开R18作品显示开关并重启软件才能阅读此作品")
}
}
val chapterList = document.select("ul > li > a.status0")
val latestChapterHref = document.select("div.book-detail > ul.detail-list > li.status > span > a.blue").first().attr("href")
chapterList.forEach {
val currentChapter = SChapter.create()
currentChapter.url = it.attr("href")
currentChapter.name = it.attr("title").trim()
// Manhuagui only provide upload date for latest chapter
if (currentChapter.url == latestChapterHref) {
currentChapter.date_upload = parseDate(document.select("div.book-detail > ul.detail-list > li.status > span > span.red").last())
}
chapters.add(currentChapter)
}
return chapters
}
private fun parseDate(element: Element): Long = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).parse(element.text()).time
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create() val manga = SManga.create()
manga.description = document.select("div#intro-all").text().trim() manga.description = document.select("div#intro-all").text().trim()
manga.thumbnail_url = document.select("p.hcover > img").attr("abs:src")
manga.artist = document.select("span:contains(漫画作者) > a , span:contains(漫畫作者) > a").text().trim()
manga.genre = document.select("span:contains(漫画剧情) > a , span:contains(漫畫劇情) > a").text().trim()
manga.status = when (document.select("div.book-detail > ul.detail-list > li.status > span > span").first().text()) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
"連載中" -> SManga.ONGOING
"已完結" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
return manga return manga
} }
@ -86,14 +238,23 @@ class Manhuagui : ParsedHttpSource() {
var LZString=(function(){var f=String.fromCharCode;var keyStrBase64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var baseReverseDic={};function getBaseValue(alphabet,character){if(!baseReverseDic[alphabet]){baseReverseDic[alphabet]={};for(var i=0;i<alphabet.length;i++){baseReverseDic[alphabet][alphabet.charAt(i)]=i}}return baseReverseDic[alphabet][character]}var LZString={decompressFromBase64:function(input){if(input==null)return"";if(input=="")return null;return LZString._0(input.length,32,function(index){return getBaseValue(keyStrBase64,input.charAt(index))})},_0:function(length,resetValue,getNextValue){var dictionary=[],next,enlargeIn=4,dictSize=4,numBits=3,entry="",result=[],i,w,bits,resb,maxpower,power,c,data={val:getNextValue(0),position:resetValue,index:1};for(i=0;i<3;i+=1){dictionary[i]=i}bits=0;maxpower=Math.pow(2,2);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}switch(next=bits){case 0:bits=0;maxpower=Math.pow(2,8);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}c=f(bits);break;case 1:bits=0;maxpower=Math.pow(2,16);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}c=f(bits);break;case 2:return""}dictionary[3]=c;w=c;result.push(c);while(true){if(data.index>length){return""}bits=0;maxpower=Math.pow(2,numBits);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}switch(c=bits){case 0:bits=0;maxpower=Math.pow(2,8);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}dictionary[dictSize++]=f(bits);c=dictSize-1;enlargeIn--;break;case 1:bits=0;maxpower=Math.pow(2,16);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}dictionary[dictSize++]=f(bits);c=dictSize-1;enlargeIn--;break;case 2:return result.join('')}if(enlargeIn==0){enlargeIn=Math.pow(2,numBits);numBits++}if(dictionary[c]){entry=dictionary[c]}else{if(c===dictSize){entry=w+w.charAt(0)}else{return null}}result.push(entry);dictionary[dictSize++]=w+entry.charAt(0);enlargeIn--;w=entry;if(enlargeIn==0){enlargeIn=Math.pow(2,numBits);numBits++}}}};return LZString})();String.prototype.splic=function(f){return LZString.decompressFromBase64(this).split(f)}; var LZString=(function(){var f=String.fromCharCode;var keyStrBase64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var baseReverseDic={};function getBaseValue(alphabet,character){if(!baseReverseDic[alphabet]){baseReverseDic[alphabet]={};for(var i=0;i<alphabet.length;i++){baseReverseDic[alphabet][alphabet.charAt(i)]=i}}return baseReverseDic[alphabet][character]}var LZString={decompressFromBase64:function(input){if(input==null)return"";if(input=="")return null;return LZString._0(input.length,32,function(index){return getBaseValue(keyStrBase64,input.charAt(index))})},_0:function(length,resetValue,getNextValue){var dictionary=[],next,enlargeIn=4,dictSize=4,numBits=3,entry="",result=[],i,w,bits,resb,maxpower,power,c,data={val:getNextValue(0),position:resetValue,index:1};for(i=0;i<3;i+=1){dictionary[i]=i}bits=0;maxpower=Math.pow(2,2);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}switch(next=bits){case 0:bits=0;maxpower=Math.pow(2,8);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}c=f(bits);break;case 1:bits=0;maxpower=Math.pow(2,16);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}c=f(bits);break;case 2:return""}dictionary[3]=c;w=c;result.push(c);while(true){if(data.index>length){return""}bits=0;maxpower=Math.pow(2,numBits);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}switch(c=bits){case 0:bits=0;maxpower=Math.pow(2,8);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}dictionary[dictSize++]=f(bits);c=dictSize-1;enlargeIn--;break;case 1:bits=0;maxpower=Math.pow(2,16);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}dictionary[dictSize++]=f(bits);c=dictSize-1;enlargeIn--;break;case 2:return result.join('')}if(enlargeIn==0){enlargeIn=Math.pow(2,numBits);numBits++}if(dictionary[c]){entry=dictionary[c]}else{if(c===dictSize){entry=w+w.charAt(0)}else{return null}}result.push(entry);dictionary[dictSize++]=w+entry.charAt(0);enlargeIn--;w=entry;if(enlargeIn==0){enlargeIn=Math.pow(2,numBits);numBits++}}}};return LZString})();String.prototype.splic=function(f){return LZString.decompressFromBase64(this).split(f)};
""" """
// Page list is javascript eval encoded and LZString encoded, these website:
// http://www.oicqzone.com/tool/eval/ , https://www.w3xue.com/tools/jseval/ ,
// https://www.w3cschool.cn/tools/index?name=evalencode can try to decode javascript eval encoded content,
// jsDecodeFunc's LZString.decompressFromBase64() can decode LZString.
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
// R18 warning element (#erroraudit_show) is remove by web page javascript, so here the warning element
// will always exist if this manga is R18 limited whether R18 verification cookies has been sent or not.
// But it will not interfere parse mechanism below.
if (document.select("#erroraudit_show").first() != null && !getShowR18())
error("R18作品显示开关未开启或未生效") // "R18 setting didn't enabled or became effective"
val html = document.html() val html = document.html()
val re = Regex("""window\[".*?"\](\(.*\)\s*\{[\s\S]+\}\s*\(.*\))""") val re = Regex("""window\[".*?"\](\(.*\)\s*\{[\s\S]+\}\s*\(.*\))""")
val imgCode = re.find(html)?.groups?.get(1)?.value val imgCode = re.find(html)?.groups?.get(1)?.value
val imgDecode = Duktape.create().use { val imgDecode = Duktape.create().use {
it.evaluate(jsDecodeFunc + imgCode) as String it.evaluate(jsDecodeFunc + imgCode) as String
} }
Log.i("jsonresult", imgDecode)
val re2 = Regex("""\{.*\}""") val re2 = Regex("""\{.*\}""")
val imgJsonStr = re2.find(imgDecode)?.groups?.get(0)?.value val imgJsonStr = re2.find(imgDecode)?.groups?.get(0)?.value
@ -101,10 +262,97 @@ class Manhuagui : ParsedHttpSource() {
return imageJson.files!!.mapIndexed { i, imgStr -> return imageJson.files!!.mapIndexed { i, imgStr ->
val imgurl = "${imageServer[0]}${imageJson.path}$imgStr?cid=${imageJson.cid}&md5=${imageJson.sl?.md5}" val imgurl = "${imageServer[0]}${imageJson.path}$imgStr?cid=${imageJson.cid}&md5=${imageJson.sl?.md5}"
Log.i("image", imgurl)
Page(i, "", imgurl) Page(i, "", imgurl)
} }
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
// Simplified/Traditional Chinese version website switch
val zhHantPreference = androidx.preference.CheckBoxPreference(screen.context).apply {
key = SHOW_ZH_HANT_WEBSITE_PREF
// "Use traditional chinese version website"
title = "使用繁体版网站"
// "You need to restart Tachiyomi"
summary = "需要重启软件。"
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putBoolean(SHOW_ZH_HANT_WEBSITE_PREF, newValue as Boolean).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
// R18+ switch
val r18Preference = androidx.preference.CheckBoxPreference(screen.context).apply {
key = SHOW_R18_PREF_Title
// "R18 Setting"
title = "R18作品显示设置"
// "Please make sure your IP is not in Manhuagui's ban list, e.g., China mainland IP. Tachiyomi restart required.
summary = "请确认您的IP不在漫画柜的屏蔽列表内例如中国大陆IP。需要重启软件以生效。"
setOnPreferenceChangeListener { _, newValue ->
try {
val newSetting = preferences.edit().putBoolean(SHOW_R18_PREF, newValue as Boolean).commit()
newSetting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(zhHantPreference)
screen.addPreference(r18Preference)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val zhHantPreference = CheckBoxPreference(screen.context).apply {
key = SHOW_ZH_HANT_WEBSITE_PREF
title = "使用繁体版网站"
summary = "需要重启软件。"
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putBoolean(SHOW_ZH_HANT_WEBSITE_PREF, newValue as Boolean).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
val r18Preference = CheckBoxPreference(screen.context).apply {
key = SHOW_R18_PREF_Title
title = "R18作品显示设置"
summary = "请确认您的IP不在漫画柜的屏蔽列表内例如中国大陆IP。需要重启软件以生效。"
setOnPreferenceChangeListener { _, newValue ->
try {
val newSetting = preferences.edit().putBoolean(SHOW_R18_PREF, newValue as Boolean).commit()
newSetting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(zhHantPreference)
screen.addPreference(r18Preference)
}
private fun getShowR18(): Boolean = preferences.getBoolean(SHOW_R18_PREF, false)
companion object {
private const val SHOW_R18_PREF_Title = "R18Setting"
private const val SHOW_R18_PREF = "showR18Default"
private const val SHOW_ZH_HANT_WEBSITE_PREF = "showZhHantWebsite"
}
} }