Merge branch 'Kohi-den:main' into main

This commit is contained in:
Dark25 2024-08-18 15:31:25 +01:00 committed by GitHub
commit 3d744de4a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 310 additions and 41 deletions

View file

@ -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
}
}
}
}
}

View file

@ -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,

View file

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

View file

@ -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()

View file

@ -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()
}

View file

@ -1,7 +1,7 @@
ext {
extName = 'OtakuAnimes'
extClass = '.OtakuAnimes'
extVersionCode = 1
extVersionCode = 2
isNsfw = true
}

View file

@ -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""") }