Various changes

This commit is contained in:
NerdNumber9
2017-11-29 20:35:10 -05:00
committed by NerdNumber9
parent 908128b55d
commit 5cb219d83e
35 changed files with 1140 additions and 649 deletions

View File

@@ -117,4 +117,5 @@ object PreferenceKeys {
fun trackToken(syncId: Int) = "track_token_$syncId"
const val eh_nh_useHighQualityThumbs = "eh_nh_hq_thumbs"
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}
}
}

View File

@@ -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"

View File

@@ -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()

View File

@@ -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()
))

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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

View 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
}
}
}