Delegate hitomi, it is now the first fully delegated factory source. To continue using hitomi please download the extension. This comes with a lot of fixes for future delegated factory sources
This commit is contained in:
parent
f8efe5d189
commit
9a3fdc23e6
@ -42,7 +42,7 @@ android {
|
||||
minSdkVersion AndroidConfig.minSdk
|
||||
targetSdkVersion AndroidConfig.targetSdk
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode 4
|
||||
versionCode 5
|
||||
versionName "1.1.1"
|
||||
|
||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||
|
@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.util.lang.launchNow
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.HITOMI_SOURCE_ID
|
||||
import exh.MERGED_SOURCE_ID
|
||||
import exh.NHENTAI_SOURCE_ID
|
||||
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||
@ -85,7 +84,6 @@ class ExtensionManager(
|
||||
PERV_EDEN_EN_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source)
|
||||
PERV_EDEN_IT_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source)
|
||||
NHENTAI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_nhentai_source)
|
||||
HITOMI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_hitomi_source)
|
||||
MERGED_SOURCE_ID -> context.getDrawable(R.mipmap.ic_merged_source)
|
||||
else -> null
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ open class SourceManager(private val context: Context) {
|
||||
source,
|
||||
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context)
|
||||
)
|
||||
val map = listOf(DelegatedSource(enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory)).associateBy { it.originalSourceQualifiedClassName }
|
||||
val map = listOf(DelegatedSource(enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory)).associateBy { it.sourceId }
|
||||
currentDelegatedSources.plusAssign(map)
|
||||
enhancedSource
|
||||
} else source
|
||||
@ -139,7 +139,6 @@ open class SourceManager(private val context: Context) {
|
||||
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, PervEdenLang.en, context)
|
||||
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it, context)
|
||||
exSrcs += NHentai(context)
|
||||
exSrcs += Hitomi(context)
|
||||
return exSrcs
|
||||
}
|
||||
// SY <--
|
||||
@ -210,10 +209,17 @@ open class SourceManager(private val context: Context) {
|
||||
1802675169972965535,
|
||||
"eu.kanade.tachiyomi.extension.all.eromuse.EroMuse",
|
||||
EightMuses::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"Hitomi",
|
||||
fillInSourceId,
|
||||
"eu.kanade.tachiyomi.extension.all.hitomi.Hitomi",
|
||||
Hitomi::class,
|
||||
true
|
||||
)
|
||||
).associateBy { it.originalSourceQualifiedClassName }
|
||||
|
||||
var currentDelegatedSources = mutableMapOf<String, DelegatedSource>()
|
||||
var currentDelegatedSources = mutableMapOf<Long, DelegatedSource>()
|
||||
|
||||
data class DelegatedSource(
|
||||
val sourceName: String,
|
||||
|
@ -3,100 +3,49 @@ package eu.kanade.tachiyomi.source.online.all
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import com.github.salomonbrys.kotson.array
|
||||
import com.github.salomonbrys.kotson.get
|
||||
import com.github.salomonbrys.kotson.string
|
||||
import com.google.gson.JsonParser
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
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.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import exh.HITOMI_SOURCE_ID
|
||||
import exh.hitomi.HitomiNozomi
|
||||
import exh.metadata.metadata.HitomiSearchMetadata
|
||||
import exh.metadata.metadata.HitomiSearchMetadata.Companion.BASE_URL
|
||||
import exh.metadata.metadata.HitomiSearchMetadata.Companion.LTN_BASE_URL
|
||||
import exh.metadata.metadata.HitomiSearchMetadata.Companion.TAG_TYPE_DEFAULT
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.metadata.metadata.base.RaisedTag
|
||||
import exh.source.DelegatedHttpSource
|
||||
import exh.ui.metadata.adapters.HitomiDescriptionAdapter
|
||||
import exh.util.urlImportFetchSearchManga
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.vepta.vdm.ByteCursor
|
||||
import rx.Observable
|
||||
import rx.Single
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Man, I hate this source :(
|
||||
*/
|
||||
class Hitomi(val context: Context) : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImportableSource {
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
override val id = HITOMI_SOURCE_ID
|
||||
|
||||
/**
|
||||
* Whether the source has support for latest updates.
|
||||
*/
|
||||
override val supportsLatest = true
|
||||
/**
|
||||
* Name of the source.
|
||||
*/
|
||||
override val name = "hitomi.la"
|
||||
/**
|
||||
* The class of the metadata used by this source
|
||||
*/
|
||||
class Hitomi(delegate: HttpSource, val context: Context) :
|
||||
DelegatedHttpSource(delegate),
|
||||
LewdSource<HitomiSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
override val metaClass = HitomiSearchMetadata::class
|
||||
override val lang = if (delegate.lang == "other") "all" else delegate.lang
|
||||
override val id: Long
|
||||
get() = if (delegate.lang == "other") otherId else delegate.id
|
||||
|
||||
private var cachedTagIndexVersion: Long? = null
|
||||
private var tagIndexVersionCacheTime: Long = 0
|
||||
private fun tagIndexVersion(): Single<Long> {
|
||||
val sCachedTagIndexVersion = cachedTagIndexVersion
|
||||
return if (sCachedTagIndexVersion == null ||
|
||||
tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()
|
||||
) {
|
||||
HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||
cachedTagIndexVersion = it
|
||||
tagIndexVersionCacheTime = System.currentTimeMillis()
|
||||
}.toSingle()
|
||||
} else {
|
||||
Single.just(sCachedTagIndexVersion)
|
||||
// Support direct URL importing
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
||||
urlImportFetchSearchManga(context, query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||
}
|
||||
}
|
||||
|
||||
private var cachedGalleryIndexVersion: Long? = null
|
||||
private var galleryIndexVersionCacheTime: Long = 0
|
||||
private fun galleryIndexVersion(): Single<Long> {
|
||||
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
|
||||
return if (sCachedGalleryIndexVersion == null ||
|
||||
galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()
|
||||
) {
|
||||
HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||
cachedGalleryIndexVersion = it
|
||||
galleryIndexVersionCacheTime = System.currentTimeMillis()
|
||||
}.toSingle()
|
||||
} else {
|
||||
Single.just(sCachedGalleryIndexVersion)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the supplied input into the supplied metadata object
|
||||
*/
|
||||
override fun parseIntoMetadata(metadata: HitomiSearchMetadata, input: Document) {
|
||||
with(metadata) {
|
||||
url = input.location()
|
||||
@ -109,41 +58,49 @@ class Hitomi(val context: Context) : HttpSource(), LewdSource<HitomiSearchMetada
|
||||
|
||||
title = galleryElement.selectFirst("h1").text()
|
||||
artists = galleryElement.select("h2 a").map { it.text() }
|
||||
tags += artists.map { RaisedTag("artist", it, TAG_TYPE_VIRTUAL) }
|
||||
tags += artists.map { RaisedTag("artist", it, RaisedSearchMetadata.TAG_TYPE_VIRTUAL) }
|
||||
|
||||
input.select(".gallery-info tr").forEach {
|
||||
val content = it.child(1)
|
||||
when (it.child(0).text().toLowerCase()) {
|
||||
"group" -> {
|
||||
group = content.text()
|
||||
tags += RaisedTag("group", group!!, TAG_TYPE_VIRTUAL)
|
||||
tags += RaisedTag("group", group!!, RaisedSearchMetadata.TAG_TYPE_VIRTUAL)
|
||||
}
|
||||
"type" -> {
|
||||
type = content.text()
|
||||
tags += RaisedTag("type", type!!, TAG_TYPE_VIRTUAL)
|
||||
tags += RaisedTag("type", type!!, RaisedSearchMetadata.TAG_TYPE_VIRTUAL)
|
||||
}
|
||||
"series" -> {
|
||||
series = content.select("a").map { it.text() }
|
||||
tags += series.map {
|
||||
RaisedTag("series", it, TAG_TYPE_VIRTUAL)
|
||||
RaisedTag("series", it, RaisedSearchMetadata.TAG_TYPE_VIRTUAL)
|
||||
}
|
||||
}
|
||||
"language" -> {
|
||||
language = content.selectFirst("a")?.attr("href")?.split('-')?.get(1)
|
||||
language?.let {
|
||||
tags += RaisedTag("language", it, TAG_TYPE_VIRTUAL)
|
||||
tags += RaisedTag("language", it, RaisedSearchMetadata.TAG_TYPE_VIRTUAL)
|
||||
}
|
||||
}
|
||||
"characters" -> {
|
||||
characters = content.select("a").map { it.text() }
|
||||
tags += characters.map { RaisedTag("character", it, TAG_TYPE_DEFAULT) }
|
||||
tags += characters.map {
|
||||
RaisedTag(
|
||||
"character", it,
|
||||
HitomiSearchMetadata.TAG_TYPE_DEFAULT
|
||||
)
|
||||
}
|
||||
}
|
||||
"tags" -> {
|
||||
tags += content.select("a").map {
|
||||
val ns = if (it.attr("href").startsWith("/tag/male")) "male"
|
||||
else if (it.attr("href").startsWith("/tag/female")) "female"
|
||||
else "misc"
|
||||
RaisedTag(ns, it.text().dropLast(if (ns == "misc") 0 else 2), TAG_TYPE_DEFAULT)
|
||||
RaisedTag(
|
||||
ns, it.text().dropLast(if (ns == "misc") 0 else 2),
|
||||
HitomiSearchMetadata.TAG_TYPE_DEFAULT
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,273 +110,6 @@ class Hitomi(val context: Context) : HttpSource(), LewdSource<HitomiSearchMetada
|
||||
}
|
||||
}
|
||||
|
||||
override val lang = "all"
|
||||
|
||||
/**
|
||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||
*/
|
||||
override val baseUrl = BASE_URL
|
||||
|
||||
/**
|
||||
* Returns the request for the popular manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet(
|
||||
"$LTN_BASE_URL/popular-all.nozomi",
|
||||
100L * (page - 1),
|
||||
99L + 100 * (page - 1)
|
||||
)
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* Returns the request for the search manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return urlImportFetchSearchManga(context, query) {
|
||||
val splitQuery = query.split(" ")
|
||||
|
||||
val positive = splitQuery.filter { !it.startsWith('-') }.toMutableList()
|
||||
val negative = (splitQuery - positive).map { it.removePrefix("-") }
|
||||
|
||||
// TODO Cache the results coming out of HitomiNozomi
|
||||
val hn = Single.zip(tagIndexVersion(), galleryIndexVersion()) { tv, gv -> tv to gv }
|
||||
.map { HitomiNozomi(client, it.first, it.second) }
|
||||
|
||||
var base = if (positive.isEmpty()) {
|
||||
hn.flatMap { n -> n.getGalleryIdsFromNozomi(null, "index", "all").map { n to it.toSet() } }
|
||||
} else {
|
||||
val q = positive.removeAt(0)
|
||||
hn.flatMap { n -> n.getGalleryIdsForQuery(q).map { n to it.toSet() } }
|
||||
}
|
||||
|
||||
base = positive.fold(base) { acc, q ->
|
||||
acc.flatMap { (nozomi, mangas) ->
|
||||
nozomi.getGalleryIdsForQuery(q).map {
|
||||
nozomi to mangas.intersect(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base = negative.fold(base) { acc, q ->
|
||||
acc.flatMap { (nozomi, mangas) ->
|
||||
nozomi.getGalleryIdsForQuery(q).map {
|
||||
nozomi to (mangas - it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.flatMap { (_, ids) ->
|
||||
val chunks = ids.chunked(PAGE_SIZE)
|
||||
|
||||
nozomiIdsToMangas(chunks[page - 1]).map { mangas ->
|
||||
MangasPage(mangas, page < chunks.size)
|
||||
}
|
||||
}.toObservable()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* Returns the request for latest manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet(
|
||||
"$LTN_BASE_URL/index-all.nozomi",
|
||||
100L * (page - 1),
|
||||
99L + 100 * (page - 1)
|
||||
)
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
return client.newCall(popularMangaRequest(page))
|
||||
.asObservableSuccess()
|
||||
.flatMap { responseToMangas(it) }
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return client.newCall(latestUpdatesRequest(page))
|
||||
.asObservableSuccess()
|
||||
.flatMap { responseToMangas(it) }
|
||||
}
|
||||
|
||||
fun responseToMangas(response: Response): Observable<MangasPage> {
|
||||
val range = response.header("Content-Range")!!
|
||||
val total = range.substringAfter('/').toLong()
|
||||
val end = range.substringBefore('/').substringAfter('-').toLong()
|
||||
val body = response.body!!
|
||||
return parseNozomiPage(body.bytes())
|
||||
.map {
|
||||
MangasPage(it, end < total - 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseNozomiPage(array: ByteArray): Observable<List<SManga>> {
|
||||
val cursor = ByteCursor(array)
|
||||
val ids = (1..array.size / 4).map {
|
||||
cursor.nextInt()
|
||||
}
|
||||
|
||||
return nozomiIdsToMangas(ids).toObservable()
|
||||
}
|
||||
|
||||
private fun nozomiIdsToMangas(ids: List<Int>): Single<List<SManga>> {
|
||||
return Single.zip(
|
||||
ids.map {
|
||||
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
||||
.asObservableSuccess()
|
||||
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
|
||||
.map { parseGalleryBlock(it) }
|
||||
.toSingle()
|
||||
}
|
||||
) { it.map { m -> m as SManga } }
|
||||
}
|
||||
|
||||
private fun parseGalleryBlock(response: Response): SManga {
|
||||
val doc = response.asJsoup()
|
||||
return SManga.create().apply {
|
||||
val titleElement = doc.selectFirst("h1")
|
||||
title = titleElement.text()
|
||||
thumbnail_url = "https:" + if (prefs.eh_hl_useHighQualityThumbs().get()) {
|
||||
doc.selectFirst("img").attr("srcset").substringBefore(' ')
|
||||
} else {
|
||||
doc.selectFirst("img").attr("src")
|
||||
}
|
||||
url = titleElement.child(0).attr("href")
|
||||
|
||||
// TODO Parse tags and stuff
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(
|
||||
Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return Observable.just(
|
||||
listOf(
|
||||
SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Chapter"
|
||||
chapter_number = 0.0f
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
return GET("$LTN_BASE_URL/galleries/${HitomiSearchMetadata.hlIdFromUrl(chapter.url)}.js")
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the details of a manga.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of chapters.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of pages.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val str = response.body!!.string()
|
||||
val json = JsonParser.parseString(str.removePrefix("var galleryinfo = "))
|
||||
return json["files"].array.mapIndexed { index, jsonElement ->
|
||||
val hash = jsonElement["hash"].string
|
||||
val ext = if (jsonElement["haswebp"].string == "0" || !prefs.hitomiAlwaysWebp().get()) jsonElement["name"].string.split('.').last() else "webp"
|
||||
val path = if (jsonElement["haswebp"].string == "0" || !prefs.hitomiAlwaysWebp().get()) "images" else "webp"
|
||||
val hashPath1 = hash.takeLast(1)
|
||||
val hashPath2 = hash.takeLast(3).take(2)
|
||||
Page(
|
||||
index,
|
||||
"",
|
||||
"https://${subdomainFromGalleryId(hashPath2)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// https://ltn.hitomi.la/common.js
|
||||
private fun subdomainFromGalleryId(pathSegment: String): Char {
|
||||
var numberOfFrontends = 3
|
||||
val b = 16
|
||||
var g = Integer.parseInt(pathSegment, b)
|
||||
if (g < 0x30) {
|
||||
numberOfFrontends = 2
|
||||
}
|
||||
if (g < 0x09) {
|
||||
g = 1
|
||||
}
|
||||
|
||||
return (97 + g.rem(numberOfFrontends)).toChar()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the absolute url to the source image.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
val request = super.imageRequest(page)
|
||||
val hlId = request.url.pathSegments.let {
|
||||
it[it.lastIndex - 1]
|
||||
}
|
||||
return request.newBuilder()
|
||||
.header("Referer", "$BASE_URL/reader/$hlId.html")
|
||||
.build()
|
||||
}
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"hitomi.la"
|
||||
)
|
||||
@ -439,9 +129,7 @@ class Hitomi(val context: Context) : HttpSource(), LewdSource<HitomiSearchMetada
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10
|
||||
private val PAGE_SIZE = 25
|
||||
|
||||
const val otherId = 2703068117101782422L
|
||||
private val DATE_FORMAT by lazy {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.US)
|
||||
|
@ -44,7 +44,6 @@ import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EXHMigrations
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.HITOMI_SOURCE_ID
|
||||
import exh.NHENTAI_SOURCE_ID
|
||||
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||
@ -221,9 +220,6 @@ class MainActivity : BaseActivity<MainActivityBinding>() {
|
||||
if (NHENTAI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES += NHENTAI_SOURCE_ID
|
||||
}
|
||||
if (HITOMI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES += HITOMI_SOURCE_ID
|
||||
}
|
||||
}
|
||||
// SY -->
|
||||
|
||||
|
@ -38,7 +38,6 @@ import eu.kanade.tachiyomi.util.system.powerManager
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.HITOMI_SOURCE_ID
|
||||
import exh.NHENTAI_SOURCE_ID
|
||||
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||
@ -189,9 +188,6 @@ class SettingsAdvancedController : SettingsController() {
|
||||
if (NHENTAI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES += NHENTAI_SOURCE_ID
|
||||
}
|
||||
if (HITOMI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES += HITOMI_SOURCE_ID
|
||||
}
|
||||
} else {
|
||||
if (EH_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES -= EH_SOURCE_ID
|
||||
@ -208,9 +204,6 @@ class SettingsAdvancedController : SettingsController() {
|
||||
if (NHENTAI_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES -= NHENTAI_SOURCE_ID
|
||||
}
|
||||
if (HITOMI_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) {
|
||||
BlacklistedSources.HIDDEN_SOURCES -= HITOMI_SOURCE_ID
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package exh
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.all.Hitomi
|
||||
import eu.kanade.tachiyomi.source.online.english.EightMuses
|
||||
import eu.kanade.tachiyomi.source.online.english.HBrowse
|
||||
import eu.kanade.tachiyomi.source.online.english.HentaiCafe
|
||||
@ -22,7 +23,7 @@ const val NHENTAI_SOURCE_ID = LEWD_SOURCE_SERIES + 7
|
||||
val HENTAI_CAFE_SOURCE_ID = delegatedSourceId<HentaiCafe>()
|
||||
val PURURIN_SOURCE_ID = delegatedSourceId<Pururin>()
|
||||
val TSUMINO_SOURCE_ID = delegatedSourceId<Tsumino>()
|
||||
const val HITOMI_SOURCE_ID = LEWD_SOURCE_SERIES + 10
|
||||
const val HITOMI_OLD_SOURCE_ID = LEWD_SOURCE_SERIES + 10
|
||||
val EIGHTMUSES_SOURCE_ID = delegatedSourceId<EightMuses>()
|
||||
val HBROWSE_SOURCE_ID = delegatedSourceId<HBrowse>()
|
||||
const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69
|
||||
@ -32,18 +33,11 @@ private val DELEGATED_LEWD_SOURCES = listOf(
|
||||
Pururin::class,
|
||||
Tsumino::class,
|
||||
HBrowse::class,
|
||||
EightMuses::class
|
||||
EightMuses::class,
|
||||
Hitomi::class
|
||||
)
|
||||
|
||||
val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf(
|
||||
EH_SOURCE_ID,
|
||||
EXH_SOURCE_ID,
|
||||
NHENTAI_SOURCE_ID,
|
||||
HENTAI_CAFE_SOURCE_ID,
|
||||
TSUMINO_SOURCE_ID,
|
||||
HITOMI_SOURCE_ID,
|
||||
PURURIN_SOURCE_ID
|
||||
)
|
||||
private val hitomiClass = listOf(Hitomi::class)
|
||||
|
||||
private inline fun <reified T> delegatedSourceId(): Long? {
|
||||
return SourceManager.DELEGATED_SOURCES.entries.find {
|
||||
@ -53,13 +47,27 @@ private inline fun <reified T> delegatedSourceId(): Long? {
|
||||
|
||||
// Used to speed up isLewdSource
|
||||
val lewdDelegatedSourceIds = SourceManager.currentDelegatedSources.filter {
|
||||
!it.value.factory && it.value.newSourceClass in DELEGATED_LEWD_SOURCES
|
||||
it.value.newSourceClass in DELEGATED_LEWD_SOURCES
|
||||
}.map { it.value.sourceId }.sorted()
|
||||
|
||||
val hitomiSourceIds = SourceManager.currentDelegatedSources.filter {
|
||||
it.value.newSourceClass in hitomiClass
|
||||
}.map { it.value.sourceId }.sorted()
|
||||
|
||||
// This method MUST be fast!
|
||||
fun isLewdSource(source: Long) = source in 6900..6999 ||
|
||||
lewdDelegatedSourceIds.binarySearch(source) >= 0
|
||||
|
||||
val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf(
|
||||
EH_SOURCE_ID,
|
||||
EXH_SOURCE_ID,
|
||||
NHENTAI_SOURCE_ID,
|
||||
HENTAI_CAFE_SOURCE_ID,
|
||||
TSUMINO_SOURCE_ID,
|
||||
PURURIN_SOURCE_ID,
|
||||
*hitomiSourceIds.toTypedArray()
|
||||
)
|
||||
|
||||
fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID
|
||||
|
||||
fun Source.isNamespaceSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID || id == NHENTAI_SOURCE_ID || id == HITOMI_SOURCE_ID || id == PURURIN_SOURCE_ID || id == TSUMINO_SOURCE_ID || id == EIGHTMUSES_SOURCE_ID || id == HBROWSE_SOURCE_ID
|
||||
fun Source.isNamespaceSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID || id == NHENTAI_SOURCE_ID || id in hitomiSourceIds || id == PURURIN_SOURCE_ID || id == TSUMINO_SOURCE_ID || id == EIGHTMUSES_SOURCE_ID || id == HBROWSE_SOURCE_ID
|
||||
|
@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||
import eu.kanade.tachiyomi.source.online.all.Hitomi
|
||||
import exh.source.BlacklistedSources
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
@ -85,6 +86,23 @@ object EXHMigrations {
|
||||
.executeAsBlocking()
|
||||
}
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
db.inTransaction {
|
||||
// Migrate Tsumino source IDs
|
||||
db.lowLevel().executeSQL(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
UPDATE ${MangaTable.TABLE}
|
||||
SET ${MangaTable.COL_SOURCE} = ${Hitomi.otherId}
|
||||
WHERE ${MangaTable.COL_SOURCE} = 6910
|
||||
""".trimIndent()
|
||||
)
|
||||
.affectsTables(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// if (oldVersion < 1) { }
|
||||
// do stuff here when releasing changed crap
|
||||
|
@ -41,7 +41,7 @@ object DebugFunctions {
|
||||
val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
fun forceUpgradeMigration() {
|
||||
prefs.eh_lastVersionCode().set(0)
|
||||
prefs.eh_lastVersionCode().set(1)
|
||||
EXHMigrations.upgrade(prefs)
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ object DebugFunctions {
|
||||
}
|
||||
private val throttleManager = EHentaiThrottleManager()
|
||||
|
||||
fun getDelegatedSourceList(): String = currentDelegatedSources.map { it.value.sourceName }.joinToString(separator = "\n")
|
||||
fun getDelegatedSourceList(): String = currentDelegatedSources.map { it.value.sourceName + " : " + it.value.sourceId + " : " + it.value.factory }.joinToString(separator = "\n")
|
||||
|
||||
fun resetEHGalleriesForUpdater() {
|
||||
throttleManager.resetThrottle()
|
||||
|
@ -239,9 +239,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
||||
override fun getFilterList() = delegate.getFilterList()
|
||||
|
||||
private fun ensureDelegateCompatible() {
|
||||
if (versionId != delegate.versionId ||
|
||||
lang != delegate.lang
|
||||
) {
|
||||
if ((versionId != delegate.versionId || lang != delegate.lang) && id != delegate.id) {
|
||||
throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!")
|
||||
}
|
||||
}
|
||||
|
@ -3,20 +3,20 @@ package exh.util
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.HITOMI_SOURCE_ID
|
||||
import exh.NHENTAI_SOURCE_ID
|
||||
import exh.PURURIN_SOURCE_ID
|
||||
import exh.TSUMINO_SOURCE_ID
|
||||
import exh.hitomiSourceIds
|
||||
import exh.metadata.metadata.base.RaisedTag
|
||||
import java.util.Locale
|
||||
|
||||
class SourceTagsUtil {
|
||||
fun getWrappedTag(sourceId: Long, namespace: String? = null, tag: String? = null, fullTag: String? = null): String? {
|
||||
return if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId == NHENTAI_SOURCE_ID || sourceId == HITOMI_SOURCE_ID) {
|
||||
return if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId == NHENTAI_SOURCE_ID || sourceId in hitomiSourceIds) {
|
||||
val parsed = if (fullTag != null) parseTag(fullTag) else if (namespace != null && tag != null) RaisedTag(namespace, tag, TAG_TYPE_DEFAULT) else null
|
||||
if (parsed?.namespace != null) {
|
||||
when (sourceId) {
|
||||
HITOMI_SOURCE_ID -> wrapTagHitomi(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
in hitomiSourceIds -> wrapTagHitomi(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
NHENTAI_SOURCE_ID -> wrapTagNHentai(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
PURURIN_SOURCE_ID -> parsed.name.substringBefore('|').trim()
|
||||
TSUMINO_SOURCE_ID -> parsed.name.substringBefore('|').trim()
|
||||
|
Loading…
x
Reference in New Issue
Block a user