fix(src/es): Fix video extraction and fix json parsing (#3140)

This commit is contained in:
imper1aldev 2024-04-16 00:52:40 -06:00 committed by GitHub
parent 0c552ed88e
commit 36b7d06ec7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 123 additions and 146 deletions

View File

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

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.animeextension.es.doramasflix
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
// -----------------------Season models------------------------// // -----------------------Season models------------------------//
@Serializable @Serializable
@ -27,7 +26,6 @@ data class ListSeason(
@SerialName("serie_name") @SerialName("serie_name")
val serieName: String?, val serieName: String?,
val poster: String?, val poster: String?,
val backdrop: String?,
@SerialName("__typename") @SerialName("__typename")
val typename: String, val typename: String,
) )
@ -53,50 +51,17 @@ data class ListEpisode(
val serieName: String?, val serieName: String?,
@SerialName("serie_name_es") @SerialName("serie_name_es")
val serieNameEs: String?, val serieNameEs: String?,
@SerialName("serie_id")
val serieId: String?,
@SerialName("still_path")
val stillPath: String?,
@SerialName("air_date") @SerialName("air_date")
val airDate: String?, val airDate: String?,
@SerialName("season_number") @SerialName("season_number")
val seasonNumber: Long?, val seasonNumber: Long?,
@SerialName("episode_number") @SerialName("episode_number")
val episodeNumber: Long?, val episodeNumber: Long?,
// val languages: List<Any?>,
val poster: String?, val poster: String?,
val backdrop: String?,
@SerialName("__typename") @SerialName("__typename")
val typename: String, val typename: String,
) )
// -----------------------Details Model------------------------//
data class DetailsModel(
val props: Props,
val page: String,
val query: Query,
val buildId: String,
val isFallback: Boolean,
val gip: Boolean,
)
data class Props(
val pageProps: PageProps,
)
data class PageProps(
val deviceType: String,
val slug: String,
// val apolloClient: Any?,
val apolloState: HashMap<String, HashMap<String, JsonObject>>,
val ssrComplete: Boolean,
)
data class Query(
val slug: String,
)
// -----------------------Pagination Model------------------------// // -----------------------Pagination Model------------------------//
@Serializable @Serializable
@ -136,67 +101,16 @@ data class Item(
@SerialName("name_es") @SerialName("name_es")
val nameEs: String?, val nameEs: String?,
val slug: String, val slug: String,
val cast: List<Cast> = emptyList(),
val names: String?, val names: String?,
val overview: String?, val overview: String?,
val languages: List<String> = emptyList(),
@SerialName("created_by")
val createdBy: List<CreatedBy> = emptyList(),
val popularity: Double?,
@SerialName("poster_path") @SerialName("poster_path")
val posterPath: String?, val posterPath: String?,
@SerialName("backdrop_path")
val backdropPath: String?,
@SerialName("first_air_date")
val firstAirDate: String?,
@SerialName("isTVShow")
val isTvshow: Boolean?,
val poster: String?, val poster: String?,
val backdrop: String?,
val genres: List<Genre> = emptyList(), val genres: List<Genre> = emptyList(),
val networks: List<Network> = emptyList(),
@SerialName("__typename") @SerialName("__typename")
val typename: String, val typename: String,
) )
@Serializable
data class Cast(
val adult: Boolean?,
val gender: Long?,
val id: Long?,
@SerialName("known_for_department")
val knownForDepartment: String?,
val name: String?,
@SerialName("original_name")
val originalName: String?,
val popularity: Double?,
@SerialName("profile_path")
val profilePath: String?,
val character: String?,
@SerialName("credit_id")
val creditId: String?,
val order: Long?,
)
@Serializable
data class CreatedBy(
val adult: Boolean?,
val gender: Long?,
val id: Long?,
@SerialName("known_for_department")
val knownForDepartment: String?,
val name: String?,
@SerialName("original_name")
val originalName: String?,
val popularity: Double?,
@SerialName("profile_path")
val profilePath: String?,
@SerialName("credit_id")
val creditId: String?,
val department: String?,
val job: String?,
)
@Serializable @Serializable
data class Genre( data class Genre(
val name: String?, val name: String?,
@ -205,14 +119,6 @@ data class Genre(
val typename: String?, val typename: String?,
) )
@Serializable
data class Network(
val name: String?,
val slug: String?,
@SerialName("__typename")
val typename: String?,
)
// -----------------------Search Model------------------------// // -----------------------Search Model------------------------//
@Serializable @Serializable
data class SearchModel( data class SearchModel(
@ -241,18 +147,6 @@ data class SearchDorama(
) )
// ------------------------------------------------------- // -------------------------------------------------------
@Serializable
data class VideoModel(
val json: JsonVideo = JsonVideo(),
)
@Serializable
data class JsonVideo(
val lang: String? = "",
val page: String? = "",
val link: String? = "",
val server: String? = "",
)
@Serializable @Serializable
data class VideoToken( data class VideoToken(
@ -274,7 +168,6 @@ data class TokenModel(
val isFallback: Boolean? = false, val isFallback: Boolean? = false,
val isExperimentalCompile: Boolean? = false, val isExperimentalCompile: Boolean? = false,
val gssp: Boolean? = false, val gssp: Boolean? = false,
// val scriptLoader: List<Any?>,
) )
@Serializable @Serializable

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Jkanime' extName = 'Jkanime'
extClass = '.Jkanime' extClass = '.Jkanime'
extVersionCode = 19 extVersionCode = 20
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
@ -10,4 +10,8 @@ dependencies {
implementation(project(':lib:okru-extractor')) implementation(project(':lib:okru-extractor'))
implementation(project(':lib:mixdrop-extractor')) implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:streamwish-extractor')) implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:voe-extractor'))
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.es.jkanime
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.jkanime.extractors.JkanimeExtractor import eu.kanade.tachiyomi.animeextension.es.jkanime.extractors.JkanimeExtractor
@ -13,11 +14,18 @@ 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.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.Serializable
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -49,13 +57,15 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360") private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server" private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Nozomi" private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf( private val SERVER_LIST = arrayOf(
"Okru", "Okru",
"Mixdrop", "Mixdrop",
"StreamWish", "StreamWish",
"Xtreme S", "Filemoon",
"HentaiJk", "Mp4Upload",
"StreamTape",
"Desuka",
"Nozomi", "Nozomi",
"Desu", "Desu",
) )
@ -114,33 +124,74 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException() override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoListParse(response: Response): List<Video> { private val languages = arrayOf(
val document = response.asJsoup() Pair("1", "[JAP]"),
val videos = mutableListOf<Video>() Pair("3", "[LAT]"),
document.select("div.col-lg-12.rounded.bg-servers.text-white.p-3.mt-2 a").forEach { it -> Pair("4", "[CHIN]"),
)
private fun String.getLang(): String {
return languages.firstOrNull { it.first == this }?.second ?: ""
}
private fun getVideoLinks(document: Document): List<Pair<String, String>> {
val servers = mutableListOf<Pair<String, String>>()
val scriptServers = document.selectFirst("script:containsData(var video = [];)")?.data() ?: return emptyList()
val jsServer = scriptServers.substringAfter("var remote = '").substringBefore("'")
val jsPath = scriptServers.substringAfter("= remote+'").substringBefore("'")
if (jsServer.isNotEmpty() && jsPath.isNotEmpty()) {
val jsLinks = client.newCall(GET(jsServer + jsPath)).execute().body.string()
.substringAfter("var servers = ").parseAs<Array<JsLinks>>().map {
Pair(String(Base64.decode(it.remote, Base64.DEFAULT)), "${it.lang}".getLang())
}
servers.addAll(jsLinks)
}
val htmlLinks = document.select("div.col-lg-12.rounded.bg-servers.text-white.p-3.mt-2 a").map {
val serverId = it.attr("data-id") val serverId = it.attr("data-id")
val langClass = it.attr("class") val lang = it.attr("class").substringAfter("lg_").substringBefore(" ").getLang()
val lang = if (langClass.contains("lg_3")) "[LAT]" else if (langClass.contains("lg_1")) "[JAP]" else "" val url = scriptServers
val scriptServers = document.selectFirst("script:containsData(var video = [];)")!! .substringAfter("video[$serverId] = '<iframe class=\"player_conte\" src=\"")
val url = scriptServers.data().substringAfter("video[$serverId] = '<iframe class=\"player_conte\" src=\"")
.substringBefore("\"") .substringBefore("\"")
.replace("/jkokru.php?u=", "http://ok.ru/videoembed/") .replace("/jkokru.php?u=", "http://ok.ru/videoembed/")
.replace("/jkvmixdrop.php?u=", "https://mixdrop.ag/e/") .replace("/jkvmixdrop.php?u=", "https://mixdrop.ag/e/")
.replace("/jksw.php?u=", "https://sfastwish.com/e/") .replace("/jksw.php?u=", "https://sfastwish.com/e/")
.replace("/jk.php?u=", "$baseUrl/") .replace("/jk.php?u=", "$baseUrl/")
Pair(if (url.contains("um2.php") || url.contains("um.php")) baseUrl + url else url, lang)
try { }
when { servers.addAll(htmlLinks)
"ok" in url -> OkruExtractor(client).videosFromUrl(url, "$lang ").forEach { videos.add(it) } return servers
"mixdrop" in url -> MixDropExtractor(client).videosFromUrl(url, prefix = "$lang ").forEach { videos.add(it) } }
"sfastwish" in url -> StreamWishExtractor(client, headers).videosFromUrl(url, prefix = "$lang StreamWish").forEach { videos.add(it) }
"stream/jkmedia" in url -> videos.add(Video(url, "$lang Xtreme S", url)) /*--------------------------------Video extractors------------------------------------*/
"um2.php" in url -> JkanimeExtractor(client).getNozomiFromUrl(baseUrl + url, "$lang ").let { if (it != null) videos.add(it) } private val okruExtractor by lazy { OkruExtractor(client) }
"um.php" in url -> JkanimeExtractor(client).getDesuFromUrl(baseUrl + url, "$lang ").let { if (it != null) videos.add(it) } private val voeExtractor by lazy { VoeExtractor(client) }
} private val filemoonExtractor by lazy { FilemoonExtractor(client) }
} catch (_: Exception) {} private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private val mixDropExtractor by lazy { MixDropExtractor(client) }
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
private val jkanimeExtractor by lazy { JkanimeExtractor(client) }
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return getVideoLinks(document).parallelCatchingFlatMapBlocking { (url, lang) ->
when {
"ok" in url -> okruExtractor.videosFromUrl(url, "$lang ")
"voe" in url -> voeExtractor.videosFromUrl(url, "$lang ")
"filemoon" in url || "moonplayer" in url -> filemoonExtractor.videosFromUrl(url, "$lang Filemoon:")
"streamtape" in url || "stp" in url || "stape" in url -> listOf(streamTapeExtractor.videoFromUrl(url, quality = "$lang StreamTape")!!)
"mp4upload" in url -> mp4uploadExtractor.videosFromUrl(url, prefix = "$lang ", headers = headers)
"mixdrop" in url || "mdbekjwqa" in url -> mixDropExtractor.videosFromUrl(url, prefix = "$lang ")
"sfastwish" in url || "wishembed" in url || "streamwish" in url || "strwish" in url || "wish" in url
-> streamWishExtractor.videosFromUrl(url, videoNameGen = { "$lang StreamWish:$it" })
"stream/jkmedia" in url -> jkanimeExtractor.getDesukaFromUrl(url, "$lang ")
"um2.php" in url -> jkanimeExtractor.getNozomiFromUrl(url, "$lang ")
"um.php" in url -> jkanimeExtractor.getDesuFromUrl(url, "$lang ")
else -> emptyList()
}
} }
return videos
} }
override fun videoListSelector() = throw UnsupportedOperationException() override fun videoListSelector() = throw UnsupportedOperationException()
@ -462,4 +513,12 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
}.also(screen::addPreference) }.also(screen::addPreference)
} }
@Serializable
data class JsLinks(
val remote: String? = null,
val server: String? = null,
val lang: Long? = null,
val slug: String? = null,
)
} }

