update for anime4up, animetitans (#991)

This commit is contained in:
Ahmed gamal
2022-10-30 14:50:20 +02:00
committed by GitHub
parent 093ed724df
commit e5898e37b2
11 changed files with 541 additions and 22 deletions

View File

@ -5,8 +5,14 @@ ext {
extName = 'Anime4up'
pkgNameSuffix = 'ar.anime4up'
extClass = '.Anime4Up'
extVersionCode = 31
extVersionCode = 32
libVersion = '13'
}
dependencies {
implementation(project(':lib-fembed-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}
apply from: "$rootDir/common.gradle"

View File

@ -2,8 +2,14 @@ package eu.kanade.tachiyomi.animeextension.ar.anime4up
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
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.MoshahdaExtractor
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.SharedExtractor
import eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors.VidBomExtractor
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
@ -11,6 +17,8 @@ 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.fembedextractor.FembedExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
@ -30,7 +38,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Anime4Up"
override val baseUrl = "https://anime4up.vip"
override val baseUrl = "https://wc.anime4up.vip"
override val lang = "ar"
@ -44,7 +52,7 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun headersBuilder(): Headers.Builder {
return super.headersBuilder()
.add("Referer", "https://anime4up.vip/") // https://s12.gemzawy.com https://moshahda.net
.add("Referer", "https://wc.anime4up.vip/") // https://s12.gemzawy.com https://moshahda.net
}
// Popular
@ -118,30 +126,110 @@ class Anime4Up : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// Call POST
val referer = response.request.url.encodedPath
val newHeaders = Headers.headersOf("referer", "$postUrl")
val ifram1 = client.newCall(POST(postUrl, newHeaders, body)).execute().asJsoup()
val iframe2 = ifram1.select("li[data-i=moshahda] a").attr("data-ep-url")
val iframeResponse = client.newCall(POST(postUrl, newHeaders, body)).execute().asJsoup()
/*val iframe2 = iframe.select("li[data-i=moshahda] a").attr("data-ep-url")
val iframeResponse = client.newCall(GET(iframe2, newHeaders))
.execute().asJsoup()
return videosFromElement(iframeResponse.selectFirst(videoListSelector()))
.execute().asJsoup()*/
return videosFromElement(iframeResponse)
}
}
override fun videoListSelector() = "script:containsData(m3u8)"
// override fun videoListSelector() = "script:containsData(m3u8)"
override fun videoListSelector() = "li[data-i] a"
private fun videosFromElement(element: Element): List<Video> {
val data = element.data().substringAfter("sources: [").substringBefore("],")
val sources = data.split("file: \"").drop(1)
val videoList = mutableListOf<Video>()
for (source in sources) {
val masterUrl = source.substringBefore("\"}")
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body!!.string()
val videoList = mutableListOf<Video>()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:").forEach {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n")
videoList.add(Video(videoUrl, quality, videoUrl))
val elements = element.select(videoListSelector())
for (element in elements) {
val location = element.ownerDocument().location()
val embedUrl = element.attr("data-ep-url")
val qualityy = element.text()
Log.i("embedUrl", "$embedUrl")
when {
embedUrl.contains("moshahda")
-> {
val headers = headers.newBuilder()
.set("referer", "https://gamertak.com/Watch1.php")
.build()
val videos = MoshahdaExtractor(client).videosFromUrl(embedUrl, headers)
videoList.addAll(videos)
}
embedUrl.contains("drive.google")
-> {
val embedUrlG = "https://gdriveplayer.to/embed2.php?link=" + embedUrl
val videos = GdrivePlayerExtractor(client).videosFromUrl(embedUrlG)
videoList.addAll(videos)
}
embedUrl.contains("vidyard")
-> {
val headers = headers.newBuilder()
.set("Referer", "https://play.vidyard.com")
.set("Accept-Encoding", "gzip, deflate, br")
.set("Accept-Language", "en-US,en;q=0.5")
.set("TE", "trailers")
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
.build()
val id = embedUrl.substringAfter("com/").substringBefore("?")
val vidUrl = "https://play.vidyard.com/player/" + id + ".json"
val videos = VidYardExtractor(client).videosFromUrl(vidUrl, headers)
videoList.addAll(videos)
}
embedUrl.contains("sbembed.com") || embedUrl.contains("sbembed1.com") || embedUrl.contains("sbplay.org") ||
embedUrl.contains("sbvideo.net") || embedUrl.contains("streamsb.net") || embedUrl.contains("sbplay.one") ||
embedUrl.contains("cloudemb.com") || embedUrl.contains("playersb.com") || embedUrl.contains("tubesb.com") ||
embedUrl.contains("sbplay1.com") || embedUrl.contains("embedsb.com") || embedUrl.contains("watchsb.com") ||
embedUrl.contains("sbplay2.com") || embedUrl.contains("japopav.tv") || embedUrl.contains("viewsb.com") ||
embedUrl.contains("sbfast") || embedUrl.contains("sbfull.com") || embedUrl.contains("javplaya.com") ||
embedUrl.contains("ssbstream.net") || embedUrl.contains("p1ayerjavseen.com") || embedUrl.contains("sbthe.com") ||
embedUrl.contains("vidmovie.xyz") || embedUrl.contains("sbspeed.com") || embedUrl.contains("streamsss.net") ||
embedUrl.contains("sblanh.com") || embedUrl.contains("tvmshow.com") || embedUrl.contains("sbanh.com") ||
embedUrl.contains("streamovies.xyz")
-> {
val videos = StreamSBExtractor(client).videosFromUrl(embedUrl, headers)
videoList.addAll(videos)
}
embedUrl.contains("fembed") ||
embedUrl.contains("anime789.com") || embedUrl.contains("24hd.club") || embedUrl.contains("fembad.org") ||
embedUrl.contains("vcdn.io") || embedUrl.contains("sharinglink.club") || embedUrl.contains("moviemaniac.org") ||
embedUrl.contains("votrefiles.club") || embedUrl.contains("femoload.xyz") || embedUrl.contains("albavido.xyz") ||
embedUrl.contains("feurl.com") || embedUrl.contains("dailyplanet.pw") || embedUrl.contains("ncdnstm.com") ||
embedUrl.contains("jplayer.net") || embedUrl.contains("xstreamcdn.com") || embedUrl.contains("fembed-hd.com") ||
embedUrl.contains("gcloud.live") || embedUrl.contains("vcdnplay.com") || embedUrl.contains("superplayxyz.club") ||
embedUrl.contains("vidohd.com") || embedUrl.contains("vidsource.me") || embedUrl.contains("cinegrabber.com") ||
embedUrl.contains("votrefile.xyz") || embedUrl.contains("zidiplay.com") || embedUrl.contains("ndrama.xyz") ||
embedUrl.contains("fcdn.stream") || embedUrl.contains("mediashore.org") || embedUrl.contains("suzihaza.com") ||
embedUrl.contains("there.to") || embedUrl.contains("femax20.com") || embedUrl.contains("javstream.top") ||
embedUrl.contains("viplayer.cc") || embedUrl.contains("sexhd.co") || embedUrl.contains("fembed.net") ||
embedUrl.contains("mrdhan.com") || embedUrl.contains("votrefilms.xyz") || // embedUrl.contains("") ||
embedUrl.contains("embedsito.com") || embedUrl.contains("dutrag.com") || // embedUrl.contains("") ||
embedUrl.contains("youvideos.ru") || embedUrl.contains("streamm4u.club") || // embedUrl.contains("") ||
embedUrl.contains("moviepl.xyz") || embedUrl.contains("asianclub.tv") || // embedUrl.contains("") ||
embedUrl.contains("vidcloud.fun") || embedUrl.contains("fplayer.info") || // embedUrl.contains("") ||
embedUrl.contains("diasfem.com") || embedUrl.contains("javpoll.com") || embedUrl.contains("reeoov.tube") ||
embedUrl.contains("suzihaza.com") || embedUrl.contains("ezsubz.com") || embedUrl.contains("vidsrc.xyz") ||
embedUrl.contains("diampokusy.com") || embedUrl.contains("diampokusy.com") || embedUrl.contains("i18n.pw") ||
embedUrl.contains("vanfem.com") || embedUrl.contains("fembed9hd.com") || embedUrl.contains("votrefilms.xyz") || embedUrl.contains("watchjavnow.xyz")
-> {
val videos = FembedExtractor(client).videosFromUrl(embedUrl)
videoList.addAll(videos)
}
embedUrl.contains("4shared") -> {
val video = SharedExtractor(client).videoFromUrl(embedUrl, qualityy)
if (video != null) {
videoList.add(video)
}
}
embedUrl.contains("vidbom") ||
embedUrl.contains("vidbem") || embedUrl.contains("vidbm") || embedUrl.contains("vedpom") ||
embedUrl.contains("vedbom") || embedUrl.contains("vedbam") || embedUrl.contains("vadbom") ||
embedUrl.contains("vidbam") || embedUrl.contains("vadbam") || embedUrl.contains("myviid") ||
embedUrl.contains("myvid") || embedUrl.contains("vidshare") || embedUrl.contains("vedsharr") ||
embedUrl.contains("vedshar") || embedUrl.contains("vedshare") || embedUrl.contains("vadshar") || embedUrl.contains("vidshar")
-> {
val videos = VidBomExtractor(client).videosFromUrl(embedUrl)
videoList.addAll(videos)
}
}
return videoList
}
return videoList
}

View File

@ -0,0 +1,126 @@
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.decodeFromString
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+)")
}
}

