fix(src/en): Fix AnimeOwl (#241)

Closes #199
This commit is contained in:
imper1aldev 2024-09-14 19:56:41 -06:00 committed by GitHub
parent 5e0d900321
commit c6ad6636ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 55 deletions

View file

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

View file

@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelFlatMap import eu.kanade.tachiyomi.util.parallelFlatMap
import eu.kanade.tachiyomi.util.parseAs import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
@ -35,12 +34,11 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.math.ceil import kotlin.math.ceil
@ExperimentalSerializationApi
class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeOwl" override val name = "AnimeOwl"
override val baseUrl = "https://animeowl.us" override val baseUrl = "https://animeowl.live"
override val lang = "en" override val lang = "en"
@ -109,6 +107,7 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
genre = document.select("div.genre > a").joinToString { it.text() } genre = document.select("div.genre > a").joinToString { it.text() }
author = document.select("div.type > a").text() author = document.select("div.type > a").text()
status = parseStatus(document.select("div.status > span").text()) status = parseStatus(document.select("div.status > span").text())
thumbnail_url = document.selectFirst(".cover-img-container > img")?.attr("abs:src")
description = buildString { description = buildString {
document.select("div.anime-desc.desc-content").text() document.select("div.anime-desc.desc-content").text()
.takeIf { it.isNotBlank() } .takeIf { it.isNotBlank() }
@ -127,32 +126,33 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val animeId = response.asJsoup().select("div#unq-anime-id").attr("animeId") val document = response.asJsoup()
val episodes = client.newCall( val sub = document.select("#anime-cover-sub-content .episode-node").mapIndexed { idx, it ->
GET("$baseUrl/api/anime/$animeId/episodes"), EpisodeResponse.Episode(
).execute() id = it.text().toDouble(),
.parseAs<EpisodeResponse>() episodeIndex = idx.toString(),
name = it.text(),
return listOf( lang = "Sub",
episodes.sub.map { it.copy(lang = "Sub") }, href = it.attr("abs:href"),
episodes.dub.map { it.copy(lang = "Dub") },
).flatten()
.groupBy { it.name }
.map { (epNum, epList) ->
SEpisode.create().apply {
url = LinkData(
epList.map { ep ->
Link(
ep.buildUrl(episodes.subSlug, episodes.dubSlug),
ep.lang!!,
) )
}, }
).toJsonString() val dub = document.select("#anime-cover-dub-content .episode-node").mapIndexed { idx, it ->
EpisodeResponse.Episode(
id = it.text().toDouble(),
episodeIndex = idx.toString(),
name = it.text(),
lang = "Dub",
href = it.attr("abs:href"),
)
}
return listOf(sub, dub).flatten().groupBy { it.name }.map { (epNum, epList) ->
SEpisode.create().apply {
url = LinkData(epList.map { ep -> Link(ep.href!!, ep.lang!!) }).toJsonString()
episode_number = epNum.toFloatOrNull() ?: 0F episode_number = epNum.toFloatOrNull() ?: 0F
name = "Episode $epNum" name = "Episode $epNum"
} }
} }.sortedByDescending { it.episode_number }
.sortedByDescending { it.episode_number }
} }
override fun episodeListSelector(): String = throw UnsupportedOperationException() override fun episodeListSelector(): String = throw UnsupportedOperationException()
@ -160,9 +160,9 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException() override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
// ============================ Video Links ============================= // ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> = override suspend fun getVideoList(episode: SEpisode): List<Video> {
json.decodeFromString<LinkData>(episode.url) return json.decodeFromString<LinkData>(episode.url).links.parallelFlatMap { owlServersExtractor.extractOwlVideo(it) }.sort()
.links.parallelFlatMap { owlServersExtractor.extractOwlVideo(it) }.sort() }
override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException() override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException()
@ -228,15 +228,7 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
) )
} }
private fun LinkData.toJsonString(): String { private fun LinkData.toJsonString(): String = json.encodeToString(this)
return json.encodeToString(this)
}
private fun EpisodeResponse.Episode.buildUrl(subSlug: String, dubSlug: String): String =
when (lang) {
"dub" -> dubSlug
else -> subSlug
}.let { "$baseUrl/watch/$it/$episodeIndex" }
private fun parseStatus(statusString: String): Int = private fun parseStatus(statusString: String): Int =
when (statusString) { when (statusString) {

View file

@ -31,11 +31,12 @@ data class EpisodeResponse(
) { ) {
@Serializable @Serializable
data class Episode( data class Episode(
val id: Int, val id: Double? = null,
val name: String, val name: String,
val lang: String? = null, val lang: String? = null,
@SerialName("episode_index") @SerialName("episode_index")
val episodeIndex: String, val episodeIndex: String,
val href: String? = null,
) )
} }

View file

@ -36,18 +36,18 @@ class OwlExtractor(private val client: OkHttpClient, private val baseUrl: String
} }
.let(Deobfuscator::deobfuscateScript) .let(Deobfuscator::deobfuscateScript)
?: throw Exception("Unable to get clean JS") ?: throw Exception("Unable to get clean JS")
val jwt = JWT_REGEX.find(epJS)?.groupValues?.get(1) ?: throw Exception("Unable to get jwt")
val jwt = findFirstJwt(epJS) ?: throw Exception("Unable to get jwt")
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val servers = client.newCall(GET("$baseUrl$dataSrc")).execute() val servers = client.newCall(GET("$baseUrl$dataSrc")).execute()
.parseAs<OwlServers>() .parseAs<OwlServers>()
coroutineScope { coroutineScope {
val lufDeferred = async { val lufDeferred = async {
servers.luffy?.let { luffy -> servers.luffy?.let { luffy ->
noRedirectClient.newCall(GET("${luffy}$jwt")).execute() noRedirectClient.newCall(GET("${luffy}$jwt")).execute()
.use { it.headers["Location"] } .use { it.headers["Location"] }
?.let { videoList.add(Video(it, "Luffy - ${link.lang} - 1080p", it)) } ?.let { videoList.add(Video(it, "${link.lang} Luffy:1080p", it)) }
} }
} }
val kaiDeferred = async { val kaiDeferred = async {
@ -70,18 +70,20 @@ class OwlExtractor(private val client: OkHttpClient, private val baseUrl: String
return videoList return videoList
} }
private fun getHLS(url: String, server: String, lang: String): List<Video> = private fun getHLS(url: String, server: String, lang: String): List<Video> {
client.newCall(GET(url)).execute() return client.newCall(GET(url)).execute().let {
.parseAs<Stream>() if (it.isSuccessful) {
.url it.parseAs<Stream>().url.let {
.let { playlistUtils.extractFromHls(it, videoNameGen = { qty -> "$lang $server:$qty" })
playlistUtils.extractFromHls( }
it, } else {
videoNameGen = { qty -> "$server - $lang - $qty" }, emptyList()
) }
}
} }
companion object { private fun findFirstJwt(text: String): String? {
private val JWT_REGEX by lazy { "const\\s+(?:[A-Za-z0-9_]*)\\s*=\\s*'([^']+)'".toRegex() } val jwtPattern = Regex("['\"]([A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+)['\"]")
return jwtPattern.find(text)?.groupValues?.get(1)
} }
} }