From 01d32cf2a33419cde42ea5c41da73733bdfed34f Mon Sep 17 00:00:00 2001 From: Samfun75 <38332931+Samfun75@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:53:09 +0300 Subject: [PATCH] fix(en/animeowl): Fix video extractor without webView and refactor (#2842) --- src/en/animeowl/build.gradle | 5 +- .../animeextension/en/animeowl/AnimeOwl.kt | 424 ++++++++---------- .../animeextension/en/animeowl/AnimeOwlDto.kt | 63 +++ .../en/animeowl/FileRequestInterceptor.kt | 86 ---- .../animeowl/extractors/GogoCdnExtractor.kt | 123 ----- .../en/animeowl/extractors/OwlExtractor.kt | 87 ++++ 6 files changed, 339 insertions(+), 449 deletions(-) create mode 100644 src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwlDto.kt delete mode 100644 src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/FileRequestInterceptor.kt delete mode 100644 src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/extractors/GogoCdnExtractor.kt create mode 100644 src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/extractors/OwlExtractor.kt diff --git a/src/en/animeowl/build.gradle b/src/en/animeowl/build.gradle index 6e7ad6b44b..40068b0e39 100644 --- a/src/en/animeowl/build.gradle +++ b/src/en/animeowl/build.gradle @@ -1,11 +1,12 @@ ext { extName = 'AnimeOwl' extClass = '.AnimeOwl' - extVersionCode = 14 + extVersionCode = 15 } apply from: "$rootDir/common.gradle" dependencies { - implementation(project(':lib:dood-extractor')) + implementation(project(":lib:synchrony")) + implementation(project(":lib:playlist-utils")) } \ No newline at end of file diff --git a/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt b/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt index 5e6464b24b..5de012b579 100644 --- a/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt +++ b/src/en/animeowl/src/eu/kanade/tachiyomi/animeextension/en/animeowl/AnimeOwl.kt @@ -4,7 +4,7 @@ import android.app.Application import android.content.SharedPreferences import androidx.preference.ListPreference import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.animeextension.en.animeowl.extractors.GogoCdnExtractor +import eu.kanade.tachiyomi.animeextension.en.animeowl.extractors.OwlExtractor import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage @@ -12,26 +12,22 @@ import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource -import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelFlatMap +import eu.kanade.tachiyomi.util.parseAs import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.float -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt @@ -44,7 +40,7 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val name = "AnimeOwl" - override val baseUrl = "https://anime-owl.net" + override val baseUrl = "https://animeowl.us" override val lang = "en" @@ -56,111 +52,107 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() { Injekt.get().getSharedPreferences("source_$id", 0x0000) } + private val owlServersExtractor by lazy { OwlExtractor(client, baseUrl) } + // ============================== Popular =============================== override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/trending?page=$page") - override fun popularAnimeSelector(): String = "div#anime-list > div.recent-anime" + override fun popularAnimeSelector(): String = "div#anime-list > div" - override fun popularAnimeNextPageSelector(): String = "ul.pagination > li.page-item > a[rel=next]" + override fun popularAnimeNextPageSelector(): String = "ul.pagination > li > a[rel=next]" override fun popularAnimeFromElement(element: Element): SAnime { return SAnime.create().apply { - setUrlWithoutDomain(element.select("div > a").attr("href")) - thumbnail_url = element.select("div.img-container > a > img").attr("src") - title = element.select("a.title-link").text() + setUrlWithoutDomain(element.select("a.title-link").attr("href")) + thumbnail_url = element.select("img[data-src]").attr("data-src") + title = element.select("a.title-link h3").text() } } // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/recent-episode/all") + override suspend fun getLatestUpdates(page: Int): AnimesPage = + advancedSearchAnime(page, sort = Sort.Latest) + + override fun latestUpdatesRequest(page: Int): Request = + throw UnsupportedOperationException() - override fun latestUpdatesSelector(): String = popularAnimeSelector() + override fun latestUpdatesSelector(): String = + throw UnsupportedOperationException() - override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() + override fun latestUpdatesNextPageSelector(): String = + throw UnsupportedOperationException() - override fun latestUpdatesFromElement(element: Element): SAnime = popularAnimeFromElement(element) + override fun latestUpdatesFromElement(element: Element): SAnime = + throw UnsupportedOperationException() // =============================== Search =============================== override suspend fun getSearchAnime( page: Int, query: String, filters: AnimeFilterList, - ): AnimesPage { - val limit = 30 - val mediaType = "application/json; charset=utf-8".toMediaType() - val body = """{"limit":$limit,"page":${page - 1},"pageCount":0,"value":"$query","sort":4,"selected":{"type":[],"genre":[],"year":[],"country":[],"season":[],"status":[],"sort":[],"language":[]}}""".toRequestBody(mediaType) - - val response = client.newCall(POST("$baseUrl/api/advance-search", body = body, headers = headers)).execute() - val result = json.decodeFromString(response.body.string()) - - val total = result["total"]!!.jsonPrimitive.int - val nextPage = ceil(total.toFloat() / limit).toInt() > page - val data = result["results"]!!.jsonArray - val animes = data.map { item -> - SAnime.create().apply { - setUrlWithoutDomain("/anime/${item.jsonObject["anime_slug"]!!.jsonPrimitive.content}/") - thumbnail_url = "$baseUrl${item.jsonObject["image"]!!.jsonPrimitive.content}" - title = item.jsonObject["anime_name"]!!.jsonPrimitive.content - } - } - - return AnimesPage(animes, nextPage) - } + ): AnimesPage = advancedSearchAnime(page, sort = Sort.Search, query = query) override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw UnsupportedOperationException() - override fun searchAnimeSelector(): String = throw UnsupportedOperationException() + override fun searchAnimeSelector(): String = + throw UnsupportedOperationException() - override fun searchAnimeNextPageSelector(): String = throw UnsupportedOperationException() + override fun searchAnimeNextPageSelector(): String = + throw UnsupportedOperationException() - override fun searchAnimeFromElement(element: Element): SAnime = throw UnsupportedOperationException() + override fun searchAnimeFromElement(element: Element): SAnime = + throw UnsupportedOperationException() // =========================== Anime Details ============================ - override fun animeDetailsParse(document: Document): SAnime { - val anime = SAnime.create() - anime.title = document.select("h3.anime-name").text() - anime.genre = document.select("div.genre > a").joinToString { it.text() } - anime.description = document.select("div.anime-desc.desc-content").text() - // No author info so use type of anime - anime.author = document.select("div.type > a").text() - anime.status = parseStatus(document.select("div.status > span").text()) - - // add alternative name to anime description - val altName = "Other name(s): " - document.select("h4.anime-alternatives").text()?.let { - if (it.isBlank().not()) { - anime.description = when { - anime.description.isNullOrBlank() -> altName + it - else -> anime.description + "\n\n$altName" + it + override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply { + genre = document.select("div.genre > a").joinToString { it.text() } + author = document.select("div.type > a").text() + status = parseStatus(document.select("div.status > span").text()) + description = buildString { + document.select("div.anime-desc.desc-content").text() + .takeIf { it.isNotBlank() } + ?.let { + appendLine(it) + appendLine() + } + document.select("h4.anime-alternatives").text() + .takeIf { it.isNotBlank() } + ?.let { + append("Other name(s): ") + append(it) } - } } - return anime } // ============================== Episodes ============================== override fun episodeListParse(response: Response): List { val animeId = response.asJsoup().select("div#unq-anime-id").attr("animeId") - val episodesJson = client.newCall(GET("$baseUrl/api/anime/$animeId/episodes")).execute().body.string() - val episodes = json.decodeFromString(episodesJson) - val subList = episodes["sub"]!!.jsonArray - val dubList = episodes["dub"]!!.jsonArray - val subSlug = episodes["sub_slug"]!!.jsonPrimitive.content - val dubSlug = episodes["dub_slug"]!!.jsonPrimitive.content - return subList.map { item -> - val dub = dubList.find { - it.jsonObject["name"]!!.jsonPrimitive.content == item.jsonObject["name"]!!.jsonPrimitive.content - } - SEpisode.create().apply { - url = "{\"Sub\": \"https://portablegaming.co/watch/$subSlug/${item.jsonObject["episode_index"]!!.jsonPrimitive.content}\"," + - if (dub != null) { - "\"Dub\": \"https://portablegaming.co/watch/$dubSlug/${dub.jsonObject["episode_index"]!!.jsonPrimitive.content}\"}" - } else { "\"Dub\": \"\"}" } - episode_number = item.jsonObject["name"]!!.jsonPrimitive.float - name = "Episode " + item.jsonObject["name"]!!.jsonPrimitive.content + val episodes = client.newCall( + GET("$baseUrl/api/anime/$animeId/episodes"), + ).execute() + .parseAs() + + return listOf( + episodes.sub.map { it.copy(lang = "Sub") }, + 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() + episode_number = epNum.toFloatOrNull() ?: 0F + name = "Episode $epNum" + } } - }.reversed() + .sortedByDescending { it.episode_number } } override fun episodeListSelector(): String = throw UnsupportedOperationException() @@ -168,34 +160,9 @@ class AnimeOwl : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException() // ============================ Video Links ============================= - override suspend fun getVideoList(episode: SEpisode): List