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:
AwkwardPeak7 2024-06-04 10:20:19 +05:00 committed by Draff
parent 61b0ab972d
commit 76fe2af9ca
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 64 additions and 69 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Hitomi'
extClass = '.HitomiFactory'
extVersionCode = 29
extVersionCode = 30
isNsfw = true
}

View File

@ -1,12 +1,7 @@
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.await
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -22,16 +17,16 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.MessageDigest
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.LinkedList
import java.util.Locale
@ -41,7 +36,7 @@ import kotlin.math.min
class Hitomi(
override val lang: String,
private val nozomiLang: String,
) : ConfigurableSource, HttpSource() {
) : HttpSource() {
override val name = "Hitomi"
@ -57,12 +52,6 @@ class Hitomi(
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()
.set("referer", "$baseUrl/")
.set("origin", baseUrl)
@ -119,7 +108,8 @@ class Hitomi(
.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(
@ -168,6 +158,10 @@ class Hitomi(
}
}
if (language != "all" && sortBy == Pair(null, "index")) {
terms += "language:$language"
}
val positiveTerms = LinkedList<String>()
val negativeTerms = LinkedList<String>()
@ -181,22 +175,35 @@ class Hitomi(
val positiveResults = positiveTerms.map {
async {
runCatching {
try {
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 {
async {
runCatching {
try {
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 {
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(sortBy.first, sortBy.second, language)
positiveTerms.isEmpty() || sortBy != Pair(null, "index")
-> getGalleryIDsFromNozomi(sortBy.first, sortBy.second, language)
else -> ArrayList()
}
@ -451,12 +458,18 @@ class Hitomi(
private suspend fun Collection<Int>.toMangaList() = coroutineScope {
map { id ->
async {
runCatching {
try {
client.newCall(GET("$ltnUrl/galleries/$id.js", headers))
.awaitSuccess()
.parseScriptAs<Gallery>()
.toSManga()
}.getOrNull()
} catch (e: IllegalArgumentException) {
if (e.message?.equals("HTTP error 404") == true) {
return@async null
} else {
throw e
}
}
}
}.awaitAll().filterNotNull()
}
@ -466,7 +479,7 @@ class Hitomi(
url = galleryurl
author = groups?.joinToString { it.formatted }
artist = artists?.joinToString { it.formatted }
genre = tags?.joinToString { it.getFormatted(iconified) }
genre = tags?.joinToString { it.formatted }
thumbnail_url = files.first().let {
val hash = it.hash
val imageId = imageIdFromHash(hash)
@ -479,7 +492,7 @@ class Hitomi(
append("Series: ", it, "\n")
}
characters?.joinToString { it.formatted }?.let {
append("Characters: ", it, "\n\n")
append("Characters: ", it, "\n")
}
append("Type: ", type, "\n")
append("Pages: ", files.size, "\n")
@ -504,26 +517,21 @@ class Hitomi(
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
override fun chapterListRequest(manga: SManga): Request {
val id = manga.url
.substringAfterLast("-")
.substringBefore(".")
return GET("$ltnUrl/galleries/$id.js#${manga.url}", headers)
}
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val gallery = response.parseScriptAs<Gallery>()
val mangaUrl = response.request.url.fragment!!
return listOf(
SChapter.create().apply {
name = "Chapter"
url = mangaUrl
url = gallery.galleryurl
scanlator = gallery.type
date_upload = runCatching {
date_upload = try {
dateFormat.parse(gallery.date.substringBeforeLast("-"))!!.time
}.getOrDefault(0L)
} catch (_: ParseException) {
0L
}
},
)
}
@ -542,6 +550,9 @@ class Hitomi(
override fun pageListParse(response: Response) = runBlocking {
val gallery = response.parseScriptAs<Gallery>()
val id = gallery.galleryurl
.substringAfterLast("-")
.substringBefore(".")
gallery.files.mapIndexed { idx, img ->
val hash = img.hash
@ -637,25 +648,9 @@ class Hitomi(
override fun popularMangaParse(response: Response) = 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 latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
companion object {
private const val PREF_TAG_GENDER_ICON = "pref_tag_gender_icon"
}
}

View File

@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class Gallery(
class Gallery(
val galleryurl: String,
val title: String,
val date: String,
@ -19,49 +19,49 @@ data class Gallery(
)
@Serializable
data class ImageFile(
class ImageFile(
val hash: String,
)
@Serializable
data class Tag(
val female: JsonPrimitive?,
val male: JsonPrimitive?,
val tag: String,
class Tag(
private val female: JsonPrimitive?,
private val male: JsonPrimitive?,
private val tag: String,
) {
fun getFormatted(iconified: Boolean) = if (female?.content == "1") {
tag.toCamelCase() + if (iconified) "" else " (Female)"
val formatted get() = if (female?.content == "1") {
tag.toCamelCase() + ""
} else if (male?.content == "1") {
tag.toCamelCase() + if (iconified) "" else " (Male)"
tag.toCamelCase() + ""
} else {
tag.toCamelCase()
}
}
@Serializable
data class Artist(
val artist: String,
class Artist(
private val artist: String,
) {
val formatted get() = artist.toCamelCase()
}
@Serializable
data class Group(
val group: String,
class Group(
private val group: String,
) {
val formatted get() = group.toCamelCase()
}
@Serializable
data class Character(
val character: String,
class Character(
private val character: String,
) {
val formatted get() = character.toCamelCase()
}
@Serializable
data class Parody(
val parody: String,
class Parody(
private val parody: String,
) {
val formatted get() = parody.toCamelCase()
}