mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-15 13:37:29 +01:00
Various changes
This commit is contained in:
@@ -117,4 +117,5 @@ object PreferenceKeys {
|
||||
|
||||
fun trackToken(syncId: Int) = "track_token_$syncId"
|
||||
|
||||
const val eh_nh_useHighQualityThumbs = "eh_nh_hq_thumbs"
|
||||
}
|
||||
|
||||
@@ -201,5 +201,7 @@ class PreferencesHelper(val context: Context) {
|
||||
fun lockLength() = rxPrefs.getInteger("lock_length", -1)
|
||||
|
||||
fun lockUseFingerprint() = rxPrefs.getBoolean("lock_finger", false)
|
||||
|
||||
fun eh_useHighQualityThumbs() = rxPrefs.getBoolean(Keys.eh_nh_useHighQualityThumbs, false)
|
||||
// <-- EH
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.source.online.russian.Mintmanga
|
||||
import eu.kanade.tachiyomi.source.online.russian.Readmanga
|
||||
import eu.kanade.tachiyomi.util.hasPermission
|
||||
import exh.*
|
||||
import exh.metadata.models.PervEdenLang
|
||||
import org.yaml.snakeyaml.Yaml
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
@@ -93,9 +94,10 @@ open class SourceManager(private val context: Context) {
|
||||
if(prefs.enableExhentai().getOrDefault()) {
|
||||
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
||||
}
|
||||
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, "en")
|
||||
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, "it")
|
||||
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, PervEdenLang.en)
|
||||
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it)
|
||||
exSrcs += NHentai(context)
|
||||
exSrcs += HentaiCafe()
|
||||
return exSrcs
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import exh.metadata.models.GalleryQuery
|
||||
import exh.metadata.models.PervEdenGalleryMetadata
|
||||
import exh.metadata.models.SearchableGalleryMetadata
|
||||
import exh.util.createUUIDObj
|
||||
import exh.util.defRealm
|
||||
import exh.util.realmTrans
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* LEWD!
|
||||
*/
|
||||
interface LewdSource<M : SearchableGalleryMetadata, I> : CatalogueSource {
|
||||
fun queryAll(): GalleryQuery<M>
|
||||
|
||||
fun queryFromUrl(url: String): GalleryQuery<M>
|
||||
|
||||
val metaParser: M.(I) -> Unit
|
||||
|
||||
fun parseToManga(query: GalleryQuery<M>, input: I): SManga
|
||||
= realmTrans { realm ->
|
||||
val meta = realm.copyFromRealm(query.query(realm).findFirst()
|
||||
?: realm.createUUIDObj(queryAll().clazz.java))
|
||||
|
||||
metaParser(meta, input)
|
||||
|
||||
realm.copyToRealmOrUpdate(meta)
|
||||
|
||||
SManga.create().apply {
|
||||
meta.copyTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun lazyLoadMeta(query: GalleryQuery<M>, parserInput: Observable<I>): Observable<M> {
|
||||
return defRealm { realm ->
|
||||
val possibleOutput = query.query(realm).findFirst()
|
||||
|
||||
if(possibleOutput == null)
|
||||
parserInput.map {
|
||||
realmTrans { realm ->
|
||||
val meta = realm.createUUIDObj(queryAll().clazz.java)
|
||||
|
||||
metaParser(meta, it)
|
||||
|
||||
realm.copyFromRealm(meta)
|
||||
}
|
||||
}
|
||||
else
|
||||
Observable.just(realm.copyFromRealm(possibleOutput))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import exh.metadata.*
|
||||
import exh.metadata.models.ExGalleryMetadata
|
||||
@@ -24,13 +25,11 @@ import okhttp3.CacheControl
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import exh.GalleryAdder
|
||||
import exh.util.*
|
||||
import io.realm.Realm
|
||||
|
||||
class EHentai(override val id: Long,
|
||||
val exh: Boolean,
|
||||
val context: Context) : HttpSource() {
|
||||
val context: Context) : HttpSource(), LewdSource<ExGalleryMetadata, Response> {
|
||||
|
||||
val schema: String
|
||||
get() = if(prefs.secureEXH().getOrDefault())
|
||||
@@ -49,8 +48,6 @@ class EHentai(override val id: Long,
|
||||
|
||||
val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
val galleryAdder = GalleryAdder()
|
||||
|
||||
/**
|
||||
* Gallery list entry
|
||||
*/
|
||||
@@ -185,90 +182,80 @@ class EHentai(override val id: Long,
|
||||
/**
|
||||
* Parse gallery page to metadata model
|
||||
*/
|
||||
override fun mangaDetailsParse(response: Response)
|
||||
= with(response.asJsoup()) {
|
||||
realmTrans { realm ->
|
||||
val url = response.request().url().encodedPath()!!
|
||||
val gId = ExGalleryMetadata.galleryId(url)
|
||||
val gToken = ExGalleryMetadata.galleryToken(url)
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
return parseToManga(queryFromUrl(response.request().url().toString()), response)
|
||||
}
|
||||
|
||||
val metdata = (realm.loadEh(gId, gToken, exh)
|
||||
?: realm.createUUIDObj(ExGalleryMetadata::class.java))
|
||||
with(metdata) {
|
||||
this.url = url
|
||||
this.gId = gId
|
||||
this.gToken = gToken
|
||||
override val metaParser: ExGalleryMetadata.(Response) -> Unit = { response ->
|
||||
with(response.asJsoup()) {
|
||||
url = response.request().url().encodedPath()!!
|
||||
gId = ExGalleryMetadata.galleryId(url!!)
|
||||
gToken = ExGalleryMetadata.galleryToken(url!!)
|
||||
|
||||
exh = this@EHentai.exh
|
||||
title = select("#gn").text().nullIfBlank()?.trim()
|
||||
exh = this@EHentai.exh
|
||||
title = select("#gn").text().nullIfBlank()?.trim()
|
||||
|
||||
altTitle = select("#gj").text().nullIfBlank()?.trim()
|
||||
altTitle = select("#gj").text().nullIfBlank()?.trim()
|
||||
|
||||
thumbnailUrl = select("#gd1 div").attr("style").nullIfBlank()?.let {
|
||||
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
||||
}
|
||||
genre = select(".ic").parents().attr("href").nullIfBlank()?.trim()?.substringAfterLast('/')
|
||||
thumbnailUrl = select("#gd1 div").attr("style").nullIfBlank()?.let {
|
||||
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
||||
}
|
||||
genre = select(".ic").parents().attr("href").nullIfBlank()?.trim()?.substringAfterLast('/')
|
||||
|
||||
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
||||
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
||||
|
||||
//Parse the table
|
||||
select("#gdd tr").forEach {
|
||||
it.select(".gdt1")
|
||||
.text()
|
||||
.nullIfBlank()
|
||||
?.trim()
|
||||
?.let { left ->
|
||||
it.select(".gdt2")
|
||||
.text()
|
||||
.nullIfBlank()
|
||||
?.trim()
|
||||
?.let { right ->
|
||||
ignore {
|
||||
when (left.removeSuffix(":")
|
||||
.toLowerCase()) {
|
||||
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||
"visible" -> visible = right.nullIfBlank()
|
||||
"language" -> {
|
||||
language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank()
|
||||
translated = right.endsWith(TR_SUFFIX, true)
|
||||
}
|
||||
"file size" -> size = parseHumanReadableByteCount(right)?.toLong()
|
||||
"length" -> length = right.removeSuffix("pages").trim().nullIfBlank()?.toInt()
|
||||
"favorited" -> favorites = right.removeSuffix("times").trim().nullIfBlank()?.toInt()
|
||||
//Parse the table
|
||||
select("#gdd tr").forEach {
|
||||
it.select(".gdt1")
|
||||
.text()
|
||||
.nullIfBlank()
|
||||
?.trim()
|
||||
?.let { left ->
|
||||
it.select(".gdt2")
|
||||
.text()
|
||||
.nullIfBlank()
|
||||
?.trim()
|
||||
?.let { right ->
|
||||
ignore {
|
||||
when (left.removeSuffix(":")
|
||||
.toLowerCase()) {
|
||||
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||
"visible" -> visible = right.nullIfBlank()
|
||||
"language" -> {
|
||||
language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank()
|
||||
translated = right.endsWith(TR_SUFFIX, true)
|
||||
}
|
||||
"file size" -> size = parseHumanReadableByteCount(right)?.toLong()
|
||||
"length" -> length = right.removeSuffix("pages").trim().nullIfBlank()?.toInt()
|
||||
"favorited" -> favorites = right.removeSuffix("times").trim().nullIfBlank()?.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Parse ratings
|
||||
ignore {
|
||||
averageRating = select("#rating_label")
|
||||
.text()
|
||||
.removePrefix("Average:")
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toDouble()
|
||||
ratingCount = select("#rating_count")
|
||||
.text()
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toInt()
|
||||
}
|
||||
//Parse ratings
|
||||
ignore {
|
||||
averageRating = select("#rating_label")
|
||||
.text()
|
||||
.removePrefix("Average:")
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toDouble()
|
||||
ratingCount = select("#rating_count")
|
||||
.text()
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toInt()
|
||||
}
|
||||
|
||||
//Parse tags
|
||||
tags.clear()
|
||||
select("#taglist tr").forEach {
|
||||
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||
tags.addAll(it.select("div").map {
|
||||
Tag(namespace, it.text().trim(), it.hasClass("gtl"))
|
||||
})
|
||||
}
|
||||
|
||||
//Copy metadata to manga
|
||||
SManga.create().apply {
|
||||
copyTo(this)
|
||||
}
|
||||
//Parse tags
|
||||
tags.clear()
|
||||
select("#taglist tr").forEach {
|
||||
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||
tags.addAll(it.select("div").map {
|
||||
Tag(namespace, it.text().trim(), it.hasClass("gtl"))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,7 +310,7 @@ class EHentai(override val id: Long,
|
||||
if (favNames == null)
|
||||
favNames = doc.getElementsByClass("nosel").first().children().filter {
|
||||
it.children().size >= 3
|
||||
}.map { it.child(2).text() }.filterNotNull()
|
||||
}.mapNotNull { it.child(2).text() }
|
||||
|
||||
//Next page
|
||||
page++
|
||||
@@ -384,9 +371,9 @@ class EHentai(override val id: Long,
|
||||
}
|
||||
|
||||
fun buildCookies(cookies: Map<String, String>)
|
||||
= cookies.entries.map {
|
||||
= cookies.entries.joinToString(separator = "; ", postfix = ";") {
|
||||
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
||||
}.joinToString(separator = "; ", postfix = ";")
|
||||
}
|
||||
|
||||
fun addParam(url: String, param: String, value: String)
|
||||
= Uri.parse(url)
|
||||
@@ -465,6 +452,9 @@ class EHentai(override val id: Long,
|
||||
else
|
||||
"E-Hentai"
|
||||
|
||||
override fun queryAll() = ExGalleryMetadata.EmptyQuery()
|
||||
override fun queryFromUrl(url: String) = ExGalleryMetadata.UrlQuery(url, exh)
|
||||
|
||||
companion object {
|
||||
val QUERY_PREFIX = "?f_apply=Apply+Filter"
|
||||
val TR_SUFFIX = "TR"
|
||||
|
||||
@@ -2,10 +2,7 @@ package eu.kanade.tachiyomi.source.online.all
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.github.salomonbrys.kotson.get
|
||||
import com.github.salomonbrys.kotson.int
|
||||
import com.github.salomonbrys.kotson.long
|
||||
import com.github.salomonbrys.kotson.string
|
||||
import com.github.salomonbrys.kotson.*
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
@@ -16,17 +13,12 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||
import exh.NHENTAI_SOURCE_ID
|
||||
import exh.metadata.copyTo
|
||||
import exh.metadata.loadNhentai
|
||||
import exh.metadata.loadNhentaiAsync
|
||||
import exh.metadata.models.NHentaiMetadata
|
||||
import exh.metadata.models.PageImageType
|
||||
import exh.metadata.models.Tag
|
||||
import exh.util.createUUIDObj
|
||||
import exh.util.defRealm
|
||||
import exh.util.realmTrans
|
||||
import exh.util.urlImportFetchSearchManga
|
||||
import exh.util.*
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
@@ -36,7 +28,7 @@ import timber.log.Timber
|
||||
* NHentai source
|
||||
*/
|
||||
|
||||
class NHentai(context: Context) : HttpSource() {
|
||||
class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiMetadata, JsonObject> {
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
//TODO There is currently no way to get the most popular mangas
|
||||
//TODO Instead, we delegate this to the latest updates thing to avoid confusing users with an empty screen
|
||||
@@ -78,8 +70,10 @@ class NHentai(context: Context) : HttpSource() {
|
||||
override fun latestUpdatesParse(response: Response)
|
||||
= parseResultPage(response)
|
||||
|
||||
override fun mangaDetailsParse(response: Response)
|
||||
= parseGallery(jsonParser.parse(response.body()!!.string()).asJsonObject)
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val obj = jsonParser.parse(response.body()!!.string()).asJsonObject
|
||||
return parseToManga(NHentaiMetadata.Query(obj["id"].long), obj)
|
||||
}
|
||||
|
||||
//Used so we can use a different URL for fetching manga details and opening the details in the browser
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
@@ -102,7 +96,8 @@ class NHentai(context: Context) : HttpSource() {
|
||||
val error = res.get("error")
|
||||
if(error == null) {
|
||||
val results = res.getAsJsonArray("result")?.map {
|
||||
parseGallery(it.asJsonObject)
|
||||
val obj = it.asJsonObject
|
||||
parseToManga(NHentaiMetadata.Query(obj["id"].long), obj)
|
||||
}
|
||||
val numPages = res.get("num_pages")?.int
|
||||
if(results != null && numPages != null)
|
||||
@@ -113,70 +108,65 @@ class NHentai(context: Context) : HttpSource() {
|
||||
return MangasPage(emptyList(), false)
|
||||
}
|
||||
|
||||
fun rawParseGallery(obj: JsonObject) = realmTrans { realm ->
|
||||
val nhId = obj.get("id").asLong
|
||||
override val metaParser: NHentaiMetadata.(JsonObject) -> Unit = { obj ->
|
||||
nhId = obj["id"].asLong
|
||||
|
||||
realm.copyFromRealm((realm.loadNhentai(nhId)
|
||||
?: realm.createUUIDObj(NHentaiMetadata::class.java)).apply {
|
||||
this.nhId = nhId
|
||||
uploadDate = obj["upload_date"].nullLong
|
||||
|
||||
uploadDate = obj.get("upload_date")?.notNull()?.long
|
||||
favoritesCount = obj["num_favorites"].nullLong
|
||||
|
||||
favoritesCount = obj.get("num_favorites")?.notNull()?.long
|
||||
mediaId = obj["media_id"].nullString
|
||||
|
||||
mediaId = obj.get("media_id")?.notNull()?.string
|
||||
obj["title"].nullObj?.let { it ->
|
||||
japaneseTitle = it["japanese"].nullString
|
||||
shortTitle = it["pretty"].nullString
|
||||
englishTitle = it["english"].nullString
|
||||
}
|
||||
|
||||
obj.get("title")?.asJsonObject?.let {
|
||||
japaneseTitle = it.get("japanese")?.notNull()?.string
|
||||
shortTitle = it.get("pretty")?.notNull()?.string
|
||||
englishTitle = it.get("english")?.notNull()?.string
|
||||
obj["images"].nullObj?.let {
|
||||
coverImageType = it["cover"]?.get("t").nullString
|
||||
it["pages"].nullArray?.mapNotNull {
|
||||
it?.asJsonObject?.get("t").nullString
|
||||
}?.map {
|
||||
PageImageType(it)
|
||||
}?.let {
|
||||
pageImageTypes.clear()
|
||||
pageImageTypes.addAll(it)
|
||||
}
|
||||
thumbnailImageType = it["thumbnail"]?.get("t").nullString
|
||||
}
|
||||
|
||||
obj.get("images")?.asJsonObject?.let {
|
||||
coverImageType = it.get("cover")?.get("t")?.notNull()?.asString
|
||||
it.get("pages")?.asJsonArray?.map {
|
||||
it?.asJsonObject?.get("t")?.notNull()?.asString
|
||||
}?.filterNotNull()?.map {
|
||||
PageImageType(it)
|
||||
}?.let {
|
||||
pageImageTypes.clear()
|
||||
pageImageTypes.addAll(it)
|
||||
}
|
||||
thumbnailImageType = it.get("thumbnail")?.get("t")?.notNull()?.asString
|
||||
}
|
||||
scanlator = obj["scanlator"].nullString
|
||||
|
||||
scanlator = obj.get("scanlator")?.notNull()?.asString
|
||||
|
||||
obj.get("tags")?.asJsonArray?.map {
|
||||
val asObj = it.asJsonObject
|
||||
Pair(asObj.get("type")?.string, asObj.get("name")?.string)
|
||||
}?.apply {
|
||||
tags.clear()
|
||||
}?.forEach {
|
||||
if(it.first != null && it.second != null)
|
||||
tags.add(Tag(it.first!!, it.second!!, false))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun parseGallery(obj: JsonObject) = rawParseGallery(obj).let {
|
||||
SManga.create().apply {
|
||||
it.copyTo(this)
|
||||
obj["tags"]?.asJsonArray?.map {
|
||||
val asObj = it.asJsonObject
|
||||
Pair(asObj["type"].nullString, asObj["name"].nullString)
|
||||
}?.apply {
|
||||
tags.clear()
|
||||
}?.forEach {
|
||||
if(it.first != null && it.second != null)
|
||||
tags.add(Tag(it.first!!, it.second!!, false))
|
||||
}
|
||||
}
|
||||
|
||||
fun lazyLoadMetadata(url: String) =
|
||||
defRealm { realm ->
|
||||
val meta = realm.loadNhentai(NHentaiMetadata.nhIdFromUrl(url))
|
||||
if(meta == null)
|
||||
val meta = NHentaiMetadata.UrlQuery(url).query(realm).findFirst()
|
||||
if(meta == null) {
|
||||
client.newCall(urlToDetailsRequest(url))
|
||||
.asObservableSuccess()
|
||||
.map {
|
||||
rawParseGallery(jsonParser.parse(it.body()!!.string())
|
||||
.asJsonObject)
|
||||
}.first()
|
||||
else
|
||||
realmTrans { realm ->
|
||||
realm.copyFromRealm(realm.createUUIDObj(queryAll().clazz.java).apply {
|
||||
metaParser(this,
|
||||
jsonParser.parse(it.body()!!.string()).asJsonObject)
|
||||
})
|
||||
}
|
||||
}
|
||||
.first()
|
||||
} else {
|
||||
Observable.just(realm.copyFromRealm(meta))
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga)
|
||||
@@ -184,8 +174,7 @@ class NHentai(context: Context) : HttpSource() {
|
||||
listOf(SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Chapter"
|
||||
//TODO Get this working later
|
||||
// date_upload = it.uploadDate ?: 0
|
||||
date_upload = ((it.uploadDate ?: 0) * 1000)
|
||||
chapter_number = 1f
|
||||
})
|
||||
}!!
|
||||
@@ -241,6 +230,9 @@ class NHentai(context: Context) : HttpSource() {
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun queryAll() = NHentaiMetadata.EmptyQuery()
|
||||
override fun queryFromUrl(url: String) = NHentaiMetadata.UrlQuery(url)
|
||||
|
||||
companion object {
|
||||
val jsonParser by lazy {
|
||||
JsonParser()
|
||||
|
||||
@@ -3,32 +3,29 @@ package eu.kanade.tachiyomi.source.online.all
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.ChapterRecognition
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import exh.metadata.copyTo
|
||||
import exh.metadata.loadPervEden
|
||||
import exh.metadata.models.PervEdenGalleryMetadata
|
||||
import exh.metadata.models.PervEdenTitle
|
||||
import exh.metadata.models.Tag
|
||||
import exh.metadata.models.*
|
||||
import exh.util.UriFilter
|
||||
import exh.util.UriGroup
|
||||
import exh.util.createUUIDObj
|
||||
import exh.util.realmTrans
|
||||
import exh.util.urlImportFetchSearchManga
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.TextNode
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class PervEden(override val id: Long, override val lang: String) : ParsedHttpSource() {
|
||||
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
|
||||
LewdSource<PervEdenGalleryMetadata, Document> {
|
||||
|
||||
override val supportsLatest = true
|
||||
override val name = "Perv Eden"
|
||||
override val baseUrl = "http://www.perveden.com"
|
||||
override val lang = pvLang.name
|
||||
|
||||
override fun popularMangaSelector() = "#topManga > ul > li"
|
||||
|
||||
@@ -45,6 +42,12 @@ class PervEden(override val id: Long, override val lang: String) : ParsedHttpSou
|
||||
|
||||
override fun popularMangaNextPageSelector(): String? = null
|
||||
|
||||
//Support direct URL importing
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||
urlImportFetchSearchManga(query, {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
})
|
||||
|
||||
override fun searchMangaSelector() = "#mangaList > tbody > tr"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
@@ -89,6 +92,7 @@ class PervEden(override val id: Long, override val lang: String) : ParsedHttpSou
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val uri = Uri.parse("$baseUrl/$lang/$lang-directory/").buildUpon()
|
||||
uri.appendQueryParameter("page", page.toString())
|
||||
uri.appendQueryParameter("title", query)
|
||||
filters.forEach {
|
||||
if(it is UriFilter) it.addToUri(uri)
|
||||
}
|
||||
@@ -99,77 +103,74 @@ class PervEden(override val id: Long, override val lang: String) : ParsedHttpSou
|
||||
throw NotImplementedError("Unused method called!")
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
realmTrans { realm ->
|
||||
val url = document.location()
|
||||
val metadata = (realm.loadPervEden(PervEdenGalleryMetadata.pvIdFromUrl(url), id)
|
||||
?: realm.createUUIDObj(PervEdenGalleryMetadata::class.java))
|
||||
with(metadata) {
|
||||
this.url = url
|
||||
override val metaParser: PervEdenGalleryMetadata.(Document) -> Unit = { document ->
|
||||
url = Uri.parse(document.location()).path
|
||||
|
||||
lang = this@PervEden.lang
|
||||
pvId = PervEdenGalleryMetadata.pvIdFromUrl(url!!)
|
||||
|
||||
title = document.getElementsByClass("manga-title").first()?.text()
|
||||
lang = this@PervEden.lang
|
||||
|
||||
thumbnailUrl = "http:" + document.getElementsByClass("mangaImage2").first()?.child(0)?.attr("src")
|
||||
title = document.getElementsByClass("manga-title").first()?.text()
|
||||
|
||||
val rightBoxElement = document.select(".rightBox:not(.info)").first()
|
||||
thumbnailUrl = "http:" + document.getElementsByClass("mangaImage2").first()?.child(0)?.attr("src")
|
||||
|
||||
tags.clear()
|
||||
var inStatus: String? = null
|
||||
rightBoxElement.childNodes().forEach {
|
||||
if(it is Element && it.tagName().toLowerCase() == "h4") {
|
||||
inStatus = it.text().trim()
|
||||
} else {
|
||||
when(inStatus) {
|
||||
"Alternative name(s)" -> {
|
||||
if(it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if(!text.isBlank())
|
||||
altTitles.add(PervEdenTitle(this, text))
|
||||
}
|
||||
}
|
||||
"Artist" -> {
|
||||
if(it is Element && it.tagName() == "a") {
|
||||
artist = it.text()
|
||||
tags.add(Tag("artist", it.text().toLowerCase(), false))
|
||||
}
|
||||
}
|
||||
"Genres" -> {
|
||||
if(it is Element && it.tagName() == "a")
|
||||
tags.add(Tag("genre", it.text().toLowerCase(), false))
|
||||
}
|
||||
"Type" -> {
|
||||
if(it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if(!text.isBlank())
|
||||
type = text
|
||||
}
|
||||
}
|
||||
"Status" -> {
|
||||
if(it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if(!text.isBlank())
|
||||
status = text
|
||||
}
|
||||
}
|
||||
val rightBoxElement = document.select(".rightBox:not(.info)").first()
|
||||
|
||||
altTitles.clear()
|
||||
tags.clear()
|
||||
var inStatus: String? = null
|
||||
rightBoxElement.childNodes().forEach {
|
||||
if(it is Element && it.tagName().toLowerCase() == "h4") {
|
||||
inStatus = it.text().trim()
|
||||
} else {
|
||||
when(inStatus) {
|
||||
"Alternative name(s)" -> {
|
||||
if(it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if(!text.isBlank())
|
||||
altTitles.add(PervEdenTitle(this, text))
|
||||
}
|
||||
}
|
||||
"Artist" -> {
|
||||
if(it is Element && it.tagName() == "a") {
|
||||
artist = it.text()
|
||||
tags.add(Tag("artist", it.text().toLowerCase(), false))
|
||||
}
|
||||
}
|
||||
"Genres" -> {
|
||||
if(it is Element && it.tagName() == "a")
|
||||
tags.add(Tag("genre", it.text().toLowerCase(), false))
|
||||
}
|
||||
"Type" -> {
|
||||
if(it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if(!text.isBlank())
|
||||
type = text
|
||||
}
|
||||
}
|
||||
"Status" -> {
|
||||
if(it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if(!text.isBlank())
|
||||
status = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rating = document.getElementById("rating-score")?.attr("value")?.toFloat()
|
||||
|
||||
return SManga.create().apply {
|
||||
copyTo(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rating = document.getElementById("rating-score")?.attr("value")?.toFloat()
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga
|
||||
= parseToManga(queryFromUrl(document.location()), document)
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val num = if(lang == "en") "0"
|
||||
else if(lang == "it") "1"
|
||||
else throw NotImplementedError("Unimplemented language!")
|
||||
val num = when (lang) {
|
||||
"en" -> "0"
|
||||
"it" -> "1"
|
||||
else -> throw NotImplementedError("Unimplemented language!")
|
||||
}
|
||||
|
||||
return GET("$baseUrl/ajax/news/$page/$num/0/")
|
||||
}
|
||||
@@ -201,6 +202,9 @@ class PervEden(override val id: Long, override val lang: String) : ParsedHttpSou
|
||||
override fun imageUrlParse(document: Document)
|
||||
= "http:" + document.getElementById("mainImg").attr("src")!!
|
||||
|
||||
override fun queryAll() = PervEdenGalleryMetadata.EmptyQuery()
|
||||
override fun queryFromUrl(url: String) = PervEdenGalleryMetadata.UrlQuery(url, PervEdenLang.source(id))
|
||||
|
||||
override fun getFilterList() = FilterList (
|
||||
AuthorFilter(),
|
||||
ArtistFilter(),
|
||||
@@ -223,7 +227,7 @@ class PervEden(override val id: Long, override val lang: String) : ParsedHttpSou
|
||||
}
|
||||
|
||||
//Explicit type arg for listOf() to workaround this: KT-16570
|
||||
class ReleaseYearGroup : UriGroup<Filter<*>>("Release Year", listOf<Filter<*>>(
|
||||
class ReleaseYearGroup : UriGroup<Filter<*>>("Release Year", listOf(
|
||||
ReleaseYearRangeFilter(),
|
||||
ReleaseYearYearFilter()
|
||||
))
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package eu.kanade.tachiyomi.source.online.english
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import exh.HENTAI_CAFE_SOURCE_ID
|
||||
import exh.metadata.models.HentaiCafeMetadata
|
||||
import exh.metadata.models.HentaiCafeMetadata.Companion.BASE_URL
|
||||
import exh.metadata.models.Tag
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
|
||||
class HentaiCafe : ParsedHttpSource(), LewdSource<HentaiCafeMetadata, Document> {
|
||||
override val id = HENTAI_CAFE_SOURCE_ID
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun queryAll() = HentaiCafeMetadata.EmptyQuery()
|
||||
override fun queryFromUrl(url: String) = HentaiCafeMetadata.UrlQuery(url)
|
||||
|
||||
override val name = "Hentai Cafe"
|
||||
override val baseUrl = "https://hentai.cafe"
|
||||
|
||||
override fun popularMangaSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||
override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException("Unused method called!")
|
||||
override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Unused method called!")
|
||||
override fun fetchPopularManga(page: Int) = fetchLatestUpdates(page)
|
||||
|
||||
override fun searchMangaSelector() = "article.post"
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val thumb = element.select(".entry-thumb > img")
|
||||
val title = element.select(".entry-title > a")
|
||||
|
||||
return SManga.create().apply {
|
||||
setUrlWithoutDomain(title.attr("href"))
|
||||
this.title = title.text()
|
||||
|
||||
thumbnail_url = thumb.attr("src")
|
||||
}
|
||||
}
|
||||
override fun searchMangaNextPageSelector() = ".x-pagination > ul > li:last-child > a.prev-next"
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = if(query.isNotBlank()) {
|
||||
//Filter by query
|
||||
"$baseUrl/page/$page/?s=${Uri.encode(query)}"
|
||||
} else if(filters.filterIsInstance<ShowBooksOnlyFilter>().any { it.state }) {
|
||||
//Filter by book
|
||||
"$baseUrl/category/book/page/$page/"
|
||||
} else {
|
||||
//Filter by tag
|
||||
val tagFilter = filters.filterIsInstance<TagFilter>().first()
|
||||
|
||||
if(tagFilter.state == 0) throw IllegalArgumentException("No filters active, no query active! What to filter?")
|
||||
|
||||
val tag = tagFilter.values[tagFilter.state]
|
||||
"$baseUrl/tag/${tag.id}/page/$page/"
|
||||
}
|
||||
|
||||
return GET(url)
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$BASE_URL/page/$page/")
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return parseToManga(queryFromUrl(document.location()), document)
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Unused method called!")
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return lazyLoadMeta(queryFromUrl(manga.url),
|
||||
client.newCall(mangaDetailsRequest(manga)).asObservableSuccess().map { it.asJsoup() }
|
||||
).map {
|
||||
listOf(SChapter.create().apply {
|
||||
url = "/manga/read/${it.readerId}/en/0/1/"
|
||||
|
||||
name = "Chapter"
|
||||
|
||||
chapter_number = 1f
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pageItems = document.select(".dropdown > li > a")
|
||||
|
||||
return pageItems.mapIndexed { index, element ->
|
||||
Page(index, element.attr("href"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document)
|
||||
= document.select("#page img").attr("src")
|
||||
|
||||
override val metaParser: HentaiCafeMetadata.(Document) -> Unit = {
|
||||
val content = it.getElementsByClass("content")
|
||||
val eTitle = content.select("h3")
|
||||
|
||||
url = Uri.decode(it.location())
|
||||
title = eTitle.text()
|
||||
|
||||
tags.clear()
|
||||
val eDetails = content.select("p > a[rel=tag]")
|
||||
eDetails.forEach {
|
||||
val href = it.attr("href")
|
||||
val parsed = Uri.parse(href)
|
||||
val firstPath = parsed.pathSegments.first()
|
||||
|
||||
when(firstPath) {
|
||||
"tag" -> tags.add(Tag("tag", it.text(), false))
|
||||
"artist" -> {
|
||||
artist = it.text()
|
||||
tags.add(Tag("artist", it.text(), false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readerId = Uri.parse(content.select("a[title=Read]").attr("href")).pathSegments[2]
|
||||
}
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TagFilter(),
|
||||
ShowBooksOnlyFilter()
|
||||
)
|
||||
|
||||
class ShowBooksOnlyFilter : Filter.CheckBox("Show books only")
|
||||
|
||||
class TagFilter : Filter.Select<HCTag>("Filter by tag", listOf(
|
||||
"???" to "None",
|
||||
|
||||
"ahegao" to "Ahegao",
|
||||
"anal" to "Anal",
|
||||
"big-ass" to "Big ass",
|
||||
"big-breast" to "Big Breast",
|
||||
"bondage" to "Bondage",
|
||||
"cheating" to "Cheating",
|
||||
"chubby" to "Chubby",
|
||||
"condom" to "Condom",
|
||||
"cosplay" to "Cosplay",
|
||||
"cunnilingus" to "Cunnilingus",
|
||||
"dark-skin" to "Dark skin",
|
||||
"defloration" to "Defloration",
|
||||
"exhibitionism" to "Exhibitionism",
|
||||
"fellatio" to "Fellatio",
|
||||
"femdom" to "Femdom",
|
||||
"flat-chest" to "Flat chest",
|
||||
"full-color" to "Full color",
|
||||
"glasses" to "Glasses",
|
||||
"group" to "Group",
|
||||
"hairy" to "Hairy",
|
||||
"handjob" to "Handjob",
|
||||
"harem" to "Harem",
|
||||
"housewife" to "Housewife",
|
||||
"incest" to "Incest",
|
||||
"large-breast" to "Large Breast",
|
||||
"lingerie" to "Lingerie",
|
||||
"loli" to "Loli",
|
||||
"masturbation" to "Masturbation",
|
||||
"nakadashi" to "Nakadashi",
|
||||
"netorare" to "Netorare",
|
||||
"office-lady" to "Office Lady",
|
||||
"osananajimi" to "Osananajimi",
|
||||
"paizuri" to "Paizuri",
|
||||
"pettanko" to "Pettanko",
|
||||
"rape" to "Rape",
|
||||
"schoolgirl" to "Schoolgirl",
|
||||
"sex-toys" to "Sex Toys",
|
||||
"shota" to "Shota",
|
||||
"stocking" to "Stocking",
|
||||
"swimsuit" to "Swimsuit",
|
||||
"teacher" to "Teacher",
|
||||
"tsundere" to "Tsundere",
|
||||
"uncensored" to "uncensored",
|
||||
"x-ray" to "X-ray"
|
||||
).map { HCTag(it.first, it.second) }.toTypedArray()
|
||||
)
|
||||
|
||||
class HCTag(val id: String, val displayName: String) {
|
||||
override fun toString() = displayName
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import exh.*
|
||||
import exh.metadata.metadataClass
|
||||
import exh.metadata.models.ExGalleryMetadata
|
||||
import exh.metadata.models.NHentaiMetadata
|
||||
import exh.metadata.models.PervEdenGalleryMetadata
|
||||
import exh.metadata.models.SearchableGalleryMetadata
|
||||
import exh.metadata.syncMangaIds
|
||||
import exh.search.SearchEngine
|
||||
@@ -89,7 +86,7 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
|
||||
val meta: RealmResults<out SearchableGalleryMetadata> = if (it.value.isNotEmpty())
|
||||
searchEngine.filterResults(it.value.where(),
|
||||
parsedQuery,
|
||||
it.value.first().titleFields)
|
||||
it.value.first()!!.titleFields)
|
||||
.findAllSorted(SearchableGalleryMetadata::mangaId.name).apply {
|
||||
totalFilteredSize += size
|
||||
}
|
||||
@@ -132,7 +129,7 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "Could not filter manga!", manga.manga)
|
||||
Timber.w(e, "Could not filter manga! %s", manga.manga)
|
||||
}
|
||||
|
||||
//Fallback to regular filter
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.PreferenceScreen
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
||||
@@ -16,6 +17,9 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import exh.ui.migration.MetadataFetchDialog
|
||||
import exh.util.realmTrans
|
||||
import io.realm.Realm
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
@@ -69,6 +73,38 @@ class SettingsAdvancedController : SettingsController() {
|
||||
|
||||
onClick { LibraryUpdateService.start(context, target = Target.TRACKING) }
|
||||
}
|
||||
preferenceCategory {
|
||||
title = "Gallery metadata"
|
||||
isPersistent = false
|
||||
|
||||
preference {
|
||||
title = "Migrate library metadata"
|
||||
isPersistent = false
|
||||
key = "ex_migrate_library"
|
||||
summary = "Fetch the library metadata to enable tag searching in the library. This button will be visible even if you have already fetched the metadata"
|
||||
|
||||
onClick {
|
||||
activity?.let {
|
||||
MetadataFetchDialog().askMigration(it, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preference {
|
||||
title = "Clear library metadata"
|
||||
isPersistent = false
|
||||
key = "ex_clear_metadata"
|
||||
summary = "Clear all library metadata. Disables tag searching in the library"
|
||||
|
||||
onClick {
|
||||
realmTrans {
|
||||
it.deleteAll()
|
||||
}
|
||||
|
||||
context.toast("Library metadata cleared!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearChapterCache() {
|
||||
|
||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.setting
|
||||
import android.support.v7.preference.PreferenceScreen
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
||||
import exh.ui.migration.MetadataFetchDialog
|
||||
import exh.ui.login.LoginController
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
||||
@@ -124,23 +123,5 @@ class SettingsEhController : SettingsController() {
|
||||
"tr_20"
|
||||
)
|
||||
}.dependency = "enable_exhentai"
|
||||
|
||||
preferenceCategory {
|
||||
title = "Advanced"
|
||||
isPersistent = false
|
||||
|
||||
preference {
|
||||
title = "Migrate library metadata"
|
||||
isPersistent = false
|
||||
key = "ex_migrate_library"
|
||||
summary = "Fetch the library metadata to enable tag searching in the library. This button will be visible even if you have already fetched the metadata"
|
||||
|
||||
onClick {
|
||||
activity?.let {
|
||||
MetadataFetchDialog().askMigration(it, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ class SettingsMainController : SettingsController() {
|
||||
titleRes = R.string.pref_category_eh
|
||||
onClick { navigateTo(SettingsEhController()) }
|
||||
}
|
||||
preference {
|
||||
iconRes = R.drawable.eh_ic_nhlogo_color
|
||||
iconTint = tintColor
|
||||
titleRes = R.string.pref_category_nh
|
||||
onClick { navigateTo(SettingsNhController()) }
|
||||
}
|
||||
preference {
|
||||
iconRes = R.drawable.ic_code_black_24dp
|
||||
iconTint = tintColor
|
||||
|
||||
21
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNhController.kt
Executable file
21
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNhController.kt
Executable file
@@ -0,0 +1,21 @@
|
||||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.support.v7.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||
|
||||
/**
|
||||
* EH Settings fragment
|
||||
*/
|
||||
|
||||
class SettingsNhController : SettingsController() {
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
|
||||
title = "nhentai"
|
||||
|
||||
switchPreference {
|
||||
title = "Use high-quality thumbnails"
|
||||
summary = "May slow down search results"
|
||||
key = PreferenceKeys.eh_nh_useHighQualityThumbs
|
||||
defaultValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user