Update hikari with minor fixes. Buzzheavier extractor less prone to failure. (#986)
* Tighter extraction * Bump version * More details * Removed unchecked cast
This commit is contained in:
parent
a4d3a117cf
commit
82a8b81de2
4 changed files with 71 additions and 22 deletions
|
@ -1,22 +1,27 @@
|
|||
package eu.kanade.tachiyomi.lib.buzzheavierextractor
|
||||
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.util.parseAs
|
||||
import java.io.IOException
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.internal.EMPTY_HEADERS
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
class BuzzheavierExtractor(
|
||||
private val client: OkHttpClient,
|
||||
private val headers: Headers,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val SIZE_REGEX = Regex("""Size\s*-\s*([0-9.]+\s*[GMK]B)""")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun videosFromUrl(url: String, prefix: String = "Buzzheavier - ", proxyUrl: String? = null): List<Video> {
|
||||
val httpUrl = url.toHttpUrl()
|
||||
|
@ -24,29 +29,52 @@ class BuzzheavierExtractor(
|
|||
|
||||
val dlHeaders = headers.newBuilder().apply {
|
||||
add("Accept", "*/*")
|
||||
add("Host", httpUrl.host)
|
||||
add("HX-Current-URL", url)
|
||||
add("HX-Request", "true")
|
||||
add("Priority", "u=1, i")
|
||||
add("Referer", url)
|
||||
}.build()
|
||||
|
||||
val videoHeaders = headers.newBuilder().apply {
|
||||
add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||
add("Priority", "u=0, i")
|
||||
add("Referer", url)
|
||||
}.build()
|
||||
|
||||
val path = client.newCall(
|
||||
GET("https://${httpUrl.host}/$id/download", dlHeaders)
|
||||
).execute().headers["hx-redirect"].orEmpty()
|
||||
val siteRequest = client.newCall(GET(url)).execute()
|
||||
val parsedHtml = siteRequest.asJsoup()
|
||||
val detailsText = parsedHtml.selectFirst("li:contains(Details:)")?.text() ?: ""
|
||||
val size = SIZE_REGEX.find(detailsText)?.groupValues?.getOrNull(1)?.trim() ?: "Unknown"
|
||||
|
||||
return if (path.isNotEmpty()) {
|
||||
val videoUrl = if (path.startsWith("http")) path else "https://${httpUrl.host}$path"
|
||||
listOf(Video(videoUrl, "${prefix}Video", videoUrl, videoHeaders))
|
||||
} else if (proxyUrl?.isNotEmpty() == true) {
|
||||
val videoUrl = client.newCall(GET(proxyUrl + id)).execute().parseAs<UrlDto>().url
|
||||
listOf(Video(videoUrl, "${prefix}Video", videoUrl, videoHeaders))
|
||||
} else {
|
||||
emptyList()
|
||||
val downloadRequest = GET("https://${httpUrl.host}/$id/download", dlHeaders)
|
||||
val path = client.executeWithRetry(downloadRequest, 5, 204).use { response ->
|
||||
response.header("hx-redirect").orEmpty()
|
||||
}
|
||||
|
||||
val videoUrl = if (path.isNotEmpty()) {
|
||||
if (path.startsWith("http")) path else "https://${httpUrl.host}$path"
|
||||
} else if (proxyUrl?.isNotEmpty() == true) {
|
||||
client.executeWithRetry(GET(proxyUrl + id), 5, 200).parseAs<UrlDto>().url
|
||||
} else {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return listOf(Video(videoUrl, "${prefix}${size}", videoUrl, videoHeaders))
|
||||
}
|
||||
|
||||
private fun OkHttpClient.executeWithRetry(request: Request, maxRetries: Int, validCode: Int): Response {
|
||||
var response: Response? = null
|
||||
for (attempt in 0 until maxRetries) {
|
||||
response?.close()
|
||||
response = this.newCall(request).execute()
|
||||
if (response.code == validCode) {
|
||||
return response
|
||||
}
|
||||
if (attempt < maxRetries - 1) {
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
}
|
||||
return response ?: throw IOException("Failed to execute request after $maxRetries attempts")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Hikari'
|
||||
extClass = '.Hikari'
|
||||
extVersionCode = 21
|
||||
extVersionCode = 22
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -29,24 +29,50 @@ data class AnimeDto(
|
|||
val aniGenre: String? = null,
|
||||
@SerialName("ani_studio")
|
||||
val aniStudio: String? = null,
|
||||
@SerialName("ani_producers")
|
||||
val aniProducers: String? = null,
|
||||
@SerialName("ani_stats")
|
||||
val aniStats: Int? = null,
|
||||
@SerialName("ani_time")
|
||||
val aniTime: String? = null,
|
||||
@SerialName("ani_ep")
|
||||
val aniEp: String? = null,
|
||||
@SerialName("ani_type")
|
||||
val aniType: Int? = null,
|
||||
@SerialName("ani_score")
|
||||
val aniScore: Double? = null,
|
||||
) {
|
||||
fun toSAnime(preferEnglish: Boolean): SAnime = SAnime.create().apply {
|
||||
url = uid
|
||||
title = if (preferEnglish) aniEName?.takeUnless(String::isBlank) ?: aniName else aniName
|
||||
thumbnail_url = aniPoster
|
||||
genre = aniGenre?.split(",")?.joinToString(transform = String::trim)
|
||||
author = aniStudio
|
||||
artist = aniStudio
|
||||
author = aniProducers?.split(",")?.joinToString(transform = String::trim)
|
||||
description = buildString {
|
||||
aniScore?.let { append("Score: %.2f/10\n\n".format(it)) }
|
||||
aniSynopsis?.trim()?.let(::append)
|
||||
append("\n\n")
|
||||
aniType?.let {
|
||||
val type = when (it) {
|
||||
1 -> "TV"
|
||||
2 -> "Movie"
|
||||
3 -> "OVA"
|
||||
4 -> "ONA"
|
||||
5 -> "Special"
|
||||
else -> "Unknown"
|
||||
}
|
||||
append("Type: $type\n")
|
||||
}
|
||||
aniEp?.let { append("Total Episode count: $it\n") }
|
||||
aniTime?.let { append("Runtime: $it\n") }
|
||||
aniSynonyms?.let { append("Synonyms: $it") }
|
||||
}.trim()
|
||||
|
||||
status = when (aniStats) {
|
||||
1 -> SAnime.ONGOING
|
||||
1 -> SAnime.UNKNOWN
|
||||
2 -> SAnime.COMPLETED
|
||||
3 -> SAnime.ONGOING
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,11 +294,6 @@ class Hikari : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||
entries = PREF_PROVIDERS
|
||||
entryValues = PREF_PROVIDERS_VALUE
|
||||
setDefaultValue(PREF_PROVIDERS_DEFAULT)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue