mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Migrate to new URL import system
This commit is contained in:
		@@ -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.net.Uri
 | 
			
		||||
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.preference.PreferencesHelper
 | 
			
		||||
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.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LewdSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import exh.eh.EHentaiUpdateHelper
 | 
			
		||||
import exh.metadata.EX_DATE_FORMAT
 | 
			
		||||
@@ -46,7 +51,7 @@ import java.lang.RuntimeException
 | 
			
		||||
// TODO Consider gallery updating when doing tabbed browsing
 | 
			
		||||
class EHentai(override val id: Long,
 | 
			
		||||
              val exh: Boolean,
 | 
			
		||||
              val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document> {
 | 
			
		||||
              val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document>, UrlImportableSource {
 | 
			
		||||
    override val metaClass = EHentaiSearchMetadata::class
 | 
			
		||||
 | 
			
		||||
    val schema: String
 | 
			
		||||
@@ -636,11 +641,67 @@ class EHentai(override val id: Long,
 | 
			
		||||
 | 
			
		||||
    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 {
 | 
			
		||||
        private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
 | 
			
		||||
        private const val TR_SUFFIX = "TR"
 | 
			
		||||
        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(
 | 
			
		||||
                "000",
 | 
			
		||||
                "f00",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package eu.kanade.tachiyomi.source.online.all
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import com.github.salomonbrys.kotson.array
 | 
			
		||||
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.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LewdSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import exh.GalleryAddEvent
 | 
			
		||||
import exh.HITOMI_SOURCE_ID
 | 
			
		||||
import exh.hitomi.HitomiNozomi
 | 
			
		||||
import exh.metadata.metadata.HitomiSearchMetadata
 | 
			
		||||
@@ -36,7 +39,7 @@ import java.util.*
 | 
			
		||||
/**
 | 
			
		||||
 * 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 jsonParser by lazy { JsonParser() }
 | 
			
		||||
 | 
			
		||||
@@ -390,6 +393,19 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document> {
 | 
			
		||||
                .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 {
 | 
			
		||||
        private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10
 | 
			
		||||
        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.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LewdSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import exh.GalleryAddEvent
 | 
			
		||||
import exh.NHENTAI_SOURCE_ID
 | 
			
		||||
import exh.metadata.metadata.NHentaiSearchMetadata
 | 
			
		||||
import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT
 | 
			
		||||
@@ -27,7 +29,7 @@ import rx.Observable
 | 
			
		||||
 * 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 fun fetchPopularManga(page: Int): Observable<MangasPage> {
 | 
			
		||||
@@ -127,9 +129,6 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
 | 
			
		||||
    override fun mangaDetailsRequest(manga: SManga)
 | 
			
		||||
            = nhGet(baseUrl + manga.url)
 | 
			
		||||
 | 
			
		||||
    fun urlToDetailsRequest(url: String)
 | 
			
		||||
            = nhGet(baseUrl + "/api/gallery/" + url.split("/").last { it.isNotBlank() })
 | 
			
		||||
 | 
			
		||||
    fun parseResultPage(response: Response): MangasPage {
 | 
			
		||||
        val doc = response.asJsoup()
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +249,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
            .newBuilder()
 | 
			
		||||
@@ -260,7 +259,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
 | 
			
		||||
                            "Chrome/56.0.2924.87 " +
 | 
			
		||||
                            "Safari/537.36 " +
 | 
			
		||||
                            "$appName/${BuildConfig.VERSION_CODE}")
 | 
			
		||||
            .tag(tag).build()!!
 | 
			
		||||
            .tag(tag).build()
 | 
			
		||||
 | 
			
		||||
    override val id = NHENTAI_SOURCE_ID
 | 
			
		||||
 | 
			
		||||
@@ -272,6 +271,19 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
 | 
			
		||||
 | 
			
		||||
    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 {
 | 
			
		||||
        private val GALLERY_JSON_REGEX = Regex("new N.gallery\\((.*)\\);")
 | 
			
		||||
        private const val REVERSE_PARAM = "TEH_REVERSE"
 | 
			
		||||
@@ -282,9 +294,4 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
 | 
			
		||||
            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.online.LewdSource
 | 
			
		||||
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.asJsoup
 | 
			
		||||
import exh.metadata.metadata.PervEdenLang
 | 
			
		||||
@@ -27,7 +28,7 @@ import java.util.*
 | 
			
		||||
 | 
			
		||||
// TODO Transform into delegated source
 | 
			
		||||
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
 | 
			
		||||
     */
 | 
			
		||||
@@ -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 {
 | 
			
		||||
        val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
 | 
			
		||||
            timeZone = TimeZone.getTimeZone("GMT")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
package eu.kanade.tachiyomi.source.online.english
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import eu.kanade.tachiyomi.network.asObservableSuccess
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SChapter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LewdSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import exh.metadata.metadata.HentaiCafeSearchMetadata
 | 
			
		||||
import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT
 | 
			
		||||
@@ -18,7 +20,7 @@ import org.jsoup.nodes.Document
 | 
			
		||||
import rx.Observable
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
     */
 | 
			
		||||
@@ -88,4 +90,17 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
 | 
			
		||||
                }
 | 
			
		||||
        )
 | 
			
		||||
    }.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.online.LewdSource
 | 
			
		||||
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.toast
 | 
			
		||||
import exh.GalleryAddEvent
 | 
			
		||||
import exh.TSUMINO_SOURCE_ID
 | 
			
		||||
import exh.ui.captcha.ActionCompletionVerifier
 | 
			
		||||
import exh.ui.captcha.BrowserActionActivity
 | 
			
		||||
@@ -33,7 +35,10 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    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 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 {
 | 
			
		||||
        val jsonParser by lazy {
 | 
			
		||||
            JsonParser()
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,13 @@ package exh
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
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.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
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.util.syncChaptersWithSource
 | 
			
		||||
import exh.metadata.metadata.EHentaiSearchMetadata
 | 
			
		||||
import okhttp3.MediaType
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.RequestBody
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.net.URI
 | 
			
		||||
import java.net.URISyntaxException
 | 
			
		||||
 | 
			
		||||
class GalleryAdder {
 | 
			
		||||
 | 
			
		||||
@@ -26,133 +16,55 @@ class GalleryAdder {
 | 
			
		||||
 | 
			
		||||
    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,
 | 
			
		||||
                   fav: Boolean = false,
 | 
			
		||||
                   forceSource: Long? = null,
 | 
			
		||||
                   forceSource: UrlImportableSource? = null,
 | 
			
		||||
                   throttleFunc: () -> Unit = {}): GalleryAddEvent {
 | 
			
		||||
        XLog.d("Importing gallery (url: %s, fav: %s, forceSource: %s)...", url, fav, forceSource)
 | 
			
		||||
        try {
 | 
			
		||||
            val urlObj = Uri.parse(url)
 | 
			
		||||
            val lowercasePs = urlObj.pathSegments.map(String::toLowerCase)
 | 
			
		||||
            val lcFirstPathSegment = lowercasePs[0]
 | 
			
		||||
            val source = when (urlObj.host.toLowerCase()) {
 | 
			
		||||
                "g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
 | 
			
		||||
                "exhentai.org" -> EXH_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)
 | 
			
		||||
                    }
 | 
			
		||||
            val uri = Uri.parse(url)
 | 
			
		||||
 | 
			
		||||
            // Find matching source
 | 
			
		||||
            val source = if(forceSource != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    if (forceSource.matchesUri(uri)) forceSource
 | 
			
		||||
                    else return GalleryAddEvent.Fail.UnknownType(url)
 | 
			
		||||
                } catch(e: Exception) {
 | 
			
		||||
                    XLog.e("Source URI match check error!", e)
 | 
			
		||||
                    return GalleryAddEvent.Fail.UnknownType(url)
 | 
			
		||||
                }
 | 
			
		||||
                "hentai.cafe" -> HENTAI_CAFE_SOURCE_ID
 | 
			
		||||
                "www.tsumino.com" -> TSUMINO_SOURCE_ID
 | 
			
		||||
                "hitomi.la" -> HITOMI_SOURCE_ID
 | 
			
		||||
                else -> return GalleryAddEvent.Fail.UnknownType(url)
 | 
			
		||||
            } else {
 | 
			
		||||
                sourceManager.getVisibleCatalogueSources()
 | 
			
		||||
                        .filterIsInstance<UrlImportableSource>()
 | 
			
		||||
                        .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) {
 | 
			
		||||
                return GalleryAddEvent.Fail.UnknownType(url)
 | 
			
		||||
            }
 | 
			
		||||
            // Map URL to manga 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)
 | 
			
		||||
                    ?: return GalleryAddEvent.Fail.Error(url, "Source not installed!")
 | 
			
		||||
 | 
			
		||||
            val realUrl = when(source) {
 | 
			
		||||
                EH_SOURCE_ID, EXH_SOURCE_ID -> when (lcFirstPathSegment) {
 | 
			
		||||
                    "g" -> {
 | 
			
		||||
                        //Is already gallery page, do nothing
 | 
			
		||||
                        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)
 | 
			
		||||
            }
 | 
			
		||||
            // Clean URL
 | 
			
		||||
            val cleanedUrl = try {
 | 
			
		||||
                source.cleanMangaUrl(realUrl)
 | 
			
		||||
            } catch(e: Exception) {
 | 
			
		||||
                XLog.e("Source URI clean error!", e)
 | 
			
		||||
                null
 | 
			
		||||
            } ?: return GalleryAddEvent.Fail.UnknownType(url)
 | 
			
		||||
 | 
			
		||||
            //Use manga in DB if possible, otherwise, make a new manga
 | 
			
		||||
            val manga = db.getManga(cleanedUrl, source).executeAsBlocking()
 | 
			
		||||
                    ?: Manga.create(source).apply {
 | 
			
		||||
            val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking()
 | 
			
		||||
                    ?: Manga.create(source.id).apply {
 | 
			
		||||
                this.url = cleanedUrl
 | 
			
		||||
                title = realUrl
 | 
			
		||||
            }
 | 
			
		||||
@@ -166,7 +78,7 @@ class GalleryAdder {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Fetch and copy details
 | 
			
		||||
            val newManga = sourceObj.fetchMangaDetails(manga).toBlocking().first()
 | 
			
		||||
            val newManga = source.fetchMangaDetails(manga).toBlocking().first()
 | 
			
		||||
            manga.copyFrom(newManga)
 | 
			
		||||
            manga.initialized = true
 | 
			
		||||
 | 
			
		||||
@@ -176,13 +88,13 @@ class GalleryAdder {
 | 
			
		||||
 | 
			
		||||
            //Fetch and copy chapters
 | 
			
		||||
            try {
 | 
			
		||||
                val chapterListObs = if(sourceObj is EHentai) {
 | 
			
		||||
                    sourceObj.fetchChapterList(manga, throttleFunc)
 | 
			
		||||
                val chapterListObs = if(source is EHentai) {
 | 
			
		||||
                    source.fetchChapterList(manga, throttleFunc)
 | 
			
		||||
                } else {
 | 
			
		||||
                    sourceObj.fetchChapterList(manga)
 | 
			
		||||
                    source.fetchChapterList(manga)
 | 
			
		||||
                }
 | 
			
		||||
                chapterListObs.map {
 | 
			
		||||
                    syncChaptersWithSource(db, it, manga, sourceObj)
 | 
			
		||||
                    syncChaptersWithSource(db, it, manga, source)
 | 
			
		||||
                }.toBlocking().first()
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                XLog.w("Failed to update chapters for gallery: ${manga.title}!", e)
 | 
			
		||||
@@ -201,20 +113,6 @@ class GalleryAdder {
 | 
			
		||||
                    ((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 {
 | 
			
		||||
@@ -224,9 +122,8 @@ sealed class GalleryAddEvent {
 | 
			
		||||
 | 
			
		||||
    class Success(override val galleryUrl: String,
 | 
			
		||||
                  val manga: Manga): GalleryAddEvent() {
 | 
			
		||||
        override val galleryTitle = manga.title
 | 
			
		||||
        override val logMessage = "Added gallery: $galleryTitle"
 | 
			
		||||
        override val galleryTitle: String
 | 
			
		||||
            get() = manga.title
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sealed class Fail: GalleryAddEvent() {
 | 
			
		||||
 
 | 
			
		||||
@@ -350,7 +350,7 @@ class FavoritesSyncHelper(val context: Context) {
 | 
			
		||||
            //Import using gallery adder
 | 
			
		||||
            val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}",
 | 
			
		||||
                    true,
 | 
			
		||||
                    EXH_SOURCE_ID,
 | 
			
		||||
                    exh,
 | 
			
		||||
                    throttleManager::throttle)
 | 
			
		||||
 | 
			
		||||
            if(result is GalleryAddEvent.Fail) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package exh.util
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.MangasPage
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
 | 
			
		||||
import exh.GalleryAddEvent
 | 
			
		||||
import exh.GalleryAdder
 | 
			
		||||
import rx.Observable
 | 
			
		||||
@@ -13,11 +13,11 @@ private val galleryAdder by lazy {
 | 
			
		||||
/**
 | 
			
		||||
 * 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 {
 | 
			
		||||
            query.startsWith("http://") || query.startsWith("https://") -> {
 | 
			
		||||
                Observable.fromCallable {
 | 
			
		||||
                    val res = galleryAdder.addGallery(query, false, id)
 | 
			
		||||
                    val res = galleryAdder.addGallery(query, false, this)
 | 
			
		||||
                    MangasPage((if(res is GalleryAddEvent.Success)
 | 
			
		||||
                        listOf(res.manga)
 | 
			
		||||
                    else
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user