diff --git a/src/en/nineanime/build.gradle b/src/en/nineanime/build.gradle index 88afe35f1..033d62717 100644 --- a/src/en/nineanime/build.gradle +++ b/src/en/nineanime/build.gradle @@ -1,16 +1,19 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = '9anime' pkgNameSuffix = 'en.nineanime' extClass = '.NineAnime' - extVersionCode = 31 + extVersionCode = 32 libVersion = '13' } dependencies { compileOnly libs.bundles.coroutines + implementation (project(':lib-streamtape-extractor')) + implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" } apply from: "$rootDir/common.gradle" diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/JsVrfInterceptor.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/JsVrfInterceptor.kt index 3052c2bb6..3cfbb51d6 100644 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/JsVrfInterceptor.kt +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/JsVrfInterceptor.kt @@ -21,6 +21,7 @@ class JsVrfInterceptor(private val baseUrl: String) { fun wake() = "" fun getVrf(query: String): String { + if (query.isBlank()) return "" val jscript = getJs(query) val cdl = CountDownLatch(1) var vrf = "" diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt index 910910b6d..fe9c7658d 100644 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt @@ -11,6 +11,8 @@ 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.streamtapeextractor.StreamTapeExtractor +import eu.kanade.tachiyomi.animeextension.en.nineanime.extractors.Mp4uploadExtractor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.util.asJsoup @@ -18,6 +20,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject @@ -58,161 +62,40 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { return Headers.Builder().add("Referer", baseUrl) } - override fun popularAnimeSelector(): String = "div.ani.items > div" + // ============================== Popular =============================== override fun popularAnimeRequest(page: Int): Request { - // make the vrf webview available beforehand. please find another solution for this :) + // make the vrf webview available beforehand vrfInterceptor.wake() return GET("$baseUrl/filter?sort=trending&page=$page") } + override fun popularAnimeSelector(): String = "div.ani.items > div" + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { setUrlWithoutDomain(element.select("a.name").attr("href").substringBefore("?")) thumbnail_url = element.select("div.poster img").attr("src") title = element.select("a.name").text() } - override fun popularAnimeNextPageSelector(): String = "nav > ul.pagination > li > a[aria-label=pagination.next]" + override fun popularAnimeNextPageSelector(): String = + "nav > ul.pagination > li > a[aria-label=pagination.next]" - override fun episodeListRequest(anime: SAnime): Request { - val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup().selectFirst("div[data-id]").attr("data-id") - val vrf = vrfInterceptor.getVrf(id) - return GET("$baseUrl/ajax/episode/list/$id?vrf=${java.net.URLEncoder.encode(vrf, "utf-8")}", headers = Headers.headersOf("url", anime.url)) + // =============================== Latest =============================== + + override fun latestUpdatesRequest(page: Int): Request { + // make the vrf webview available beforehand. + vrfInterceptor.wake() + return GET("$baseUrl/filter?sort=recently_updated&page=$page") } - private fun Iterable.parallelMap(f: suspend (A) -> B): List = - runBlocking { - map { async(Dispatchers.Default) { f(it) } }.awaitAll() - } + override fun latestUpdatesSelector(): String = popularAnimeSelector() - override fun episodeListParse(response: Response): List { - val animeUrl = response.request.header("url").toString() - val responseObject = json.decodeFromString(response.body!!.string()) - val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content)) - val episodeElements = document.select(episodeListSelector()) - return episodeElements.parallelMap { episodeFromElements(it, animeUrl) }.reversed() - } + override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) - override fun episodeListSelector() = "div.episodes ul > li > a" + override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() - private fun episodeFromElements(element: Element, url: String): SEpisode { - val episode = SEpisode.create() - val epNum = element.attr("data-num") - val ids = element.attr("data-ids") - val sub = element.attr("data-sub").toInt().toBoolean() - val dub = element.attr("data-dub").toInt().toBoolean() - episode.url = "/ajax/server/list/$ids?vrf=&epurl=$url/ep-$epNum" - episode.episode_number = epNum.toFloat() - val langPrefix = "[" + if (sub) { - "Sub" - } else { - "" - } + if (dub) { - ",Dub" - } else { - "" - } + "]" - val name = element.parent()?.select("span.d-title")?.text().orEmpty() - val namePrefix = "Episode $epNum" - episode.name = "Episode $epNum" + if (sub || dub) { - ": $langPrefix" - } else { - "" - } + if (name.isNotEmpty() && name != namePrefix) { - " $name" - } else { - "" - } - return episode - } - - override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used") - - private fun Int.toBoolean() = this == 1 - - override fun videoListRequest(episode: SEpisode): Request { - val ids = episode.url.substringAfter("list/").substringBefore("?vrf") - val vrf = vrfInterceptor.getVrf(ids) - val url = "/ajax/server/list/$ids?vrf=${java.net.URLEncoder.encode(vrf, "utf-8")}" - val epurl = episode.url.substringAfter("epurl=") - return GET(baseUrl + url, headers = Headers.headersOf("url", epurl)) - } - - override fun videoListParse(response: Response): List.parallelMap(f: suspend (A) -> B): List = runBlocking { + map { async(Dispatchers.Default) { f(it) } }.awaitAll() + } } diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/extractors/FilemoonExtractor.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/extractors/FilemoonExtractor.kt new file mode 100644 index 000000000..950f33fdb --- /dev/null +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/extractors/FilemoonExtractor.kt @@ -0,0 +1,79 @@ +package eu.kanade.tachiyomi.animeextension.en.nineanime + +import dev.datlag.jsunpacker.JsUnpacker +import eu.kanade.tachiyomi.animesource.model.Track +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.OkHttpClient + +@Serializable +data class CaptionElement( + val file: String, + val label: String, + val kind: String +) + +class FilemoonExtractor(private val client: OkHttpClient) { + fun videoFromUrl(url: String, prefix: String = "Filemoon"): List