Various changes
This commit is contained in:
parent
908128b55d
commit
5cb219d83e
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,5 +7,6 @@
|
|||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
*/build
|
*/build
|
||||||
/mainframer.sh
|
/mainframer
|
||||||
|
/.mainframer
|
||||||
*.apk
|
*.apk
|
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
- Many performance improvements
|
||||||
|
- Stability improvements and bug fixes
|
||||||
|
- Upstream merge
|
||||||
|
- Fix PervEden search
|
||||||
|
- Add ability to use high-quality thumbnails on nhentai
|
||||||
|
- Enable PervEden link importing
|
@ -35,10 +35,6 @@ android {
|
|||||||
buildToolsVersion "26.0.2"
|
buildToolsVersion "26.0.2"
|
||||||
publishNonDefault true
|
publishNonDefault true
|
||||||
|
|
||||||
dexOptions {
|
|
||||||
javaMaxHeapSize "4g"
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "eu.kanade.tachiyomi.eh2"
|
applicationId "eu.kanade.tachiyomi.eh2"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
@ -96,6 +92,9 @@ android {
|
|||||||
exclude 'META-INF/LICENSE'
|
exclude 'META-INF/LICENSE'
|
||||||
exclude 'META-INF/LICENSE.txt'
|
exclude 'META-INF/LICENSE.txt'
|
||||||
exclude 'META-INF/NOTICE'
|
exclude 'META-INF/NOTICE'
|
||||||
|
|
||||||
|
// Compatibility for two RxJava versions (EXH)
|
||||||
|
exclude 'META-INF/rxjava.properties'
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@ -237,17 +236,17 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
//Pin lock view (EXH)
|
//Pin lock view (EXH)
|
||||||
compile 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
||||||
|
|
||||||
//Reprint (EXH)
|
//Reprint (EXH)
|
||||||
compile 'com.github.ajalt.reprint:core:3.2.0@aar' // required: supports marshmallow devices
|
implementation 'com.github.ajalt.reprint:core:3.2.0@aar' // required: supports marshmallow devices
|
||||||
compile 'com.github.ajalt.reprint:rxjava:3.2.0@aar' // optional: the RxJava 1 interface
|
implementation 'com.github.ajalt.reprint:rxjava:3.2.0@aar' // optional: the RxJava 1 interface
|
||||||
|
|
||||||
//Swirl (EXH)
|
//Swirl (EXH)
|
||||||
compile 'com.mattprecious.swirl:swirl:1.0.0'
|
implementation 'com.mattprecious.swirl:swirl:1.0.0'
|
||||||
|
|
||||||
//RxJava 2 interop for Realm (EXH)
|
//RxJava 2 interop for Realm (EXH)
|
||||||
compile 'com.lvla.android:rxjava2-interop-kt:0.2.1'
|
implementation 'com.lvla.android:rxjava2-interop-kt:0.2.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
@ -143,6 +143,10 @@
|
|||||||
android:host="nhentai.net"
|
android:host="nhentai.net"
|
||||||
android:pathPrefix="/g/"
|
android:pathPrefix="/g/"
|
||||||
android:scheme="https"/>
|
android:scheme="https"/>
|
||||||
|
<data
|
||||||
|
android:host="nhentai.net"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
@ -117,4 +117,5 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
fun trackToken(syncId: Int) = "track_token_$syncId"
|
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 lockLength() = rxPrefs.getInteger("lock_length", -1)
|
||||||
|
|
||||||
fun lockUseFingerprint() = rxPrefs.getBoolean("lock_finger", false)
|
fun lockUseFingerprint() = rxPrefs.getBoolean("lock_finger", false)
|
||||||
|
|
||||||
|
fun eh_useHighQualityThumbs() = rxPrefs.getBoolean(Keys.eh_nh_useHighQualityThumbs, false)
|
||||||
// <-- EH
|
// <-- 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.source.online.russian.Readmanga
|
||||||
import eu.kanade.tachiyomi.util.hasPermission
|
import eu.kanade.tachiyomi.util.hasPermission
|
||||||
import exh.*
|
import exh.*
|
||||||
|
import exh.metadata.models.PervEdenLang
|
||||||
import org.yaml.snakeyaml.Yaml
|
import org.yaml.snakeyaml.Yaml
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -93,9 +94,10 @@ open class SourceManager(private val context: Context) {
|
|||||||
if(prefs.enableExhentai().getOrDefault()) {
|
if(prefs.enableExhentai().getOrDefault()) {
|
||||||
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
||||||
}
|
}
|
||||||
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, "en")
|
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, PervEdenLang.en)
|
||||||
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, "it")
|
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it)
|
||||||
exSrcs += NHentai(context)
|
exSrcs += NHentai(context)
|
||||||
|
exSrcs += HentaiCafe()
|
||||||
return exSrcs
|
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.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.model.*
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import exh.metadata.*
|
import exh.metadata.*
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
@ -24,13 +25,11 @@ import okhttp3.CacheControl
|
|||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import exh.GalleryAdder
|
|
||||||
import exh.util.*
|
import exh.util.*
|
||||||
import io.realm.Realm
|
|
||||||
|
|
||||||
class EHentai(override val id: Long,
|
class EHentai(override val id: Long,
|
||||||
val exh: Boolean,
|
val exh: Boolean,
|
||||||
val context: Context) : HttpSource() {
|
val context: Context) : HttpSource(), LewdSource<ExGalleryMetadata, Response> {
|
||||||
|
|
||||||
val schema: String
|
val schema: String
|
||||||
get() = if(prefs.secureEXH().getOrDefault())
|
get() = if(prefs.secureEXH().getOrDefault())
|
||||||
@ -49,8 +48,6 @@ class EHentai(override val id: Long,
|
|||||||
|
|
||||||
val prefs: PreferencesHelper by injectLazy()
|
val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
val galleryAdder = GalleryAdder()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gallery list entry
|
* Gallery list entry
|
||||||
*/
|
*/
|
||||||
@ -185,90 +182,80 @@ class EHentai(override val id: Long,
|
|||||||
/**
|
/**
|
||||||
* Parse gallery page to metadata model
|
* Parse gallery page to metadata model
|
||||||
*/
|
*/
|
||||||
override fun mangaDetailsParse(response: Response)
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
= with(response.asJsoup()) {
|
return parseToManga(queryFromUrl(response.request().url().toString()), response)
|
||||||
realmTrans { realm ->
|
}
|
||||||
val url = response.request().url().encodedPath()!!
|
|
||||||
val gId = ExGalleryMetadata.galleryId(url)
|
|
||||||
val gToken = ExGalleryMetadata.galleryToken(url)
|
|
||||||
|
|
||||||
val metdata = (realm.loadEh(gId, gToken, exh)
|
override val metaParser: ExGalleryMetadata.(Response) -> Unit = { response ->
|
||||||
?: realm.createUUIDObj(ExGalleryMetadata::class.java))
|
with(response.asJsoup()) {
|
||||||
with(metdata) {
|
url = response.request().url().encodedPath()!!
|
||||||
this.url = url
|
gId = ExGalleryMetadata.galleryId(url!!)
|
||||||
this.gId = gId
|
gToken = ExGalleryMetadata.galleryToken(url!!)
|
||||||
this.gToken = gToken
|
|
||||||
|
|
||||||
exh = this@EHentai.exh
|
exh = this@EHentai.exh
|
||||||
title = select("#gn").text().nullIfBlank()?.trim()
|
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 {
|
thumbnailUrl = select("#gd1 div").attr("style").nullIfBlank()?.let {
|
||||||
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
||||||
}
|
}
|
||||||
genre = select(".ic").parents().attr("href").nullIfBlank()?.trim()?.substringAfterLast('/')
|
genre = select(".ic").parents().attr("href").nullIfBlank()?.trim()?.substringAfterLast('/')
|
||||||
|
|
||||||
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
||||||
|
|
||||||
//Parse the table
|
//Parse the table
|
||||||
select("#gdd tr").forEach {
|
select("#gdd tr").forEach {
|
||||||
it.select(".gdt1")
|
it.select(".gdt1")
|
||||||
.text()
|
.text()
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.trim()
|
?.trim()
|
||||||
?.let { left ->
|
?.let { left ->
|
||||||
it.select(".gdt2")
|
it.select(".gdt2")
|
||||||
.text()
|
.text()
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.trim()
|
?.trim()
|
||||||
?.let { right ->
|
?.let { right ->
|
||||||
ignore {
|
ignore {
|
||||||
when (left.removeSuffix(":")
|
when (left.removeSuffix(":")
|
||||||
.toLowerCase()) {
|
.toLowerCase()) {
|
||||||
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||||
"visible" -> visible = right.nullIfBlank()
|
"visible" -> visible = right.nullIfBlank()
|
||||||
"language" -> {
|
"language" -> {
|
||||||
language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank()
|
language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank()
|
||||||
translated = right.endsWith(TR_SUFFIX, true)
|
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()
|
|
||||||
}
|
}
|
||||||
|
"file size" -> size = parseHumanReadableByteCount(right)?.toLong()
|
||||||
|
"length" -> length = right.removeSuffix("pages").trim().nullIfBlank()?.toInt()
|
||||||
|
"favorited" -> favorites = right.removeSuffix("times").trim().nullIfBlank()?.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Parse ratings
|
//Parse ratings
|
||||||
ignore {
|
ignore {
|
||||||
averageRating = select("#rating_label")
|
averageRating = select("#rating_label")
|
||||||
.text()
|
.text()
|
||||||
.removePrefix("Average:")
|
.removePrefix("Average:")
|
||||||
.trim()
|
.trim()
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.toDouble()
|
?.toDouble()
|
||||||
ratingCount = select("#rating_count")
|
ratingCount = select("#rating_count")
|
||||||
.text()
|
.text()
|
||||||
.trim()
|
.trim()
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.toInt()
|
?.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse tags
|
//Parse tags
|
||||||
tags.clear()
|
tags.clear()
|
||||||
select("#taglist tr").forEach {
|
select("#taglist tr").forEach {
|
||||||
val namespace = it.select(".tc").text().removeSuffix(":")
|
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||||
tags.addAll(it.select("div").map {
|
tags.addAll(it.select("div").map {
|
||||||
Tag(namespace, it.text().trim(), it.hasClass("gtl"))
|
Tag(namespace, it.text().trim(), it.hasClass("gtl"))
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
//Copy metadata to manga
|
|
||||||
SManga.create().apply {
|
|
||||||
copyTo(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,7 +310,7 @@ class EHentai(override val id: Long,
|
|||||||
if (favNames == null)
|
if (favNames == null)
|
||||||
favNames = doc.getElementsByClass("nosel").first().children().filter {
|
favNames = doc.getElementsByClass("nosel").first().children().filter {
|
||||||
it.children().size >= 3
|
it.children().size >= 3
|
||||||
}.map { it.child(2).text() }.filterNotNull()
|
}.mapNotNull { it.child(2).text() }
|
||||||
|
|
||||||
//Next page
|
//Next page
|
||||||
page++
|
page++
|
||||||
@ -384,9 +371,9 @@ class EHentai(override val id: Long,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun buildCookies(cookies: Map<String, String>)
|
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")}"
|
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
||||||
}.joinToString(separator = "; ", postfix = ";")
|
}
|
||||||
|
|
||||||
fun addParam(url: String, param: String, value: String)
|
fun addParam(url: String, param: String, value: String)
|
||||||
= Uri.parse(url)
|
= Uri.parse(url)
|
||||||
@ -465,6 +452,9 @@ class EHentai(override val id: Long,
|
|||||||
else
|
else
|
||||||
"E-Hentai"
|
"E-Hentai"
|
||||||
|
|
||||||
|
override fun queryAll() = ExGalleryMetadata.EmptyQuery()
|
||||||
|
override fun queryFromUrl(url: String) = ExGalleryMetadata.UrlQuery(url, exh)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val QUERY_PREFIX = "?f_apply=Apply+Filter"
|
val QUERY_PREFIX = "?f_apply=Apply+Filter"
|
||||||
val TR_SUFFIX = "TR"
|
val TR_SUFFIX = "TR"
|
||||||
|
@ -2,10 +2,7 @@ package eu.kanade.tachiyomi.source.online.all
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.github.salomonbrys.kotson.get
|
import com.github.salomonbrys.kotson.*
|
||||||
import com.github.salomonbrys.kotson.int
|
|
||||||
import com.github.salomonbrys.kotson.long
|
|
||||||
import com.github.salomonbrys.kotson.string
|
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
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.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.model.*
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||||
import exh.NHENTAI_SOURCE_ID
|
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.NHentaiMetadata
|
||||||
import exh.metadata.models.PageImageType
|
import exh.metadata.models.PageImageType
|
||||||
import exh.metadata.models.Tag
|
import exh.metadata.models.Tag
|
||||||
import exh.util.createUUIDObj
|
import exh.util.*
|
||||||
import exh.util.defRealm
|
|
||||||
import exh.util.realmTrans
|
|
||||||
import exh.util.urlImportFetchSearchManga
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -36,7 +28,7 @@ import timber.log.Timber
|
|||||||
* NHentai source
|
* NHentai source
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class NHentai(context: Context) : HttpSource() {
|
class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiMetadata, JsonObject> {
|
||||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
//TODO There is currently no way to get the most popular mangas
|
//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
|
//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)
|
override fun latestUpdatesParse(response: Response)
|
||||||
= parseResultPage(response)
|
= parseResultPage(response)
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response)
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
= parseGallery(jsonParser.parse(response.body()!!.string()).asJsonObject)
|
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
|
//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> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
@ -102,7 +96,8 @@ class NHentai(context: Context) : HttpSource() {
|
|||||||
val error = res.get("error")
|
val error = res.get("error")
|
||||||
if(error == null) {
|
if(error == null) {
|
||||||
val results = res.getAsJsonArray("result")?.map {
|
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
|
val numPages = res.get("num_pages")?.int
|
||||||
if(results != null && numPages != null)
|
if(results != null && numPages != null)
|
||||||
@ -113,70 +108,65 @@ class NHentai(context: Context) : HttpSource() {
|
|||||||
return MangasPage(emptyList(), false)
|
return MangasPage(emptyList(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rawParseGallery(obj: JsonObject) = realmTrans { realm ->
|
override val metaParser: NHentaiMetadata.(JsonObject) -> Unit = { obj ->
|
||||||
val nhId = obj.get("id").asLong
|
nhId = obj["id"].asLong
|
||||||
|
|
||||||
realm.copyFromRealm((realm.loadNhentai(nhId)
|
uploadDate = obj["upload_date"].nullLong
|
||||||
?: realm.createUUIDObj(NHentaiMetadata::class.java)).apply {
|
|
||||||
this.nhId = nhId
|
|
||||||
|
|
||||||
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 {
|
obj["images"].nullObj?.let {
|
||||||
japaneseTitle = it.get("japanese")?.notNull()?.string
|
coverImageType = it["cover"]?.get("t").nullString
|
||||||
shortTitle = it.get("pretty")?.notNull()?.string
|
it["pages"].nullArray?.mapNotNull {
|
||||||
englishTitle = it.get("english")?.notNull()?.string
|
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 {
|
scanlator = obj["scanlator"].nullString
|
||||||
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.get("scanlator")?.notNull()?.asString
|
obj["tags"]?.asJsonArray?.map {
|
||||||
|
val asObj = it.asJsonObject
|
||||||
obj.get("tags")?.asJsonArray?.map {
|
Pair(asObj["type"].nullString, asObj["name"].nullString)
|
||||||
val asObj = it.asJsonObject
|
}?.apply {
|
||||||
Pair(asObj.get("type")?.string, asObj.get("name")?.string)
|
tags.clear()
|
||||||
}?.apply {
|
}?.forEach {
|
||||||
tags.clear()
|
if(it.first != null && it.second != null)
|
||||||
}?.forEach {
|
tags.add(Tag(it.first!!, it.second!!, false))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lazyLoadMetadata(url: String) =
|
fun lazyLoadMetadata(url: String) =
|
||||||
defRealm { realm ->
|
defRealm { realm ->
|
||||||
val meta = realm.loadNhentai(NHentaiMetadata.nhIdFromUrl(url))
|
val meta = NHentaiMetadata.UrlQuery(url).query(realm).findFirst()
|
||||||
if(meta == null)
|
if(meta == null) {
|
||||||
client.newCall(urlToDetailsRequest(url))
|
client.newCall(urlToDetailsRequest(url))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
rawParseGallery(jsonParser.parse(it.body()!!.string())
|
realmTrans { realm ->
|
||||||
.asJsonObject)
|
realm.copyFromRealm(realm.createUUIDObj(queryAll().clazz.java).apply {
|
||||||
}.first()
|
metaParser(this,
|
||||||
else
|
jsonParser.parse(it.body()!!.string()).asJsonObject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.first()
|
||||||
|
} else {
|
||||||
Observable.just(realm.copyFromRealm(meta))
|
Observable.just(realm.copyFromRealm(meta))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga)
|
override fun fetchChapterList(manga: SManga)
|
||||||
@ -184,8 +174,7 @@ class NHentai(context: Context) : HttpSource() {
|
|||||||
listOf(SChapter.create().apply {
|
listOf(SChapter.create().apply {
|
||||||
url = manga.url
|
url = manga.url
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
//TODO Get this working later
|
date_upload = ((it.uploadDate ?: 0) * 1000)
|
||||||
// date_upload = it.uploadDate ?: 0
|
|
||||||
chapter_number = 1f
|
chapter_number = 1f
|
||||||
})
|
})
|
||||||
}!!
|
}!!
|
||||||
@ -241,6 +230,9 @@ class NHentai(context: Context) : HttpSource() {
|
|||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override fun queryAll() = NHentaiMetadata.EmptyQuery()
|
||||||
|
override fun queryFromUrl(url: String) = NHentaiMetadata.UrlQuery(url)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val jsonParser by lazy {
|
val jsonParser by lazy {
|
||||||
JsonParser()
|
JsonParser()
|
||||||
|
@ -3,32 +3,29 @@ package eu.kanade.tachiyomi.source.online.all
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.*
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
|
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.ChapterRecognition
|
import eu.kanade.tachiyomi.util.ChapterRecognition
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import exh.metadata.copyTo
|
import exh.metadata.models.*
|
||||||
import exh.metadata.loadPervEden
|
|
||||||
import exh.metadata.models.PervEdenGalleryMetadata
|
|
||||||
import exh.metadata.models.PervEdenTitle
|
|
||||||
import exh.metadata.models.Tag
|
|
||||||
import exh.util.UriFilter
|
import exh.util.UriFilter
|
||||||
import exh.util.UriGroup
|
import exh.util.UriGroup
|
||||||
import exh.util.createUUIDObj
|
import exh.util.urlImportFetchSearchManga
|
||||||
import exh.util.realmTrans
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.jsoup.nodes.TextNode
|
import org.jsoup.nodes.TextNode
|
||||||
import timber.log.Timber
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
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 supportsLatest = true
|
||||||
override val name = "Perv Eden"
|
override val name = "Perv Eden"
|
||||||
override val baseUrl = "http://www.perveden.com"
|
override val baseUrl = "http://www.perveden.com"
|
||||||
|
override val lang = pvLang.name
|
||||||
|
|
||||||
override fun popularMangaSelector() = "#topManga > ul > li"
|
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
|
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 searchMangaSelector() = "#mangaList > tbody > tr"
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
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 {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val uri = Uri.parse("$baseUrl/$lang/$lang-directory/").buildUpon()
|
val uri = Uri.parse("$baseUrl/$lang/$lang-directory/").buildUpon()
|
||||||
uri.appendQueryParameter("page", page.toString())
|
uri.appendQueryParameter("page", page.toString())
|
||||||
|
uri.appendQueryParameter("title", query)
|
||||||
filters.forEach {
|
filters.forEach {
|
||||||
if(it is UriFilter) it.addToUri(uri)
|
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!")
|
throw NotImplementedError("Unused method called!")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override val metaParser: PervEdenGalleryMetadata.(Document) -> Unit = { document ->
|
||||||
realmTrans { realm ->
|
url = Uri.parse(document.location()).path
|
||||||
val url = document.location()
|
|
||||||
val metadata = (realm.loadPervEden(PervEdenGalleryMetadata.pvIdFromUrl(url), id)
|
|
||||||
?: realm.createUUIDObj(PervEdenGalleryMetadata::class.java))
|
|
||||||
with(metadata) {
|
|
||||||
this.url = url
|
|
||||||
|
|
||||||
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()
|
val rightBoxElement = document.select(".rightBox:not(.info)").first()
|
||||||
var inStatus: String? = null
|
|
||||||
rightBoxElement.childNodes().forEach {
|
altTitles.clear()
|
||||||
if(it is Element && it.tagName().toLowerCase() == "h4") {
|
tags.clear()
|
||||||
inStatus = it.text().trim()
|
var inStatus: String? = null
|
||||||
} else {
|
rightBoxElement.childNodes().forEach {
|
||||||
when(inStatus) {
|
if(it is Element && it.tagName().toLowerCase() == "h4") {
|
||||||
"Alternative name(s)" -> {
|
inStatus = it.text().trim()
|
||||||
if(it is TextNode) {
|
} else {
|
||||||
val text = it.text().trim()
|
when(inStatus) {
|
||||||
if(!text.isBlank())
|
"Alternative name(s)" -> {
|
||||||
altTitles.add(PervEdenTitle(this, text))
|
if(it is TextNode) {
|
||||||
}
|
val text = it.text().trim()
|
||||||
}
|
if(!text.isBlank())
|
||||||
"Artist" -> {
|
altTitles.add(PervEdenTitle(this, text))
|
||||||
if(it is Element && it.tagName() == "a") {
|
}
|
||||||
artist = it.text()
|
}
|
||||||
tags.add(Tag("artist", it.text().toLowerCase(), false))
|
"Artist" -> {
|
||||||
}
|
if(it is Element && it.tagName() == "a") {
|
||||||
}
|
artist = it.text()
|
||||||
"Genres" -> {
|
tags.add(Tag("artist", it.text().toLowerCase(), false))
|
||||||
if(it is Element && it.tagName() == "a")
|
}
|
||||||
tags.add(Tag("genre", it.text().toLowerCase(), false))
|
}
|
||||||
}
|
"Genres" -> {
|
||||||
"Type" -> {
|
if(it is Element && it.tagName() == "a")
|
||||||
if(it is TextNode) {
|
tags.add(Tag("genre", it.text().toLowerCase(), false))
|
||||||
val text = it.text().trim()
|
}
|
||||||
if(!text.isBlank())
|
"Type" -> {
|
||||||
type = text
|
if(it is TextNode) {
|
||||||
}
|
val text = it.text().trim()
|
||||||
}
|
if(!text.isBlank())
|
||||||
"Status" -> {
|
type = text
|
||||||
if(it is TextNode) {
|
}
|
||||||
val text = it.text().trim()
|
}
|
||||||
if(!text.isBlank())
|
"Status" -> {
|
||||||
status = text
|
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 {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
val num = if(lang == "en") "0"
|
val num = when (lang) {
|
||||||
else if(lang == "it") "1"
|
"en" -> "0"
|
||||||
else throw NotImplementedError("Unimplemented language!")
|
"it" -> "1"
|
||||||
|
else -> throw NotImplementedError("Unimplemented language!")
|
||||||
|
}
|
||||||
|
|
||||||
return GET("$baseUrl/ajax/news/$page/$num/0/")
|
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)
|
override fun imageUrlParse(document: Document)
|
||||||
= "http:" + document.getElementById("mainImg").attr("src")!!
|
= "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 (
|
override fun getFilterList() = FilterList (
|
||||||
AuthorFilter(),
|
AuthorFilter(),
|
||||||
ArtistFilter(),
|
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
|
//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(),
|
ReleaseYearRangeFilter(),
|
||||||
ReleaseYearYearFilter()
|
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 eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import exh.*
|
import exh.*
|
||||||
import exh.metadata.metadataClass
|
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.models.SearchableGalleryMetadata
|
||||||
import exh.metadata.syncMangaIds
|
import exh.metadata.syncMangaIds
|
||||||
import exh.search.SearchEngine
|
import exh.search.SearchEngine
|
||||||
@ -89,7 +86,7 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
|
|||||||
val meta: RealmResults<out SearchableGalleryMetadata> = if (it.value.isNotEmpty())
|
val meta: RealmResults<out SearchableGalleryMetadata> = if (it.value.isNotEmpty())
|
||||||
searchEngine.filterResults(it.value.where(),
|
searchEngine.filterResults(it.value.where(),
|
||||||
parsedQuery,
|
parsedQuery,
|
||||||
it.value.first().titleFields)
|
it.value.first()!!.titleFields)
|
||||||
.findAllSorted(SearchableGalleryMetadata::mangaId.name).apply {
|
.findAllSorted(SearchableGalleryMetadata::mangaId.name).apply {
|
||||||
totalFilteredSize += size
|
totalFilteredSize += size
|
||||||
}
|
}
|
||||||
@ -132,7 +129,7 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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
|
//Fallback to regular filter
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Dialog
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.preference.PreferenceScreen
|
import android.support.v7.preference.PreferenceScreen
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import com.bluelinelabs.conductor.RouterTransaction
|
||||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
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.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
|
import exh.ui.migration.MetadataFetchDialog
|
||||||
|
import exh.util.realmTrans
|
||||||
|
import io.realm.Realm
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
@ -69,6 +73,38 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
|
|
||||||
onClick { LibraryUpdateService.start(context, target = Target.TRACKING) }
|
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() {
|
private fun clearChapterCache() {
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.setting
|
|||||||
import android.support.v7.preference.PreferenceScreen
|
import android.support.v7.preference.PreferenceScreen
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import com.bluelinelabs.conductor.RouterTransaction
|
||||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
||||||
import exh.ui.migration.MetadataFetchDialog
|
|
||||||
import exh.ui.login.LoginController
|
import exh.ui.login.LoginController
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
|
||||||
@ -124,23 +123,5 @@ class SettingsEhController : SettingsController() {
|
|||||||
"tr_20"
|
"tr_20"
|
||||||
)
|
)
|
||||||
}.dependency = "enable_exhentai"
|
}.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
|
titleRes = R.string.pref_category_eh
|
||||||
onClick { navigateTo(SettingsEhController()) }
|
onClick { navigateTo(SettingsEhController()) }
|
||||||
}
|
}
|
||||||
|
preference {
|
||||||
|
iconRes = R.drawable.eh_ic_nhlogo_color
|
||||||
|
iconTint = tintColor
|
||||||
|
titleRes = R.string.pref_category_nh
|
||||||
|
onClick { navigateTo(SettingsNhController()) }
|
||||||
|
}
|
||||||
preference {
|
preference {
|
||||||
iconRes = R.drawable.ic_code_black_24dp
|
iconRes = R.drawable.ic_code_black_24dp
|
||||||
iconTint = tintColor
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ val PERV_EDEN_IT_SOURCE_ID = LEWD_SOURCE_SERIES + 6
|
|||||||
|
|
||||||
val NHENTAI_SOURCE_ID = LEWD_SOURCE_SERIES + 7
|
val NHENTAI_SOURCE_ID = LEWD_SOURCE_SERIES + 7
|
||||||
|
|
||||||
|
val HENTAI_CAFE_SOURCE_ID = LEWD_SOURCE_SERIES + 8
|
||||||
|
|
||||||
fun isLewdSource(source: Long) = source in 6900..6999
|
fun isLewdSource(source: Long) = source in 6900..6999
|
||||||
|
|
||||||
fun isEhSource(source: Long) = source == EH_SOURCE_ID
|
fun isEhSource(source: Long) = source == EH_SOURCE_ID
|
||||||
|
@ -10,19 +10,16 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
||||||
import exh.metadata.copyTo
|
|
||||||
import exh.metadata.loadEh
|
|
||||||
import exh.metadata.loadNhentai
|
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
import exh.metadata.models.NHentaiMetadata
|
import exh.metadata.models.NHentaiMetadata
|
||||||
|
import exh.metadata.models.PervEdenGalleryMetadata
|
||||||
|
import exh.metadata.models.PervEdenLang
|
||||||
import exh.util.defRealm
|
import exh.util.defRealm
|
||||||
import io.realm.Realm
|
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.MalformedURLException
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URISyntaxException
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
@ -70,10 +67,19 @@ class GalleryAdder {
|
|||||||
forceSource: Long? = null): GalleryAddEvent {
|
forceSource: Long? = null): GalleryAddEvent {
|
||||||
try {
|
try {
|
||||||
val urlObj = Uri.parse(url)
|
val urlObj = Uri.parse(url)
|
||||||
val source = when (urlObj.host) {
|
val lowercasePs = urlObj.pathSegments.map(String::toLowerCase)
|
||||||
|
val firstPathSegment = lowercasePs[0]
|
||||||
|
val source = when (urlObj.host.toLowerCase()) {
|
||||||
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
|
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
|
||||||
"exhentai.org" -> EXH_SOURCE_ID
|
"exhentai.org" -> EXH_SOURCE_ID
|
||||||
"nhentai.net" -> NHENTAI_SOURCE_ID
|
"nhentai.net" -> NHENTAI_SOURCE_ID
|
||||||
|
"www.perveden.com" -> {
|
||||||
|
when(lowercasePs[1]) {
|
||||||
|
"en-manga" -> PERV_EDEN_EN_SOURCE_ID
|
||||||
|
"it-manga" -> PERV_EDEN_IT_SOURCE_ID
|
||||||
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +87,6 @@ class GalleryAdder {
|
|||||||
return GalleryAddEvent.Fail.UnknownType(url)
|
return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
val firstPathSegment = urlObj.pathSegments.firstOrNull()?.toLowerCase()
|
|
||||||
val realUrl = when(source) {
|
val realUrl = when(source) {
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID -> when (firstPathSegment) {
|
EH_SOURCE_ID, EXH_SOURCE_ID -> when (firstPathSegment) {
|
||||||
"g" -> {
|
"g" -> {
|
||||||
@ -94,10 +99,19 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
NHENTAI_SOURCE_ID -> when {
|
NHENTAI_SOURCE_ID -> {
|
||||||
firstPathSegment == "g" -> url
|
if(firstPathSegment != "g")
|
||||||
urlObj.pathSegments.size >= 3 -> "https://nhentai.net/g/${urlObj.pathSegments[1]}/"
|
return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
|
"https://nhentai.net/g/${urlObj.pathSegments[1]}/"
|
||||||
|
}
|
||||||
|
PERV_EDEN_EN_SOURCE_ID,
|
||||||
|
PERV_EDEN_IT_SOURCE_ID -> {
|
||||||
|
val uri = Uri.parse("http://www.perveden.com/").buildUpon()
|
||||||
|
urlObj.pathSegments.take(3).forEach {
|
||||||
|
uri.appendPath(it)
|
||||||
|
}
|
||||||
|
uri.toString()
|
||||||
}
|
}
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
@ -108,6 +122,8 @@ class GalleryAdder {
|
|||||||
val cleanedUrl = when(source) {
|
val cleanedUrl = when(source) {
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
EH_SOURCE_ID, EXH_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
||||||
NHENTAI_SOURCE_ID -> realUrl //nhentai uses URLs directly (oops, my bad when implementing this source)
|
NHENTAI_SOURCE_ID -> realUrl //nhentai uses URLs directly (oops, my bad when implementing this source)
|
||||||
|
PERV_EDEN_EN_SOURCE_ID,
|
||||||
|
PERV_EDEN_IT_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,17 +135,27 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Copy basics
|
//Copy basics
|
||||||
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
|
val newManga = sourceObj.fetchMangaDetails(manga).toBlocking().first()
|
||||||
|
manga.copyFrom(newManga)
|
||||||
|
manga.title = newManga.title //Forcibly copy title as copyFrom does not copy title
|
||||||
|
|
||||||
//Apply metadata
|
//Apply metadata
|
||||||
defRealm { realm ->
|
defRealm { realm ->
|
||||||
when (source) {
|
when (source) {
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID ->
|
EH_SOURCE_ID, EXH_SOURCE_ID ->
|
||||||
realm.loadEh(ExGalleryMetadata.galleryId(realUrl),
|
ExGalleryMetadata.UrlQuery(realUrl, isExSource(source))
|
||||||
ExGalleryMetadata.galleryToken(realUrl),
|
.query(realm)
|
||||||
isExSource(source))?.copyTo(manga)
|
.findFirst()?.copyTo(manga)
|
||||||
NHENTAI_SOURCE_ID ->
|
NHENTAI_SOURCE_ID ->
|
||||||
realm.loadNhentai(NHentaiMetadata.nhIdFromUrl(realUrl))
|
NHentaiMetadata.UrlQuery(realUrl)
|
||||||
|
.query(realm)
|
||||||
|
.findFirst()
|
||||||
|
?.copyTo(manga)
|
||||||
|
PERV_EDEN_EN_SOURCE_ID,
|
||||||
|
PERV_EDEN_IT_SOURCE_ID ->
|
||||||
|
PervEdenGalleryMetadata.UrlQuery(realUrl, PervEdenLang.source(source))
|
||||||
|
.query(realm)
|
||||||
|
.findFirst()
|
||||||
?.copyTo(manga)
|
?.copyTo(manga)
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
@ -160,16 +186,16 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getUrlWithoutDomain(orig: String): String {
|
private fun getUrlWithoutDomain(orig: String): String {
|
||||||
try {
|
return try {
|
||||||
val uri = URI(orig)
|
val uri = URI(orig)
|
||||||
var out = uri.path
|
var out = uri.path
|
||||||
if (uri.query != null)
|
if (uri.query != null)
|
||||||
out += "?" + uri.query
|
out += "?" + uri.query
|
||||||
if (uri.fragment != null)
|
if (uri.fragment != null)
|
||||||
out += "#" + uri.fragment
|
out += "#" + uri.fragment
|
||||||
return out
|
out
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
return orig
|
orig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,121 +1,32 @@
|
|||||||
package exh.metadata
|
package exh.metadata
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import exh.*
|
import exh.*
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.*
|
||||||
import exh.metadata.models.NHentaiMetadata
|
|
||||||
import exh.metadata.models.PervEdenGalleryMetadata
|
|
||||||
import exh.metadata.models.SearchableGalleryMetadata
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import rx.Observable
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
fun Realm.ehMetaQueryFromUrl(url: String,
|
|
||||||
exh: Boolean,
|
|
||||||
meta: RealmQuery<ExGalleryMetadata>? = null) =
|
|
||||||
ehMetadataQuery(
|
|
||||||
ExGalleryMetadata.galleryId(url),
|
|
||||||
ExGalleryMetadata.galleryToken(url),
|
|
||||||
exh,
|
|
||||||
meta
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Realm.ehMetadataQuery(gId: String,
|
|
||||||
gToken: String,
|
|
||||||
exh: Boolean,
|
|
||||||
meta: RealmQuery<ExGalleryMetadata>? = null)
|
|
||||||
= (meta ?: where(ExGalleryMetadata::class.java))
|
|
||||||
.equalTo(ExGalleryMetadata::gId.name, gId)
|
|
||||||
.equalTo(ExGalleryMetadata::gToken.name, gToken)
|
|
||||||
.equalTo(ExGalleryMetadata::exh.name, exh)
|
|
||||||
|
|
||||||
fun Realm.loadEh(gId: String, gToken: String, exh: Boolean): ExGalleryMetadata?
|
|
||||||
= ehMetadataQuery(gId, gToken, exh)
|
|
||||||
.findFirst()
|
|
||||||
|
|
||||||
fun Realm.loadEhAsync(gId: String, gToken: String, exh: Boolean): Observable<ExGalleryMetadata?>
|
|
||||||
= ehMetadataQuery(gId, gToken, exh)
|
|
||||||
.findFirstAsync()
|
|
||||||
.asObservable()
|
|
||||||
|
|
||||||
private fun pervEdenSourceToLang(source: Long)
|
|
||||||
= when (source) {
|
|
||||||
PERV_EDEN_EN_SOURCE_ID -> "en"
|
|
||||||
PERV_EDEN_IT_SOURCE_ID -> "it"
|
|
||||||
else -> throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Realm.pervEdenMetaQueryFromUrl(url: String,
|
|
||||||
source: Long,
|
|
||||||
meta: RealmQuery<PervEdenGalleryMetadata>? = null) =
|
|
||||||
pervEdenMetadataQuery(
|
|
||||||
PervEdenGalleryMetadata.pvIdFromUrl(url),
|
|
||||||
source,
|
|
||||||
meta
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Realm.pervEdenMetadataQuery(pvId: String,
|
|
||||||
source: Long,
|
|
||||||
meta: RealmQuery<PervEdenGalleryMetadata>? = null)
|
|
||||||
= (meta ?: where(PervEdenGalleryMetadata::class.java))
|
|
||||||
.equalTo(PervEdenGalleryMetadata::lang.name, pervEdenSourceToLang(source))
|
|
||||||
.equalTo(PervEdenGalleryMetadata::pvId.name, pvId)
|
|
||||||
|
|
||||||
fun Realm.loadPervEden(pvId: String, source: Long): PervEdenGalleryMetadata?
|
|
||||||
= pervEdenMetadataQuery(pvId, source)
|
|
||||||
.findFirst()
|
|
||||||
|
|
||||||
fun Realm.loadPervEdenAsync(pvId: String, source: Long): Observable<PervEdenGalleryMetadata?>
|
|
||||||
= pervEdenMetadataQuery(pvId, source)
|
|
||||||
.findFirstAsync()
|
|
||||||
.asObservable()
|
|
||||||
|
|
||||||
fun Realm.nhentaiMetaQueryFromUrl(url: String,
|
|
||||||
meta: RealmQuery<NHentaiMetadata>? = null) =
|
|
||||||
nhentaiMetadataQuery(
|
|
||||||
NHentaiMetadata.nhIdFromUrl(url),
|
|
||||||
meta
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Realm.nhentaiMetadataQuery(nhId: Long,
|
|
||||||
meta: RealmQuery<NHentaiMetadata>? = null)
|
|
||||||
= (meta ?: where(NHentaiMetadata::class.java))
|
|
||||||
.equalTo(NHentaiMetadata::nhId.name, nhId)
|
|
||||||
|
|
||||||
fun Realm.loadNhentai(nhId: Long): NHentaiMetadata?
|
|
||||||
= nhentaiMetadataQuery(nhId)
|
|
||||||
.findFirst()
|
|
||||||
|
|
||||||
fun Realm.loadNhentaiAsync(nhId: Long): Observable<NHentaiMetadata?>
|
|
||||||
= nhentaiMetadataQuery(nhId)
|
|
||||||
.findFirstAsync()
|
|
||||||
.asObservable()
|
|
||||||
|
|
||||||
fun Realm.loadAllMetadata(): Map<KClass<out SearchableGalleryMetadata>, RealmResults<out SearchableGalleryMetadata>> =
|
fun Realm.loadAllMetadata(): Map<KClass<out SearchableGalleryMetadata>, RealmResults<out SearchableGalleryMetadata>> =
|
||||||
listOf<Pair<KClass<out SearchableGalleryMetadata>, RealmQuery<out SearchableGalleryMetadata>>>(
|
Injekt.get<SourceManager>().getOnlineSources().filterIsInstance<LewdSource<*, *>>().map {
|
||||||
Pair(ExGalleryMetadata::class, where(ExGalleryMetadata::class.java)),
|
it.queryAll()
|
||||||
Pair(NHentaiMetadata::class, where(NHentaiMetadata::class.java)),
|
}.associate {
|
||||||
Pair(PervEdenGalleryMetadata::class, where(PervEdenGalleryMetadata::class.java))
|
it.clazz to it.query(this@loadAllMetadata).findAllSorted(SearchableGalleryMetadata::mangaId.name)
|
||||||
).map {
|
|
||||||
Pair(it.first, it.second.findAllSorted(SearchableGalleryMetadata::mangaId.name))
|
|
||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
fun Realm.queryMetadataFromManga(manga: Manga,
|
fun Realm.queryMetadataFromManga(manga: Manga,
|
||||||
meta: RealmQuery<out SearchableGalleryMetadata>? = null): RealmQuery<out SearchableGalleryMetadata> =
|
meta: RealmQuery<SearchableGalleryMetadata>? = null):
|
||||||
when(manga.source) {
|
RealmQuery<out SearchableGalleryMetadata> =
|
||||||
EH_SOURCE_ID -> ehMetaQueryFromUrl(manga.url, false, meta as? RealmQuery<ExGalleryMetadata>)
|
Injekt.get<SourceManager>().get(manga.source)?.let {
|
||||||
EXH_SOURCE_ID -> ehMetaQueryFromUrl(manga.url, true, meta as? RealmQuery<ExGalleryMetadata>)
|
(it as LewdSource<*, *>).queryFromUrl(manga.url) as GalleryQuery<SearchableGalleryMetadata>
|
||||||
PERV_EDEN_EN_SOURCE_ID,
|
}?.query(this, meta) ?: throw IllegalArgumentException("Unknown source type!")
|
||||||
PERV_EDEN_IT_SOURCE_ID ->
|
|
||||||
pervEdenMetaQueryFromUrl(manga.url, manga.source, meta as? RealmQuery<PervEdenGalleryMetadata>)
|
|
||||||
NHENTAI_SOURCE_ID -> nhentaiMetaQueryFromUrl(manga.url, meta as? RealmQuery<NHentaiMetadata>)
|
|
||||||
else -> throw IllegalArgumentException("Unknown source type!")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Realm.syncMangaIds(mangas: List<LibraryItem>) {
|
fun Realm.syncMangaIds(mangas: List<LibraryItem>) {
|
||||||
Timber.d("--> EH: Begin syncing ${mangas.size} manga IDs...")
|
Timber.d("--> EH: Begin syncing ${mangas.size} manga IDs...")
|
||||||
@ -138,11 +49,4 @@ fun Realm.syncMangaIds(mangas: List<LibraryItem>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val Manga.metadataClass
|
val Manga.metadataClass
|
||||||
get() = when (source) {
|
get() = (Injekt.get<SourceManager>().get(source) as? LewdSource<*, *>)?.queryAll()?.clazz
|
||||||
EH_SOURCE_ID,
|
|
||||||
EXH_SOURCE_ID -> ExGalleryMetadata::class
|
|
||||||
PERV_EDEN_IT_SOURCE_ID,
|
|
||||||
PERV_EDEN_EN_SOURCE_ID -> PervEdenGalleryMetadata::class
|
|
||||||
NHENTAI_SOURCE_ID -> NHentaiMetadata::class
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package exh.metadata
|
package exh.metadata
|
||||||
|
|
||||||
|
import exh.metadata.models.SearchableGalleryMetadata
|
||||||
|
import exh.plusAssign
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata utils
|
* Metadata utils
|
||||||
*/
|
*/
|
||||||
@ -44,4 +49,37 @@ fun <T> ignore(expr: () -> T): T? {
|
|||||||
|
|
||||||
fun <K,V> Set<Map.Entry<K,V>>.forEach(action: (K, V) -> Unit) {
|
fun <K,V> Set<Map.Entry<K,V>>.forEach(action: (K, V) -> Unit) {
|
||||||
forEach { action(it.key, it.value) }
|
forEach { action(it.key, it.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ONGOING_SUFFIX = arrayOf(
|
||||||
|
"[ongoing]",
|
||||||
|
"(ongoing)",
|
||||||
|
"{ongoing}",
|
||||||
|
"<ongoing>",
|
||||||
|
"ongoing",
|
||||||
|
"[incomplete]",
|
||||||
|
"(incomplete)",
|
||||||
|
"{incomplete}",
|
||||||
|
"<incomplete>",
|
||||||
|
"incomplete",
|
||||||
|
"[wip]",
|
||||||
|
"(wip)",
|
||||||
|
"{wip}",
|
||||||
|
"<wip>",
|
||||||
|
"wip"
|
||||||
|
)
|
||||||
|
|
||||||
|
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
||||||
|
|
||||||
|
fun buildTagsDescription(metadata: SearchableGalleryMetadata)
|
||||||
|
= StringBuilder("Tags:\n").apply {
|
||||||
|
//BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
||||||
|
metadata.tags.groupBy {
|
||||||
|
it.namespace
|
||||||
|
}.entries.forEach { namespace, tags ->
|
||||||
|
if (tags.isNotEmpty()) {
|
||||||
|
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
||||||
|
this += "▪ $namespace: $joinedTags\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,219 +0,0 @@
|
|||||||
package exh.metadata
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
|
||||||
import eu.kanade.tachiyomi.source.online.all.PervEden
|
|
||||||
import exh.metadata.models.*
|
|
||||||
import exh.plusAssign
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies gallery metadata to a manga object
|
|
||||||
*/
|
|
||||||
|
|
||||||
private const val EH_ARTIST_NAMESPACE = "artist"
|
|
||||||
private const val EH_AUTHOR_NAMESPACE = "author"
|
|
||||||
|
|
||||||
private const val NHENTAI_ARTIST_NAMESPACE = "artist"
|
|
||||||
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
|
||||||
|
|
||||||
private val ONGOING_SUFFIX = arrayOf(
|
|
||||||
"[ongoing]",
|
|
||||||
"(ongoing)",
|
|
||||||
"{ongoing}"
|
|
||||||
)
|
|
||||||
|
|
||||||
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
|
||||||
|
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
fun ExGalleryMetadata.copyTo(manga: SManga) {
|
|
||||||
//TODO Find some way to do this with SManga
|
|
||||||
/*exh?.let {
|
|
||||||
manga.source = if(it)
|
|
||||||
2
|
|
||||||
else
|
|
||||||
1
|
|
||||||
}*/
|
|
||||||
url?.let { manga.url = it }
|
|
||||||
thumbnailUrl?.let { manga.thumbnail_url = it }
|
|
||||||
|
|
||||||
//No title bug?
|
|
||||||
val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
|
|
||||||
altTitle ?: title
|
|
||||||
else
|
|
||||||
title
|
|
||||||
titleObj?.let { manga.title = it }
|
|
||||||
|
|
||||||
//Set artist (if we can find one)
|
|
||||||
tags.filter { it.namespace == EH_ARTIST_NAMESPACE }.let {
|
|
||||||
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name!! })
|
|
||||||
}
|
|
||||||
//Set author (if we can find one)
|
|
||||||
tags.filter { it.namespace == EH_AUTHOR_NAMESPACE }.let {
|
|
||||||
if(it.isNotEmpty()) manga.author = it.joinToString(transform = { it.name!! })
|
|
||||||
}
|
|
||||||
//Set genre
|
|
||||||
genre?.let { manga.genre = it }
|
|
||||||
|
|
||||||
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
|
||||||
//We default to completed
|
|
||||||
manga.status = SManga.COMPLETED
|
|
||||||
title?.let { t ->
|
|
||||||
ONGOING_SUFFIX.find {
|
|
||||||
t.endsWith(it, ignoreCase = true)
|
|
||||||
}?.let {
|
|
||||||
manga.status = SManga.ONGOING
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Build a nice looking description out of what we know
|
|
||||||
val titleDesc = StringBuilder()
|
|
||||||
title?.let { titleDesc += "Title: $it\n" }
|
|
||||||
altTitle?.let { titleDesc += "Alternate Title: $it\n" }
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
|
||||||
datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
|
||||||
visible?.let { detailsDesc += "Visible: $it\n" }
|
|
||||||
language?.let {
|
|
||||||
detailsDesc += "Language: $it"
|
|
||||||
if(translated == true) detailsDesc += " TR"
|
|
||||||
detailsDesc += "\n"
|
|
||||||
}
|
|
||||||
size?.let { detailsDesc += "File Size: ${humanReadableByteCount(it, true)}\n" }
|
|
||||||
length?.let { detailsDesc += "Length: $it pages\n" }
|
|
||||||
favorites?.let { detailsDesc += "Favorited: $it times\n" }
|
|
||||||
averageRating?.let {
|
|
||||||
detailsDesc += "Rating: $it"
|
|
||||||
ratingCount?.let { detailsDesc += " ($it)" }
|
|
||||||
detailsDesc += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
val tagsDesc = buildTagsDescription(this)
|
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PervEdenGalleryMetadata.copyTo(manga: SManga) {
|
|
||||||
url?.let { manga.url = it }
|
|
||||||
thumbnailUrl?.let { manga.thumbnail_url = it }
|
|
||||||
|
|
||||||
val titleDesc = StringBuilder()
|
|
||||||
title?.let {
|
|
||||||
manga.title = it
|
|
||||||
titleDesc += "Title: $it\n"
|
|
||||||
}
|
|
||||||
if(altTitles.isNotEmpty())
|
|
||||||
titleDesc += "Alternate Titles: \n" + altTitles.map {
|
|
||||||
"▪ ${it.title}"
|
|
||||||
}.joinToString(separator = "\n", postfix = "\n")
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
artist?.let {
|
|
||||||
manga.artist = it
|
|
||||||
detailsDesc += "Artist: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
type?.let {
|
|
||||||
manga.genre = it
|
|
||||||
detailsDesc += "Type: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
status?.let {
|
|
||||||
manga.status = when(it) {
|
|
||||||
"Ongoing" -> SManga.ONGOING
|
|
||||||
"Completed", "Suspended" -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
detailsDesc += "Status: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
rating?.let {
|
|
||||||
detailsDesc += "Rating: %.2\n".format(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val tagsDesc = buildTagsDescription(this)
|
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NHentaiMetadata.copyTo(manga: SManga) {
|
|
||||||
url?.let { manga.url = it }
|
|
||||||
|
|
||||||
//TODO next update allow this to be changed to use HD covers
|
|
||||||
if(mediaId != null)
|
|
||||||
NHentaiMetadata.typeToExtension(thumbnailImageType)?.let {
|
|
||||||
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/thumb.$it"
|
|
||||||
}
|
|
||||||
|
|
||||||
manga.title = englishTitle ?: japaneseTitle ?: shortTitle!!
|
|
||||||
|
|
||||||
//Set artist (if we can find one)
|
|
||||||
tags.filter { it.namespace == NHENTAI_ARTIST_NAMESPACE }.let {
|
|
||||||
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name!! })
|
|
||||||
}
|
|
||||||
|
|
||||||
tags.filter { it.namespace == NHENTAI_CATEGORIES_NAMESPACE }.let {
|
|
||||||
if(it.isNotEmpty()) manga.genre = it.joinToString(transform = { it.name!! })
|
|
||||||
}
|
|
||||||
|
|
||||||
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
|
||||||
//We default to completed
|
|
||||||
manga.status = SManga.COMPLETED
|
|
||||||
englishTitle?.let { t ->
|
|
||||||
ONGOING_SUFFIX.find {
|
|
||||||
t.endsWith(it, ignoreCase = true)
|
|
||||||
}?.let {
|
|
||||||
manga.status = SManga.ONGOING
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val titleDesc = StringBuilder()
|
|
||||||
englishTitle?.let { titleDesc += "English Title: $it\n" }
|
|
||||||
japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
|
||||||
shortTitle?.let { titleDesc += "Short Title: $it\n" }
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
uploadDate?.let { detailsDesc += "Upload Date: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
|
||||||
pageImageTypes.size.let { detailsDesc += "Length: $it pages\n" }
|
|
||||||
favoritesCount?.let { detailsDesc += "Favorited: $it times\n" }
|
|
||||||
scanlator?.nullIfBlank()?.let { detailsDesc += "Scanlator: $it\n" }
|
|
||||||
|
|
||||||
val tagsDesc = buildTagsDescription(this)
|
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SearchableGalleryMetadata.genericCopyTo(manga: SManga): Boolean {
|
|
||||||
when(this) {
|
|
||||||
is ExGalleryMetadata -> this.copyTo(manga)
|
|
||||||
is PervEdenGalleryMetadata -> this.copyTo(manga)
|
|
||||||
is NHentaiMetadata -> this.copyTo(manga)
|
|
||||||
else -> return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildTagsDescription(metadata: SearchableGalleryMetadata)
|
|
||||||
= StringBuilder("Tags:\n").apply {
|
|
||||||
//BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
|
||||||
metadata.tags.groupBy {
|
|
||||||
it.namespace
|
|
||||||
}.entries.forEach { namespace, tags ->
|
|
||||||
if (tags.isNotEmpty()) {
|
|
||||||
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
|
||||||
this += "▪ $namespace: $joinedTags\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,22 @@
|
|||||||
package exh.metadata.models
|
package exh.metadata.models
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import exh.metadata.EX_DATE_FORMAT
|
||||||
|
import exh.metadata.ONGOING_SUFFIX
|
||||||
|
import exh.metadata.buildTagsDescription
|
||||||
|
import exh.metadata.humanReadableByteCount
|
||||||
|
import exh.plusAssign
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.Ignore
|
import io.realm.annotations.Ignore
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
import io.realm.annotations.RealmClass
|
import io.realm.annotations.RealmClass
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,12 +71,99 @@ open class ExGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
|
|||||||
@Index
|
@Index
|
||||||
override var mangaId: Long? = null
|
override var mangaId: Long? = null
|
||||||
|
|
||||||
|
class EmptyQuery : GalleryQuery<ExGalleryMetadata>(ExGalleryMetadata::class)
|
||||||
|
|
||||||
|
class UrlQuery(
|
||||||
|
val url: String,
|
||||||
|
val exh: Boolean
|
||||||
|
) : GalleryQuery<ExGalleryMetadata>(ExGalleryMetadata::class) {
|
||||||
|
override fun transform() = Query(
|
||||||
|
galleryId(url),
|
||||||
|
galleryToken(url),
|
||||||
|
exh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Query(val gId: String,
|
||||||
|
val gToken: String,
|
||||||
|
val exh: Boolean
|
||||||
|
) : GalleryQuery<ExGalleryMetadata>(ExGalleryMetadata::class) {
|
||||||
|
override fun map() = mapOf(
|
||||||
|
ExGalleryMetadata::gId to Query::gId,
|
||||||
|
ExGalleryMetadata::gToken to Query::gToken,
|
||||||
|
ExGalleryMetadata::exh to Query::exh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copyTo(manga: SManga) {
|
||||||
|
url?.let { manga.url = it }
|
||||||
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
|
||||||
|
//No title bug?
|
||||||
|
val titleObj = if(Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault())
|
||||||
|
altTitle ?: title
|
||||||
|
else
|
||||||
|
title
|
||||||
|
titleObj?.let { manga.title = it }
|
||||||
|
|
||||||
|
//Set artist (if we can find one)
|
||||||
|
tags.filter { it.namespace == EH_ARTIST_NAMESPACE }.let {
|
||||||
|
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name!! })
|
||||||
|
}
|
||||||
|
//Set author (if we can find one)
|
||||||
|
tags.filter { it.namespace == EH_AUTHOR_NAMESPACE }.let {
|
||||||
|
if(it.isNotEmpty()) manga.author = it.joinToString(transform = { it.name!! })
|
||||||
|
}
|
||||||
|
//Set genre
|
||||||
|
genre?.let { manga.genre = it }
|
||||||
|
|
||||||
|
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||||
|
//We default to completed
|
||||||
|
manga.status = SManga.COMPLETED
|
||||||
|
title?.let { t ->
|
||||||
|
ONGOING_SUFFIX.find {
|
||||||
|
t.endsWith(it, ignoreCase = true)
|
||||||
|
}?.let {
|
||||||
|
manga.status = SManga.ONGOING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Build a nice looking description out of what we know
|
||||||
|
val titleDesc = StringBuilder()
|
||||||
|
title?.let { titleDesc += "Title: $it\n" }
|
||||||
|
altTitle?.let { titleDesc += "Alternate Title: $it\n" }
|
||||||
|
|
||||||
|
val detailsDesc = StringBuilder()
|
||||||
|
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
||||||
|
datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
||||||
|
visible?.let { detailsDesc += "Visible: $it\n" }
|
||||||
|
language?.let {
|
||||||
|
detailsDesc += "Language: $it"
|
||||||
|
if(translated == true) detailsDesc += " TR"
|
||||||
|
detailsDesc += "\n"
|
||||||
|
}
|
||||||
|
size?.let { detailsDesc += "File Size: ${humanReadableByteCount(it, true)}\n" }
|
||||||
|
length?.let { detailsDesc += "Length: $it pages\n" }
|
||||||
|
favorites?.let { detailsDesc += "Favorited: $it times\n" }
|
||||||
|
averageRating?.let {
|
||||||
|
detailsDesc += "Rating: $it"
|
||||||
|
ratingCount?.let { detailsDesc += " ($it)" }
|
||||||
|
detailsDesc += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
val tagsDesc = buildTagsDescription(this)
|
||||||
|
|
||||||
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun splitGalleryUrl(url: String)
|
private fun splitGalleryUrl(url: String)
|
||||||
= url.let {
|
= url.let {
|
||||||
Uri.parse(it).pathSegments
|
Uri.parse(it).pathSegments
|
||||||
.filterNot(String::isNullOrBlank)
|
.filterNot(String::isNullOrBlank)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun galleryId(url: String) = splitGalleryUrl(url).let { it[it.size - 2] }
|
fun galleryId(url: String) = splitGalleryUrl(url).let { it[it.size - 2] }
|
||||||
|
|
||||||
@ -77,5 +174,9 @@ open class ExGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
|
|||||||
ExGalleryMetadata::title.name,
|
ExGalleryMetadata::title.name,
|
||||||
ExGalleryMetadata::altTitle.name
|
ExGalleryMetadata::altTitle.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private const val EH_ARTIST_NAMESPACE = "artist"
|
||||||
|
private const val EH_AUTHOR_NAMESPACE = "author"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
68
app/src/main/java/exh/metadata/models/GalleryQuery.kt
Executable file
68
app/src/main/java/exh/metadata/models/GalleryQuery.kt
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import io.realm.*
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
|
abstract class GalleryQuery<T : SearchableGalleryMetadata>(val clazz: KClass<T>) {
|
||||||
|
open fun map(): Map<*, *> = emptyMap<KProperty<T>, KProperty1<GalleryQuery<T>, *>>()
|
||||||
|
|
||||||
|
open fun transform(): GalleryQuery<T>? = this
|
||||||
|
|
||||||
|
open fun override(meta: RealmQuery<T>): RealmQuery<T> = meta
|
||||||
|
|
||||||
|
fun query(realm: Realm, meta: RealmQuery<T>? = null): RealmQuery<T>
|
||||||
|
= (meta ?: realm.where(clazz.java)).let {
|
||||||
|
val visited = mutableListOf<GalleryQuery<T>>()
|
||||||
|
|
||||||
|
var top: GalleryQuery<T>? = null
|
||||||
|
var newMeta = it
|
||||||
|
while(true) {
|
||||||
|
//DIFFERENT BEHAVIOR from: top?.transform() ?: this
|
||||||
|
top = if(top != null) top.transform() else this
|
||||||
|
|
||||||
|
if(top == null) break
|
||||||
|
|
||||||
|
if(top in visited) break
|
||||||
|
|
||||||
|
newMeta = top.applyMap(newMeta)
|
||||||
|
newMeta = top.override(newMeta)
|
||||||
|
|
||||||
|
visited += top
|
||||||
|
}
|
||||||
|
|
||||||
|
newMeta
|
||||||
|
}!!
|
||||||
|
|
||||||
|
fun applyMap(meta: RealmQuery<T>): RealmQuery<T> {
|
||||||
|
var newMeta = meta
|
||||||
|
|
||||||
|
map().forEach { (t, u) ->
|
||||||
|
t as KProperty<T>
|
||||||
|
u as KProperty1<GalleryQuery<T>, *>
|
||||||
|
|
||||||
|
val v = u.get(this)
|
||||||
|
val n = t.name
|
||||||
|
|
||||||
|
if(v != null) {
|
||||||
|
newMeta = when (v) {
|
||||||
|
is Date -> newMeta.equalTo(n, v)
|
||||||
|
is Boolean -> newMeta.equalTo(n, v)
|
||||||
|
is Byte -> newMeta.equalTo(n, v)
|
||||||
|
is ByteArray -> newMeta.equalTo(n, v)
|
||||||
|
is Double -> newMeta.equalTo(n, v)
|
||||||
|
is Float -> newMeta.equalTo(n, v)
|
||||||
|
is Int -> newMeta.equalTo(n, v)
|
||||||
|
is Long -> newMeta.equalTo(n, v)
|
||||||
|
is Short -> newMeta.equalTo(n, v)
|
||||||
|
is String -> newMeta.equalTo(n, v, Case.INSENSITIVE)
|
||||||
|
else -> throw IllegalArgumentException("Unknown type: ${v::class.qualifiedName}!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMeta
|
||||||
|
}
|
||||||
|
}
|
91
app/src/main/java/exh/metadata/models/HentaiCafeMetadata.kt
Normal file
91
app/src/main/java/exh/metadata/models/HentaiCafeMetadata.kt
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import exh.metadata.buildTagsDescription
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.Ignore
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
import io.realm.annotations.RealmClass
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@RealmClass
|
||||||
|
open class HentaiCafeMetadata : RealmObject(), SearchableGalleryMetadata {
|
||||||
|
@PrimaryKey
|
||||||
|
override var uuid: String = UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var hcId: String? = null
|
||||||
|
var readerId: String? = null
|
||||||
|
|
||||||
|
var url get() = hcId?.let { "$BASE_URL/$it" }
|
||||||
|
set(a) {
|
||||||
|
a?.let {
|
||||||
|
hcId = hcIdFromUrl(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String? = null
|
||||||
|
|
||||||
|
var artist: String? = null
|
||||||
|
|
||||||
|
override var uploader: String? = null
|
||||||
|
|
||||||
|
override var tags: RealmList<Tag> = RealmList()
|
||||||
|
|
||||||
|
override fun getTitles() = listOf(title).filterNotNull()
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
override val titleFields = listOf(
|
||||||
|
HentaiCafeMetadata::title.name
|
||||||
|
)
|
||||||
|
|
||||||
|
@Index
|
||||||
|
override var mangaId: Long? = null
|
||||||
|
|
||||||
|
override fun copyTo(manga: SManga) {
|
||||||
|
manga.title = title!!
|
||||||
|
manga.artist = artist
|
||||||
|
manga.author = artist
|
||||||
|
|
||||||
|
//Not available
|
||||||
|
manga.status = SManga.UNKNOWN
|
||||||
|
|
||||||
|
val detailsDesc = "Title: $title\n" +
|
||||||
|
"Artist: $artist\n"
|
||||||
|
|
||||||
|
val tagsDesc = buildTagsDescription(this)
|
||||||
|
|
||||||
|
manga.genre = tags.filter { it.namespace == "tag" }.joinToString {
|
||||||
|
it.name!!
|
||||||
|
}
|
||||||
|
|
||||||
|
manga.description = listOf(detailsDesc, tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmptyQuery : GalleryQuery<HentaiCafeMetadata>(HentaiCafeMetadata::class)
|
||||||
|
|
||||||
|
class UrlQuery(
|
||||||
|
val url: String
|
||||||
|
) : GalleryQuery<HentaiCafeMetadata>(HentaiCafeMetadata::class) {
|
||||||
|
override fun transform() = Query(
|
||||||
|
hcIdFromUrl(url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Query(val hcId: String): GalleryQuery<HentaiCafeMetadata>(HentaiCafeMetadata::class) {
|
||||||
|
override fun map() = mapOf(
|
||||||
|
HentaiCafeMetadata::hcId to Query::hcId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val BASE_URL = "https://hentai.cafe"
|
||||||
|
|
||||||
|
fun hcIdFromUrl(url: String)
|
||||||
|
= url.split("/").last { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,21 @@
|
|||||||
package exh.metadata.models
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import exh.metadata.EX_DATE_FORMAT
|
||||||
|
import exh.metadata.ONGOING_SUFFIX
|
||||||
|
import exh.metadata.buildTagsDescription
|
||||||
|
import exh.metadata.nullIfBlank
|
||||||
|
import exh.plusAssign
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.Ignore
|
import io.realm.annotations.Ignore
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
import io.realm.annotations.RealmClass
|
import io.realm.annotations.RealmClass
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,18 +68,92 @@ open class NHentaiMetadata : RealmObject(), SearchableGalleryMetadata {
|
|||||||
@Index
|
@Index
|
||||||
override var mangaId: Long? = null
|
override var mangaId: Long? = null
|
||||||
|
|
||||||
|
class EmptyQuery : GalleryQuery<NHentaiMetadata>(NHentaiMetadata::class)
|
||||||
|
|
||||||
|
class UrlQuery(
|
||||||
|
val url: String
|
||||||
|
) : GalleryQuery<NHentaiMetadata>(NHentaiMetadata::class) {
|
||||||
|
override fun transform() = Query(
|
||||||
|
nhIdFromUrl(url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Query(
|
||||||
|
val nhId: Long
|
||||||
|
) : GalleryQuery<NHentaiMetadata>(NHentaiMetadata::class) {
|
||||||
|
override fun map() = mapOf(
|
||||||
|
NHentaiMetadata::nhId to Query::nhId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copyTo(manga: SManga) {
|
||||||
|
url?.let { manga.url = it }
|
||||||
|
|
||||||
|
if(mediaId != null)
|
||||||
|
NHentaiMetadata.typeToExtension(thumbnailImageType)?.let {
|
||||||
|
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${
|
||||||
|
if(Injekt.get<PreferencesHelper>().eh_useHighQualityThumbs().getOrDefault())
|
||||||
|
"cover"
|
||||||
|
else
|
||||||
|
"thumb"
|
||||||
|
}.$it"
|
||||||
|
}
|
||||||
|
|
||||||
|
manga.title = englishTitle ?: japaneseTitle ?: shortTitle!!
|
||||||
|
|
||||||
|
//Set artist (if we can find one)
|
||||||
|
tags.filter { it.namespace == NHENTAI_ARTIST_NAMESPACE }.let {
|
||||||
|
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name!! })
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.filter { it.namespace == NHENTAI_CATEGORIES_NAMESPACE }.let {
|
||||||
|
if(it.isNotEmpty()) manga.genre = it.joinToString(transform = { it.name!! })
|
||||||
|
}
|
||||||
|
|
||||||
|
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||||
|
//We default to completed
|
||||||
|
manga.status = SManga.COMPLETED
|
||||||
|
englishTitle?.let { t ->
|
||||||
|
ONGOING_SUFFIX.find {
|
||||||
|
t.endsWith(it, ignoreCase = true)
|
||||||
|
}?.let {
|
||||||
|
manga.status = SManga.ONGOING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val titleDesc = StringBuilder()
|
||||||
|
englishTitle?.let { titleDesc += "English Title: $it\n" }
|
||||||
|
japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
||||||
|
shortTitle?.let { titleDesc += "Short Title: $it\n" }
|
||||||
|
|
||||||
|
val detailsDesc = StringBuilder()
|
||||||
|
uploadDate?.let { detailsDesc += "Upload Date: ${EX_DATE_FORMAT.format(Date(it * 1000))}\n" }
|
||||||
|
pageImageTypes.size.let { detailsDesc += "Length: $it pages\n" }
|
||||||
|
favoritesCount?.let { detailsDesc += "Favorited: $it times\n" }
|
||||||
|
scanlator?.nullIfBlank()?.let { detailsDesc += "Scanlator: $it\n" }
|
||||||
|
|
||||||
|
val tagsDesc = buildTagsDescription(this)
|
||||||
|
|
||||||
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val BASE_URL = "https://nhentai.net"
|
val BASE_URL = "https://nhentai.net"
|
||||||
|
|
||||||
|
private const val NHENTAI_ARTIST_NAMESPACE = "artist"
|
||||||
|
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
||||||
|
|
||||||
fun typeToExtension(t: String?) =
|
fun typeToExtension(t: String?) =
|
||||||
when(t) {
|
when(t) {
|
||||||
"p" -> "png"
|
"p" -> "png"
|
||||||
"j" -> "jpg"
|
"j" -> "jpg"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nhIdFromUrl(url: String)
|
fun nhIdFromUrl(url: String)
|
||||||
= url.split("/").last { it.isNotBlank() }.toLong()
|
= url.split("/").last { it.isNotBlank() }.toLong()
|
||||||
|
|
||||||
val TITLE_FIELDS = listOf(
|
val TITLE_FIELDS = listOf(
|
||||||
NHentaiMetadata::japaneseTitle.name,
|
NHentaiMetadata::japaneseTitle.name,
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
package exh.metadata.models
|
package exh.metadata.models
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||||
|
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||||
|
import exh.metadata.buildTagsDescription
|
||||||
|
import exh.plusAssign
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmQuery
|
||||||
import io.realm.annotations.Ignore
|
import io.realm.annotations.Ignore
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
@ -50,11 +56,79 @@ open class PervEdenGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
|
|||||||
@Index
|
@Index
|
||||||
override var mangaId: Long? = null
|
override var mangaId: Long? = null
|
||||||
|
|
||||||
|
override fun copyTo(manga: SManga) {
|
||||||
|
url?.let { manga.url = it }
|
||||||
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
|
||||||
|
val titleDesc = StringBuilder()
|
||||||
|
title?.let {
|
||||||
|
manga.title = it
|
||||||
|
titleDesc += "Title: $it\n"
|
||||||
|
}
|
||||||
|
if(altTitles.isNotEmpty())
|
||||||
|
titleDesc += "Alternate Titles: \n" + altTitles.map {
|
||||||
|
"▪ ${it.title}"
|
||||||
|
}.joinToString(separator = "\n", postfix = "\n")
|
||||||
|
|
||||||
|
val detailsDesc = StringBuilder()
|
||||||
|
artist?.let {
|
||||||
|
manga.artist = it
|
||||||
|
detailsDesc += "Artist: $it\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
type?.let {
|
||||||
|
manga.genre = it
|
||||||
|
detailsDesc += "Type: $it\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
status?.let {
|
||||||
|
manga.status = when(it) {
|
||||||
|
"Ongoing" -> SManga.ONGOING
|
||||||
|
"Completed", "Suspended" -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
detailsDesc += "Status: $it\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
rating?.let {
|
||||||
|
detailsDesc += "Rating: %.2\n".format(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tagsDesc = buildTagsDescription(this)
|
||||||
|
|
||||||
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmptyQuery : GalleryQuery<PervEdenGalleryMetadata>(PervEdenGalleryMetadata::class)
|
||||||
|
|
||||||
|
class UrlQuery(
|
||||||
|
val url: String,
|
||||||
|
val lang: PervEdenLang
|
||||||
|
) : GalleryQuery<PervEdenGalleryMetadata>(PervEdenGalleryMetadata::class) {
|
||||||
|
override fun transform() = Query(
|
||||||
|
pvIdFromUrl(url),
|
||||||
|
lang
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Query(val pvId: String,
|
||||||
|
val lang: PervEdenLang
|
||||||
|
) : GalleryQuery<PervEdenGalleryMetadata>(PervEdenGalleryMetadata::class) {
|
||||||
|
override fun map() = mapOf(
|
||||||
|
PervEdenGalleryMetadata::pvId to Query::pvId
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun override(meta: RealmQuery<PervEdenGalleryMetadata>)
|
||||||
|
= meta.equalTo(PervEdenGalleryMetadata::lang.name, lang.name)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun splitGalleryUrl(url: String)
|
private fun splitGalleryUrl(url: String)
|
||||||
= url.let {
|
= url.let {
|
||||||
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
|
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
|
||||||
|
|
||||||
@ -88,3 +162,14 @@ open class PervEdenTitle(var metadata: PervEdenGalleryMetadata? = null,
|
|||||||
|
|
||||||
override fun toString() = "PervEdenTitle(metadata=$metadata, title=$title)"
|
override fun toString() = "PervEdenTitle(metadata=$metadata, title=$title)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class PervEdenLang(val id: Long) {
|
||||||
|
en(PERV_EDEN_EN_SOURCE_ID),
|
||||||
|
it(PERV_EDEN_IT_SOURCE_ID);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun source(id: Long)
|
||||||
|
= PervEdenLang.values().find { it.id == id }
|
||||||
|
?: throw IllegalArgumentException("Unknown source ID: $id!")
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,8 @@
|
|||||||
package exh.metadata.models
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmModel
|
import io.realm.RealmModel
|
||||||
import io.realm.annotations.Index
|
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.HashMap
|
|
||||||
import kotlin.reflect.KCallable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A gallery that can be searched using the EH search engine
|
* A gallery that can be searched using the EH search engine
|
||||||
@ -23,4 +20,6 @@ interface SearchableGalleryMetadata: RealmModel {
|
|||||||
val titleFields: List<String>
|
val titleFields: List<String>
|
||||||
|
|
||||||
var mangaId: Long?
|
var mangaId: Long?
|
||||||
|
|
||||||
|
fun copyTo(manga: SManga)
|
||||||
}
|
}
|
@ -18,12 +18,11 @@ class SearchEngine {
|
|||||||
fun matchTagList(namespace: String?,
|
fun matchTagList(namespace: String?,
|
||||||
component: Text?,
|
component: Text?,
|
||||||
excluded: Boolean) {
|
excluded: Boolean) {
|
||||||
if(excluded)
|
when {
|
||||||
rQuery.not()
|
excluded -> rQuery.not()
|
||||||
else if (queryEmpty)
|
queryEmpty -> queryEmpty = false
|
||||||
queryEmpty = false
|
else -> rQuery.or()
|
||||||
else
|
}
|
||||||
rQuery.or()
|
|
||||||
|
|
||||||
rQuery.beginGroup()
|
rQuery.beginGroup()
|
||||||
//Match namespace if specified
|
//Match namespace if specified
|
||||||
|
@ -11,10 +11,8 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import exh.isExSource
|
import exh.isExSource
|
||||||
import exh.isLewdSource
|
import exh.isLewdSource
|
||||||
import exh.metadata.genericCopyTo
|
|
||||||
import exh.metadata.queryMetadataFromManga
|
import exh.metadata.queryMetadataFromManga
|
||||||
import exh.util.defRealm
|
import exh.util.defRealm
|
||||||
import exh.util.realmTrans
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -64,7 +62,7 @@ class MetadataFetchDialog {
|
|||||||
val source = sourceManager.get(manga.source)
|
val source = sourceManager.get(manga.source)
|
||||||
source?.let {
|
source?.let {
|
||||||
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
|
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
|
||||||
realm.queryMetadataFromManga(manga).findFirst()?.genericCopyTo(manga)
|
realm.queryMetadataFromManga(manga).findFirst()?.copyTo(manga)
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Timber.e(t, "Could not migrate manga!")
|
Timber.e(t, "Could not migrate manga!")
|
||||||
|
@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import exh.isExSource
|
import exh.isExSource
|
||||||
import exh.isLewdSource
|
import exh.isLewdSource
|
||||||
import exh.metadata.ehMetaQueryFromUrl
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
import exh.util.realmTrans
|
import exh.util.realmTrans
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@ -43,7 +43,9 @@ class UrlMigrator {
|
|||||||
//Build fixed URL
|
//Build fixed URL
|
||||||
val urlWithSlash = "/" + manga.url
|
val urlWithSlash = "/" + manga.url
|
||||||
//Fix metadata if required
|
//Fix metadata if required
|
||||||
val metadata = realm.ehMetaQueryFromUrl(manga.url, isExSource(manga.source)).findFirst()
|
val metadata = ExGalleryMetadata.UrlQuery(manga.url, isExSource(manga.source))
|
||||||
|
.query(realm)
|
||||||
|
.findFirst()
|
||||||
metadata?.url?.let {
|
metadata?.url?.let {
|
||||||
if (it.startsWith("g/")) { //Check if metadata URL has no slash
|
if (it.startsWith("g/")) { //Check if metadata URL has no slash
|
||||||
metadata.url = urlWithSlash //Fix it
|
metadata.url = urlWithSlash //Fix it
|
||||||
|
@ -480,19 +480,19 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
return query.average(fieldName)
|
return query.average(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun min(fieldName: String): Number {
|
fun min(fieldName: String): Number? {
|
||||||
return query.min(fieldName)
|
return query.min(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun minimumDate(fieldName: String): Date {
|
fun minimumDate(fieldName: String): Date? {
|
||||||
return query.minimumDate(fieldName)
|
return query.minimumDate(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun max(fieldName: String): Number {
|
fun max(fieldName: String): Number? {
|
||||||
return query.max(fieldName)
|
return query.max(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maximumDate(fieldName: String): Date {
|
fun maximumDate(fieldName: String): Date? {
|
||||||
return query.maximumDate(fieldName)
|
return query.maximumDate(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,7 +540,7 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
return query.findAllSortedAsync(fieldName1, sortOrder1, fieldName2, sortOrder2)
|
return query.findAllSortedAsync(fieldName1, sortOrder1, fieldName2, sortOrder2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findFirst(): E {
|
fun findFirst(): E? {
|
||||||
return query.findFirst()
|
return query.findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,24 +7,8 @@ import java.util.*
|
|||||||
|
|
||||||
inline fun <T> realmTrans(block: (Realm) -> T): T {
|
inline fun <T> realmTrans(block: (Realm) -> T): T {
|
||||||
return defRealm {
|
return defRealm {
|
||||||
it.beginTransaction()
|
it.trans {
|
||||||
try {
|
block(it)
|
||||||
val res = block(it)
|
|
||||||
it.commitTransaction()
|
|
||||||
res
|
|
||||||
} catch(t: Throwable) {
|
|
||||||
if (it.isInTransaction) {
|
|
||||||
it.cancelTransaction()
|
|
||||||
} else {
|
|
||||||
RealmLog.warn("Could not cancel transaction, not currently in a transaction.")
|
|
||||||
}
|
|
||||||
|
|
||||||
throw t
|
|
||||||
} finally {
|
|
||||||
//Just in case
|
|
||||||
if (it.isInTransaction) {
|
|
||||||
it.cancelTransaction()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,5 +19,27 @@ inline fun <T> defRealm(block: (Realm) -> T): T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T> Realm.trans(block: () -> T): T {
|
||||||
|
beginTransaction()
|
||||||
|
try {
|
||||||
|
val res = block()
|
||||||
|
commitTransaction()
|
||||||
|
return res
|
||||||
|
} catch(t: Throwable) {
|
||||||
|
if (isInTransaction) {
|
||||||
|
cancelTransaction()
|
||||||
|
} else {
|
||||||
|
RealmLog.warn("Could not cancel transaction, not currently in a transaction.")
|
||||||
|
}
|
||||||
|
|
||||||
|
throw t
|
||||||
|
} finally {
|
||||||
|
//Just in case
|
||||||
|
if (isInTransaction) {
|
||||||
|
cancelTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun <T : RealmModel> Realm.createUUIDObj(clazz: Class<T>)
|
fun <T : RealmModel> Realm.createUUIDObj(clazz: Class<T>)
|
||||||
= createObject(clazz, UUID.randomUUID().toString())
|
= createObject(clazz, UUID.randomUUID().toString())!!
|
||||||
|
15
app/src/main/res/drawable/eh_ic_nhlogo_color.xml
Normal file
15
app/src/main/res/drawable/eh_ic_nhlogo_color.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="470.25"
|
||||||
|
android:viewportWidth="603.195" android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<group
|
||||||
|
android:translateY="100"
|
||||||
|
android:scaleX="1.2"
|
||||||
|
android:scaleY="1.2">
|
||||||
|
<path android:fillColor="#EC2854"
|
||||||
|
android:pathData="M172.2,36.03c-16.6,6.91 -52.73,34.03 -36.25,58.47c7.29,10.81 19.94,18.44 31.47,22.06c10.73,3.36 23.9,-0.76 33.71,3.72c-2.09,5.1 -9.48,23.69 -15.81,22.32c-11.83,-2.54 -23.79,-0.44 -33.07,8.48c-18.96,-26.3 -45.97,-36.97 -75.74,-29.68c22.07,-27.2 16.72,-55.69 -6.47,-81.62c-14,-15.66 -47.99,-37.96 -69.85,-28.85C54.78,-11.81 121.31,5.38 172.2,36.03C163.38,39.7 168.58,33.85 172.2,36.03z"
|
||||||
|
android:strokeColor="#EC2854" android:strokeWidth="1"/>
|
||||||
|
<path android:fillColor="#EC2854"
|
||||||
|
android:pathData="M310.36,36.03c16.59,6.91 52.73,34.03 36.25,58.47c-7.29,10.81 -19.94,18.44 -31.47,22.06c-10.73,3.37 -23.9,-0.76 -33.71,3.72c2.1,5.11 9.46,23.67 15.81,22.32c11.83,-2.54 23.79,-0.45 33.07,8.48c18.96,-26.29 45.97,-36.97 75.74,-29.68c-22.06,-27.21 -16.73,-55.68 6.47,-81.62c14,-15.65 47.99,-37.97 69.85,-28.85C427.78,-11.8 361.25,5.37 310.36,36.03C319.18,39.7 313.98,33.85 310.36,36.03z"
|
||||||
|
android:strokeColor="#EC2854" android:strokeWidth="1"/>
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M180.43,97.11h23.2v15.92c6.87,-6.56 14.15,-11.27 21.84,-14.14c7.69,-2.86 16.23,-4.3 25.64,-4.3c20.62,0 34.55,5.55 41.78,16.65c3.98,6.07 5.97,14.77 5.97,26.08v71.95h-24.83v-70.69c0,-6.84 -1.31,-12.36 -3.93,-16.55c-4.34,-6.98 -12.21,-10.47 -23.6,-10.47c-5.79,0 -10.54,0.46 -14.25,1.36c-6.69,1.54 -12.57,4.61 -17.64,9.22c-4.07,3.7 -6.72,7.52 -7.94,11.47c-1.22,3.94 -1.83,9.58 -1.83,16.91v58.75h-24.42L180.43,97.11L180.43,97.11z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -451,4 +451,5 @@
|
|||||||
<!-- EXH -->
|
<!-- EXH -->
|
||||||
<string name="label_login">Login</string>
|
<string name="label_login">Login</string>
|
||||||
<string name="pref_category_eh">E-Hentai</string>
|
<string name="pref_category_eh">E-Hentai</string>
|
||||||
|
<string name="pref_category_nh">nhentai</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user