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 { ext {
extName = 'Hitomi' extName = 'Hitomi'
extClass = '.HitomiFactory' extClass = '.HitomiFactory'
extVersionCode = 29 extVersionCode = 30
isNsfw = true isNsfw = true
} }

View File

@ -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"
}
} }

View File

@ -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()
} }