Nine: move episode list to consumet as different vrf is being used from search (#1382)

This commit is contained in:
Samfun75
2023-03-13 03:29:18 +03:00
committed by GitHub
parent 00ba6f66eb
commit 7966c9ab0d
4 changed files with 54 additions and 67 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = '9anime' extName = '9anime'
pkgNameSuffix = 'en.nineanime' pkgNameSuffix = 'en.nineanime'
extClass = '.NineAnime' extClass = '.NineAnime'
extVersionCode = 34 extVersionCode = 35
libVersion = '13' libVersion = '13'
} }

View File

@ -27,7 +27,7 @@ class JsInterceptor(private val lang: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
handler.post { handler.post {
context.let { Toast.makeText(it, "This might take a while, Don't close me", Toast.LENGTH_LONG).show() } context.let { Toast.makeText(it, "Getting $lang. This might take a while, Don't close me", Toast.LENGTH_LONG).show() }
} }
val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("Someting went wrong") val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("Someting went wrong")

View File

@ -18,8 +18,6 @@ class JsVrfInterceptor(private val baseUrl: String) {
private val handler by lazy { Handler(Looper.getMainLooper()) } private val handler by lazy { Handler(Looper.getMainLooper()) }
private val vrfWebView = createWebView() private val vrfWebView = createWebView()
fun wake() = ""
fun getVrf(query: String): String { fun getVrf(query: String): String {
if (query.isBlank()) return "" if (query.isBlank()) return ""
val jscript = getJs(query) val jscript = getJs(query)

View File

@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -24,13 +23,10 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
@ -65,8 +61,6 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request { override fun popularAnimeRequest(page: Int): Request {
// make the vrf webview available beforehand
vrfInterceptor.wake()
return GET("$baseUrl/filter?sort=trending&page=$page") return GET("$baseUrl/filter?sort=trending&page=$page")
} }
@ -84,8 +78,6 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// =============================== Latest =============================== // =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
// make the vrf webview available beforehand.
vrfInterceptor.wake()
return GET("$baseUrl/filter?sort=recently_updated&page=$page") return GET("$baseUrl/filter?sort=recently_updated&page=$page")
} }
@ -131,11 +123,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeSelector(): String = popularAnimeSelector() override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element): SAnime = SAnime.create().apply { override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
setUrlWithoutDomain(element.select("div.poster a").attr("href"))
thumbnail_url = element.select("div.poster img").attr("src")
title = element.select("a.name").text()
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
@ -167,37 +155,30 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListRequest(anime: SAnime): Request { override fun episodeListRequest(anime: SAnime): Request {
val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() val link = "https://api.consumet.org/anime/9anime" + anime.url.replace("watch", "info", true)
.selectFirst("div[data-id]")!!.attr("data-id") return GET(link, headers = Headers.headersOf("url", anime.url))
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),
)
} }
override fun episodeListSelector() = "div.episodes ul > li > a" override fun episodeListSelector() = throw Exception("not used")
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val animeUrl = response.request.header("url").toString() val animeUrl = response.request.header("url").toString()
val responseObject = json.decodeFromString<JsonObject>(response.body.string()) val responseObject = json.decodeFromString<EpisodeResponse>(response.body.string())
val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content)) return responseObject.episodes.parallelMap { episodeFromElements(it, animeUrl) }.reversed()
val episodeElements = document.select(episodeListSelector())
return episodeElements.parallelMap { episodeFromElements(it, animeUrl) }.reversed()
} }
override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used") override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used")
private fun episodeFromElements(element: Element, url: String): SEpisode { private fun episodeFromElements(item: EpisodeResponse.Episode, url: String): SEpisode {
val episode = SEpisode.create() val episode = SEpisode.create()
val epNum = element.attr("data-num") val epNum = item.epNum
val ids = element.attr("data-ids") val ids = item.subId + if (item.dubId.isNullOrBlank()) "" else ",${item.dubId}"
val sub = element.attr("data-sub").toInt().toBoolean() val sub = item.subId.isNotBlank()
val dub = element.attr("data-dub").toInt().toBoolean() val dub = !item.dubId.isNullOrBlank()
episode.url = "/ajax/server/list/$ids?vrf=&epurl=$url/ep-$epNum" episode.url = "$ids&epurl=$url/ep-$epNum"
episode.episode_number = epNum.toFloat() episode.episode_number = epNum.toFloat()
episode.scanlator = (if (sub) "Sub" else "") + if (dub) ", Dub" else "" episode.scanlator = (if (sub) "Sub" else "") + if (dub) ", Dub" else ""
val name = element.parent()?.select("span.d-title")?.text().orEmpty() val name = item.title
val namePrefix = "Episode $epNum" val namePrefix = "Episode $epNum"
episode.name = "Episode $epNum" + episode.name = "Episode $epNum" +
if (name.isNotEmpty() && name != namePrefix) ": $name" else "" if (name.isNotEmpty() && name != namePrefix) ": $name" else ""
@ -206,36 +187,28 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListRequest(episode: SEpisode): Request { override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
val ids = episode.url.substringAfter("list/").substringBefore("?vrf") val ids = episode.url.substringBefore("&").split(",")
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=") val epurl = episode.url.substringAfter("epurl=")
return GET(baseUrl + url, headers = Headers.headersOf("url", epurl))
}
override fun videoListParse(response: Response): List<Video> {
val epurl = response.request.header("url").toString()
val responseObject = json.decodeFromString<JsonObject>(response.body.string())
val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content))
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val servers = mutableListOf<Triple<String, String, String>>() val servers = mutableListOf<Triple<String, String, String>>()
val ids = response.request.url.encodedPath.substringAfter("list/")
.substringBefore("?")
.split(",")
ids.getOrNull(0)?.let { subId -> ids.getOrNull(0)?.let { subId ->
document.select("li[data-ep-id=$subId]").map { serverElement -> val resp = client.newCall(GET("https://api.consumet.org/anime/9anime/servers/$subId")).execute()
val server = serverElement.text().let { val parsed = json.decodeFromString<List<Servers>>(resp.body.string())
if (it == "Vidstream") "vizcloud" else it.lowercase() parsed.map { serverElement ->
val server = serverElement.name.let {
if (it == "vidstream") "vizcloud" else it.lowercase()
} }
servers.add(Triple("Sub", subId, server)) servers.add(Triple("Sub", subId, server))
} }
} }
ids.getOrNull(1)?.let { dubId -> ids.getOrNull(1)?.let { dubId ->
document.select("li[data-ep-id=$dubId]").map { serverElement -> val resp = client.newCall(GET("https://api.consumet.org/anime/9anime/servers/$dubId")).execute()
val server = serverElement.text().let { val parsed = json.decodeFromString<List<Servers>>(resp.body.string())
if (it == "Vidstream") "vizcloud" else it.lowercase() parsed.map { serverElement ->
val server = serverElement.name.let {
if (it == "vidstream") "vizcloud" else it.lowercase()
} }
servers.add(Triple("Dub", dubId, server)) servers.add(Triple("Dub", dubId, server))
} }
@ -244,18 +217,16 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
servers.filter { servers.filter {
listOf("vizcloud", "filemoon", "streamtape").contains(it.third) listOf("vizcloud", "filemoon", "streamtape").contains(it.third)
}.parallelMap { videoList.addAll(extractVideoConsumet(it)) } }.parallelMap { videoList.addAll(extractVideoConsumet(it)) }
if (videoList.isNotEmpty()) return videoList
if (videoList.isNotEmpty()) return Observable.just(videoList.sort())
// If the above fail fallback to webview method // If the above fail fallback to webview method
// Sub // Sub
document.selectFirst("div[data-type=sub] > ul > li[data-sv-id=41]") ids.getOrNull(0)?.let { videoList.addAll(extractVizVideo("Sub", epurl)) }
?.attr("data-link-id")
?.let { videoList.addAll(extractVizVideo("Sub", epurl)) }
// Dub // Dub
document.selectFirst("div[data-type=dub] > ul > li[data-sv-id=41]") ids.getOrNull(1)?.let { videoList.addAll(extractVizVideo("Dub", epurl)) }
?.attr("data-link-id")
?.let { videoList.addAll(extractVizVideo("Dub", epurl)) } return Observable.just(videoList.sort())
return videoList
} }
override fun videoListSelector() = throw Exception("not used") override fun videoListSelector() = throw Exception("not used")
@ -330,8 +301,6 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
private fun Int.toBoolean() = this == 1
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "1080")!! val quality = preferences.getString("preferred_quality", "1080")!!
val lang = preferences.getString("preferred_language", "Sub")!! val lang = preferences.getString("preferred_language", "Sub")!!
@ -350,6 +319,26 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
@Serializable
data class EpisodeResponse(
val episodes: List<Episode>,
) {
@Serializable
data class Episode(
@SerialName("id")
val subId: String,
val dubId: String? = null,
@SerialName("number")
val epNum: Int,
val title: String,
)
}
@Serializable
data class Servers(
val name: String,
)
@Serializable @Serializable
data class WatchResponse( data class WatchResponse(
val headers: Header, val headers: Header,