diff --git a/src/all/hitomi/build.gradle b/src/all/hitomi/build.gradle index 04020bcb5..8765224b6 100644 --- a/src/all/hitomi/build.gradle +++ b/src/all/hitomi/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Hitomi' extClass = '.HitomiFactory' - extVersionCode = 29 + extVersionCode = 30 isNsfw = true } diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt index 6aa09db12..bc12adbf1 100644 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt +++ b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt @@ -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().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() val negativeTerms = LinkedList() @@ -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.toMangaList() = coroutineScope { map { id -> async { - runCatching { + try { client.newCall(GET("$ltnUrl/galleries/$id.js", headers)) .awaitSuccess() .parseScriptAs() .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 { val gallery = response.parseScriptAs() - 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() + 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" - } } diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiDto.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiDto.kt index d60ba4178..5b45b254a 100644 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiDto.kt +++ b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiDto.kt @@ -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() }