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'
pkgNameSuffix = 'en.nineanime'
extClass = '.NineAnime'
extVersionCode = 34
extVersionCode = 35
libVersion = '13'
}

View File

@ -27,7 +27,7 @@ class JsInterceptor(private val lang: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
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")

View File

@ -18,8 +18,6 @@ class JsVrfInterceptor(private val baseUrl: String) {
private val handler by lazy { Handler(Looper.getMainLooper()) }
private val vrfWebView = createWebView()
fun wake() = ""
fun getVrf(query: String): String {
if (query.isBlank()) return ""
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.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -24,13 +23,10 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
@ -65,8 +61,6 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request {
// make the vrf webview available beforehand
vrfInterceptor.wake()
return GET("$baseUrl/filter?sort=trending&page=$page")
}
@ -84,8 +78,6 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request {
// make the vrf webview available beforehand.
vrfInterceptor.wake()
return GET("$baseUrl/filter?sort=recently_updated&page=$page")
}
@ -131,11 +123,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
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 searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
@ -167,37 +155,30 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Episodes ==============================
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),
)
val link = "https://api.consumet.org/anime/9anime" + anime.url.replace("watch", "info", true)
return GET(link, 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> {
val animeUrl = response.request.header("url").toString()
val responseObject = json.decodeFromString<JsonObject>(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()
val responseObject = json.decodeFromString<EpisodeResponse>(response.body.string())
return responseObject.episodes.parallelMap { episodeFromElements(it, animeUrl) }.reversed()
}
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 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"
val epNum = item.epNum
val ids = item.subId + if (item.dubId.isNullOrBlank()) "" else ",${item.dubId}"
val sub = item.subId.isNotBlank()
val dub = !item.dubId.isNullOrBlank()
episode.url = "$ids&epurl=$url/ep-$epNum"
episode.episode_number = epNum.toFloat()
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"
episode.name = "Episode $epNum" +
if (name.isNotEmpty() && name != namePrefix) ": $name" else ""
@ -206,36 +187,28 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links =============================
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")}"
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
val ids = episode.url.substringBefore("&").split(",")
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 servers = mutableListOf<Triple<String, String, String>>()
val ids = response.request.url.encodedPath.substringAfter("list/")
.substringBefore("?")
.split(",")
ids.getOrNull(0)?.let { subId ->
document.select("li[data-ep-id=$subId]").map { serverElement ->
val server = serverElement.text().let {
if (it == "Vidstream") "vizcloud" else it.lowercase()
val resp = client.newCall(GET("https://api.consumet.org/anime/9anime/servers/$subId")).execute()
val parsed = json.decodeFromString<List<Servers>>(resp.body.string())
parsed.map { serverElement ->
val server = serverElement.name.let {
if (it == "vidstream") "vizcloud" else it.lowercase()
}
servers.add(Triple("Sub", subId, server))
}
}
ids.getOrNull(1)?.let { dubId ->
document.select("li[data-ep-id=$dubId]").map { serverElement ->
val server = serverElement.text().let {
if (it == "Vidstream") "vizcloud" else it.lowercase()
val resp = client.newCall(GET("https://api.consumet.org/anime/9anime/servers/$dubId")).execute()
val parsed = json.decodeFromString<List<Servers>>(resp.body.string())
parsed.map { serverElement ->
val server = serverElement.name.let {
if (it == "vidstream") "vizcloud" else it.lowercase()
}
servers.add(Triple("Dub", dubId, server))
}
@ -244,18 +217,16 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
servers.filter {
listOf("vizcloud", "filemoon", "streamtape").contains(it.third)
}.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
// Sub
document.selectFirst("div[data-type=sub] > ul > li[data-sv-id=41]")
?.attr("data-link-id")
?.let { videoList.addAll(extractVizVideo("Sub", epurl)) }
ids.getOrNull(0)?.let { videoList.addAll(extractVizVideo("Sub", epurl)) }
// Dub
document.selectFirst("div[data-type=dub] > ul > li[data-sv-id=41]")
?.attr("data-link-id")
?.let { videoList.addAll(extractVizVideo("Dub", epurl)) }
return videoList
ids.getOrNull(1)?.let { videoList.addAll(extractVizVideo("Dub", epurl)) }
return Observable.just(videoList.sort())
}
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> {
val quality = preferences.getString("preferred_quality", "1080")!!
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
data class WatchResponse(
val headers: Header,