KickAssAnime: fix pink bird (#1249)
This commit is contained in:
@ -6,7 +6,7 @@ ext {
|
|||||||
extName = 'KickAssAnime'
|
extName = 'KickAssAnime'
|
||||||
pkgNameSuffix = 'en.kickassanime'
|
pkgNameSuffix = 'en.kickassanime'
|
||||||
extClass = '.KickAssAnime'
|
extClass = '.KickAssAnime'
|
||||||
extVersionCode = 14
|
extVersionCode = 15
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import android.util.Base64
|
|||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.GogoCdnExtractor
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.GogoCdnExtractor
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.PinkBird
|
||||||
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.AnimesPage
|
||||||
@ -170,38 +171,29 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
videoList.addAll(
|
videoList.addAll(
|
||||||
sources.parallelMap { source ->
|
sources.parallelMap { source ->
|
||||||
runCatching {
|
runCatching {
|
||||||
when (source.jsonObject["name"]!!.jsonPrimitive.content) {
|
val src = source.jsonObject["src"]!!.jsonPrimitive.content
|
||||||
|
val name = source.jsonObject["name"]!!.jsonPrimitive.content
|
||||||
|
when (name) {
|
||||||
in deadServers -> { null }
|
in deadServers -> { null }
|
||||||
"SAPPHIRE-DUCK" -> {
|
"SAPPHIRE-DUCK" -> {
|
||||||
extractSapphireVideo(
|
extractSapphireVideo(src, name)
|
||||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
}
|
||||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
"PINK-BIRD" -> {
|
||||||
)
|
PinkBird(client, json).videosFromUrl(src, name)
|
||||||
}
|
}
|
||||||
"BETAPLAYER" -> {
|
"BETAPLAYER" -> {
|
||||||
extractBetaVideo(
|
extractBetaVideo(src, name)
|
||||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
|
||||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
"KICKASSANIMEV2", "ORIGINAL-QUALITY-V2", "BETA-SERVER" -> {
|
"KICKASSANIMEV2", "ORIGINAL-QUALITY-V2", "BETA-SERVER" -> {
|
||||||
extractKickasssVideo(
|
extractKickasssVideo(src, name)
|
||||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
|
||||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
"DAILYMOTION" -> {
|
"DAILYMOTION" -> {
|
||||||
extractDailymotion(
|
extractDailymotion(src, name)
|
||||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
|
||||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
else -> {
|
"MAVERICKKI" -> {
|
||||||
extractVideo(
|
extractMavrick(src, name)
|
||||||
source.jsonObject["src"]!!.jsonPrimitive.content,
|
|
||||||
source.jsonObject["name"]!!.jsonPrimitive.content
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}.filterNotNull().flatten()
|
}.filterNotNull().flatten()
|
||||||
@ -211,12 +203,9 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
return videoList
|
return videoList
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractVideo(serverLink: String, server: String): List<Video> {
|
private fun extractMavrick(serverLink: String, server: String): List<Video> {
|
||||||
val playlist = mutableListOf<Video>()
|
val playlist = mutableListOf<Video>()
|
||||||
val subsList = mutableListOf<Track>()
|
val subsList = mutableListOf<Track>()
|
||||||
var vidHeader = headers
|
|
||||||
|
|
||||||
val resp = if (server == "MAVERICKKI") {
|
|
||||||
val apiLink = serverLink.replace("embed", "api/source")
|
val apiLink = serverLink.replace("embed", "api/source")
|
||||||
val embedHeader = Headers.headersOf("referer", serverLink)
|
val embedHeader = Headers.headersOf("referer", serverLink)
|
||||||
val apiResponse = client.newCall(GET(apiLink, embedHeader)).execute()
|
val apiResponse = client.newCall(GET(apiLink, embedHeader)).execute()
|
||||||
@ -230,12 +219,7 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
subsList.add(Track(subUrl, subLang))
|
subsList.add(Track(subUrl, subLang))
|
||||||
} catch (_: Error) {}
|
} catch (_: Error) {}
|
||||||
}
|
}
|
||||||
vidHeader = embedHeader
|
val resp = client.newCall(GET("${uri.scheme}://${uri.host}" + json["hls"]!!.jsonPrimitive.content, embedHeader)).execute()
|
||||||
client.newCall(GET("${uri.scheme}://${uri.host}" + json["hls"]!!.jsonPrimitive.content, embedHeader)).execute()
|
|
||||||
} else {
|
|
||||||
val kickAssClient = client.newBuilder().addInterceptor(MasterPlaylistInterceptor()).build()
|
|
||||||
kickAssClient.newCall(GET(serverLink, headers)).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.body!!.string().substringAfter("#EXT-X-STREAM-INF:")
|
resp.body!!.string().substringAfter("#EXT-X-STREAM-INF:")
|
||||||
.split("#EXT-X-STREAM-INF:").map {
|
.split("#EXT-X-STREAM-INF:").map {
|
||||||
@ -246,12 +230,11 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
videoUrl = resp.request.url.toString().substringBeforeLast("/") + "/$videoUrl"
|
videoUrl = resp.request.url.toString().substringBeforeLast("/") + "/$videoUrl"
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
playlist.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subsList, headers = vidHeader))
|
playlist.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subsList, headers = embedHeader))
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
playlist.add(Video(videoUrl, quality, videoUrl, headers = vidHeader))
|
playlist.add(Video(videoUrl, quality, videoUrl, headers = embedHeader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlist
|
return playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.kickassanime
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.webkit.WebResourceRequest
|
|
||||||
import android.webkit.WebResourceResponse
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class MasterPlaylistInterceptor : Interceptor {
|
|
||||||
|
|
||||||
private val context = Injekt.get<Application>()
|
|
||||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val originalRequest = chain.request()
|
|
||||||
|
|
||||||
val newRequest = resolveWithWebView(originalRequest) ?: throw Exception("Could not find playlist")
|
|
||||||
|
|
||||||
return chain.proceed(newRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
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)
|
|
||||||
|
|
||||||
var webView: WebView? = null
|
|
||||||
|
|
||||||
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
|
|
||||||
with(webview.settings) {
|
|
||||||
javaScriptEnabled = true
|
|
||||||
domStorageEnabled = true
|
|
||||||
databaseEnabled = true
|
|
||||||
useWideViewPort = false
|
|
||||||
loadWithOverviewMode = false
|
|
||||||
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
webview.webViewClient = object : WebViewClient() {
|
|
||||||
override fun shouldInterceptRequest(
|
|
||||||
view: WebView,
|
|
||||||
request: WebResourceRequest,
|
|
||||||
): WebResourceResponse? {
|
|
||||||
if (request.url.toString().contains(".m3u8")) {
|
|
||||||
newRequest = GET(request.url.toString(), request.requestHeaders.toHeaders())
|
|
||||||
latch.countDown()
|
|
||||||
}
|
|
||||||
return super.shouldInterceptRequest(view, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webView?.loadUrl(origRequestUrl, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a reasonable amount of time to retrieve the solution. The minimum should be
|
|
||||||
// around 4 seconds but it can take more due to slow networks or server issues.
|
|
||||||
latch.await(9, TimeUnit.SECONDS)
|
|
||||||
|
|
||||||
handler.post {
|
|
||||||
webView?.stopLoading()
|
|
||||||
webView?.destroy()
|
|
||||||
webView = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return newRequest
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,45 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
class PinkBird(private val client: OkHttpClient, private val json: Json) {
|
||||||
|
fun videosFromUrl(serverUrl: String, server: String): List<Video> {
|
||||||
|
return try {
|
||||||
|
val apiLink = serverUrl.replace("player.php", "pref.php")
|
||||||
|
val resp = client.newCall(GET(apiLink)).execute()
|
||||||
|
val jsonResp = json.decodeFromString<JsonObject>(resp.body!!.string())
|
||||||
|
jsonResp["data"]!!.jsonArray.map { el ->
|
||||||
|
val eid = el.jsonObject["eid"]!!.jsonPrimitive.content.decodeBase64()
|
||||||
|
val response = client.newCall(GET("https://pb.kaast1.com/manifest/$eid/master.m3u8")).execute()
|
||||||
|
if (response.code != 200) return emptyList()
|
||||||
|
response.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"
|
||||||
|
var videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||||
|
if (videoUrl.startsWith("https").not()) {
|
||||||
|
videoUrl = "https://${response.request.url.host}$videoUrl"
|
||||||
|
}
|
||||||
|
Video(videoUrl, quality, videoUrl)
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.decodeBase64(): String {
|
||||||
|
return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user