mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-23 02:42:35 +01:00
Hitomi: Add Image Format Preferences (#4101)
* Add image filetype preferences * Update extVersionCode * Apply suggestions - Confirm jxl by using its signatures when passing through interceptor - & others * Update HitomiDto.kt * Fix * Fix - Apply Suggestion * Apply suggestion Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com> * Fix * Lint Fix? --------- Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
This commit is contained in:
parent
c6f6162a3b
commit
6da63a37fa
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Hitomi'
|
||||
extClass = '.HitomiFactory'
|
||||
extVersionCode = 31
|
||||
extVersionCode = 32
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,12 @@
|
||||
package eu.kanade.tachiyomi.extension.all.hitomi
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
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
|
||||
@ -19,9 +24,14 @@ import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Call
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
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
|
||||
@ -30,13 +40,14 @@ import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.LinkedList
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class Hitomi(
|
||||
override val lang: String,
|
||||
private val nozomiLang: String,
|
||||
) : HttpSource() {
|
||||
) : HttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "Hitomi"
|
||||
|
||||
@ -50,7 +61,14 @@ class Hitomi(
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override val client = network.cloudflareClient
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.addInterceptor(::Intercept)
|
||||
.build()
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
private fun imageType() = preferences.getString(PREF_IMAGETYPE, "webp")!!
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.set("referer", "$baseUrl/")
|
||||
@ -488,7 +506,7 @@ class Hitomi(
|
||||
private suspend fun Gallery.toSManga() = SManga.create().apply {
|
||||
title = this@toSManga.title
|
||||
url = galleryurl
|
||||
author = groups?.joinToString { it.formatted }
|
||||
author = groups?.joinToString { it.formatted } ?: artists?.joinToString { it.formatted }
|
||||
artist = artists?.joinToString { it.formatted }
|
||||
genre = tags?.joinToString { it.formatted }
|
||||
thumbnail_url = files.first().let {
|
||||
@ -567,14 +585,25 @@ class Hitomi(
|
||||
|
||||
gallery.files.mapIndexed { idx, img ->
|
||||
val hash = img.hash
|
||||
|
||||
val typePref = imageType()
|
||||
val avif = img.hasavif == 1 && typePref == "avif"
|
||||
val jxl = img.hasjxl == 1 && typePref == "jxl"
|
||||
|
||||
val commonId = commonImageId()
|
||||
val imageId = imageIdFromHash(hash)
|
||||
val subDomain = 'a' + subdomainOffset(imageId)
|
||||
|
||||
val imageUrl = when {
|
||||
jxl -> "https://${subDomain}a.$domain/jxl/$commonId$imageId/$hash.jxl"
|
||||
avif -> "https://${subDomain}a.$domain/avif/$commonId$imageId/$hash.avif"
|
||||
else -> "https://${subDomain}a.$domain/webp/$commonId$imageId/$hash.webp"
|
||||
}
|
||||
|
||||
Page(
|
||||
idx,
|
||||
"$baseUrl/reader/$id.html",
|
||||
"https://${subDomain}a.$domain/webp/$commonId$imageId/$hash.webp",
|
||||
imageUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -657,6 +686,45 @@ class Hitomi(
|
||||
return hash.replace(Regex("""^.*(..)(.)$"""), "$2/$1")
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_IMAGETYPE
|
||||
title = "Images Type"
|
||||
entries = arrayOf("webp", "avif", "jxl")
|
||||
entryValues = arrayOf("webp", "avif", "jxl")
|
||||
summary = "Clear chapter cache to apply changes"
|
||||
setDefaultValue("webp")
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
private fun List<Int>.toBytesList(): ByteArray = this.map { it.toByte() }.toByteArray()
|
||||
private val signatureOne = listOf(0xFF, 0x0A).toBytesList()
|
||||
private val signatureTwo = listOf(0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A).toBytesList()
|
||||
fun ByteArray.startsWith(byteArray: ByteArray): Boolean {
|
||||
if (this.size < byteArray.size) return false
|
||||
return this.sliceArray(byteArray.indices).contentEquals(byteArray)
|
||||
}
|
||||
|
||||
private fun Intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
if (response.headers["Content-Type"] != "application/octet-stream") {
|
||||
return response
|
||||
}
|
||||
|
||||
val bytesPeek = max(signatureOne.size, signatureTwo.size).toLong()
|
||||
val bytesArray = response.peekBody(bytesPeek).bytes()
|
||||
if (!(bytesArray.startsWith(signatureOne) || bytesArray.startsWith(signatureTwo))) {
|
||||
return response
|
||||
}
|
||||
|
||||
val type = "image/jxl"
|
||||
val body = response.body.bytes().toResponseBody(type.toMediaType())
|
||||
return response.newBuilder()
|
||||
.body(body)
|
||||
.header("Content-Type", type)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||
@ -664,4 +732,8 @@ class Hitomi(
|
||||
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 {
|
||||
const val PREF_IMAGETYPE = "pref_image_type"
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ class Gallery(
|
||||
@Serializable
|
||||
class ImageFile(
|
||||
val hash: String,
|
||||
val haswebp: Int,
|
||||
val hasavif: Int,
|
||||
val hasjxl: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
Loading…
Reference in New Issue
Block a user