fix(src/es): JkAnime fixes (#504)

* JkAnime fixes

Closes #355

* Update StreamWishExtractor.kt
This commit is contained in:
imper1aldev 2025-01-09 05:34:54 -06:00 committed by GitHub
parent 48f1bac534
commit f9650081e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 101 additions and 27 deletions

View file

@ -1,15 +1,20 @@
package eu.kanade.tachiyomi.lib.streamwishextractor
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.OkHttpClient
class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
private val json = Json { isLenient = true; ignoreUnknownKeys = true }
fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix - $it" }
@ -32,7 +37,9 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers:
?.takeIf(String::isNotBlank)
?: return emptyList()
return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen)
val subtitleList = extractSubtitles(scriptBody)
return playlistUtils.extractFromHls(masterUrl, url, videoNameGen = videoNameGen, subtitleList = subtitleList)
}
private fun getEmbedUrl(url: String): String {
@ -43,4 +50,21 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers:
url
}
}
private fun extractSubtitles(script: String): List<Track> {
return try {
val subtitleStr = script
.substringAfter("tracks")
.substringAfter("[")
.substringBefore("]")
json.decodeFromString<List<TrackDto>>("[$subtitleStr]")
.filter { it.kind.equals("captions", true) }
.map { Track(it.file, it.label ?: "") }
} catch (e: SerializationException) {
emptyList()
}
}
@Serializable
private data class TrackDto(val file: String, val kind: String, val label: String? = null)
}

View file

@ -1,7 +1,7 @@
ext {
extName = 'Jkanime'
extClass = '.Jkanime'
extVersionCode = 25
extVersionCode = 26
}
apply from: "$rootDir/common.gradle"
@ -14,4 +14,6 @@ dependencies {
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:vidhide-extractor'))
implementation 'org.mozilla:rhino:1.7.14'
}

View file

@ -20,16 +20,21 @@ import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
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.vidhideextractor.VidHideExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.mozilla.javascript.Context
import org.mozilla.javascript.Scriptable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -43,6 +48,13 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true
private val json = Json {
ignoreUnknownKeys = true
isLenient = true
prettyPrint = true
coerceInputValues = true
}
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -172,6 +184,7 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private val mixDropExtractor by lazy { MixDropExtractor(client) }
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
private val vidHideExtractor by lazy { VidHideExtractor(client, headers) }
private val jkanimeExtractor by lazy { JkanimeExtractor(client) }
override fun videoListParse(response: Response): List<Video> {
@ -180,12 +193,12 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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" })
arrayOf("filemoon", "filemooon", "moon").any(url) -> filemoonExtractor.videosFromUrl(url, "$lang Filemoon:")
arrayOf("streamtape", "stp", "stape").any(url) -> listOf(streamTapeExtractor.videoFromUrl(url, quality = "$lang StreamTape")!!)
arrayOf("mp4upload").any(url) -> mp4uploadExtractor.videosFromUrl(url, prefix = "$lang ", headers = headers)
arrayOf("vidhide", "filelions.top", "vid.").any(url) -> vidHideExtractor.videosFromUrl(url) { "$lang VidHide:$it" }
arrayOf("mixdrop", "mdbekjwqa").any(url) -> mixDropExtractor.videosFromUrl(url, prefix = "$lang ")
arrayOf("wishembed", "streamwish", "strwish", "wish").any(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 ")
@ -277,23 +290,56 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
anime
}
} else { // is filtered
document.select(".card.mb-3.custom_item2").map { animeData ->
latestUpdatesFromElement(animeData)
}
document.selectFirst("script:containsData(var animes =)")?.data()?.let { script ->
parseJavaScriptArray(decodeUnicode(script.substringAfter("var animes = ").substringBefore("];")) + "]")?.let { jsonData ->
json.decodeFromString<List<SearchAnimeDto>>(jsonData).map {
SAnime.create().apply {
title = it.title ?: ""
thumbnail_url = it.image
setUrlWithoutDomain("$baseUrl/${it.slug}")
}
}
}
} ?: emptyList()
}
return AnimesPage(animeList, hasNextPage)
}
private fun parseJavaScriptArray(jsCode: String): String? {
return try {
val cx = Context.enter()
cx.optimizationLevel = -1
val scope: Scriptable = cx.initStandardObjects()
val script = """
var animes = $jsCode;
JSON.stringify(animes);
""".trimIndent()
cx.evaluateString(scope, script, "script", 1, null) as String
} catch (e: Exception) {
null
} finally {
Context.exit()
}
}
private fun decodeUnicode(input: String): String {
val unicodePattern = Regex("""\\u([0-9a-fA-F]{4})""")
return unicodePattern.replace(input) { match ->
val charCode = match.groupValues[1].toInt(16)
charCode.toChar().toString()
}
}
override fun searchAnimeFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun searchAnimeNextPageSelector(): String = throw UnsupportedOperationException()
override fun searchAnimeSelector(): String = throw UnsupportedOperationException()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("div.col-lg-3 div.anime__details__pic.set-bg")!!.attr("data-setbg")
anime.title = document.selectFirst("div.anime__details__text div.anime__details__title h3")!!.text()
anime.description = document.selectFirst("div.col-lg-9 div.anime__details__text p")!!.ownText()
document.select("div.row div.col-lg-6.col-md-6 ul li").forEach { animeData ->
anime.thumbnail_url = document.selectFirst(".anime__details__content .set-bg")?.attr("data-setbg")
anime.title = document.selectFirst(".anime__details__title h3")?.text() ?: ""
anime.description = document.selectFirst(".sinopsis")?.ownText()
document.select(".aninfo li").forEach { animeData ->
val data = animeData.select("span").text()
if (data.contains("Genero")) {
anime.genre = animeData.select("a").joinToString { it.text() }
@ -318,20 +364,13 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
}
override fun latestUpdatesNextPageSelector(): String = "div.container div.navigation a.text.nav-next"
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select(".custom_thumb2 > a").attr("abs:href"))
anime.title = element.select(".card-title > a").text()
anime.thumbnail_url = element.select(".custom_thumb2 a img").attr("abs:src")
anime.description = element.select(".synopsis").text()
return anime
}
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/directorio/$page/desc/")
override fun latestUpdatesSelector(): String = "div.card.mb-3.custom_item2"
override fun latestUpdatesParse(response: Response): AnimesPage = searchAnimeParse(response)
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto no incluye todos los filtros"),
@ -459,6 +498,8 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private class Tags(name: String) : AnimeFilter.Text(name)
private fun Array<String>.any(url: String): Boolean = this.any { url.contains(it, ignoreCase = true) }
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
@ -521,4 +562,11 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val lang: Long? = null,
val slug: String? = null,
)
@Serializable
data class SearchAnimeDto(
@SerialName("title") var title: String? = null,
@SerialName("image") var image: String? = null,
@SerialName("slug") var slug: String? = null,
)
}