KickAssAnime: Fix Sapphire, use reanimation jutsu on some dead server… (#1194)
This commit is contained in:
@ -1,15 +1,17 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'KickAssAnime'
|
||||
pkgNameSuffix = 'en.kickassanime'
|
||||
extClass = '.KickAssAnime'
|
||||
extVersionCode = 13
|
||||
extVersionCode = 14
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly libs.bundles.coroutines
|
||||
implementation(project(':lib-streamsb-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
}
|
||||
|
@ -19,7 +19,13 @@ import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
@ -29,6 +35,7 @@ import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@ -45,7 +52,12 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override val name = "KickAssAnime"
|
||||
|
||||
override val baseUrl by lazy { preferences.getString("preferred_domain", "https://www2.kickassanime.ro")!! }
|
||||
override val baseUrl by lazy {
|
||||
preferences.getString(
|
||||
"preferred_domain",
|
||||
"https://www2.kickassanime.ro"
|
||||
)!!
|
||||
}
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
@ -67,24 +79,30 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// Add non working server names here
|
||||
private val deadServers = listOf(
|
||||
"BETA-SERVER", "BETASERVER1", "BETASERVER3", "DEVSTREAM",
|
||||
"THETA-ORIGINAL-V4", "DAILYMOTION", "KICKASSANIME1"
|
||||
"BETASERVER1", "BETASERVER3", "DEVSTREAM",
|
||||
"THETA-ORIGINAL-V4", "KICKASSANIME1"
|
||||
)
|
||||
|
||||
private val workingServers = arrayOf(
|
||||
"StreamSB", "PINK-BIRD", "Doodstream", "MAVERICKKI",
|
||||
"StreamSB", "PINK-BIRD", "Doodstream", "MAVERICKKI", "BETA-SERVER", "DAILYMOTION",
|
||||
"BETAPLAYER", "Vidstreaming", "SAPPHIRE-DUCK", "KICKASSANIMEV2", "ORIGINAL-QUALITY-V2"
|
||||
)
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/api/get_anime_list/all/$page")
|
||||
override fun popularAnimeRequest(page: Int): Request =
|
||||
GET("$baseUrl/api/get_anime_list/all/$page")
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val responseObject = json.decodeFromString<JsonObject>(response.body!!.string())
|
||||
val data = responseObject["data"]!!.jsonArray
|
||||
val animes = data.map { item ->
|
||||
SAnime.create().apply {
|
||||
setUrlWithoutDomain(item.jsonObject["slug"]!!.jsonPrimitive.content.substringBefore("/episode"))
|
||||
thumbnail_url = "$baseUrl/uploads/" + item.jsonObject["poster"]!!.jsonPrimitive.content
|
||||
setUrlWithoutDomain(
|
||||
item.jsonObject["slug"]!!.jsonPrimitive.content.substringBefore(
|
||||
"/episode"
|
||||
)
|
||||
)
|
||||
thumbnail_url =
|
||||
"$baseUrl/uploads/" + item.jsonObject["poster"]!!.jsonPrimitive.content
|
||||
title = item.jsonObject["name"]!!.jsonPrimitive.content
|
||||
}
|
||||
}
|
||||
@ -149,35 +167,45 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val resp = client.newCall(GET(link)).execute()
|
||||
val sources = getVideoSource(resp.asJsoup())
|
||||
|
||||
sources.forEach { source ->
|
||||
when (source.jsonObject["name"]!!.jsonPrimitive.content) {
|
||||
in deadServers -> {}
|
||||
"BETAPLAYER" -> {
|
||||
videoList.addAll(
|
||||
extractBetaVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
)
|
||||
}
|
||||
"KICKASSANIMEV2", "ORIGINAL-QUALITY-V2" -> {
|
||||
videoList.addAll(
|
||||
extractKickasssVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
videoList.addAll(
|
||||
extractVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
videoList.addAll(
|
||||
sources.parallelMap { source ->
|
||||
runCatching {
|
||||
when (source.jsonObject["name"]!!.jsonPrimitive.content) {
|
||||
in deadServers -> { null }
|
||||
"SAPPHIRE-DUCK" -> {
|
||||
extractSapphireVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
}
|
||||
"BETAPLAYER" -> {
|
||||
extractBetaVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
}
|
||||
"KICKASSANIMEV2", "ORIGINAL-QUALITY-V2", "BETA-SERVER" -> {
|
||||
extractKickasssVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
}
|
||||
"DAILYMOTION" -> {
|
||||
extractDailymotion(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
extractVideo(
|
||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
||||
)
|
||||
}
|
||||
}
|
||||
}.getOrNull()
|
||||
}.filterNotNull().flatten()
|
||||
)
|
||||
}
|
||||
}
|
||||
return videoList
|
||||
@ -186,13 +214,13 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
private fun extractVideo(serverLink: String, server: String): List<Video> {
|
||||
val playlist = mutableListOf<Video>()
|
||||
val subsList = mutableListOf<Track>()
|
||||
val data: MutableList<Pair<String, Headers>>
|
||||
var vidHeader = headers
|
||||
|
||||
if (server == "MAVERICKKI") {
|
||||
val resp = if (server == "MAVERICKKI") {
|
||||
val apiLink = serverLink.replace("embed", "api/source")
|
||||
// for some reason the request to the api is only working reliably this way
|
||||
val apiResponse = client.newCall(GET(apiLink, headers)).execute().asJsoup().text()
|
||||
val json = Json.decodeFromString<JsonObject>(apiResponse)
|
||||
val embedHeader = Headers.headersOf("referer", serverLink)
|
||||
val apiResponse = client.newCall(GET(apiLink, embedHeader)).execute()
|
||||
val json = Json.decodeFromString<JsonObject>(apiResponse.body!!.string())
|
||||
val uri = Uri.parse(serverLink)
|
||||
|
||||
json["subtitles"]!!.jsonArray.forEach {
|
||||
@ -200,52 +228,46 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val subUrl = "${uri.scheme}://${uri.host}" + it.jsonObject["src"]!!.jsonPrimitive.content
|
||||
try {
|
||||
subsList.add(Track(subUrl, subLang))
|
||||
} catch (e: Error) {}
|
||||
} catch (_: Error) {}
|
||||
}
|
||||
data = mutableListOf(Pair("${uri.scheme}://${uri.host}" + json["hls"]!!.jsonPrimitive.content, headers))
|
||||
vidHeader = embedHeader
|
||||
client.newCall(GET("${uri.scheme}://${uri.host}" + json["hls"]!!.jsonPrimitive.content, embedHeader)).execute()
|
||||
} else {
|
||||
val playlistInterceptor = MasterPlaylistInterceptor()
|
||||
val kickAssClient = client.newBuilder().addInterceptor(playlistInterceptor).build()
|
||||
val kickAssClient = client.newBuilder().addInterceptor(MasterPlaylistInterceptor()).build()
|
||||
kickAssClient.newCall(GET(serverLink, headers)).execute()
|
||||
data = playlistInterceptor.playlist
|
||||
}
|
||||
|
||||
data.forEach { (videoLink, headers) ->
|
||||
val masterPlaylist = client.newCall(GET(videoLink, headers)).execute().body!!.string()
|
||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").map {
|
||||
val quality = it.substringAfter("RESOLUTION=").split(",")[0].split("\n")[0].substringAfter("x") + "p $server" +
|
||||
if (subsList.size > 0) { " (Toggleable Sub Available)" } else { "" }
|
||||
var videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
if (videoUrl.startsWith("https").not()) {
|
||||
val pos = videoLink.lastIndexOf('/') + 1
|
||||
videoUrl = videoLink.substring(0, pos) + videoUrl
|
||||
}
|
||||
try {
|
||||
playlist.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subsList, headers = headers))
|
||||
} catch (e: Error) {
|
||||
playlist.add(Video(videoUrl, quality, videoUrl, headers = headers))
|
||||
}
|
||||
resp.body!!.string().substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").map {
|
||||
val quality = it.substringAfter("RESOLUTION=").split(",")[0].split("\n")[0].substringAfter("x") + "p $server" +
|
||||
if (subsList.size > 0) { " (Toggleable Sub Available)" } else { "" }
|
||||
var videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
if (videoUrl.startsWith("https").not()) {
|
||||
videoUrl = resp.request.url.toString().substringBeforeLast("/") + "/$videoUrl"
|
||||
}
|
||||
}
|
||||
try {
|
||||
playlist.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subsList, headers = vidHeader))
|
||||
} catch (e: Error) {
|
||||
playlist.add(Video(videoUrl, quality, videoUrl, headers = vidHeader))
|
||||
}
|
||||
}
|
||||
|
||||
return playlist
|
||||
}
|
||||
|
||||
private fun extractBetaVideo(serverLink: String, server: String): List<Video> {
|
||||
val headers = Headers.headersOf("referer", "https://kaast1.com/")
|
||||
val document = client.newCall(GET(serverLink, headers)).execute().asJsoup()
|
||||
val scripts = document.getElementsByTag("script")
|
||||
var playlistArray = JsonArray(arrayListOf())
|
||||
for (element in scripts) {
|
||||
if (element.data().contains("window.files")) {
|
||||
val pattern = Pattern.compile(".*JSON\\.parse\\('(.*)'\\)")
|
||||
val matcher = pattern.matcher(element.data())
|
||||
if (matcher.find()) {
|
||||
playlistArray = json.decodeFromString(matcher.group(1)!!.toString())
|
||||
}
|
||||
break
|
||||
|
||||
document.selectFirst("script:containsData(window.files)")?.data()?.let {
|
||||
val pattern = Pattern.compile(".*JSON\\.parse\\('(.*)'\\)")
|
||||
val matcher = pattern.matcher(it)
|
||||
if (matcher.find()) {
|
||||
playlistArray = json.decodeFromString(matcher.group(1)!!.toString())
|
||||
}
|
||||
}
|
||||
|
||||
val playlist = mutableListOf<Video>()
|
||||
playlistArray.forEach {
|
||||
val quality = it.jsonObject["label"]!!.jsonPrimitive.content + " $server"
|
||||
@ -258,26 +280,25 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
private fun extractKickasssVideo(serverLink: String, server: String): List<Video> {
|
||||
val url = serverLink.replace("embed.php", "pref.php")
|
||||
val url = serverLink.replace("(?:embed|player)\\.php".toRegex(), "pref.php")
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val scripts = document.getElementsByTag("script")
|
||||
var playlistArray = JsonArray(arrayListOf())
|
||||
for (element in scripts) {
|
||||
if (element.data().contains("document.write")) {
|
||||
val pattern = Pattern.compile(".*atob\\(\"(.*)\"\\)")
|
||||
val matcher = pattern.matcher(element.data())
|
||||
if (matcher.find()) {
|
||||
val player = Base64.decode(matcher.group(1)!!.toString(), Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||
val playerPattern = Pattern.compile(".*setup\\(\\{sources:\\[(.*)\\]")
|
||||
val playerMatcher = playerPattern.matcher(player)
|
||||
if (playerMatcher.find()) {
|
||||
val playlistString = "[" + playerMatcher.group(1)!!.toString() + "]"
|
||||
playlistArray = json.decodeFromString(playlistString)
|
||||
}
|
||||
|
||||
document.selectFirst("script:containsData(document.write)")?.data()?.let {
|
||||
val pattern = if (server.contains("Beta", true)) Pattern.compile(".*decode\\(\"(.*)\"\\)")
|
||||
else Pattern.compile(".*atob\\(\"(.*)\"\\)")
|
||||
val matcher = pattern.matcher(it)
|
||||
if (matcher.find()) {
|
||||
val player = matcher.group(1)!!.toString().decodeBase64()
|
||||
val playerPattern = Pattern.compile(".*sources:[ ]*\\[(.*)\\]")
|
||||
val playerMatcher = playerPattern.matcher(player)
|
||||
if (playerMatcher.find()) {
|
||||
val playlistString = "[" + playerMatcher.group(1)!!.toString() + "]"
|
||||
playlistArray = json.decodeFromString(playlistString)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val playlist = mutableListOf<Video>()
|
||||
playlistArray.forEach {
|
||||
val quality = it.jsonObject["label"]!!.jsonPrimitive.content + " $server"
|
||||
@ -289,6 +310,77 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
return playlist
|
||||
}
|
||||
|
||||
private fun extractDailymotion(serverLink: String, server: String): List<Video> {
|
||||
val url = serverLink.replace("player.php", "pref.php")
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
var masterPlaylist = listOf<Video>()
|
||||
|
||||
document.selectFirst("script:containsData(Base64.decode)")?.data()?.let { iframe ->
|
||||
val embedUrl = iframe.substringAfter("decode(\"").substringBefore("\"").decodeBase64()
|
||||
.substringAfter("src=\"").substringBefore("\"").substringBefore("?")
|
||||
.replace("/embed/", "/player/metadata/")
|
||||
val response = client.newCall(GET(embedUrl, headers)).execute()
|
||||
val decodedJson = json.decodeFromString<DailyQuality>(response.body!!.string())
|
||||
masterPlaylist = decodedJson.qualities.auto.parallelMap { item ->
|
||||
runCatching {
|
||||
val resp = client.newCall(GET(item.url)).execute().body!!.string()
|
||||
resp.substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").map {
|
||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
val proxy = videoUrl.substringAfter("proxy-").substringBefore(".")
|
||||
val quality = it.substringAfter("RESOLUTION=").split(",")[0].split("\n")[0].substringAfter("x") + "p $server" +
|
||||
if (proxy.isNotBlank()) " $proxy" else ""
|
||||
Video(videoUrl, quality, videoUrl, headers = Headers.headersOf("referer", "https://www.dailymotion.com/"))
|
||||
}
|
||||
}.getOrNull()
|
||||
}.filterNotNull().flatten().distinct()
|
||||
}
|
||||
|
||||
return masterPlaylist
|
||||
}
|
||||
|
||||
private fun String.decodeBase64(): String {
|
||||
return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
private fun extractSapphireVideo(serverLink: String, server: String): List<Video> {
|
||||
val url = serverLink.toHttpUrl().newBuilder().addQueryParameter("action", "config").build()
|
||||
val response = client.newCall(GET(url.toString(), Headers.headersOf("referer", serverLink))).execute()
|
||||
val rawJson = response.body!!.string().let {
|
||||
var decoded = it
|
||||
while (!decoded.startsWith("{\"id")) decoded = decoded.decodeBase64()
|
||||
return@let decoded
|
||||
}
|
||||
val decodedJson = json.decodeFromString<Sapphire>(rawJson)
|
||||
val subsList = decodedJson.subtitles.mapNotNull {
|
||||
try {
|
||||
Track(it.url, it.language.getLocale())
|
||||
} catch (_: Error) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return decodedJson.streams.filter { it.format == "adaptive_hls" }.parallelMap { stream ->
|
||||
runCatching {
|
||||
val playlist = client.newCall(GET(stream.url)).execute().body!!.string()
|
||||
playlist.substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").map {
|
||||
val quality = it.substringAfter("RESOLUTION=").split(",")[0].split("\n")[0].substringAfter("x") + "p $server" +
|
||||
(if (stream.audio.getLocale().isNotBlank()) " - Aud: ${stream.audio.getLocale()}" else "") +
|
||||
(if (stream.hardSub.getLocale().isNotBlank()) " - HardSub: ${stream.hardSub}" else "")
|
||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
try {
|
||||
Video(videoUrl, quality, videoUrl, subtitleTracks = subsList)
|
||||
} catch (e: Error) {
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
}
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
.filterNotNull()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
private fun extractGogoVideo(link: String): List<Video> {
|
||||
var url = decode(link).substringAfter("data=").substringBefore("&vref")
|
||||
if (url.startsWith("https").not()) {
|
||||
@ -298,7 +390,7 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
|
||||
// Vidstreaming:
|
||||
videoList.addAll(GogoCdnExtractor(network.client, json).videosFromUrl(url))
|
||||
videoList.addAll(GogoCdnExtractor(client, json).videosFromUrl(url))
|
||||
// Doodstream mirror:
|
||||
document.select("div#list-server-more > ul > li.linkserver:contains(Doodstream)")
|
||||
.firstOrNull()?.attr("data-video")
|
||||
@ -311,41 +403,15 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString("preferred_quality", "1080")
|
||||
val server = preferences.getString("preferred_server", "MAVERICKKI")
|
||||
val quality = preferences.getString("preferred_quality", "1080")!!
|
||||
val server = preferences.getString("preferred_server", "MAVERICKKI")!!
|
||||
|
||||
if (quality != null || server != null) {
|
||||
val qualityList = mutableListOf<Video>()
|
||||
if (quality != null) {
|
||||
var preferred = 0
|
||||
for (video in this) {
|
||||
if (video.quality.contains(quality)) {
|
||||
qualityList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
qualityList.add(video)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qualityList.addAll(this)
|
||||
}
|
||||
val newList = mutableListOf<Video>()
|
||||
if (server != null) {
|
||||
var preferred = 0
|
||||
for (video in qualityList) {
|
||||
if (video.quality.contains(server)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
newList.add(video)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newList.addAll(qualityList)
|
||||
}
|
||||
return newList
|
||||
}
|
||||
return this
|
||||
return this.sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
{ it.quality.contains(server) }
|
||||
)
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
@ -358,7 +424,8 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
val animes = animeList.map { item ->
|
||||
SAnime.create().apply {
|
||||
setUrlWithoutDomain(item.jsonObject["slug"]!!.jsonPrimitive.content)
|
||||
thumbnail_url = "$baseUrl/uploads/" + item.jsonObject["poster"]!!.jsonPrimitive.content
|
||||
thumbnail_url =
|
||||
"$baseUrl/uploads/" + item.jsonObject["poster"]!!.jsonPrimitive.content
|
||||
title = item.jsonObject["name"]!!.jsonPrimitive.content
|
||||
}
|
||||
}
|
||||
@ -371,19 +438,21 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
if (appData.isEmpty().not()) {
|
||||
val ani = appData["anime"]!!.jsonObject
|
||||
anime.title = ani["name"]!!.jsonPrimitive.content
|
||||
anime.genre = ani["genres"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }
|
||||
anime.genre =
|
||||
ani["genres"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }
|
||||
anime.description = JSONUtil.unescape(ani["description"]!!.jsonPrimitive.content)
|
||||
anime.status = parseStatus(ani["status"]!!.jsonPrimitive.content)
|
||||
|
||||
val altName = "Other name(s): "
|
||||
json.decodeFromString<JsonArray>(ani["alternate"].toString().replace("\"\"", "[]")).let { altArray ->
|
||||
if (altArray.isEmpty().not()) {
|
||||
anime.description = when {
|
||||
anime.description.isNullOrBlank() -> altName + altArray.joinToString { it.jsonPrimitive.content }
|
||||
else -> anime.description + "\n\n$altName" + altArray.joinToString { it.jsonPrimitive.content }
|
||||
json.decodeFromString<JsonArray>(ani["alternate"].toString().replace("\"\"", "[]"))
|
||||
.let { altArray ->
|
||||
if (altArray.isEmpty().not()) {
|
||||
anime.description = when {
|
||||
anime.description.isNullOrBlank() -> altName + altArray.joinToString { it.jsonPrimitive.content }
|
||||
else -> anime.description + "\n\n$altName" + altArray.joinToString { it.jsonPrimitive.content }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return anime
|
||||
}
|
||||
@ -482,4 +551,72 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
private fun encode(input: String): String = java.net.URLEncoder.encode(input, "utf-8")
|
||||
|
||||
private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8")
|
||||
|
||||
private fun String.getLocale(): String {
|
||||
return arrayOf(
|
||||
Pair("ar-ME", "Arabic"),
|
||||
Pair("ar-SA", "Arabic (Saudi Arabia)"),
|
||||
Pair("de-DE", "German"),
|
||||
Pair("en-US", "English"),
|
||||
Pair("es-419", "Spanish"),
|
||||
Pair("es-ES", "Spanish (Spain)"),
|
||||
Pair("es-LA", "Spanish (Spanish)"),
|
||||
Pair("fr-FR", "French"),
|
||||
Pair("ja-JP", "Japanese"),
|
||||
Pair("it-IT", "Italian"),
|
||||
Pair("pt-BR", "Portuguese (Brazil)"),
|
||||
Pair("pl-PL", "Polish"),
|
||||
Pair("ru-RU", "Russian"),
|
||||
Pair("tr-TR", "Turkish"),
|
||||
Pair("uk-UK", "Ukrainian"),
|
||||
Pair("he-IL", "Hebrew"),
|
||||
Pair("ro-RO", "Romanian"),
|
||||
Pair("sv-SE", "Swedish")
|
||||
).firstOrNull { it.first == this }?.second ?: ""
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class DailyQuality(
|
||||
val qualities: Auto
|
||||
) {
|
||||
@Serializable
|
||||
data class Auto(
|
||||
val auto: List<Item>
|
||||
) {
|
||||
@Serializable
|
||||
data class Item(
|
||||
val type: String,
|
||||
val url: String
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Sapphire(
|
||||
val subtitles: List<Subtitle>,
|
||||
val streams: List<Stream>
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Subtitle(
|
||||
val language: String,
|
||||
val url: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Stream(
|
||||
@SerialName("audio_lang")
|
||||
val audio: String,
|
||||
@SerialName("hardsub_lang")
|
||||
val hardSub: String,
|
||||
val url: String,
|
||||
val format: String
|
||||
)
|
||||
}
|
||||
|
||||
// From Dopebox
|
||||
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
|
||||
runBlocking {
|
||||
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import okhttp3.Headers
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
@ -22,17 +22,17 @@ class MasterPlaylistInterceptor : Interceptor {
|
||||
|
||||
private val context = Injekt.get<Application>()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
val playlist = mutableListOf<Pair<String, Headers>>()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
val newRequest = resolveWithWebView(originalRequest)
|
||||
val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("Could not find playlist")
|
||||
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun resolveWithWebView(request: Request): Request {
|
||||
private fun resolveWithWebView(request: Request): Request? {
|
||||
// We need to lock this thread until the WebView finds the challenge solution url, because
|
||||
// OkHttp doesn't support asynchronous interceptors.
|
||||
val latch = CountDownLatch(1)
|
||||
@ -42,6 +42,8 @@ class MasterPlaylistInterceptor : Interceptor {
|
||||
val origRequestUrl = request.url.toString()
|
||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
var newRequest: Request? = null
|
||||
|
||||
handler.post {
|
||||
val webview = WebView(context)
|
||||
webView = webview
|
||||
@ -51,8 +53,7 @@ class MasterPlaylistInterceptor : Interceptor {
|
||||
databaseEnabled = true
|
||||
useWideViewPort = false
|
||||
loadWithOverviewMode = false
|
||||
userAgentString = request.header("User-Agent")
|
||||
?: "\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 Edg/88.0.705.63\""
|
||||
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0"
|
||||
}
|
||||
|
||||
webview.webViewClient = object : WebViewClient() {
|
||||
@ -61,7 +62,7 @@ class MasterPlaylistInterceptor : Interceptor {
|
||||
request: WebResourceRequest,
|
||||
): WebResourceResponse? {
|
||||
if (request.url.toString().contains(".m3u8")) {
|
||||
playlist.add(Pair(request.url.toString(), request.requestHeaders.toHeaders()))
|
||||
newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders())
|
||||
latch.countDown()
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
@ -81,6 +82,6 @@ class MasterPlaylistInterceptor : Interceptor {
|
||||
webView = null
|
||||
}
|
||||
|
||||
return request
|
||||
return newRequest
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user