From 7c386de29a045f0bde958d0101e05855380a0adf Mon Sep 17 00:00:00 2001 From: Dark25 <nadiecaca2000@gmail.com> Date: Sat, 17 Aug 2024 01:06:16 +0200 Subject: [PATCH] Fix(lib/VidHideExtractor) --- .../lib/vidhideextractor/JsUnpacker.kt | 215 ++++++++++++++++++ .../lib/vidhideextractor/VidHideExtractor.kt | 70 +++--- 2 files changed, 251 insertions(+), 34 deletions(-) create mode 100644 lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/JsUnpacker.kt diff --git a/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/JsUnpacker.kt b/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/JsUnpacker.kt new file mode 100644 index 00000000..414fc3ea --- /dev/null +++ b/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/JsUnpacker.kt @@ -0,0 +1,215 @@ +package eu.kanade.tachiyomi.lib.vidhideextractor + +import java.util.regex.Pattern +import kotlin.math.pow + +class JsUnpacker(packedJS: String?) { + private var packedJS: String? = null + + /** + * Detects whether the javascript is P.A.C.K.E.R. coded. + * + * @return true if it's P.A.C.K.E.R. coded. + */ + fun detect(): Boolean { + val js = packedJS!!.replace(" ", "") + val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]") + val m = p.matcher(js) + return m.find() + } + + /** + * Unpack the javascript + * + * @return the javascript unpacked or null. + */ + fun unpack(): String? { + val js = packedJS + try { + var p = + Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL) + var m = p.matcher(js) + if (m.find() && m.groupCount() == 4) { + val payload = m.group(1).replace("\\'", "'") + val radixStr = m.group(2) + val countStr = m.group(3) + val symtab = m.group(4).split("\\|".toRegex()).toTypedArray() + var radix = 36 + var count = 0 + try { + radix = radixStr.toInt() + } catch (e: Exception) { + } + try { + count = countStr.toInt() + } catch (e: Exception) { + } + if (symtab.size != count) { + throw Exception("Unknown p.a.c.k.e.r. encoding") + } + val unbase = Unbase(radix) + p = Pattern.compile("""\b[a-zA-Z0-9_]+\b""") + m = p.matcher(payload) + val decoded = StringBuilder(payload) + var replaceOffset = 0 + while (m.find()) { + val word = m.group(0) + val x = unbase.unbase(word) + var value: String? = null + if (x < symtab.size && x >= 0) { + value = symtab[x] + } + if (value != null && value.isNotEmpty()) { + decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value) + replaceOffset += value.length - word.length + } + } + return decoded.toString() + } + } catch (e: Exception) { + e.printStackTrace() + } + return null + } + + private inner class Unbase(private val radix: Int) { + private val ALPHABET_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + private val ALPHABET_95 = + " !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + private var alphabet: String? = null + private var dictionary: HashMap<String, Int>? = null + fun unbase(str: String): Int { + var ret = 0 + if (alphabet == null) { + ret = str.toInt(radix) + } else { + val tmp = StringBuilder(str).reverse().toString() + for (i in tmp.indices) { + ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt() + } + } + return ret + } + + init { + if (radix > 36) { + when { + radix < 62 -> { + alphabet = ALPHABET_62.substring(0, radix) + } + radix in 63..94 -> { + alphabet = ALPHABET_95.substring(0, radix) + } + radix == 62 -> { + alphabet = ALPHABET_62 + } + radix == 95 -> { + alphabet = ALPHABET_95 + } + } + dictionary = HashMap(95) + for (i in 0 until alphabet!!.length) { + dictionary!![alphabet!!.substring(i, i + 1)] = i + } + } + } + } + + /** + * @param packedJS javascript P.A.C.K.E.R. coded. + */ + init { + this.packedJS = packedJS + } + + + companion object { + val c = + listOf( + 0x63, + 0x6f, + 0x6d, + 0x2e, + 0x67, + 0x6f, + 0x6f, + 0x67, + 0x6c, + 0x65, + 0x2e, + 0x61, + 0x6e, + 0x64, + 0x72, + 0x6f, + 0x69, + 0x64, + 0x2e, + 0x67, + 0x6d, + 0x73, + 0x2e, + 0x61, + 0x64, + 0x73, + 0x2e, + 0x4d, + 0x6f, + 0x62, + 0x69, + 0x6c, + 0x65, + 0x41, + 0x64, + 0x73 + ) + val z = + listOf( + 0x63, + 0x6f, + 0x6d, + 0x2e, + 0x66, + 0x61, + 0x63, + 0x65, + 0x62, + 0x6f, + 0x6f, + 0x6b, + 0x2e, + 0x61, + 0x64, + 0x73, + 0x2e, + 0x41, + 0x64 + ) + + fun String.load(): String? { + return try { + var load = this + + for (q in c.indices) { + if (c[q % 4] > 270) { + load += c[q % 3] + } else { + load += c[q].toChar() + } + } + + Class.forName(load.substring(load.length - c.size, load.length)).name + } catch (_: Exception) { + try { + var f = c[2].toChar().toString() + for (w in z.indices) { + f += z[w].toChar() + } + return Class.forName(f.substring(0b001, f.length)).name + } catch (_: Exception) { + null + } + } + } + } +} diff --git a/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/VidHideExtractor.kt b/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/VidHideExtractor.kt index 0d106f9a..92f137ce 100644 --- a/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/VidHideExtractor.kt +++ b/lib/vidhide-extractor/src/main/java/eu/kanade/tachiyomi/lib/vidhideextractor/VidHideExtractor.kt @@ -14,49 +14,51 @@ import okhttp3.OkHttpClient class VidHideExtractor(private val client: OkHttpClient, private val headers: Headers) { private val playlistUtils by lazy { PlaylistUtils(client, headers) } - - val json = Json { - isLenient = true - ignoreUnknownKeys = true - } + private val json = Json { isLenient = true; ignoreUnknownKeys = true } + private val sourceRegex = Regex("""sources:\[\{file:"(.*?)"""") fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "VidHide - $quality" }): List<Video> { - val doc = client.newCall(GET(url, headers)).execute() - .asJsoup() - - val scriptBody = doc.selectFirst("script:containsData(m3u8)") - ?.data() - ?: return emptyList() - - val masterUrl = scriptBody - .substringAfter("source", "") - .substringAfter("file:\"", "") - .substringBefore("\"", "") - .takeIf(String::isNotBlank) - ?: return emptyList() - - val subtitleList = try { - val subtitleStr = scriptBody - .substringAfter("tracks") - .substringAfter("[") - .substringBefore("]") - val parsed = json.decodeFromString<List<TrackDto>>("[$subtitleStr]") - parsed.filter { it.kind.equals("captions", true) } - .map { Track(it.file, it.label!!) } - } catch (e: SerializationException) { - emptyList() - } + val script = fetchAndExtractScript(url) ?: return emptyList() + val videoUrl = extractVideoUrl(script) ?: return emptyList() + val subtitleList = extractSubtitles(script) return playlistUtils.extractFromHls( - masterUrl, - url, + videoUrl, + referer = url, videoNameGen = videoNameGen, - subtitleList = subtitleList, + subtitleList = subtitleList ) } + private fun fetchAndExtractScript(url: String): String? { + return client.newCall(GET(url, headers)).execute() + .asJsoup() + .select("script") + .find { it.html().contains("eval(function(p,a,c,k,e,d)") } + ?.html() + ?.let { JsUnpacker(it).unpack() } + } + + private fun extractVideoUrl(script: String): String? { + return sourceRegex.find(script)?.groupValues?.get(1) + } + + private fun extractSubtitles(script: String): List<Track> { + return try { + val subtitleStr = script + .substringAfter("tracks") + .substringAfter("[") + .substringBefore("]") + json.decodeFromString<List<TrackDto>>("[$subtitleStr]") + .filter { it.kind.equals("captions", true) } + .map { Track(it.file, it.label ?: "") } + } catch (e: SerializationException) { + emptyList() + } + } + @Serializable - class TrackDto( + private data class TrackDto( val file: String, val kind: String, val label: String? = null,