Fix(lib/ChillxExtractor): Unify extraction logic, improve key fetching and subtitle handling (#169)

This commit is contained in:
Dark25 2024-08-23 18:24:01 +01:00 committed by GitHub
parent decac82e65
commit b41c963dda
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 59 deletions

View file

@ -12,24 +12,19 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.jsoup.Jsoup
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class ChillxExtractor(private val client: OkHttpClient, private val headers: Headers) { class ChillxExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val playlistUtils by lazy { PlaylistUtils(client, headers) } private val playlistUtils by lazy { PlaylistUtils(client, headers) }
companion object { companion object {
private val REGEX_MASTER_JS by lazy { Regex("""JScript[\w+]?\s*=\s*'([^']+)""") } private val REGEX_MASTER_JS by lazy { Regex("""\s*=\s*'([^']+)""") }
private val REGEX_EVAL_KEY by lazy { Regex("""eval\(\S+\("(\S+)",\d+,"(\S+)",(\d+),(\d+),""") }
private val REGEX_SOURCES by lazy { Regex("""sources:\s*\[\{"file":"([^"]+)""") } private val REGEX_SOURCES by lazy { Regex("""sources:\s*\[\{"file":"([^"]+)""") }
private val REGEX_FILE by lazy { Regex("""file: ?"([^"]+)"""") } private val REGEX_FILE by lazy { Regex("""file: ?"([^"]+)"""") }
private val REGEX_SOURCE by lazy { Regex("""source = ?"([^"]+)"""") } private val REGEX_SOURCE by lazy { Regex("""source = ?"([^"]+)"""") }
private val REGEX_SUBS by lazy { Regex("""\[(.*?)\](https?://[^\s,]+)""") }
// matches "[language]https://...," private const val KEY_SOURCE = "https://raw.githubusercontent.com/Rowdy-Avocado/multi-keys/keys/index.html"
private val REGEX_SUBS by lazy { Regex("""\[(.*?)\](.*?)"?\,""") }
private const val KEY_SOURCE = "https://rowdy-avocado.github.io/multi-keys/"
} }
fun videoFromUrl(url: String, referer: String, prefix: String = "Chillx - "): List<Video> { fun videoFromUrl(url: String, referer: String, prefix: String = "Chillx - "): List<Video> {
@ -42,7 +37,7 @@ class ChillxExtractor(private val client: OkHttpClient, private val headers: Hea
val master = REGEX_MASTER_JS.find(body)?.groupValues?.get(1) ?: return emptyList() val master = REGEX_MASTER_JS.find(body)?.groupValues?.get(1) ?: return emptyList()
val aesJson = json.decodeFromString<CryptoInfo>(master) val aesJson = json.decodeFromString<CryptoInfo>(master)
val key = fetchKey() val key = fetchKey() ?: throw ErrorLoadingException("Unable to get key")
val decryptedScript = decryptWithSalt(aesJson.ciphertext, aesJson.salt, key) val decryptedScript = decryptWithSalt(aesJson.ciphertext, aesJson.salt, key)
.replace("\\n", "\n") .replace("\\n", "\n")
.replace("\\", "") .replace("\\", "")
@ -52,30 +47,11 @@ class ChillxExtractor(private val client: OkHttpClient, private val headers: Hea
?: REGEX_SOURCE.find(decryptedScript)?.groupValues?.get(1) ?: REGEX_SOURCE.find(decryptedScript)?.groupValues?.get(1)
?: return emptyList() ?: return emptyList()
val subtitleList = buildList<Track> { val subtitleList = buildList {
body.takeIf { it.contains("<track kind=\"captions\"") } val subtitles = REGEX_SUBS.findAll(decryptedScript)
?.let(Jsoup::parse) subtitles.forEach {
?.select("track[kind=captions]") add(Track(it.groupValues[2], decodeUnicodeEscape(it.groupValues[1])))
?.forEach { }
add(Track(it.attr("src"), it.attr("label")))
}
decryptedScript.takeIf { it.contains("subtitle:") }
?.substringAfter("subtitle: ")
?.substringBefore("\n")
?.let(REGEX_SUBS::findAll)
?.forEach { add(Track(it.groupValues[2], it.groupValues[1])) }
decryptedScript.takeIf { it.contains("tracks:") }
?.substringAfter("tracks: ")
?.substringBefore("\n")
?.also {
runCatching {
json.decodeFromString<List<TrackDto>>(it)
.filter { it.kind == "captions" }
.forEach { add(Track(it.file, it.label)) }
}
}
} }
return playlistUtils.extractFromHls( return playlistUtils.extractFromHls(
@ -87,25 +63,15 @@ class ChillxExtractor(private val client: OkHttpClient, private val headers: Hea
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
private fun fetchKey(): String { private fun fetchKey(): String? {
return client.newCall(GET(KEY_SOURCE)).execute().parseAs<KeysData>().keys.get(0) return client.newCall(GET(KEY_SOURCE)).execute().parseAs<KeysData>().keys.firstOrNull()
} }
private fun getKey(body: String): String { private fun decodeUnicodeEscape(input: String): String {
val (encrypted, pass, offset, index) = REGEX_EVAL_KEY.find(body)!!.groupValues.drop(1) val regex = Regex("u([0-9a-fA-F]{4})")
val decrypted = decryptScript(encrypted, pass, offset.toInt(), index.toInt()) return regex.replace(input) {
return decrypted.substringAfter("'").substringBefore("'") it.groupValues[1].toInt(16).toChar().toString()
} }
private fun decryptScript(encrypted: String, pass: String, offset: Int, index: Int): String {
val trimmedPass = pass.substring(0, index)
val bits = encrypted.split(pass[index]).map { item ->
trimmedPass.foldIndexed(item) { index, acc, it ->
acc.replace(it.toString(), index.toString())
}
}.filter(String::isNotBlank)
return bits.joinToString("") { Char(it.toInt(index) - offset).toString() }
} }
@Serializable @Serializable
@ -116,16 +82,10 @@ class ChillxExtractor(private val client: OkHttpClient, private val headers: Hea
val salt: String, val salt: String,
) )
@Serializable
data class TrackDto(
val kind: String,
val label: String = "",
val file: String,
)
@Serializable @Serializable
data class KeysData( data class KeysData(
@SerialName("chillx") @SerialName("chillx")
val keys: List<String> val keys: List<String>
) )
} }
class ErrorLoadingException(message: String) : Exception(message)

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Tokuzilla' extName = 'Tokuzilla'
extClass = '.Tokuzilla' extClass = '.Tokuzilla'
extVersionCode = 16 extVersionCode = 17
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.AnimeSAGA' extClass = '.AnimeSAGA'
themePkg = 'dooplay' themePkg = 'dooplay'
baseUrl = 'https://www.animesaga.in' baseUrl = 'https://www.animesaga.in'
overrideVersionCode = 10 overrideVersionCode = 11
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"