View File

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors
import android.util.Log
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient
class MoshahdaExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
val callPlayer = client.newCall(GET(url, headers)).execute().asJsoup()
Log.i("embeddd", "$callPlayer")
val data = callPlayer.data().substringAfter("sources: [").substringBefore("],")
val sources = data.split("file: \"").drop(1)
val videoList = mutableListOf<Video>()
for (source in sources) {
val masterUrl = source.substringBefore("\"}")
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body!!.string()
val videoList = mutableListOf<Video>()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x")
.substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n")
videoList.add(Video(videoUrl, quality, videoUrl))
}
return videoList
}
return videoList
}
}

View File

@ -0,0 +1,19 @@
package eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class SharedExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String): Video? {
val document = client.newCall(GET(url)).execute().asJsoup()
val check = document.select("div.error4shared").text()
val videoUrl = document.select("source").attr("src")
return if (check.contains("This file is not available any more")) {
Video(url, "no 1video", "https")
} else {
Video(url, quality, videoUrl)
}
}
}

View File

@ -0,0 +1,54 @@
package eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class VidBomExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url)).execute().asJsoup()
val script = doc.selectFirst("script:containsData(sources)")
val data = script.data().substringAfter("sources: [").substringBefore("],")
val sources = data.split("file:\"").drop(1)
val videoList = mutableListOf<Video>()
for (source in sources) {
val src = source.substringBefore("\"")
val quality = "Vidbom:" + source.substringAfter("label:\"").substringBefore("\"") // .substringAfter("format: '")
val video = Video(src, quality, src)
videoList.add(video)
}
return videoList
/*Log.i("looool", "$js")
val json = JSONObject(js)
Log.i("looool", "$json")
val videoList = mutableListOf<Video>()
val jsonArray = json.getJSONArray("sources")
for (i in 0 until jsonArray.length()) {
val `object` = jsonArray.getJSONObject(i)
val videoUrl = `object`.getString("file")
Log.i("looool", videoUrl)
val quality = "Vidbom:" + `object`.getString("label")
videoList.add(Video(videoUrl, quality, videoUrl))
}
return videoList*/
/*if (jas.contains("sources")) {
val js = script.data()
val json = JSONObject(js)
val videoList = mutableListOf<Video>()
val jsonArray = json.getJSONArray("sources")
for (i in 0 until jsonArray.length()) {
val `object` = jsonArray.getJSONObject(i)
val videoUrl = `object`.getString("file")
Log.i("lol", videoUrl)
val quality = "Vidbom:" + `object`.getString("label")
videoList.add(Video(videoUrl, quality, videoUrl))
}
return videoList
} else {
val videoList = mutableListOf<Video>()
videoList.add(Video(url, "no 2video", null))
return videoList
}*/
}
}

