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,