Migrate to new URL import system
This commit is contained in:
parent
46636b537c
commit
e915fd28cb
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
|
interface UrlImportableSource : Source {
|
||||||
|
val matchingHosts: List<String>
|
||||||
|
|
||||||
|
fun matchesUri(uri: Uri): Boolean {
|
||||||
|
return (uri.host ?: "").toLowerCase() in matchingHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is allowed to block for IO if necessary
|
||||||
|
fun mapUrlToMangaUrl(uri: Uri): String?
|
||||||
|
|
||||||
|
fun cleanMangaUrl(url: String): String {
|
||||||
|
return try {
|
||||||
|
val uri = URI(url)
|
||||||
|
var out = uri.path
|
||||||
|
if (uri.query != null)
|
||||||
|
out += "?" + uri.query
|
||||||
|
if (uri.fragment != null)
|
||||||
|
out += "#" + uri.fragment
|
||||||
|
out
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,10 @@ 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.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
|
import com.github.salomonbrys.kotson.*
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
@ -12,6 +16,7 @@ import eu.kanade.tachiyomi.network.asObservableWithAsyncStacktrace
|
|||||||
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.source.online.LewdSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import exh.eh.EHentaiUpdateHelper
|
import exh.eh.EHentaiUpdateHelper
|
||||||
import exh.metadata.EX_DATE_FORMAT
|
import exh.metadata.EX_DATE_FORMAT
|
||||||
@ -46,7 +51,7 @@ import java.lang.RuntimeException
|
|||||||
// TODO Consider gallery updating when doing tabbed browsing
|
// TODO Consider gallery updating when doing tabbed browsing
|
||||||
class EHentai(override val id: Long,
|
class EHentai(override val id: Long,
|
||||||
val exh: Boolean,
|
val exh: Boolean,
|
||||||
val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document> {
|
val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document>, UrlImportableSource {
|
||||||
override val metaClass = EHentaiSearchMetadata::class
|
override val metaClass = EHentaiSearchMetadata::class
|
||||||
|
|
||||||
val schema: String
|
val schema: String
|
||||||
@ -636,11 +641,67 @@ class EHentai(override val id: Long,
|
|||||||
|
|
||||||
class GalleryNotFoundException(cause: Throwable): RuntimeException("Gallery not found!", cause)
|
class GalleryNotFoundException(cause: Throwable): RuntimeException("Gallery not found!", cause)
|
||||||
|
|
||||||
|
// === URL IMPORT STUFF
|
||||||
|
|
||||||
|
override val matchingHosts: List<String> = if(exh) listOf(
|
||||||
|
"exhentai.org"
|
||||||
|
) else listOf(
|
||||||
|
"g.e-hentai.org",
|
||||||
|
"e-hentai.org"
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
return when (uri.pathSegments.firstOrNull()) {
|
||||||
|
"g" -> {
|
||||||
|
//Is already gallery page, do nothing
|
||||||
|
uri.toString()
|
||||||
|
}
|
||||||
|
"s" -> {
|
||||||
|
//Is page, fetch gallery token and use that
|
||||||
|
getGalleryUrlFromPage(uri)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cleanMangaUrl(url: String): String {
|
||||||
|
return EHentaiSearchMetadata.normalizeUrl(super.cleanMangaUrl(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getGalleryUrlFromPage(uri: Uri): String {
|
||||||
|
val lastSplit = uri.pathSegments.last().split("-")
|
||||||
|
val pageNum = lastSplit.last()
|
||||||
|
val gallery = lastSplit.first()
|
||||||
|
val pageToken = uri.pathSegments.elementAt(1)
|
||||||
|
|
||||||
|
val json = JsonObject()
|
||||||
|
json["method"] = "gtoken"
|
||||||
|
json["pagelist"] = JsonArray().apply {
|
||||||
|
add(JsonArray().apply {
|
||||||
|
add(gallery.toInt())
|
||||||
|
add(pageToken)
|
||||||
|
add(pageNum.toInt())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val outJson = JsonParser().parse(client.newCall(Request.Builder()
|
||||||
|
.url(EH_API_BASE)
|
||||||
|
.post(RequestBody.create(JSON, json.toString()))
|
||||||
|
.build()).execute().body()!!.string()).obj
|
||||||
|
|
||||||
|
val obj = outJson["tokenlist"].array.first()
|
||||||
|
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
|
private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
|
||||||
private const val TR_SUFFIX = "TR"
|
private const val TR_SUFFIX = "TR"
|
||||||
private const val REVERSE_PARAM = "TEH_REVERSE"
|
private const val REVERSE_PARAM = "TEH_REVERSE"
|
||||||
|
|
||||||
|
private const val EH_API_BASE = "https://api.e-hentai.org/api.php"
|
||||||
|
private val JSON = MediaType.parse("application/json; charset=utf-8")!!
|
||||||
|
|
||||||
private val FAVORITES_BORDER_HEX_COLORS = listOf(
|
private val FAVORITES_BORDER_HEX_COLORS = listOf(
|
||||||
"000",
|
"000",
|
||||||
"f00",
|
"f00",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.source.online.all
|
package eu.kanade.tachiyomi.source.online.all
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.github.salomonbrys.kotson.array
|
import com.github.salomonbrys.kotson.array
|
||||||
import com.github.salomonbrys.kotson.get
|
import com.github.salomonbrys.kotson.get
|
||||||
@ -12,7 +13,9 @@ 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.source.online.LewdSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import exh.GalleryAddEvent
|
||||||
import exh.HITOMI_SOURCE_ID
|
import exh.HITOMI_SOURCE_ID
|
||||||
import exh.hitomi.HitomiNozomi
|
import exh.hitomi.HitomiNozomi
|
||||||
import exh.metadata.metadata.HitomiSearchMetadata
|
import exh.metadata.metadata.HitomiSearchMetadata
|
||||||
@ -36,7 +39,7 @@ import java.util.*
|
|||||||
/**
|
/**
|
||||||
* Man, I hate this source :(
|
* Man, I hate this source :(
|
||||||
*/
|
*/
|
||||||
class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document> {
|
class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImportableSource {
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
private val jsonParser by lazy { JsonParser() }
|
private val jsonParser by lazy { JsonParser() }
|
||||||
|
|
||||||
@ -390,6 +393,19 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document> {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val matchingHosts = listOf(
|
||||||
|
"hitomi.la"
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
|
if(lcFirstPathSegment != "galleries" && lcFirstPathSegment != "reader")
|
||||||
|
return null
|
||||||
|
|
||||||
|
return "https://hitomi.la/galleries/${uri.pathSegments[1].substringBefore('.')}.html"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10
|
private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10
|
||||||
private val PAGE_SIZE = 25
|
private val PAGE_SIZE = 25
|
||||||
|
@ -13,7 +13,9 @@ 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.source.online.LewdSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import exh.GalleryAddEvent
|
||||||
import exh.NHENTAI_SOURCE_ID
|
import exh.NHENTAI_SOURCE_ID
|
||||||
import exh.metadata.metadata.NHentaiSearchMetadata
|
import exh.metadata.metadata.NHentaiSearchMetadata
|
||||||
import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT
|
import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT
|
||||||
@ -27,7 +29,7 @@ import rx.Observable
|
|||||||
* NHentai source
|
* NHentai source
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata, Response> {
|
class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata, Response>, UrlImportableSource {
|
||||||
override val metaClass = NHentaiSearchMetadata::class
|
override val metaClass = NHentaiSearchMetadata::class
|
||||||
|
|
||||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
@ -127,9 +129,6 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
override fun mangaDetailsRequest(manga: SManga)
|
override fun mangaDetailsRequest(manga: SManga)
|
||||||
= nhGet(baseUrl + manga.url)
|
= nhGet(baseUrl + manga.url)
|
||||||
|
|
||||||
fun urlToDetailsRequest(url: String)
|
|
||||||
= nhGet(baseUrl + "/api/gallery/" + url.split("/").last { it.isNotBlank() })
|
|
||||||
|
|
||||||
fun parseResultPage(response: Response): MangasPage {
|
fun parseResultPage(response: Response): MangasPage {
|
||||||
val doc = response.asJsoup()
|
val doc = response.asJsoup()
|
||||||
|
|
||||||
@ -250,7 +249,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
)
|
)
|
||||||
|
|
||||||
val appName by lazy {
|
val appName by lazy {
|
||||||
context.getString(R.string.app_name)!!
|
context.getString(R.string.app_name)
|
||||||
}
|
}
|
||||||
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
@ -260,7 +259,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
"Chrome/56.0.2924.87 " +
|
"Chrome/56.0.2924.87 " +
|
||||||
"Safari/537.36 " +
|
"Safari/537.36 " +
|
||||||
"$appName/${BuildConfig.VERSION_CODE}")
|
"$appName/${BuildConfig.VERSION_CODE}")
|
||||||
.tag(tag).build()!!
|
.tag(tag).build()
|
||||||
|
|
||||||
override val id = NHENTAI_SOURCE_ID
|
override val id = NHENTAI_SOURCE_ID
|
||||||
|
|
||||||
@ -272,6 +271,19 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
// === URL IMPORT STUFF
|
||||||
|
|
||||||
|
override val matchingHosts = listOf(
|
||||||
|
"nhentai.net"
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
if(uri.pathSegments.firstOrNull()?.toLowerCase() != "g")
|
||||||
|
return null
|
||||||
|
|
||||||
|
return "https://nhentai.net/g/${uri.pathSegments[1]}/"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val GALLERY_JSON_REGEX = Regex("new N.gallery\\((.*)\\);")
|
private val GALLERY_JSON_REGEX = Regex("new N.gallery\\((.*)\\);")
|
||||||
private const val REVERSE_PARAM = "TEH_REVERSE"
|
private const val REVERSE_PARAM = "TEH_REVERSE"
|
||||||
@ -282,9 +294,4 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
JsonParser()
|
JsonParser()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun JsonElement.notNull() =
|
|
||||||
if(this is JsonNull)
|
|
||||||
null
|
|
||||||
else this
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
|
|||||||
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.LewdSource
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
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.metadata.PervEdenLang
|
import exh.metadata.metadata.PervEdenLang
|
||||||
@ -27,7 +28,7 @@ import java.util.*
|
|||||||
|
|
||||||
// TODO Transform into delegated source
|
// TODO Transform into delegated source
|
||||||
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
|
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
|
||||||
LewdSource<PervEdenSearchMetadata, Document> {
|
LewdSource<PervEdenSearchMetadata, Document>, UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* The class of the metadata used by this source
|
* The class of the metadata used by this source
|
||||||
*/
|
*/
|
||||||
@ -306,6 +307,23 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val matchingHosts = listOf("www.perveden.com")
|
||||||
|
|
||||||
|
override fun matchesUri(uri: Uri): Boolean {
|
||||||
|
return super.matchesUri(uri) && uri.pathSegments.firstOrNull()?.toLowerCase() == when(pvLang) {
|
||||||
|
PervEdenLang.en -> "en-manga"
|
||||||
|
PervEdenLang.it -> "it-manga"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
val newUri = Uri.parse("http://www.perveden.com/").buildUpon()
|
||||||
|
uri.pathSegments.take(3).forEach {
|
||||||
|
newUri.appendPath(it)
|
||||||
|
}
|
||||||
|
return newUri.toString()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
|
val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
|
||||||
timeZone = TimeZone.getTimeZone("GMT")
|
timeZone = TimeZone.getTimeZone("GMT")
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.source.online.english
|
package eu.kanade.tachiyomi.source.online.english
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.source.online.LewdSource
|
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import exh.metadata.metadata.HentaiCafeSearchMetadata
|
import exh.metadata.metadata.HentaiCafeSearchMetadata
|
||||||
import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT
|
import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT
|
||||||
@ -18,7 +20,7 @@ import org.jsoup.nodes.Document
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||||
LewdSource<HentaiCafeSearchMetadata, Document> {
|
LewdSource<HentaiCafeSearchMetadata, Document>, UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||||
*/
|
*/
|
||||||
@ -88,4 +90,17 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}.toObservable()
|
}.toObservable()
|
||||||
|
|
||||||
|
override val matchingHosts = listOf(
|
||||||
|
"hentai.cafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
|
return if(lcFirstPathSegment == "manga")
|
||||||
|
"https://hentai.cafe/${uri.pathSegments[2]}"
|
||||||
|
else
|
||||||
|
"https://hentai.cafe/$lcFirstPathSegment"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
|
|||||||
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.LewdSource
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
|
import exh.GalleryAddEvent
|
||||||
import exh.TSUMINO_SOURCE_ID
|
import exh.TSUMINO_SOURCE_ID
|
||||||
import exh.ui.captcha.ActionCompletionVerifier
|
import exh.ui.captcha.ActionCompletionVerifier
|
||||||
import exh.ui.captcha.BrowserActionActivity
|
import exh.ui.captcha.BrowserActionActivity
|
||||||
@ -33,7 +35,10 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<TsuminoSearchMetadata, Document>, ActionCompletionVerifier {
|
class Tsumino(private val context: Context): ParsedHttpSource(),
|
||||||
|
LewdSource<TsuminoSearchMetadata, Document>,
|
||||||
|
ActionCompletionVerifier,
|
||||||
|
UrlImportableSource {
|
||||||
override val metaClass = TsuminoSearchMetadata::class
|
override val metaClass = TsuminoSearchMetadata::class
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
@ -400,6 +405,19 @@ class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<Tsum
|
|||||||
class MinimumRatingFilter : Filter.Select<String>("Minimum rating", (0 .. 5).map { "$it stars" }.toTypedArray())
|
class MinimumRatingFilter : Filter.Select<String>("Minimum rating", (0 .. 5).map { "$it stars" }.toTypedArray())
|
||||||
class ExcludeParodiesFilter : Filter.CheckBox("Exclude parodies")
|
class ExcludeParodiesFilter : Filter.CheckBox("Exclude parodies")
|
||||||
|
|
||||||
|
override val matchingHosts = listOf(
|
||||||
|
"www.tsumino.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
|
if(lcFirstPathSegment != "read" && lcFirstPathSegment != "book")
|
||||||
|
return null
|
||||||
|
|
||||||
|
return "https://tsumino.com/Book/Info/${uri.pathSegments[2]}"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val jsonParser by lazy {
|
val jsonParser by lazy {
|
||||||
JsonParser()
|
JsonParser()
|
||||||
|
@ -2,23 +2,13 @@ package exh
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
import com.github.salomonbrys.kotson.*
|
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import com.google.gson.JsonParser
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
||||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
|
||||||
import okhttp3.MediaType
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URI
|
|
||||||
import java.net.URISyntaxException
|
|
||||||
|
|
||||||
class GalleryAdder {
|
class GalleryAdder {
|
||||||
|
|
||||||
@ -26,133 +16,55 @@ class GalleryAdder {
|
|||||||
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
private val networkHelper: NetworkHelper by injectLazy()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val EH_API_BASE = "https://api.e-hentai.org/api.php"
|
|
||||||
val JSON = MediaType.parse("application/json; charset=utf-8")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getGalleryUrlFromPage(url: String): String {
|
|
||||||
val uri = Uri.parse(url)
|
|
||||||
val lastSplit = uri.pathSegments.last().split("-")
|
|
||||||
val pageNum = lastSplit.last()
|
|
||||||
val gallery = lastSplit.first()
|
|
||||||
val pageToken = uri.pathSegments.elementAt(1)
|
|
||||||
|
|
||||||
val json = JsonObject()
|
|
||||||
json["method"] = "gtoken"
|
|
||||||
json["pagelist"] = JsonArray().apply {
|
|
||||||
add(JsonArray().apply {
|
|
||||||
add(gallery.toInt())
|
|
||||||
add(pageToken)
|
|
||||||
add(pageNum.toInt())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val outJson = JsonParser().parse(networkHelper.client.newCall(Request.Builder()
|
|
||||||
.url(EH_API_BASE)
|
|
||||||
.post(RequestBody.create(JSON, json.toString()))
|
|
||||||
.build()).execute().body()!!.string()).obj
|
|
||||||
|
|
||||||
val obj = outJson["tokenlist"].array.first()
|
|
||||||
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addGallery(url: String,
|
fun addGallery(url: String,
|
||||||
fav: Boolean = false,
|
fav: Boolean = false,
|
||||||
forceSource: Long? = null,
|
forceSource: UrlImportableSource? = null,
|
||||||
throttleFunc: () -> Unit = {}): GalleryAddEvent {
|
throttleFunc: () -> Unit = {}): GalleryAddEvent {
|
||||||
XLog.d("Importing gallery (url: %s, fav: %s, forceSource: %s)...", url, fav, forceSource)
|
XLog.d("Importing gallery (url: %s, fav: %s, forceSource: %s)...", url, fav, forceSource)
|
||||||
try {
|
try {
|
||||||
val urlObj = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
val lowercasePs = urlObj.pathSegments.map(String::toLowerCase)
|
|
||||||
val lcFirstPathSegment = lowercasePs[0]
|
// Find matching source
|
||||||
val source = when (urlObj.host.toLowerCase()) {
|
val source = if(forceSource != null) {
|
||||||
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
|
try {
|
||||||
"exhentai.org" -> EXH_SOURCE_ID
|
if (forceSource.matchesUri(uri)) forceSource
|
||||||
"nhentai.net" -> NHENTAI_SOURCE_ID
|
else return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
"www.perveden.com" -> {
|
} catch(e: Exception) {
|
||||||
when(lowercasePs[1]) {
|
XLog.e("Source URI match check error!", e)
|
||||||
"en-manga" -> PERV_EDEN_EN_SOURCE_ID
|
return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
"it-manga" -> PERV_EDEN_IT_SOURCE_ID
|
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"hentai.cafe" -> HENTAI_CAFE_SOURCE_ID
|
} else {
|
||||||
"www.tsumino.com" -> TSUMINO_SOURCE_ID
|
sourceManager.getVisibleCatalogueSources()
|
||||||
"hitomi.la" -> HITOMI_SOURCE_ID
|
.filterIsInstance<UrlImportableSource>()
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
.find {
|
||||||
|
try {
|
||||||
|
it.matchesUri(uri)
|
||||||
|
} catch(e: Exception) {
|
||||||
|
XLog.e("Source URI match check error!", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} ?: return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(forceSource != null && source != forceSource) {
|
// Map URL to manga URL
|
||||||
return GalleryAddEvent.Fail.UnknownType(url)
|
val realUrl = try {
|
||||||
}
|
source.mapUrlToMangaUrl(uri)
|
||||||
|
} catch(e: Exception) {
|
||||||
|
XLog.e("Source URI map-to-manga error!", e)
|
||||||
|
null
|
||||||
|
} ?: return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
|
|
||||||
val sourceObj = sourceManager.get(source)
|
// Clean URL
|
||||||
?: return GalleryAddEvent.Fail.Error(url, "Source not installed!")
|
val cleanedUrl = try {
|
||||||
|
source.cleanMangaUrl(realUrl)
|
||||||
val realUrl = when(source) {
|
} catch(e: Exception) {
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID -> when (lcFirstPathSegment) {
|
XLog.e("Source URI clean error!", e)
|
||||||
"g" -> {
|
null
|
||||||
//Is already gallery page, do nothing
|
} ?: return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
url
|
|
||||||
}
|
|
||||||
"s" -> {
|
|
||||||
//Is page, fetch gallery token and use that
|
|
||||||
getGalleryUrlFromPage(url)
|
|
||||||
}
|
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
}
|
|
||||||
NHENTAI_SOURCE_ID -> {
|
|
||||||
if(lcFirstPathSegment != "g")
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
HENTAI_CAFE_SOURCE_ID -> {
|
|
||||||
if(lcFirstPathSegment == "manga")
|
|
||||||
"https://hentai.cafe/${urlObj.pathSegments[2]}"
|
|
||||||
|
|
||||||
"https://hentai.cafe/$lcFirstPathSegment"
|
|
||||||
}
|
|
||||||
TSUMINO_SOURCE_ID -> {
|
|
||||||
if(lcFirstPathSegment != "read" && lcFirstPathSegment != "book")
|
|
||||||
return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
|
|
||||||
"https://tsumino.com/Book/Info/${urlObj.pathSegments[2]}"
|
|
||||||
}
|
|
||||||
HITOMI_SOURCE_ID -> {
|
|
||||||
if(lcFirstPathSegment != "galleries" && lcFirstPathSegment != "reader")
|
|
||||||
return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
|
|
||||||
"https://hitomi.la/galleries/${urlObj.pathSegments[1].substringBefore('.')}.html"
|
|
||||||
}
|
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
val cleanedUrl = when(source) {
|
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID -> EHentaiSearchMetadata.normalizeUrl(getUrlWithoutDomain(realUrl))
|
|
||||||
NHENTAI_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
|
||||||
PERV_EDEN_EN_SOURCE_ID,
|
|
||||||
PERV_EDEN_IT_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
|
||||||
HENTAI_CAFE_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
|
||||||
TSUMINO_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
|
||||||
HITOMI_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Use manga in DB if possible, otherwise, make a new manga
|
//Use manga in DB if possible, otherwise, make a new manga
|
||||||
val manga = db.getManga(cleanedUrl, source).executeAsBlocking()
|
val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking()
|
||||||
?: Manga.create(source).apply {
|
?: Manga.create(source.id).apply {
|
||||||
this.url = cleanedUrl
|
this.url = cleanedUrl
|
||||||
title = realUrl
|
title = realUrl
|
||||||
}
|
}
|
||||||
@ -166,7 +78,7 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and copy details
|
// Fetch and copy details
|
||||||
val newManga = sourceObj.fetchMangaDetails(manga).toBlocking().first()
|
val newManga = source.fetchMangaDetails(manga).toBlocking().first()
|
||||||
manga.copyFrom(newManga)
|
manga.copyFrom(newManga)
|
||||||
manga.initialized = true
|
manga.initialized = true
|
||||||
|
|
||||||
@ -176,13 +88,13 @@ class GalleryAdder {
|
|||||||
|
|
||||||
//Fetch and copy chapters
|
//Fetch and copy chapters
|
||||||
try {
|
try {
|
||||||
val chapterListObs = if(sourceObj is EHentai) {
|
val chapterListObs = if(source is EHentai) {
|
||||||
sourceObj.fetchChapterList(manga, throttleFunc)
|
source.fetchChapterList(manga, throttleFunc)
|
||||||
} else {
|
} else {
|
||||||
sourceObj.fetchChapterList(manga)
|
source.fetchChapterList(manga)
|
||||||
}
|
}
|
||||||
chapterListObs.map {
|
chapterListObs.map {
|
||||||
syncChaptersWithSource(db, it, manga, sourceObj)
|
syncChaptersWithSource(db, it, manga, source)
|
||||||
}.toBlocking().first()
|
}.toBlocking().first()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
XLog.w("Failed to update chapters for gallery: ${manga.title}!", e)
|
XLog.w("Failed to update chapters for gallery: ${manga.title}!", e)
|
||||||
@ -201,20 +113,6 @@ class GalleryAdder {
|
|||||||
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim())
|
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUrlWithoutDomain(orig: String): String {
|
|
||||||
return try {
|
|
||||||
val uri = URI(orig)
|
|
||||||
var out = uri.path
|
|
||||||
if (uri.query != null)
|
|
||||||
out += "?" + uri.query
|
|
||||||
if (uri.fragment != null)
|
|
||||||
out += "#" + uri.fragment
|
|
||||||
out
|
|
||||||
} catch (e: URISyntaxException) {
|
|
||||||
orig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class GalleryAddEvent {
|
sealed class GalleryAddEvent {
|
||||||
@ -224,9 +122,8 @@ sealed class GalleryAddEvent {
|
|||||||
|
|
||||||
class Success(override val galleryUrl: String,
|
class Success(override val galleryUrl: String,
|
||||||
val manga: Manga): GalleryAddEvent() {
|
val manga: Manga): GalleryAddEvent() {
|
||||||
|
override val galleryTitle = manga.title
|
||||||
override val logMessage = "Added gallery: $galleryTitle"
|
override val logMessage = "Added gallery: $galleryTitle"
|
||||||
override val galleryTitle: String
|
|
||||||
get() = manga.title
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Fail: GalleryAddEvent() {
|
sealed class Fail: GalleryAddEvent() {
|
||||||
|
@ -350,7 +350,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
//Import using gallery adder
|
//Import using gallery adder
|
||||||
val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}",
|
val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}",
|
||||||
true,
|
true,
|
||||||
EXH_SOURCE_ID,
|
exh,
|
||||||
throttleManager::throttle)
|
throttleManager::throttle)
|
||||||
|
|
||||||
if(result is GalleryAddEvent.Fail) {
|
if(result is GalleryAddEvent.Fail) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package exh.util
|
package exh.util
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import exh.GalleryAddEvent
|
import exh.GalleryAddEvent
|
||||||
import exh.GalleryAdder
|
import exh.GalleryAdder
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -13,11 +13,11 @@ private val galleryAdder by lazy {
|
|||||||
/**
|
/**
|
||||||
* A version of fetchSearchManga that supports URL importing
|
* A version of fetchSearchManga that supports URL importing
|
||||||
*/
|
*/
|
||||||
fun Source.urlImportFetchSearchManga(query: String, fail: () -> Observable<MangasPage>) =
|
fun UrlImportableSource.urlImportFetchSearchManga(query: String, fail: () -> Observable<MangasPage>) =
|
||||||
when {
|
when {
|
||||||
query.startsWith("http://") || query.startsWith("https://") -> {
|
query.startsWith("http://") || query.startsWith("https://") -> {
|
||||||
Observable.fromCallable {
|
Observable.fromCallable {
|
||||||
val res = galleryAdder.addGallery(query, false, id)
|
val res = galleryAdder.addGallery(query, false, this)
|
||||||
MangasPage((if(res is GalleryAddEvent.Success)
|
MangasPage((if(res is GalleryAddEvent.Success)
|
||||||
listOf(res.manga)
|
listOf(res.manga)
|
||||||
else
|
else
|
||||||
|
Loading…
x
Reference in New Issue
Block a user