fix(pt/animesdigital): Fix video extractor (#2892)

This commit is contained in:
Claudemirovsky 2024-02-08 10:46:54 -03:00 committed by GitHub
parent 566b9e43f0
commit a77dcad9cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 85 additions and 57 deletions

View File

@ -8,4 +8,4 @@ apply from: "$rootDir/common.gradle"
dependencies { dependencies {
implementation(project(":lib:unpacker")) implementation(project(":lib:unpacker"))
} }

View File

@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.animeextension.pt.animesdigital
import android.app.Application import android.app.Application
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.animesdigital.extractors.ProtectorExtractor
import eu.kanade.tachiyomi.animeextension.pt.animesdigital.extractors.ScriptExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.AnimesPage
@ -10,14 +12,12 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parseAs import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
@ -27,7 +27,6 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
@ -41,8 +40,6 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl) override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl)
private val json: Json by injectLazy()
private val preferences by lazy { private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
@ -126,7 +123,7 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage {
return runCatching { return runCatching {
val data = response.parseAs<SearchResponseDto>() val data = response.parseAs<SearchResponseDto>()
val animes = data.results.map(Jsoup::parse) val animes = data.results.map(Jsoup::parseBodyFragment)
.mapNotNull { it.selectFirst(searchAnimeSelector()) } .mapNotNull { it.selectFirst(searchAnimeSelector()) }
.map(::searchAnimeFromElement) .map(::searchAnimeFromElement)
val hasNext = data.total_page > data.page val hasNext = data.total_page > data.page
@ -156,15 +153,15 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
else -> SAnime.UNKNOWN else -> SAnime.UNKNOWN
} }
val infos = doc.selectFirst("div.crw > div.dados")!! with(doc.selectFirst("div.crw > div.dados")!!) {
artist = getInfo("Estúdio")
author = getInfo("Autor") ?: getInfo("Diretor")
artist = infos.getInfo("Estúdio") title = selectFirst("h1")!!.text()
author = infos.getInfo("Autor") ?: infos.getInfo("Diretor") genre = select("div.genre a").eachText().joinToString()
title = infos.selectFirst("h1")!!.text() description = selectFirst("div.sinopse")?.text()
genre = infos.select("div.genre a").eachText().joinToString() }
description = infos.selectFirst("div.sinopse")?.text()
} }
// ============================== Episodes ============================== // ============================== Episodes ==============================
@ -195,9 +192,9 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
episode_number = epname.substringAfterLast(" ").toFloatOrNull() ?: 1F episode_number = epname.substringAfterLast(" ").toFloatOrNull() ?: 1F
name = buildString { name = buildString {
append(epname) append(epname)
element.selectFirst("div.sub_title")?.text()?.let { element.selectFirst("div.sub_title")?.text()?.also {
if (!it.contains("Ainda não tem um titulo oficial")) { if (!it.contains("Ainda não tem um titulo oficial")) {
append(" - $it") append(" - ", it)
} }
} }
} }
@ -207,7 +204,9 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val player = response.asJsoup().selectFirst("div#player")!! val player = response.asJsoup().selectFirst("div#player")!!
return player.select("div.tab-video").flatMap { div -> return player.select("div.tab-video").flatMap { div ->
div.select(videoListSelector()).flatMap { element -> val noComment = div.outerHtml().replace("<!--", "").replace("-->", "")
val newDoc = Jsoup.parseBodyFragment(noComment)
newDoc.select(videoListSelector()).ifEmpty { newDoc.select("a") }.flatMap { element ->
runCatching { runCatching {
videosFromElement(element) videosFromElement(element)
}.onFailure { it.printStackTrace() }.getOrElse { emptyList() } }.onFailure { it.printStackTrace() }.getOrElse { emptyList() }
@ -215,52 +214,28 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
private val protectorExtractor by lazy { ProtectorExtractor(client) }
private fun videosFromElement(element: Element): List<Video> { private fun videosFromElement(element: Element): List<Video> {
return when (element.tagName()) { return when (element.tagName()) {
"iframe" -> { "iframe" -> {
val url = element.attr("data-lazy-src").ifEmpty { element.attr("src") } val url = element.absUrl("data-lazy-src").ifEmpty { element.absUrl("src") }
.let {
when {
it.startsWith("/") -> baseUrl + it
else -> it
}
}
client.newCall(GET(url, headers)).execute() client.newCall(GET(url, headers)).execute()
.asJsoup() .asJsoup()
.select(videoListSelector()) .select(videoListSelector())
.flatMap(::videosFromElement) .flatMap(::videosFromElement)
} }
"script" -> { "script" -> ScriptExtractor.videosFromScript(element.data(), headers)
val scriptData = element.data().let { "a" -> protectorExtractor.videosFromUrl(element.attr("href"))
when {
"eval(function" in it -> Unpacker.unpack(it)
else -> it
}
}.ifEmpty { null }?.replace("\\", "")
scriptData?.let(::videosFromScript).orEmpty()
}
else -> emptyList() else -> emptyList()
} }
} }
private fun videosFromScript(script: String): List<Video> { private val scriptSelectors = listOf("eval", "player.src", "this.src", "sources:")
return script.substringAfter("sources:").substringAfter(".src(") .joinToString { "script:containsData($it):not(:containsData(/bg.mp4))" }
.substringBefore(")")
.substringAfter("[")
.substringBefore("]")
.split("{")
.drop(1)
.map {
val quality = it.substringAfter("label", "")
.substringAfterKey()
.ifEmpty { name }
val url = it.substringAfter("file").substringAfter("src")
.substringAfterKey()
Video(url, quality, url, headers)
}
}
override fun videoListSelector() = "iframe, script:containsData(eval), script:containsData(player.src), script:containsData(this.src), script:containsData(sources:)" override fun videoListSelector() = "iframe, $scriptSelectors"
override fun videoFromElement(element: Element): Video { override fun videoFromElement(element: Element): Video {
throw UnsupportedOperationException() throw UnsupportedOperationException()
@ -285,7 +260,7 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.let(screen::addPreference) }.also(screen::addPreference)
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
@ -312,12 +287,6 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
private fun String.substringAfterKey() = substringAfter(":")
.substringAfter('"')
.substringBefore('"')
.substringAfter("'")
.substringBefore("'")
companion object { companion object {
const val PREFIX_SEARCH = "id:" const val PREFIX_SEARCH = "id:"

View File

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.animeextension.pt.animesdigital.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
private const val HOST = "https://sabornutritivo.com"
class ProtectorExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
val fixedUrl = if (!url.startsWith("https")) "https:$url" else url
val token = fixedUrl.toHttpUrl().queryParameter("token")!!
val headers = Headers.headersOf("cookie", "token=$token;")
val doc = client.newCall(GET("$HOST/social.php", headers)).execute().asJsoup()
val videoHeaders = Headers.headersOf("referer", doc.location())
val iframeUrl = doc.selectFirst("iframe")!!.attr("src").trim()
return listOf(Video(iframeUrl, "Animes Digital", iframeUrl, videoHeaders))
}
}

View File

@ -0,0 +1,37 @@
package eu.kanade.tachiyomi.animeextension.pt.animesdigital.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
import okhttp3.Headers
object ScriptExtractor {
fun videosFromScript(scriptData: String, headers: Headers): List<Video> {
val script = when {
"eval(function" in scriptData -> Unpacker.unpack(scriptData)
else -> scriptData
}.ifEmpty { null }?.replace("\\", "") ?: return emptyList()
return script.substringAfter("sources:").substringAfter(".src(")
.substringBefore(")")
.substringAfter("[")
.substringBefore("]")
.split("{")
.drop(1)
.map {
val quality = it.substringAfter("label", "")
.substringAfterKey()
.trim()
.ifEmpty { "Animes Digital" }
val url = it.substringAfter("file").substringAfter("src")
.substringAfterKey()
.trim()
Video(url, quality, url, headers)
}
}
private fun String.substringAfterKey() = substringAfter(':')
.substringAfter('"')
.substringBefore('"')
.substringAfter("'")
.substringBefore("'")
}