View File

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.animeextension.ar.anime4up.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
class VidYardExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
val callPlayer = client.newCall(GET(url)).execute().body!!.string()
val data = callPlayer.substringAfter("hls\":[").substringBefore("]")
val sources = data.split("profile\":\"").drop(1)
val videoList = mutableListOf<Video>()
for (source in sources) {
val src = source.substringAfter("url\":\"").substringBefore("\"")
val quality = source.substringBefore("\"")
val video = Video(src, quality, src, headers = headers)
videoList.add(video)
}
return videoList
}
}

View File

@ -5,13 +5,14 @@ ext {
extName = 'AnimeTitans'
pkgNameSuffix = 'ar.animetitans'
extClass = '.AnimeTitans'
extVersionCode = 1
extVersionCode = 2
libVersion = '13'
}
dependencies {
implementation(project(':lib-fembed-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}

View File

@ -6,8 +6,10 @@ import android.util.Log
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.ar.animetitans.extractors.AnimeTitansExtractor
import eu.kanade.tachiyomi.animeextension.ar.animetitans.extractors.GdrivePlayerExtractor
import eu.kanade.tachiyomi.animeextension.ar.animetitans.extractors.SharedExtractor
import eu.kanade.tachiyomi.animeextension.ar.animetitans.extractors.VidBomExtractor
import eu.kanade.tachiyomi.animeextension.ar.animetitans.extractors.VidYardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
@ -111,12 +113,26 @@ class AnimeTitans : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val embedUrl = decoded.substringAfter("src=\"").substringBefore("\"")
Log.i("embedUrl", "$embedUrl")
when {
embedUrl.contains("vidyard")
-> {
val headers = headers.newBuilder()
.set("Referer", "https://play.vidyard.com")
.set("Accept-Encoding", "gzip, deflate, br")
.set("Accept-Language", "en-US,en;q=0.5")
.set("TE", "trailers")
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
.build()
val id = embedUrl.substringAfter("com/").substringBefore("?")
val vidUrl = "https://play.vidyard.com/player/" + id + ".json"
val videos = VidYardExtractor(client).videosFromUrl(vidUrl, headers)
videoList.addAll(videos)
}
embedUrl.contains("animetitans.net")
-> {
val headers = headers.newBuilder()
.set("Referer", "$baseUrl/")
.set("Referer", "https://animetitans.net/")
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36")
.set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
.set("Accept", "*/*")
.set("Accept-Language", "en-US,en;q=0.5")
.set("Accept-Encoding", "gzip, deflate, br")
.build()
@ -136,6 +152,12 @@ class AnimeTitans : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val videos = StreamSBExtractor(client).videosFromUrl(embedUrl, headers)
videoList.addAll(videos)
}
embedUrl.contains("drive.google")
-> {
val embedUrlG = "https://gdriveplayer.to/embed2.php?link=" + embedUrl
val videos = GdrivePlayerExtractor(client).videosFromUrl(embedUrlG)
videoList.addAll(videos)
}
embedUrl.contains("fembed") ||
embedUrl.contains("anime789.com") || embedUrl.contains("24hd.club") || embedUrl.contains("fembad.org") ||
embedUrl.contains("vcdn.io") || embedUrl.contains("sharinglink.club") || embedUrl.contains("moviemaniac.org") ||

View File

@ -0,0 +1,126 @@
package eu.kanade.tachiyomi.animeextension.ar.animetitans.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.decodeFromString
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+)")
}
}

View File

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.animeextension.ar.animetitans.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
class VidYardExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, headers: Headers): List<Video> {
val callPlayer = client.newCall(GET(url)).execute().body!!.string()
val data = callPlayer.substringAfter("hls\":[").substringBefore("]")
val sources = data.split("profile\":\"").drop(1)
val videoList = mutableListOf<Video>()
for (source in sources) {
val src = source.substringAfter("url\":\"").substringBefore("\"")
val quality = source.substringBefore("\"")
val video = Video(src, quality, src, headers = headers)
videoList.add(video)
}
return videoList
}
}