fix 9anime

This commit is contained in:
jmir1
2022-07-25 23:29:06 +02:00
parent c36009ee71
commit bf82113cf4
2 changed files with 57 additions and 78 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = '9anime' extName = '9anime'
pkgNameSuffix = 'en.nineanime' pkgNameSuffix = 'en.nineanime'
extClass = '.NineAnime' extClass = '.NineAnime'
extVersionCode = 7 extVersionCode = 8
libVersion = '12' libVersion = '12'
} }

View File

@ -2,16 +2,17 @@ package eu.kanade.tachiyomi.animeextension.en.nineanime
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -54,76 +55,60 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return Headers.Builder().add("Referer", baseUrl) return Headers.Builder().add("Referer", baseUrl)
} }
override fun popularAnimeSelector(): String = "li" override fun popularAnimeSelector(): String = "div.ani.items > div"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ajax/home/widget?name=trending&page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filter?sort=trending&page=$page")
override fun popularAnimeParse(response: Response): AnimesPage {
val responseObject = json.decodeFromString<JsonObject>(response.body!!.string())
val document = Jsoup.parse(JSONUtil.unescape(responseObject["html"]!!.jsonPrimitive.content))
val animes = document.select(popularAnimeSelector()).map { element ->
popularAnimeFromElement(element)
}
val hasNextPage = popularAnimeNextPageSelector().let { selector ->
document.select(selector).first()
} != null
return AnimesPage(animes, hasNextPage)
}
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.select("a.name").attr("href").substringBefore("?")) setUrlWithoutDomain(element.select("a.name").attr("href").substringBefore("?"))
thumbnail_url = element.select("a.poster img").attr("src") thumbnail_url = element.select("div.poster img").attr("src")
title = element.select("a.name").text() title = element.select("a.name").text()
} }
override fun popularAnimeNextPageSelector(): String = "li" override fun popularAnimeNextPageSelector(): String = "li"
override fun episodeListRequest(anime: SAnime): Request { override fun episodeListRequest(anime: SAnime): Request {
val animeId = anime.url.substringAfterLast(".") val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup().selectFirst("div[data-id]").attr("data-id")
val vrf = encode(getVrf(animeId)) val vrf = encodeVrf(id)
return GET("$baseUrl/ajax/anime/servers?id=$animeId&vrf=$vrf") return GET("$baseUrl/ajax/episode/list/$id?vrf=$vrf")
} }
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val responseObject = json.decodeFromString<JsonObject>(response.body!!.string()) val responseObject = json.decodeFromString<JsonObject>(response.body!!.string())
val document = Jsoup.parse(JSONUtil.unescape(responseObject["html"]!!.jsonPrimitive.content)) val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content))
val animeId = response.request.url.queryParameter("id")!! return document.select(episodeListSelector()).map(::episodeFromElement).reversed()
val vrf = encode(response.request.url.queryParameter("vrf")!!)
return document.select(episodeListSelector()).map { episodeFromElement(it, animeId, vrf) }.reversed()
} }
override fun episodeListSelector() = "ul.episodes li a" override fun episodeListSelector() = "div.episodes ul > li > a"
private fun episodeFromElement(element: Element, animeId: String, vrf: String): SEpisode { override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create() val episode = SEpisode.create()
val epNum = element.attr("data-base") val epNum = element.attr("data-num")
episode.url = "/ajax/anime/servers?id=$animeId&vrf=$vrf&episode=$epNum" val ids = element.attr("data-ids")
val vrf = encodeVrf(ids)
episode.url = "/ajax/server/list/$ids?vrf=$vrf"
episode.episode_number = epNum.toFloat() episode.episode_number = epNum.toFloat()
episode.name = "Episode $epNum" val name = element.parent()?.select("span.d-title")?.text().orEmpty()
val namePrefix = "Episode $epNum"
episode.name = if (name.isNotEmpty() && name != namePrefix) {
"Episode $epNum: $name"
} else {
"Episode $epNum"
}
return episode return episode
} }
override fun episodeFromElement(element: Element) = throw Exception("not used")
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val responseObject = json.decodeFromString<JsonObject>(response.body!!.string()) val responseObject = json.decodeFromString<JsonObject>(response.body!!.string())
val document = Jsoup.parse(JSONUtil.unescape(responseObject["html"]!!.jsonPrimitive.content)) val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content))
val epNum = response.request.url.queryParameter("episode") val sourceId = document.select("ul > li[data-sv-id=41]").attr("data-link-id")
val sources = document.select("ul.episodes li a[data-base=$epNum]").attr("data-sources") val vrf = encodeVrf(sourceId)
val sourceId = json.decodeFromString<JsonObject>(sources)["41"]!!.jsonPrimitive.content val episodeBody = network.client.newCall(GET("$baseUrl/ajax/server/$sourceId?vrf=$vrf"))
fun getEpisodeBody(): String? { .execute().body!!.string()
val res = network.client Log.i("bruh", episodeBody)
.newCall(GET("$baseUrl/ajax/anime/episode?id=$sourceId")) val encryptedSourceUrl = json.decodeFromString<JsonObject>(episodeBody)["result"]!!
.execute() .jsonObject["url"]!!.jsonPrimitive.content
return if (res.code == 200) res.body!!.string() else null val embedLink = decodeVrf(encryptedSourceUrl)
}
// sometimes I have to retry the request for some reason (???)
val episodeBody = getEpisodeBody() ?: getEpisodeBody()!!
val encryptedSourceUrl = json.decodeFromString<JsonObject>(episodeBody)["url"]!!.jsonPrimitive.content
val embedLink = getLink(encryptedSourceUrl)
val vizcloudClient = client.newBuilder().addInterceptor(VizcloudInterceptor()).build() val vizcloudClient = client.newBuilder().addInterceptor(VizcloudInterceptor()).build()
val referer = Headers.headersOf("Referer", "$baseUrl/") val referer = Headers.headersOf("Referer", "$baseUrl/")
val sourceObject = json.decodeFromString<JsonObject>( val sourceObject = json.decodeFromString<JsonObject>(
@ -171,18 +156,18 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeFromElement(element: Element): SAnime { override fun searchAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.setUrlWithoutDomain(baseUrl + element.select("a.name").attr("href")) anime.setUrlWithoutDomain(baseUrl + element.select("a.name").attr("href"))
anime.thumbnail_url = element.select("a.poster img").attr("src") anime.thumbnail_url = element.select("div.poster img").attr("src")
anime.title = element.select("a.name").text() anime.title = element.select("a.name").text()
return anime return anime
} }
override fun searchAnimeNextPageSelector(): String = "a.btn-primary.next:not(.disabled)" override fun searchAnimeNextPageSelector(): String = "a.btn-primary.next:not(.disabled)"
override fun searchAnimeSelector(): String = "ul.anime-list li" override fun searchAnimeSelector(): String = "div.ani.items div"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val vrf = encode(getVrf(query)) val vrf = encodeVrf(query)
return GET("$baseUrl/search?keyword=${encode(query)}&vrf=$vrf&page=$page") return GET("$baseUrl/filter?keyword=${encode(query)}&vrf=$vrf&page=$page")
} }
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
@ -258,19 +243,12 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
screen.addPreference(videoQualityPref) screen.addPreference(videoQualityPref)
} }
private fun getVrf(id: String): String { private fun encodeVrf(id: String) = encode(encrypt(cipher(encode(id))).replace("""=+$""".toRegex(), ""))
val reversed = ue(encode(id) + "0000000").slice(0..5).reversed()
return reversed + ue(je(reversed, encode(id))).replace("""=+$""".toRegex(), "")
}
private fun getLink(url: String): String { private fun decodeVrf(text: String) = decode(cipher(decrypt(text)))
val i = url.slice(0..5)
val n = url.slice(6..url.lastIndex)
return decode(je(i, ze(n)))
}
private fun ue(input: String): String { private fun encrypt(input: String): String {
if (input.any { it.code >= 256 }) throw Exception("illegal characters!") if (input.any { it.code > 255 }) throw Exception("illegal characters!")
var output = "" var output = ""
for (i in input.indices step 3) { for (i in input.indices step 3) {
val a = intArrayOf(-1, -1, -1, -1) val a = intArrayOf(-1, -1, -1, -1)
@ -287,38 +265,38 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
for (n in a) { for (n in a) {
if (n == -1) output += "=" if (n == -1) output += "="
else { else {
if (n in 0..63) output += key[n] if (n in 0..63) output += nineAnimeKey[n]
} }
} }
} }
return output return output
} }
private fun je(inputOne: String, inputTwo: String): String { private fun cipher(input: String): String {
val arr = IntArray(256) { it } val arr = IntArray(256) { it }
var output = ""
var u = 0 var u = 0
var r: Int var r: Int
for (a in arr.indices) { arr.indices.forEach {
u = (u + arr[a] + inputOne[a % inputOne.length].code) % 256 u = (u + arr[it] + cipherKey[it % cipherKey.length].code) % 256
r = arr[a] r = arr[it]
arr[a] = arr[u] arr[it] = arr[u]
arr[u] = r arr[u] = r
} }
u = 0 u = 0
var c = 0 var c = 0
for (f in inputTwo.indices) {
c = (c + f) % 256 return input.indices.map { j ->
c = (c + 1) % 256
u = (u + arr[c]) % 256 u = (u + arr[c]) % 256
r = arr[c] r = arr[c]
arr[c] = arr[u] arr[c] = arr[u]
arr[u] = r arr[u] = r
output += (inputTwo[f].code xor arr[(arr[c] + arr[u]) % 256]).toChar() (input[j].code xor arr[(arr[c] + arr[u]) % 256]).toChar()
} }.joinToString("")
return output
} }
private fun ze(input: String): String { private fun decrypt(input: String): String {
val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) { val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) {
input.replace("""==?$""".toRegex(), "") input.replace("""==?$""".toRegex(), "")
} else input } else input
@ -329,7 +307,7 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
var u = 0 var u = 0
for (o in t.indices) { for (o in t.indices) {
e = e shl 6 e = e shl 6
i = key.indexOf(t[o]) i = nineAnimeKey.indexOf(t[o])
e = e or i e = e or i
u += 6 u += 6
if (24 == u) { if (24 == u) {
@ -358,4 +336,5 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8") private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8")
} }
private const val key = "c/aUAorINHBLxWTy3uRiPt8J+vjsOheFG1E0q2X9CYwDZlnmd4Kb5M6gSVzfk7pQ" private const val nineAnimeKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
private const val cipherKey = "rTKp3auwu0ULA6II"