Skip to content

Commit

Permalink
Fix(lib/VidHideExtractor)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dark25 committed Aug 16, 2024
1 parent 310000c commit a59edb4
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
private val json = Json { isLenient = true; ignoreUnknownKeys = true }
private val sourceRegex = Regex("""sources:\[\{file:"(.*?)"""")

val json = Json {
isLenient = true
ignoreUnknownKeys = true
fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "VidHide - $quality" }): List<Video> {
val script = fetchAndExtractScript(url) ?: return emptyList()
val videoUrl = extractVideoUrl(script) ?: return emptyList()
val subtitleList = extractSubtitles(script)

return playlistUtils.extractFromHls(
videoUrl,
referer = url,
videoNameGen = videoNameGen,
subtitleList = subtitleList
)
}

fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "VidHide - $quality" }): List<Video> {
val doc = client.newCall(GET(url, headers)).execute()
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() }
}

val scriptBody = doc.selectFirst("script:containsData(m3u8)")
?.data()
?: return emptyList()

val masterUrl = scriptBody
.substringAfter("source", "")
.substringAfter("file:\"", "")
.substringBefore("\"", "")
.takeIf(String::isNotBlank)
?: return emptyList()
private fun extractVideoUrl(script: String): String? {
return sourceRegex.find(script)?.groupValues?.get(1)
}

val subtitleList = try {
val subtitleStr = scriptBody
private fun extractSubtitles(script: String): List<Track> {
return try {
val subtitleStr = script
.substringAfter("tracks")
.substringAfter("[")
.substringBefore("]")
val parsed = json.decodeFromString<List<TrackDto>>("[$subtitleStr]")
parsed.filter { it.kind.equals("captions", true) }
.map { Track(it.file, it.label!!) }
json.decodeFromString<List<TrackDto>>("[$subtitleStr]")
.filter { it.kind.equals("captions", true) }
.map { Track(it.file, it.label ?: "") }
} catch (e: SerializationException) {
emptyList()
}

return playlistUtils.extractFromHls(
masterUrl,
url,
videoNameGen = videoNameGen,
subtitleList = subtitleList,
)
}

@Serializable
class TrackDto(
private data class TrackDto(
val file: String,
val kind: String,
val label: String? = null,
Expand Down

0 comments on commit a59edb4

Please sign in to comment.