View File

@ -4,17 +4,18 @@ import eu.kanade.tachiyomi.animesource.model.Video
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.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.Serializable
import okhttp3.Headers import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
class JkanimeExtractor( class JkanimeExtractor(
private val client: OkHttpClient, private val client: OkHttpClient,
) { ) {
fun getNozomiFromUrl(url: String, prefix: String = ""): Video? { fun getNozomiFromUrl(url: String, prefix: String = ""): List<Video> {
val dataKeyHeaders = Headers.Builder().add("Referer", url).build() val dataKeyHeaders = Headers.Builder().add("Referer", url).build()
val doc = client.newCall(GET(url, dataKeyHeaders)).execute().asJsoup() val doc = client.newCall(GET(url, dataKeyHeaders)).execute().asJsoup()
val dataKey = doc.select("form input[value]").attr("value") val dataKey = doc.select("form input[value]").attr("value")
@ -26,20 +27,40 @@ class JkanimeExtractor(
val nozomiBody = "v=$postKey".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()) val nozomiBody = "v=$postKey".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull())
val nozomiResponse = client.newCall(POST("https://jkanime.net/gsplay/api.php", body = nozomiBody)).execute() val nozomiResponse = client.newCall(POST("https://jkanime.net/gsplay/api.php", body = nozomiBody)).execute()
val nozomiUrl = JSONObject(nozomiResponse.body.string()).getString("file") val nozomiUrl = nozomiResponse.body.string().parseAs<NozomiResponse>().file ?: return emptyList()
if (nozomiResponse.isSuccessful && nozomiUrl.isNotBlank()) {
return Video(nozomiUrl, "${prefix}Nozomi", nozomiUrl) return listOf(Video(nozomiUrl, "${prefix}Nozomi", nozomiUrl))
}
return null
} }
fun getDesuFromUrl(url: String, prefix: String = ""): Video? { fun getDesuFromUrl(url: String, prefix: String = ""): List<Video> {
val document = client.newCall(GET(url)).execute() val document = client.newCall(GET(url)).execute()
val script = document.asJsoup().selectFirst("script:containsData(var parts = {)")!!.data() val streamUrl = document.asJsoup()
val streamUrl = script.substringAfter("url: '").substringBefore("'") .selectFirst("script:containsData(var parts = {)")
if (document.isSuccessful && streamUrl.isNotBlank()) { ?.data()?.substringAfter("url: '")
return Video(streamUrl, "${prefix}Desu", streamUrl) ?.substringBefore("'") ?: return emptyList()
}
return null return listOf(Video(streamUrl, "${prefix}Desu", streamUrl))
} }
fun getDesukaFromUrl(url: String, prefix: String = ""): List<Video> {
val document = client.newCall(GET(url)).execute()
val contentType = document.header("Content-Type") ?: ""
if (contentType.startsWith("video/")) {
val realUrl = document.networkResponse.toString()
.substringAfter("url=")
.substringBefore("}")
return listOf(Video(realUrl, "${prefix}Desuka", realUrl))
}
val streamUrl = document.asJsoup()
.selectFirst("script:containsData(new DPlayer({)")
?.data()?.substringAfter("url: '")
?.substringBefore("'") ?: return emptyList()
return listOf(Video(streamUrl, "${prefix}Desuka", streamUrl))
}
@Serializable
data class NozomiResponse(val file: String? = null)
} }