Hitomi: small refactor (#3389)
* Hitomi: small refactor * bump * coauthor Co-authored-by: ZIDOUZI <53157536+ZIDOUZI@users.noreply.github.com> * fix potential oom and optimize language query when some sort is applied, it will already fetch correct language so no need for separate query --------- Co-authored-by: ZIDOUZI <53157536+ZIDOUZI@users.noreply.github.com>
This commit is contained in:
parent
61b0ab972d
commit
76fe2af9ca
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Hitomi'
|
extName = 'Hitomi'
|
||||||
extClass = '.HitomiFactory'
|
extClass = '.HitomiFactory'
|
||||||
extVersionCode = 29
|
extVersionCode = 30
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.hitomi
|
package eu.kanade.tachiyomi.extension.all.hitomi
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
@ -22,16 +17,16 @@ import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -41,7 +36,7 @@ import kotlin.math.min
|
||||||
class Hitomi(
|
class Hitomi(
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
private val nozomiLang: String,
|
private val nozomiLang: String,
|
||||||
) : ConfigurableSource, HttpSource() {
|
) : HttpSource() {
|
||||||
|
|
||||||
override val name = "Hitomi"
|
override val name = "Hitomi"
|
||||||
|
|
||||||
|
@ -57,12 +52,6 @@ class Hitomi(
|
||||||
|
|
||||||
override val client = network.cloudflareClient
|
override val client = network.cloudflareClient
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var iconified = preferences.getBoolean(PREF_TAG_GENDER_ICON, false)
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.set("referer", "$baseUrl/")
|
.set("referer", "$baseUrl/")
|
||||||
.set("origin", baseUrl)
|
.set("origin", baseUrl)
|
||||||
|
@ -119,7 +108,8 @@ class Hitomi(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.newCall(GET(url, rangeHeaders)).awaitSuccess().use { it.body.bytes() }
|
return client.newCall(GET(url, rangeHeaders, CacheControl.FORCE_NETWORK))
|
||||||
|
.awaitSuccess().use { it.body.bytes() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun hitomiSearch(
|
private suspend fun hitomiSearch(
|
||||||
|
@ -168,6 +158,10 @@ class Hitomi(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (language != "all" && sortBy == Pair(null, "index")) {
|
||||||
|
terms += "language:$language"
|
||||||
|
}
|
||||||
|
|
||||||
val positiveTerms = LinkedList<String>()
|
val positiveTerms = LinkedList<String>()
|
||||||
val negativeTerms = LinkedList<String>()
|
val negativeTerms = LinkedList<String>()
|
||||||
|
|
||||||
|
@ -181,22 +175,35 @@ class Hitomi(
|
||||||
|
|
||||||
val positiveResults = positiveTerms.map {
|
val positiveResults = positiveTerms.map {
|
||||||
async {
|
async {
|
||||||
runCatching {
|
try {
|
||||||
getGalleryIDsForQuery(it, language)
|
getGalleryIDsForQuery(it, language)
|
||||||
}.getOrDefault(ArrayList())
|
} catch (e: IllegalArgumentException) {
|
||||||
|
if (e.message?.equals("HTTP error 404") == true) {
|
||||||
|
throw Exception("Unknown query: \"$it\"")
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val negativeResults = negativeTerms.map {
|
val negativeResults = negativeTerms.map {
|
||||||
async {
|
async {
|
||||||
runCatching {
|
try {
|
||||||
getGalleryIDsForQuery(it, language)
|
getGalleryIDsForQuery(it, language)
|
||||||
}.getOrDefault(ArrayList())
|
} catch (e: IllegalArgumentException) {
|
||||||
|
if (e.message?.equals("HTTP error 404") == true) {
|
||||||
|
throw Exception("Unknown query: \"$it\"")
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val results = when {
|
val results = when {
|
||||||
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(sortBy.first, sortBy.second, language)
|
positiveTerms.isEmpty() || sortBy != Pair(null, "index")
|
||||||
|
-> getGalleryIDsFromNozomi(sortBy.first, sortBy.second, language)
|
||||||
else -> ArrayList()
|
else -> ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,12 +458,18 @@ class Hitomi(
|
||||||
private suspend fun Collection<Int>.toMangaList() = coroutineScope {
|
private suspend fun Collection<Int>.toMangaList() = coroutineScope {
|
||||||
map { id ->
|
map { id ->
|
||||||
async {
|
async {
|
||||||
runCatching {
|
try {
|
||||||
client.newCall(GET("$ltnUrl/galleries/$id.js", headers))
|
client.newCall(GET("$ltnUrl/galleries/$id.js", headers))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseScriptAs<Gallery>()
|
.parseScriptAs<Gallery>()
|
||||||
.toSManga()
|
.toSManga()
|
||||||
}.getOrNull()
|
} catch (e: IllegalArgumentException) {
|
||||||
|
if (e.message?.equals("HTTP error 404") == true) {
|
||||||
|
return@async null
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.awaitAll().filterNotNull()
|
}.awaitAll().filterNotNull()
|
||||||
}
|
}
|
||||||
|
@ -466,7 +479,7 @@ class Hitomi(
|
||||||
url = galleryurl
|
url = galleryurl
|
||||||
author = groups?.joinToString { it.formatted }
|
author = groups?.joinToString { it.formatted }
|
||||||
artist = artists?.joinToString { it.formatted }
|
artist = artists?.joinToString { it.formatted }
|
||||||
genre = tags?.joinToString { it.getFormatted(iconified) }
|
genre = tags?.joinToString { it.formatted }
|
||||||
thumbnail_url = files.first().let {
|
thumbnail_url = files.first().let {
|
||||||
val hash = it.hash
|
val hash = it.hash
|
||||||
val imageId = imageIdFromHash(hash)
|
val imageId = imageIdFromHash(hash)
|
||||||
|
@ -479,7 +492,7 @@ class Hitomi(
|
||||||
append("Series: ", it, "\n")
|
append("Series: ", it, "\n")
|
||||||
}
|
}
|
||||||
characters?.joinToString { it.formatted }?.let {
|
characters?.joinToString { it.formatted }?.let {
|
||||||
append("Characters: ", it, "\n\n")
|
append("Characters: ", it, "\n")
|
||||||
}
|
}
|
||||||
append("Type: ", type, "\n")
|
append("Type: ", type, "\n")
|
||||||
append("Pages: ", files.size, "\n")
|
append("Pages: ", files.size, "\n")
|
||||||
|
@ -504,26 +517,21 @@ class Hitomi(
|
||||||
|
|
||||||
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||||
val id = manga.url
|
|
||||||
.substringAfterLast("-")
|
|
||||||
.substringBefore(".")
|
|
||||||
|
|
||||||
return GET("$ltnUrl/galleries/$id.js#${manga.url}", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val gallery = response.parseScriptAs<Gallery>()
|
val gallery = response.parseScriptAs<Gallery>()
|
||||||
val mangaUrl = response.request.url.fragment!!
|
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
url = mangaUrl
|
url = gallery.galleryurl
|
||||||
scanlator = gallery.type
|
scanlator = gallery.type
|
||||||
date_upload = runCatching {
|
date_upload = try {
|
||||||
dateFormat.parse(gallery.date.substringBeforeLast("-"))!!.time
|
dateFormat.parse(gallery.date.substringBeforeLast("-"))!!.time
|
||||||
}.getOrDefault(0L)
|
} catch (_: ParseException) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -542,6 +550,9 @@ class Hitomi(
|
||||||
|
|
||||||
override fun pageListParse(response: Response) = runBlocking {
|
override fun pageListParse(response: Response) = runBlocking {
|
||||||
val gallery = response.parseScriptAs<Gallery>()
|
val gallery = response.parseScriptAs<Gallery>()
|
||||||
|
val id = gallery.galleryurl
|
||||||
|
.substringAfterLast("-")
|
||||||
|
.substringBefore(".")
|
||||||
|
|
||||||
gallery.files.mapIndexed { idx, img ->
|
gallery.files.mapIndexed { idx, img ->
|
||||||
val hash = img.hash
|
val hash = img.hash
|
||||||
|
@ -637,25 +648,9 @@ class Hitomi(
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
|
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = PREF_TAG_GENDER_ICON
|
|
||||||
title = "Show gender as text or icon in tags (requires refresh)"
|
|
||||||
summaryOff = "Show gender as text"
|
|
||||||
summaryOn = "Show gender as icon"
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
iconified = newValue == true
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
}
|
|
||||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
|
||||||
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
|
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
companion object {
|
|
||||||
private const val PREF_TAG_GENDER_ICON = "pref_tag_gender_icon"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Gallery(
|
class Gallery(
|
||||||
val galleryurl: String,
|
val galleryurl: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val date: String,
|
val date: String,
|
||||||
|
@ -19,49 +19,49 @@ data class Gallery(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ImageFile(
|
class ImageFile(
|
||||||
val hash: String,
|
val hash: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(
|
class Tag(
|
||||||
val female: JsonPrimitive?,
|
private val female: JsonPrimitive?,
|
||||||
val male: JsonPrimitive?,
|
private val male: JsonPrimitive?,
|
||||||
val tag: String,
|
private val tag: String,
|
||||||
) {
|
) {
|
||||||
fun getFormatted(iconified: Boolean) = if (female?.content == "1") {
|
val formatted get() = if (female?.content == "1") {
|
||||||
tag.toCamelCase() + if (iconified) " ♀" else " (Female)"
|
tag.toCamelCase() + " ♀"
|
||||||
} else if (male?.content == "1") {
|
} else if (male?.content == "1") {
|
||||||
tag.toCamelCase() + if (iconified) " ♂" else " (Male)"
|
tag.toCamelCase() + " ♂"
|
||||||
} else {
|
} else {
|
||||||
tag.toCamelCase()
|
tag.toCamelCase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Artist(
|
class Artist(
|
||||||
val artist: String,
|
private val artist: String,
|
||||||
) {
|
) {
|
||||||
val formatted get() = artist.toCamelCase()
|
val formatted get() = artist.toCamelCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Group(
|
class Group(
|
||||||
val group: String,
|
private val group: String,
|
||||||
) {
|
) {
|
||||||
val formatted get() = group.toCamelCase()
|
val formatted get() = group.toCamelCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Character(
|
class Character(
|
||||||
val character: String,
|
private val character: String,
|
||||||
) {
|
) {
|
||||||
val formatted get() = character.toCamelCase()
|
val formatted get() = character.toCamelCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Parody(
|
class Parody(
|
||||||
val parody: String,
|
private val parody: String,
|
||||||
) {
|
) {
|
||||||
val formatted get() = parody.toCamelCase()
|
val formatted get() = parody.toCamelCase()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue