refactor: Make extensions use GdrivePlayer lib (#1909)
This commit is contained in:
@ -10,6 +10,7 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-gdriveplayer-extractor'))
|
||||
implementation(project(':lib-streamsb-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
implementation(project(':lib-voe-extractor'))
|
||||
|
@ -6,10 +6,9 @@ import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.SharedExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.VidYardExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.VidYardExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
@ -18,6 +17,7 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||
import eu.kanade.tachiyomi.lib.vidbomextractor.VidBomExtractor
|
||||
@ -135,7 +135,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
}
|
||||
url.contains("drive.google") -> {
|
||||
val embedUrlG = "https://gdriveplayer.to/embed2.php?link=$url"
|
||||
GdrivePlayerExtractor(client).videosFromUrl(embedUrlG)
|
||||
GdrivePlayerExtractor(client).videosFromUrl(embedUrlG, "GdrivePlayer", headers = headers)
|
||||
}
|
||||
url.contains("vidyard") -> {
|
||||
val headers = headers.newBuilder()
|
||||
@ -162,7 +162,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
val finalUrl = VIDBOM_REGEX.find(url)!!.groupValues[0]
|
||||
VidBomExtractor(client).videosFromUrl("https://www.$finalUrl.html")
|
||||
}
|
||||
STREAMWISH_REGEX.containsMatchIn(url) -> {
|
||||
STREAMWISH_REGEX.containsMatchIn(url) -> {
|
||||
val headers = headers.newBuilder()
|
||||
.set("Referer", url)
|
||||
.set("Accept-Encoding", "gzip, deflate, br")
|
||||
@ -178,6 +178,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
else -> null
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
// override fun videoListSelector() = "script:containsData(m3u8)"
|
||||
override fun videoListSelector() = "li[data-i] a"
|
||||
|
||||
@ -380,7 +381,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
Type("Special", "special1"),
|
||||
Type("TV", "tv2"),
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
private fun getStatusList() = listOf(
|
||||
Status("أختر", ""),
|
||||
@ -388,7 +389,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
Status("مكتمل", "complete"),
|
||||
Status("يعرض الان", "%d9%8a%d8%b9%d8%b1%d8%b6-%d8%a7%d9%84%d8%a7%d9%86-1"),
|
||||
|
||||
)
|
||||
)
|
||||
companion object {
|
||||
private val VIDBOM_REGEX = Regex("(?:v[aie]d[bp][aoe]?m|myvii?d|segavid|v[aei]{1,2}dshar[er]?)\\.(?:com|net|org|xyz)(?::\\d+)?/(?:embed[/-])?([A-Za-z0-9]+)")
|
||||
private val STREAMSB_REGEX = Regex("(?:view|watch|embed(?:tv)?|tube|player|cloudemb|japopav|javplaya|p1ayerjavseen|gomovizplay|stream(?:ovies)?|vidmovie|javside|aintahalu|finaltayibin|yahlusubh|taeyabathuna|)?s{0,2}b?(?:embed\\d?|play\\d?|video|fast|full|streams{0,3}|the|speed|l?anh|tvmshow|longvu|arslanrocky|chill|rity|hight|brisk|face|lvturbo|net|one|asian|ani|rapid|sonic|lona)?\\.(?:com|net|org|one|tv|xyz|fun|pro|sbs)")
|
||||
|
@ -1,125 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors
|
||||
|
||||
import android.util.Base64
|
||||
import dev.datlag.jsunpacker.JsUnpacker
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
val body = client.newCall(GET(url.replace(".me", ".to"))).execute()
|
||||
.body.string()
|
||||
val eval = JsUnpacker.unpackAndCombine(body)!!.replace("\\", "")
|
||||
val json = Json.decodeFromString<JsonObject>(REGEX_DATAJSON.getFirst(eval))
|
||||
val sojson = REGEX_SOJSON.getFirst(eval)
|
||||
.split(Regex("\\D+"))
|
||||
.joinToString("") {
|
||||
Char(it.toInt()).toString()
|
||||
}
|
||||
val password = REGEX_PASSWORD.getFirst(sojson).toByteArray()
|
||||
val decrypted = decryptAES(password, json)!!
|
||||
val secondEval = JsUnpacker.unpackAndCombine(decrypted)!!.replace("\\", "")
|
||||
return REGEX_VIDEOURL.findAll(secondEval)
|
||||
.distinctBy { it.groupValues[2] } // remove duplicates by quality
|
||||
.map {
|
||||
val qualityStr = it.groupValues[2]
|
||||
val quality = "$PLAYER_NAME - ${qualityStr}p"
|
||||
val videoUrl = "https:" + it.groupValues[1] + "&res=$qualityStr"
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun decryptAES(password: ByteArray, json: JsonObject): String? {
|
||||
val salt = json["s"]!!.jsonPrimitive.content
|
||||
val encodedCiphetext = json["ct"]!!.jsonPrimitive.content
|
||||
val ciphertext = Base64.decode(encodedCiphetext, Base64.DEFAULT)
|
||||
val (key, iv) = generateKeyAndIv(password, salt.decodeHex())
|
||||
?: return null
|
||||
val keySpec = SecretKeySpec(key, "AES")
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
|
||||
val decryptedData = String(cipher.doFinal(ciphertext))
|
||||
return decryptedData
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
password: ByteArray,
|
||||
salt: ByteArray,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1,
|
||||
): List<ByteArray>? {
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.getDigestLength()
|
||||
val targetKeySize = keyLength + ivLength
|
||||
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||
var generatedData = ByteArray(requiredLength)
|
||||
var generatedLength = 0
|
||||
|
||||
try {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0) {
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength,
|
||||
)
|
||||
}
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
|
||||
for (i in 1 until iterations) {
|
||||
md.update(generatedData, generatedLength, digestLength)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
}
|
||||
|
||||
generatedLength += digestLength
|
||||
}
|
||||
val result = listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize),
|
||||
)
|
||||
return result
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Regex.getFirst(item: String): String {
|
||||
return find(item)?.groups?.elementAt(1)?.value!!
|
||||
}
|
||||
|
||||
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
|
||||
private fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PLAYER_NAME = "GDRIVE"
|
||||
|
||||
private val REGEX_DATAJSON = Regex("data='(\\S+?)'")
|
||||
private val REGEX_PASSWORD = Regex("var pass = \"(\\S+?)\"")
|
||||
private val REGEX_SOJSON = Regex("null,['|\"](\\w+)['|\"]")
|
||||
private val REGEX_VIDEOURL = Regex("file\":\"(\\S+?)\".*?res=(\\d+)")
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ class StreamWishExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(url: String, headers: Headers): List<Video> {
|
||||
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||
val script = doc.selectFirst("script:containsData(sources)")!!.data()
|
||||
val scriptData = if(script.contains("eval")) JsUnpacker.unpackAndCombine(script)!! else script
|
||||
val scriptData = if (script.contains("eval")) JsUnpacker.unpackAndCombine(script)!! else script
|
||||
val m3u8 = Regex("sources:\\s*\\[\\{\\s*\\t*file:\\s*[\"']([^\"']+)").find(scriptData)!!.groupValues[1]
|
||||
val streamLink = Regex("(.*)_,(.*),\\.urlset/master(.*)").find(m3u8)!!
|
||||
val streamQuality = streamLink.groupValues[2].split(",").reversed()
|
||||
@ -19,7 +19,7 @@ class StreamWishExtractor(private val client: OkHttpClient) {
|
||||
val qRegex = Regex("\".*?\":\\s*\"(.*?)\"").findAll(qualities)
|
||||
return qRegex.mapIndexed { index, matchResult ->
|
||||
val src = streamLink.groupValues[1] + "_" + streamQuality[index] + "/index-v1-a1" + streamLink.groupValues[3]
|
||||
val quality = "Mirror: " + matchResult.groupValues[1]
|
||||
val quality = "Mirror: " + matchResult.groupValues[1]
|
||||
Video(src, quality, src, headers)
|
||||
}.toList()
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-gdriveplayer-extractor'))
|
||||
implementation(project(':lib-streamtape-extractor'))
|
||||
implementation(project(':lib-streamsb-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
|
@ -4,7 +4,6 @@ import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.SharedExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.UQLoadExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.VidBomExtractor
|
||||
@ -15,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
@ -189,7 +189,7 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
embedUrl.contains("drive.google")
|
||||
-> {
|
||||
val embedUrlG = "https://gdriveplayer.to/embed2.php?link=" + embedUrl
|
||||
val videos = GdrivePlayerExtractor(client).videosFromUrl(embedUrlG)
|
||||
val videos = GdrivePlayerExtractor(client).videosFromUrl(embedUrlG, "GdrivePlayer", headers = headers)
|
||||
videoList.addAll(videos)
|
||||
}
|
||||
embedUrl.contains("streamtape") -> {
|
||||
|
@ -1,125 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.ar.animerco.extractors
|
||||
|
||||
import android.util.Base64
|
||||
import dev.datlag.jsunpacker.JsUnpacker
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
val body = client.newCall(GET(url.replace(".me", ".to"))).execute()
|
||||
.body.string()
|
||||
val eval = JsUnpacker.unpackAndCombine(body)!!.replace("\\", "")
|
||||
val json = Json.decodeFromString<JsonObject>(REGEX_DATAJSON.getFirst(eval))
|
||||
val sojson = REGEX_SOJSON.getFirst(eval)
|
||||
.split(Regex("\\D+"))
|
||||
.joinToString("") {
|
||||
Char(it.toInt()).toString()
|
||||
}
|
||||
val password = REGEX_PASSWORD.getFirst(sojson).toByteArray()
|
||||
val decrypted = decryptAES(password, json)!!
|
||||
val secondEval = JsUnpacker.unpackAndCombine(decrypted)!!.replace("\\", "")
|
||||
return REGEX_VIDEOURL.findAll(secondEval)
|
||||
.distinctBy { it.groupValues[2] } // remove duplicates by quality
|
||||
.map {
|
||||
val qualityStr = it.groupValues[2]
|
||||
val quality = "$PLAYER_NAME - ${qualityStr}p"
|
||||
val videoUrl = "https:" + it.groupValues[1] + "&res=$qualityStr"
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun decryptAES(password: ByteArray, json: JsonObject): String? {
|
||||
val salt = json["s"]!!.jsonPrimitive.content
|
||||
val encodedCiphetext = json["ct"]!!.jsonPrimitive.content
|
||||
val ciphertext = Base64.decode(encodedCiphetext, Base64.DEFAULT)
|
||||
val (key, iv) = generateKeyAndIv(password, salt.decodeHex())
|
||||
?: return null
|
||||
val keySpec = SecretKeySpec(key, "AES")
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
|
||||
val decryptedData = String(cipher.doFinal(ciphertext))
|
||||
return decryptedData
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
password: ByteArray,
|
||||
salt: ByteArray,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1,
|
||||
): List<ByteArray>? {
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.getDigestLength()
|
||||
val targetKeySize = keyLength + ivLength
|
||||
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||
var generatedData = ByteArray(requiredLength)
|
||||
var generatedLength = 0
|
||||
|
||||
try {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0) {
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength,
|
||||
)
|
||||
}
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
|
||||
for (i in 1 until iterations) {
|
||||
md.update(generatedData, generatedLength, digestLength)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
}
|
||||
|
||||
generatedLength += digestLength
|
||||
}
|
||||
val result = listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize),
|
||||
)
|
||||
return result
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Regex.getFirst(item: String): String {
|
||||
return find(item)?.groups?.elementAt(1)?.value!!
|
||||
}
|
||||
|
||||
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
|
||||
private fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PLAYER_NAME = "GDRIVE"
|
||||
|
||||
private val REGEX_DATAJSON = Regex("data='(\\S+?)'")
|
||||
private val REGEX_PASSWORD = Regex("var pass = \"(\\S+?)\"")
|
||||
private val REGEX_SOJSON = Regex("null,['|\"](\\w+)['|\"]")
|
||||
private val REGEX_VIDEOURL = Regex("file\":\"(\\S+?)\".*?res=(\\d+)")
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ArabAnime: ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
class ArabAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override val name = "ArabAnime"
|
||||
|
||||
@ -81,20 +81,20 @@ class ArabAnime: ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val watchData = response.asJsoup().select("div#datawatch").text().decodeBase64()
|
||||
val serversJson = json.decodeFromString<Episode>(watchData)
|
||||
val selectServer =serversJson.ep_info[0].stream_servers[0].decodeBase64()
|
||||
val selectServer = serversJson.ep_info[0].stream_servers[0].decodeBase64()
|
||||
val watchPage = client.newCall(GET(selectServer)).execute().asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
watchPage.select("option").forEach { it ->
|
||||
val link = it.attr("data-src").decodeBase64()
|
||||
if (link.contains("www.arabanime.net/embed")){
|
||||
if (link.contains("www.arabanime.net/embed")) {
|
||||
val sources = client.newCall(GET(link)).execute().asJsoup().select("source")
|
||||
sources.forEach { source ->
|
||||
if(!source.attr("src").contains("static")){
|
||||
val quality = source.attr("label").let {q ->
|
||||
if(q.contains("p")) q else q + "p"
|
||||
if (!source.attr("src").contains("static")) {
|
||||
val quality = source.attr("label").let { q ->
|
||||
if (q.contains("p")) q else q + "p"
|
||||
}
|
||||
videoList.add(
|
||||
Video(source.attr("src"), "${it.text()}: $quality" ,source.attr("src"))
|
||||
Video(source.attr("src"), "${it.text()}: $quality", source.attr("src")),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -132,7 +132,7 @@ class ArabAnime: ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
return if(response.body.contentType() == "application/json".toMediaType()){
|
||||
return if (response.body.contentType() == "application/json".toMediaType()) {
|
||||
popularAnimeParse(response)
|
||||
} else {
|
||||
val searchResult = response.asJsoup().select("div.show")
|
||||
@ -178,7 +178,7 @@ class ArabAnime: ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val animeList = latestEpisodes.map {
|
||||
SAnime.create().apply {
|
||||
val url = it.select("a.as-info").attr("href")
|
||||
.replace("watch","show").substringBeforeLast("/")
|
||||
.replace("watch", "show").substringBeforeLast("/")
|
||||
setUrlWithoutDomain(url)
|
||||
title = it.select("a.as-info").text()
|
||||
thumbnail_url = it.select("img").attr("src")
|
||||
@ -210,20 +210,20 @@ class ArabAnime: ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
CatUnit("اختر", ""),
|
||||
CatUnit("التقييم", "2"),
|
||||
CatUnit("اخر الانميات المضافة", "1"),
|
||||
CatUnit("الابجدية", "0")
|
||||
CatUnit("الابجدية", "0"),
|
||||
)
|
||||
|
||||
private fun getTypeFilterList() = listOf(
|
||||
CatUnit("اختر", ""),
|
||||
CatUnit("الكل", ""),
|
||||
CatUnit("فيلم", "0"),
|
||||
CatUnit("انمى", "1")
|
||||
CatUnit("انمى", "1"),
|
||||
)
|
||||
private fun getStatFilterList() = listOf(
|
||||
CatUnit("اختر", ""),
|
||||
CatUnit("الكل", ""),
|
||||
CatUnit("مستمر", "1"),
|
||||
CatUnit("مكتمل", "0")
|
||||
CatUnit("مكتمل", "0"),
|
||||
)
|
||||
|
||||
// =============================== Preferences ===============================
|
||||
|
@ -5,14 +5,16 @@ import java.io.ByteArrayOutputStream
|
||||
fun String.decodeBase64(): String = String(this.toByteArray().decodeBase64())
|
||||
|
||||
fun ByteArray.decodeBase64(): ByteArray {
|
||||
val table = intArrayOf(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
val table = intArrayOf(
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,
|
||||
-1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
)
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
var position = 0
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
|
||||
data class PopularAnimeResponse(
|
||||
val Shows: List<String>,
|
||||
val current_page: Int,
|
||||
val last_page: Int
|
||||
val last_page: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -18,20 +18,23 @@ data class AnimeItem(
|
||||
val anime_score: String,
|
||||
val anime_slug: String,
|
||||
val anime_type: String,
|
||||
val info_src: String
|
||||
val info_src: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ShowItem(
|
||||
val EPS: List<EPS>,
|
||||
val show: List<Show>
|
||||
val show: List<Show>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EPS(
|
||||
val episode_name: String,
|
||||
val episode_number: Int,
|
||||
@SerialName("info-src")
|
||||
val `info-src`: String
|
||||
val `info-src`: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Show(
|
||||
val anime_cover_image_url: String,
|
||||
@ -45,13 +48,15 @@ data class Show(
|
||||
val anime_status: String,
|
||||
val anime_type: String,
|
||||
val show_episode_count: Int,
|
||||
val wallpapaer: String
|
||||
val wallpapaer: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Episode(
|
||||
val ep_info: List<EpInfo>
|
||||
val ep_info: List<EpInfo>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EpInfo(
|
||||
val stream_servers: List<String>
|
||||
val stream_servers: List<String>,
|
||||
)
|
||||
|
@ -163,13 +163,13 @@ class Tuktukcinema : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
DoodExtractor(client).videoFromUrl("https://www.$finalUrl", "Dood mirror", false)?.let(::listOf)
|
||||
}
|
||||
url.contains("uqload") -> {
|
||||
UQLoadExtractor(client).videoFromUrl(url, "Uqload mirror")?.let(::listOf)
|
||||
UQLoadExtractor(client).videoFromUrl(url, "Uqload mirror")?.let(::listOf)
|
||||
}
|
||||
url.contains("tape") -> {
|
||||
StreamTapeExtractor(client).videoFromUrl(url)?.let(::listOf)
|
||||
}
|
||||
url.contains("upstream", ignoreCase = true) -> {
|
||||
UpStreamExtractor(client).videoFromUrl(url.replace("//","//www."))
|
||||
UpStreamExtractor(client).videoFromUrl(url.replace("//", "//www."))
|
||||
}
|
||||
else -> null
|
||||
} ?: emptyList()
|
||||
@ -338,6 +338,5 @@ class Tuktukcinema : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
companion object {
|
||||
private val VIDBOM_REGEX = Regex("(?:v[aie]d[bp][aoe]?m|myvii?d|govad|segavid|v[aei]{1,2}dshar[er]?)\\.(?:com|net|org|xyz)(?::\\d+)?/(?:embed[/-])?([A-Za-z0-9]+).html")
|
||||
private val DOOD_REGEX = Regex("(do*d(?:stream)?\\.(?:com?|watch|to|s[ho]|cx|la|w[sf]|pm|re|yt|stream))/[de]/([0-9a-zA-Z]+)")
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class UpStreamExtractor(private val client: OkHttpClient) {
|
||||
fun videoFromUrl(url: String): List<Video> {
|
||||
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||
val script = doc.selectFirst("script:containsData(sources)")?.data() ?: return emptyList()
|
||||
val scriptData = if("eval" in script) JsUnpacker.unpackAndCombine(script)!! else script
|
||||
val scriptData = if ("eval" in script) JsUnpacker.unpackAndCombine(script)!! else script
|
||||
val m3u8 = Regex("sources:\\s*\\[\\{\\s*\\t*file:\\s*[\"']([^\"']+)").find(scriptData)!!.groupValues[1]
|
||||
return Video(m3u8, "Upstream", m3u8).let(::listOf)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-gdriveplayer-extractor'))
|
||||
implementation(project(':lib-okru-extractor'))
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.en.myanime.extractors.DailymotionExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.en.myanime.extractors.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.en.myanime.extractors.YouTubeExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
@ -14,6 +13,7 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
@ -216,7 +216,8 @@ class Myanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
YouTubeExtractor(client).videosFromUrl(url, "YouTube - ")
|
||||
}
|
||||
url.contains("gdriveplayer") -> {
|
||||
GdrivePlayerExtractor(client).videosFromUrl(url, name = "Gdriveplayer")
|
||||
val newHeaders = headersBuilder().add("Referer", baseUrl).build()
|
||||
GdrivePlayerExtractor(client).videosFromUrl(url, name = "Gdriveplayer", headers = newHeaders)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
@ -1,155 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.myanime.extractors
|
||||
|
||||
import android.util.Base64
|
||||
import dev.datlag.jsunpacker.JsUnpacker
|
||||
import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.Jsoup
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
val headers = Headers.headersOf(
|
||||
"Accept",
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Host",
|
||||
"gdriveplayer.to",
|
||||
"Referer",
|
||||
"https://myanime.live/",
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0",
|
||||
)
|
||||
val body = client.newCall(GET(url.replace(".us", ".to"), headers = headers)).execute()
|
||||
.body.string()
|
||||
val subtitleUrl = Jsoup.parse(body).selectFirst("div:contains(\\.srt)")
|
||||
val subtitleList = mutableListOf<Track>()
|
||||
if (subtitleUrl != null) {
|
||||
try {
|
||||
subtitleList.add(
|
||||
Track(
|
||||
"https://gdriveplayer.to/?subtitle=" + subtitleUrl.text(),
|
||||
"Subtitles",
|
||||
),
|
||||
)
|
||||
} catch (a: Exception) { }
|
||||
}
|
||||
|
||||
val eval = JsUnpacker.unpackAndCombine(body)!!.replace("\\", "")
|
||||
val json = Json.decodeFromString<JsonObject>(REGEX_DATAJSON.getFirst(eval))
|
||||
val sojson = REGEX_SOJSON.getFirst(eval)
|
||||
.split(Regex("\\D+"))
|
||||
.joinToString("") {
|
||||
Char(it.toInt()).toString()
|
||||
}
|
||||
val password = REGEX_PASSWORD.getFirst(sojson).toByteArray()
|
||||
val decrypted = decryptAES(password, json)!!
|
||||
val secondEval = JsUnpacker.unpackAndCombine(decrypted)!!.replace("\\", "")
|
||||
return REGEX_VIDEOURL.findAll(secondEval)
|
||||
.distinctBy { it.groupValues[2] } // remove duplicates by quality
|
||||
.map {
|
||||
val qualityStr = it.groupValues[2]
|
||||
val quality = "$PLAYER_NAME ${qualityStr}p - $name"
|
||||
val videoUrl = "https:" + it.groupValues[1] + "&res=$qualityStr"
|
||||
try {
|
||||
Video(videoUrl, quality, videoUrl, subtitleTracks = subtitleList)
|
||||
} catch (a: Exception) {
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun decryptAES(password: ByteArray, json: JsonObject): String? {
|
||||
val salt = json["s"]!!.jsonPrimitive.content
|
||||
val encodedCiphetext = json["ct"]!!.jsonPrimitive.content
|
||||
val ciphertext = Base64.decode(encodedCiphetext, Base64.DEFAULT)
|
||||
val (key, iv) = generateKeyAndIv(password, salt.decodeHex())
|
||||
?: return null
|
||||
val keySpec = SecretKeySpec(key, "AES")
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
|
||||
val decryptedData = String(cipher.doFinal(ciphertext))
|
||||
return decryptedData
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
password: ByteArray,
|
||||
salt: ByteArray,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1,
|
||||
): List<ByteArray>? {
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.getDigestLength()
|
||||
val targetKeySize = keyLength + ivLength
|
||||
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||
var generatedData = ByteArray(requiredLength)
|
||||
var generatedLength = 0
|
||||
|
||||
try {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0) {
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength,
|
||||
)
|
||||
}
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
|
||||
for (i in 1 until iterations) {
|
||||
md.update(generatedData, generatedLength, digestLength)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
}
|
||||
|
||||
generatedLength += digestLength
|
||||
}
|
||||
val result = listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize),
|
||||
)
|
||||
return result
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Regex.getFirst(item: String): String {
|
||||
return find(item)?.groups?.elementAt(1)?.value!!
|
||||
}
|
||||
|
||||
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
|
||||
private fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PLAYER_NAME = "GDRIVE"
|
||||
|
||||
private val REGEX_DATAJSON = Regex("data='(\\S+?)'")
|
||||
private val REGEX_PASSWORD = Regex("var pass = \"(\\S+?)\"")
|
||||
private val REGEX_SOJSON = Regex("null,['|\"](\\w+)['|\"]")
|
||||
private val REGEX_VIDEOURL = Regex("file\":\"(\\S+?)\".*?res=(\\d+)")
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-gdriveplayer-extractor'))
|
||||
implementation(project(':lib-yourupload-extractor'))
|
||||
implementation(project(':lib-okru-extractor'))
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
|
@ -6,7 +6,6 @@ import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.id.neonime.extractors.BloggerExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.neonime.extractors.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.id.neonime.extractors.LinkBoxExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
@ -15,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
@ -254,7 +254,8 @@ class NeoNime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
when {
|
||||
iframeUrl.contains("gdriveplayer.to") -> {
|
||||
videoList.addAll(GdrivePlayerExtractor(client).videosFromUrl(iframeUrl, it.text()))
|
||||
val newHeaders = headersBuilder().add("Referer", baseUrl).build()
|
||||
videoList.addAll(GdrivePlayerExtractor(client).videosFromUrl(iframeUrl, it.text(), headers = newHeaders))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,138 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.id.neonime.extractors
|
||||
|
||||
import android.util.Base64
|
||||
import dev.datlag.jsunpacker.JsUnpacker
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.DigestException
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class GdrivePlayerExtractor(private val client: OkHttpClient) {
|
||||
|
||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
val headers = Headers.headersOf(
|
||||
"Accept",
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Host",
|
||||
"gdriveplayer.to",
|
||||
"Referer",
|
||||
"https://neonime.fun/",
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0",
|
||||
)
|
||||
|
||||
val body = client.newCall(GET(url.replace(".me", ".to"), headers = headers)).execute()
|
||||
.body.string()
|
||||
|
||||
val eval = JsUnpacker.unpackAndCombine(body)!!.replace("\\", "")
|
||||
val json = Json.decodeFromString<JsonObject>(REGEX_DATAJSON.getFirst(eval))
|
||||
val sojson = REGEX_SOJSON.getFirst(eval)
|
||||
.split(Regex("\\D+"))
|
||||
.joinToString("") {
|
||||
Char(it.toInt()).toString()
|
||||
}
|
||||
val password = REGEX_PASSWORD.getFirst(sojson).toByteArray()
|
||||
val decrypted = decryptAES(password, json)!!
|
||||
val secondEval = JsUnpacker.unpackAndCombine(decrypted)!!.replace("\\", "")
|
||||
return REGEX_VIDEOURL.findAll(secondEval)
|
||||
.distinctBy { it.groupValues[2] } // remove duplicates by quality
|
||||
.map {
|
||||
val qualityStr = it.groupValues[2]
|
||||
val quality = "$PLAYER_NAME ${qualityStr}p - $name"
|
||||
val videoUrl = "https:" + it.groupValues[1] + "&res=$qualityStr"
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun decryptAES(password: ByteArray, json: JsonObject): String? {
|
||||
val salt = json["s"]!!.jsonPrimitive.content
|
||||
val encodedCiphetext = json["ct"]!!.jsonPrimitive.content
|
||||
val ciphertext = Base64.decode(encodedCiphetext, Base64.DEFAULT)
|
||||
val (key, iv) = generateKeyAndIv(password, salt.decodeHex())
|
||||
?: return null
|
||||
val keySpec = SecretKeySpec(key, "AES")
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
|
||||
val decryptedData = String(cipher.doFinal(ciphertext))
|
||||
return decryptedData
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/41434590/8166854
|
||||
private fun generateKeyAndIv(
|
||||
password: ByteArray,
|
||||
salt: ByteArray,
|
||||
hashAlgorithm: String = "MD5",
|
||||
keyLength: Int = 32,
|
||||
ivLength: Int = 16,
|
||||
iterations: Int = 1,
|
||||
): List<ByteArray>? {
|
||||
val md = MessageDigest.getInstance(hashAlgorithm)
|
||||
val digestLength = md.getDigestLength()
|
||||
val targetKeySize = keyLength + ivLength
|
||||
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
|
||||
var generatedData = ByteArray(requiredLength)
|
||||
var generatedLength = 0
|
||||
|
||||
try {
|
||||
md.reset()
|
||||
|
||||
while (generatedLength < targetKeySize) {
|
||||
if (generatedLength > 0) {
|
||||
md.update(
|
||||
generatedData,
|
||||
generatedLength - digestLength,
|
||||
digestLength,
|
||||
)
|
||||
}
|
||||
|
||||
md.update(password)
|
||||
md.update(salt, 0, 8)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
|
||||
for (i in 1 until iterations) {
|
||||
md.update(generatedData, generatedLength, digestLength)
|
||||
md.digest(generatedData, generatedLength, digestLength)
|
||||
}
|
||||
|
||||
generatedLength += digestLength
|
||||
}
|
||||
val result = listOf(
|
||||
generatedData.copyOfRange(0, keyLength),
|
||||
generatedData.copyOfRange(keyLength, targetKeySize),
|
||||
)
|
||||
return result
|
||||
} catch (e: DigestException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Regex.getFirst(item: String): String {
|
||||
return find(item)?.groups?.elementAt(1)?.value!!
|
||||
}
|
||||
|
||||
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
|
||||
private fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PLAYER_NAME = "GDRIVE"
|
||||
|
||||
private val REGEX_DATAJSON = Regex("data='(\\S+?)'")
|
||||
private val REGEX_PASSWORD = Regex("var pass = \"(\\S+?)\"")
|
||||
private val REGEX_SOJSON = Regex("null,['|\"](\\w+)['|\"]")
|
||||
private val REGEX_VIDEOURL = Regex("file\":\"(\\S+?)\".*?res=(\\d+)")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user