From 3ed674a20141425fe2c68b113f279d2f14c59f23 Mon Sep 17 00:00:00 2001
From: Dark25 <nadiecaca2000@gmail.com>
Date: Sat, 17 Aug 2024 10:44:17 +0100
Subject: [PATCH 1/3] Fix(lib/VidHideExtractor) (#151)

---
 .../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,

From 7e7aa7da3c64af178796596b4703d42b18b20324 Mon Sep 17 00:00:00 2001
From: WebDitto <webditto@proton.me>
Date: Sat, 17 Aug 2024 16:15:18 -0300
Subject: [PATCH 2/3] fix(pt/anitube): Fixed pt/Anitube sources (fix #141)
 (#155)

---
 src/pt/anitube/build.gradle                   |  2 +-
 .../animeextension/pt/anitube/Anitube.kt      |  2 +-
 .../pt/anitube/extractors/AnitubeExtractor.kt | 43 ++++++++++++++++++-
 3 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/src/pt/anitube/build.gradle b/src/pt/anitube/build.gradle
index cb8f6056..d88cedbb 100644
--- a/src/pt/anitube/build.gradle
+++ b/src/pt/anitube/build.gradle
@@ -1,7 +1,7 @@
 ext {
     extName = 'Anitube'
     extClass = '.Anitube'
-    extVersionCode = 14
+    extVersionCode = 15
 }
 
 apply from: "$rootDir/common.gradle"
diff --git a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt
index 644545f9..d5ab8012 100644
--- a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt
+++ b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/Anitube.kt
@@ -176,7 +176,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
     }
 
     // ============================ Video Links =============================
-    override fun videoListParse(response: Response) = AnitubeExtractor.getVideoList(response, headers)
+    override fun videoListParse(response: Response) = AnitubeExtractor.getVideoList(response, headers, client)
     override fun videoListSelector() = throw UnsupportedOperationException()
     override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
     override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
diff --git a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt
index 457e2843..e2c3854c 100644
--- a/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt
+++ b/src/pt/anitube/src/eu/kanade/tachiyomi/animeextension/pt/anitube/extractors/AnitubeExtractor.kt
@@ -1,17 +1,23 @@
 package eu.kanade.tachiyomi.animeextension.pt.anitube.extractors
 
 import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.util.asJsoup
+import okhttp3.FormBody
 import okhttp3.Headers
+import okhttp3.OkHttpClient
 import okhttp3.Response
 
 object AnitubeExtractor {
-    fun getVideoList(response: Response, headers: Headers): List<Video> {
+    fun getVideoList(response: Response, headers: Headers, client: OkHttpClient): List<Video> {
         val doc = response.asJsoup()
         val hasFHD = doc.selectFirst("div.abaItem:contains(FULLHD)") != null
         val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!!
             .attr("content")
             .replace("cdn1", "cdn3")
+        val thumbUrl = doc.selectFirst("meta[itemprop=thumbnailUrl]")!!
+            .attr("content")
         val type = serverUrl.split("/").get(3)
         val qualities = listOfNotNull("SD", "HD", if (hasFHD) "FULLHD" else null)
         val paths = listOf("appsd", "apphd").let {
@@ -21,9 +27,42 @@ object AnitubeExtractor {
                 it
             }
         } + listOf("appfullhd")
+
+        val videoName = serverUrl.split('/').last()
+
+        val adsUrl =
+            client.newCall(GET("https://www.anitube.vip/playerricas.php?name=apphd/$videoName&img=$thumbUrl&url=$serverUrl"))
+                .execute()
+                .body.string()
+                .substringAfter("ADS_URL")
+                .substringAfter('"')
+                .substringBefore('"')
+
+        val adsContent = client.newCall(GET(adsUrl)).execute().body.string()
+
+        val body = FormBody.Builder()
+            .add("category", "client")
+            .add("type", "premium")
+            .add("ad", adsContent)
+            .build()
+
+        val publicidade = client.newCall(POST("https://ads.anitube.vip/", body = body))
+            .execute()
+            .body.string()
+            .substringAfter("\"publicidade\"")
+            .substringAfter('"')
+            .substringBefore('"')
+
+        val authCode = client.newCall(GET("https://ads.anitube.vip/?token=$publicidade"))
+            .execute()
+            .body.string()
+            .substringAfter("\"publicidade\"")
+            .substringAfter('"')
+            .substringBefore('"')
+
         return qualities.mapIndexed { index, quality ->
             val path = paths[index]
-            val url = serverUrl.replace(type, path)
+            val url = serverUrl.replace(type, path) + authCode
             Video(url, quality, url, headers = headers)
         }.reversed()
     }

From f72391c11604a4af6ae63ceaf0d7e952ac80e1f7 Mon Sep 17 00:00:00 2001
From: WebDitto <webditto@proton.me>
Date: Sat, 17 Aug 2024 16:15:30 -0300
Subject: [PATCH 3/3] fix(pt/otakuanimes): Fixed images on search for
 pt/otakuanimes (#154)

---
 src/pt/otakuanimes/build.gradle                 |  2 +-
 .../pt/otakuanimes/OtakuAnimes.kt               | 17 +++++++++++++++--
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/src/pt/otakuanimes/build.gradle b/src/pt/otakuanimes/build.gradle
index cba7bfa4..6420730f 100644
--- a/src/pt/otakuanimes/build.gradle
+++ b/src/pt/otakuanimes/build.gradle
@@ -1,7 +1,7 @@
 ext {
     extName = 'OtakuAnimes'
     extClass = '.OtakuAnimes'
-    extVersionCode = 1
+    extVersionCode = 2
     isNsfw = true
 }
 
diff --git a/src/pt/otakuanimes/src/eu/kanade/tachiyomi/animeextension/pt/otakuanimes/OtakuAnimes.kt b/src/pt/otakuanimes/src/eu/kanade/tachiyomi/animeextension/pt/otakuanimes/OtakuAnimes.kt
index cd5b6ca7..cde366c7 100644
--- a/src/pt/otakuanimes/src/eu/kanade/tachiyomi/animeextension/pt/otakuanimes/OtakuAnimes.kt
+++ b/src/pt/otakuanimes/src/eu/kanade/tachiyomi/animeextension/pt/otakuanimes/OtakuAnimes.kt
@@ -49,7 +49,7 @@ class OtakuAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
     override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
         setUrlWithoutDomain(element.attr("href"))
         title = element.selectFirst("div.aniNome")!!.text().trim()
-        thumbnail_url = element.selectFirst("img")?.attr("data-lazy-src")
+        thumbnail_url = element.selectFirst("img")?.getImageUrl()
     }
 
     override fun popularAnimeNextPageSelector() = null
@@ -111,7 +111,7 @@ class OtakuAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
         return SAnime.create().apply {
             setUrlWithoutDomain(doc.location())
             title = doc.selectFirst("div.animeFirstContainer h1")!!.text()
-            thumbnail_url = doc.selectFirst("div.animeCapa img")?.attr("data-lazy-src")
+            thumbnail_url = doc.selectFirst("div.animeCapa img")?.getImageUrl()
             description = doc.selectFirst("div.animeSecondContainer > p")?.text()
             genre = doc.select("ul.animeGen li").eachText()?.joinToString(", ")
         }
@@ -222,6 +222,19 @@ class OtakuAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
         return document
     }
 
+    /**
+     * Tries to get the image url via various possible attributes.
+     * Taken from Tachiyomi's Madara multisrc.
+     */
+    protected open fun Element.getImageUrl(): String? {
+        return when {
+            hasAttr("data-src") -> attr("abs:data-src")
+            hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
+            hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ")
+            else -> attr("abs:src")
+        }.substringBefore("?resize")
+    }
+
     companion object {
         const val PREFIX_SEARCH = "path:"
         private val REGEX_QUALITY by lazy { Regex("""(\d+)p""") }