fix(pt/PiFansubs): Fix episode list, anime details and video extractor (#1625)

* fix: Update BaseURL

* refactor: Use GDrivePlayer shared-lib

* fix: Fix adorodoramas extractor

* chore: Bump version
This commit is contained in:
Claudemirovsky 2023-05-20 14:05:20 -03:00 committed by GitHub
parent d58bb7fc79
commit 7b15981991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 12 additions and 141 deletions

View File

@ -1,5 +1,5 @@
dependencies {
implementation(project(':lib-streamsb-extractor'))
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
implementation(project(':lib-gdriveplayer-extractor'))
}

View File

@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.animeextension.pt.pifansubs
import android.net.Uri
import eu.kanade.tachiyomi.animeextension.pt.pifansubs.extractors.AdoroDoramasExtractor
import eu.kanade.tachiyomi.animeextension.pt.pifansubs.extractors.GdrivePlayerExtractor
import eu.kanade.tachiyomi.animeextension.pt.pifansubs.extractors.JMVStreamExtractor
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
@ -16,7 +16,7 @@ import org.jsoup.nodes.Element
class PiFansubs : DooPlay(
"pt-BR",
"Pi Fansubs",
"https://pifansubs.org",
"https://pifansubs.club",
) {
override fun headersBuilder() = super.headersBuilder()
@ -45,15 +45,15 @@ class PiFansubs : DooPlay(
}
private fun getPlayerVideos(url: String): List<Video> {
val streamsbDomains = listOf("sbspeed", "sbanh", "streamsb", "sbfull", "sbbrisk")
val streamsbDomains = listOf("sbspeed", "sbanh", "streamsb", "sbfull", "sbbrisk", "lvturbo")
return when {
"player.jmvstream" in url ->
JMVStreamExtractor(client).videosFromUrl(url)
"gdriveplayer." in url ->
GdrivePlayerExtractor(client).videosFromUrl(url)
GdrivePlayerExtractor(client).videosFromUrl(url, "GdrivePlayer", headers)
streamsbDomains.any { it in url } ->
StreamSBExtractor(client).videosFromUrl(url, headers)
"adorodoramas.com" in url ->
"https://adorodoramas.com" in url ->
AdoroDoramasExtractor(client).videosFromUrl(url)
"/jwplayer/?source" in url -> {
val videoUrl = Uri.parse(url).getQueryParameter("source")!!

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.animeextension.pt.pifansubs.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.OkHttpClient
@ -12,13 +11,11 @@ class AdoroDoramasExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
val body = client.newCall(GET(url)).execute()
.use { it.body.string() }
val unpacked = JsUnpacker.unpackAndCombine(body)
?.replace("\\", "")
?: return emptyList<Video>()
val listStr = unpacked.substringAfter("sources:[").substringBefore("]")
return listStr.split("}").filter { it.isNotBlank() }.map {
val quality = it.substringAfter("label':'").substringBefore("'")
val videoUrl = it.substringAfter("file':'").substringBefore("'")
.substringAfter("sources: [")
.substringBefore("],")
return body.split("}").filter { it.isNotBlank() }.map {
val quality = it.substringAfter("size: ").substringBefore(" ") + "p"
val videoUrl = it.substringAfter("src: '").substringBefore("'")
Video(url, "$PLAYER_NAME - $quality", videoUrl)
}
}

View File

@ -1,126 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pifansubs.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()
.use { it.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

@ -20,7 +20,7 @@ class DooPlayGenerator : ThemeSourceGenerator {
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true),
SingleLang("pactedanime", "https://pactedanime.com", "en", isNsfw = false, overrideVersionCode = 4),
SingleLang("AnimeOnline360", "https://animeonline360.me", "en", isNsfw = false),
SingleLang("Pi Fansubs", "https://pifansubs.org", "pt-BR", isNsfw = true, overrideVersionCode = 14),
SingleLang("Pi Fansubs", "https://pifansubs.org", "pt-BR", isNsfw = true, overrideVersionCode = 15),
SingleLang("DonghuaX", "https://donghuax.com", "pt-BR", isNsfw = false),
)