forked from recloudstream/cloudstream
commit
64e5712b46
2 changed files with 535 additions and 0 deletions
|
@ -30,6 +30,7 @@ object APIHolder {
|
||||||
val apis = arrayListOf(
|
val apis = arrayListOf(
|
||||||
TrailersToProvider(),
|
TrailersToProvider(),
|
||||||
//ShiroProvider(), // v2 fucked me
|
//ShiroProvider(), // v2 fucked me
|
||||||
|
AnimePaheProvider(),
|
||||||
TenshiProvider(),
|
TenshiProvider(),
|
||||||
WcoProvider(),
|
WcoProvider(),
|
||||||
// MeloMovieProvider(), // Captcha for links
|
// MeloMovieProvider(), // Captcha for links
|
||||||
|
|
|
@ -0,0 +1,534 @@
|
||||||
|
package com.lagradost.cloudstream3.animeproviders
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import khttp.structures.cookie.CookieJar
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
|
||||||
|
class AnimePaheProvider : MainAPI() {
|
||||||
|
companion object {
|
||||||
|
var cookies = CookieJar()
|
||||||
|
private fun getType(t: String): TvType {
|
||||||
|
return if (t.contains("OVA") || t.contains("Special")) TvType.ONA
|
||||||
|
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||||
|
else TvType.Anime
|
||||||
|
}
|
||||||
|
fun generateSession(): Boolean {
|
||||||
|
if (cookies.entries.size != 0) return true
|
||||||
|
return try {
|
||||||
|
val response = khttp.get("https://animepahe.com/")
|
||||||
|
cookies = response.cookies
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val YTSM = "ysmm = '([^']+)".toRegex()
|
||||||
|
|
||||||
|
val KWIK_PARAMS_RE = Regex("""\(\"(\w+)\",\d+,\"(\w+)\",(\d+),(\d+),\d+\)""")
|
||||||
|
val KWIK_D_URL = Regex("action=\"([^\"]+)\"")
|
||||||
|
val KWIK_D_TOKEN = Regex("value=\"([^\"]+)\"")
|
||||||
|
val YOUTUBE_VIDEO_LINK = Regex("""(^(?:https?:)?(?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(?:\-nocookie)?\.(?:[A-Za-z]{2,4}|[A-Za-z]{2,3}\.[A-Za-z]{2})\/)(?:watch|embed\/|vi?\/)*(?:\?[\w=&]*vi?=)?[^#&\?\/]{11}.*${'$'})""")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val mainUrl: String
|
||||||
|
get() = "https://animepahe.com"
|
||||||
|
override val name: String
|
||||||
|
get() = "AnimePahe"
|
||||||
|
override val hasQuickSearch: Boolean
|
||||||
|
get() = false
|
||||||
|
override val hasMainPage: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val supportedTypes: Set<TvType>
|
||||||
|
get() = setOf(
|
||||||
|
TvType.AnimeMovie,
|
||||||
|
TvType.Anime,
|
||||||
|
TvType.ONA
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getMainPage(): HomePageResponse? {
|
||||||
|
data class Data (
|
||||||
|
@JsonProperty("id") val id : Int,
|
||||||
|
@JsonProperty("anime_id") val animeId : Int,
|
||||||
|
@JsonProperty("anime_title") val animeTitle : String,
|
||||||
|
@JsonProperty("episode") val episode : Int,
|
||||||
|
@JsonProperty("snapshot") val snapshot : String,
|
||||||
|
@JsonProperty("created_at") val createdAt : String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnimePaheLatestReleases (
|
||||||
|
@JsonProperty("total") val total : Int,
|
||||||
|
@JsonProperty("data") val data : List<Data>
|
||||||
|
)
|
||||||
|
|
||||||
|
val urls = listOf(
|
||||||
|
Pair("$mainUrl/api?m=airing&page=1", "Latest Releases"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val items = ArrayList<HomePageList>()
|
||||||
|
for (i in urls) {
|
||||||
|
try {
|
||||||
|
val response = khttp.get(i.first)
|
||||||
|
val episodes = mapper.readValue<AnimePaheLatestReleases>(response.text).data.map {
|
||||||
|
|
||||||
|
AnimeSearchResponse(
|
||||||
|
it.animeTitle,
|
||||||
|
"https://pahe.win/a/${it.animeId}",
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
EnumSet.of(DubStatus.Subbed),
|
||||||
|
null,
|
||||||
|
it.episode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(HomePageList(i.second, episodes))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(items.size <= 0) throw ErrorLoadingException()
|
||||||
|
return HomePageResponse(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun search(query: String): ArrayList<SearchResponse> {
|
||||||
|
|
||||||
|
data class AnimePaheSearchData (
|
||||||
|
@JsonProperty("id") val id : Int,
|
||||||
|
@JsonProperty("slug") val slug : String,
|
||||||
|
@JsonProperty("title") val title : String,
|
||||||
|
@JsonProperty("type") val type : String,
|
||||||
|
@JsonProperty("episodes") val episodes : Int,
|
||||||
|
@JsonProperty("status") val status : String,
|
||||||
|
@JsonProperty("season") val season : String,
|
||||||
|
@JsonProperty("year") val year : Int,
|
||||||
|
@JsonProperty("score") val score : Double,
|
||||||
|
@JsonProperty("poster") val poster : String,
|
||||||
|
@JsonProperty("session") val session : String,
|
||||||
|
@JsonProperty("relevance") val relevance : String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnimePaheSearch (
|
||||||
|
@JsonProperty("total") val total : Int,
|
||||||
|
@JsonProperty("data") val data : List<AnimePaheSearchData>
|
||||||
|
)
|
||||||
|
|
||||||
|
val url = "https://animepahe.com/api?m=search&l=8&q=$query"
|
||||||
|
val headers = mapOf("referer" to "https://animepahe.com/")
|
||||||
|
|
||||||
|
val req = khttp.get(url, headers=headers)
|
||||||
|
val data = req.let { mapper.readValue<AnimePaheSearch>(it.text) }
|
||||||
|
|
||||||
|
return ArrayList(data.data.map {
|
||||||
|
AnimeSearchResponse(
|
||||||
|
it.title,
|
||||||
|
"https://pahe.win/a/${it.id}",
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
it.poster,
|
||||||
|
it.year,
|
||||||
|
null,
|
||||||
|
EnumSet.of(DubStatus.Subbed),
|
||||||
|
null,
|
||||||
|
it.episodes
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AnimeData(
|
||||||
|
@JsonProperty("id") val id: Int,
|
||||||
|
@JsonProperty("anime_id") val animeId: Int,
|
||||||
|
@JsonProperty("episode") val episode: Int,
|
||||||
|
@JsonProperty("title") val title: String,
|
||||||
|
@JsonProperty("snapshot") val snapshot: String,
|
||||||
|
@JsonProperty("session") val session: String,
|
||||||
|
@JsonProperty("filler") val filler: Int,
|
||||||
|
@JsonProperty("created_at") val createdAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class AnimePaheAnimeData(
|
||||||
|
@JsonProperty("total") val total: Int,
|
||||||
|
@JsonProperty("per_page") val perPage: Int,
|
||||||
|
@JsonProperty("current_page") val currentPage: Int,
|
||||||
|
@JsonProperty("last_page") val lastPage: Int,
|
||||||
|
@JsonProperty("next_page_url") val nextPageUrl: String?,
|
||||||
|
@JsonProperty("prev_page_url") val prevPageUrl: String?,
|
||||||
|
@JsonProperty("from") val from: Int,
|
||||||
|
@JsonProperty("to") val to: Int,
|
||||||
|
@JsonProperty("data") val data: List<AnimeData>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
private fun generateListOfEpisodes(link: String): ArrayList<AnimeEpisode> {
|
||||||
|
try {
|
||||||
|
val attrs = link.split('/')
|
||||||
|
val id = attrs[attrs.size - 1]
|
||||||
|
|
||||||
|
val uri = "https://animepahe.com/api?m=release&id=$id&sort=episode_asc&page=1"
|
||||||
|
val headers = mapOf("referer" to "https://animepahe.com/")
|
||||||
|
|
||||||
|
val req = khttp.get(uri, headers = headers)
|
||||||
|
val data = req.let { mapper.readValue<AnimePaheAnimeData>(it.text) }
|
||||||
|
|
||||||
|
val lastPage = data.lastPage
|
||||||
|
val perPage = data.perPage
|
||||||
|
val total = data.total
|
||||||
|
var ep = 1
|
||||||
|
val episodes = ArrayList<AnimeEpisode>()
|
||||||
|
|
||||||
|
fun getEpisodeTitle(k: AnimeData): String {
|
||||||
|
return if (k.title.length == 0) {
|
||||||
|
"Episode ${k.episode}"
|
||||||
|
} else {
|
||||||
|
k.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPage == 1 && perPage > total) {
|
||||||
|
data.data.forEach {
|
||||||
|
episodes.add(
|
||||||
|
AnimeEpisode(
|
||||||
|
"https://animepahe.com/api?m=links&id=${it.animeId}&session=${it.session}&p=kwik!!TRUE!!",
|
||||||
|
getEpisodeTitle(it),
|
||||||
|
if (it.snapshot.length == 0) {null} else {it.snapshot},
|
||||||
|
it.createdAt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (page in 0 until lastPage) {
|
||||||
|
for (i in 0 until perPage) {
|
||||||
|
if (ep <= total) {
|
||||||
|
episodes.add(
|
||||||
|
AnimeEpisode(
|
||||||
|
"https://animepahe.com/api?m=release&id=${id}&sort=episode_asc&page=${page + 1}&ep=${ep}!!FALSE!!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
++ep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return episodes
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return ArrayList<AnimeEpisode>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun load(url: String): LoadResponse? {
|
||||||
|
return normalSafeApiCall {
|
||||||
|
val html = khttp.get(url).text
|
||||||
|
val doc = Jsoup.parse(html)
|
||||||
|
|
||||||
|
val title = doc.selectFirst(".anime-poster img")?.attr("alt")?.replace("Poster of ", "")
|
||||||
|
val japTitle = doc.selectFirst("h2.japanese")?.text()
|
||||||
|
val poster = doc.selectFirst(".anime-poster a").attr("href")
|
||||||
|
|
||||||
|
val TvType = doc.selectFirst("""a[href*="/anime/type/"]""")?.text()
|
||||||
|
|
||||||
|
val trailer: String? = if (html.contains("https://www.youtube.com/watch")) {
|
||||||
|
YOUTUBE_VIDEO_LINK.find(html)?.destructured?.component1()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val episodes = generateListOfEpisodes(url) ?: ArrayList<AnimeEpisode>()
|
||||||
|
val year = """<strong>Aired:<\/strong>[^,]*, (\d+)""".toRegex().find(html)!!.destructured?.component1()?.toIntOrNull()
|
||||||
|
val status = when ("""<strong>Status:<\/strong>[^a]*a href=[\"']\/anime\/(.*?)[\"']""".toRegex().find(html)!!.destructured?.component1().toString()) {
|
||||||
|
"airing" -> ShowStatus.Ongoing
|
||||||
|
"completed" -> ShowStatus.Completed
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
val synopsis = doc.selectFirst(".anime-synopsis").text()
|
||||||
|
|
||||||
|
var anilistId: Int? = null
|
||||||
|
var malId: Int? = null
|
||||||
|
|
||||||
|
doc.select(".external-links > a").forEach { aTag ->
|
||||||
|
val split = aTag.attr("href").split("/")
|
||||||
|
|
||||||
|
if (aTag.attr("href").contains("anilist.co")) {
|
||||||
|
anilistId = split[split.size-1].toIntOrNull()
|
||||||
|
} else if (aTag.attr("href").contains("myanimelist.net")) {
|
||||||
|
malId = split[split.size-1].toIntOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimeLoadResponse(
|
||||||
|
title,
|
||||||
|
japTitle,
|
||||||
|
title.toString(),
|
||||||
|
url,
|
||||||
|
this.name,
|
||||||
|
getType(TvType.toString()),
|
||||||
|
poster,
|
||||||
|
year,
|
||||||
|
null,
|
||||||
|
episodes,
|
||||||
|
status,
|
||||||
|
synopsis,
|
||||||
|
if (!doc.select(".anime-genre > ul a").isEmpty()) {
|
||||||
|
ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() })
|
||||||
|
} else { null },
|
||||||
|
ArrayList(),
|
||||||
|
malId,
|
||||||
|
anilistId,
|
||||||
|
null,
|
||||||
|
trailer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun isNumber(s: String?): Boolean {
|
||||||
|
return s?.toIntOrNull() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pow(base: Int, expo: Int): Long{
|
||||||
|
// does not handle infinity
|
||||||
|
var exponent = expo
|
||||||
|
var result: Long = 1
|
||||||
|
|
||||||
|
while (exponent != 0) {
|
||||||
|
result *= base.toLong()
|
||||||
|
--exponent
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cookieStrToMap(cookie: String): Map<String, String> {
|
||||||
|
val cookies = mutableMapOf<String, String>()
|
||||||
|
for (string in cookie.split("; ")) {
|
||||||
|
val split = string.split("=").toMutableList()
|
||||||
|
val name = split.removeFirst().trim()
|
||||||
|
val value = if (split.size == 0) {"true"} else {split.joinToString("=")}
|
||||||
|
cookies[name] = value
|
||||||
|
}
|
||||||
|
return cookies.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getString(content: String, s1: Int, s2:Int): String {
|
||||||
|
val characterMap: String = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/";
|
||||||
|
|
||||||
|
val slice2 = characterMap.slice(0..s2-1)
|
||||||
|
var acc: Long = 0
|
||||||
|
|
||||||
|
|
||||||
|
for ((n, i) in content.reversed().withIndex()) {
|
||||||
|
acc += (when(isNumber("$i")) {
|
||||||
|
true -> "$i".toLong()
|
||||||
|
false -> "0".toLong()
|
||||||
|
}) * Math.pow(s1.toDouble(), n.toDouble()).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
var k = ""
|
||||||
|
|
||||||
|
while (acc > 0) {
|
||||||
|
k = slice2[(acc % s2).toInt()] + k
|
||||||
|
acc = (acc - (acc % s2)) / s2
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (k != "") {
|
||||||
|
true -> k
|
||||||
|
false -> "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String {
|
||||||
|
|
||||||
|
var r = ""
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
while (i < fullString.length) {
|
||||||
|
var s = ""
|
||||||
|
|
||||||
|
while (fullString[i] != key[v2]) {
|
||||||
|
s += fullString[i]
|
||||||
|
++i
|
||||||
|
}
|
||||||
|
var j = 0
|
||||||
|
|
||||||
|
while (j < key.length) {
|
||||||
|
s = s.replace(key[j].toString(), j.toString())
|
||||||
|
++j
|
||||||
|
}
|
||||||
|
r += (getString(s, v2, 10).toInt() - v1).toChar()
|
||||||
|
++i
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zipGen(gen: Sequence<Pair<Int, Int>>): ArrayList<Pair<Pair<Int, Int>, Pair<Int, Int>>> {
|
||||||
|
val allItems = gen.toList().toMutableList()
|
||||||
|
val newList = ArrayList<Pair<Pair<Int, Int>, Pair<Int, Int>>>()
|
||||||
|
|
||||||
|
while (allItems.size > 1) {
|
||||||
|
newList.add(Pair<Pair<Int, Int>, Pair<Int, Int>>(allItems[0], allItems[1]))
|
||||||
|
allItems.removeAt(0)
|
||||||
|
allItems.removeAt(0)
|
||||||
|
}
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeAdfly(codedKey: String): String {
|
||||||
|
var r = ""; var j = ""
|
||||||
|
|
||||||
|
for ((n, l) in codedKey.withIndex()) {
|
||||||
|
if (n % 2 != 0) {
|
||||||
|
j = l + j
|
||||||
|
} else {
|
||||||
|
r += l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val encodedUri = ((r + j).toCharArray().map { it.toString() }).toMutableList()
|
||||||
|
val numbers = sequence {
|
||||||
|
for ((i, n) in encodedUri.withIndex()) {
|
||||||
|
if (isNumber(n)) {
|
||||||
|
yield(Pair<Int, Int>(i, n.toInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((first, second) in zipGen(numbers)) {
|
||||||
|
val xor = first.second.xor(second.second)
|
||||||
|
if (xor < 10) {
|
||||||
|
encodedUri[first.first] = xor.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var returnValue = String(encodedUri.joinToString("").toByteArray(), Charsets.UTF_8)
|
||||||
|
returnValue = String(android.util.Base64.decode(returnValue, android.util.Base64.DEFAULT), Charsets.ISO_8859_1)
|
||||||
|
return returnValue.slice(16..returnValue.length-17)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class VideoQuality (
|
||||||
|
@JsonProperty("id") val id : Int?,
|
||||||
|
@JsonProperty("audio") val audio : String?,
|
||||||
|
@JsonProperty("kwik") val kwik : String?,
|
||||||
|
@JsonProperty("kwik_adfly") val kwikAdfly : String
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class AnimePaheEpisodeLoadLinks (
|
||||||
|
@JsonProperty("data") val data : List<Map<String, VideoQuality>>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
private fun bypassAdfly(adflyUri: String): String {
|
||||||
|
if (!generateSession()) {
|
||||||
|
return bypassAdfly(adflyUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseCode = 302
|
||||||
|
var adflyContent: khttp.responses.Response? = null
|
||||||
|
var tries = 0
|
||||||
|
|
||||||
|
while (responseCode != 200 && tries < 20) {
|
||||||
|
adflyContent = khttp.get(khttp.get(adflyUri, cookies=cookies, allowRedirects = false).headers.getValue("location"), cookies=cookies, allowRedirects = false)
|
||||||
|
cookies.putAll(adflyContent.cookies.toMap())
|
||||||
|
responseCode = adflyContent.statusCode
|
||||||
|
++tries
|
||||||
|
}
|
||||||
|
if (tries > 19) {
|
||||||
|
throw Exception("Failed to bypass adfly.")
|
||||||
|
}
|
||||||
|
return decodeAdfly(YTSM.find(adflyContent?.text.toString())!!.destructured.component1())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStreamUrlFromKwik(adflyUri: String): String {
|
||||||
|
val fContent = khttp.get(bypassAdfly(adflyUri), headers=mapOf("referer" to "https://kwik.cx/"), cookies=cookies)
|
||||||
|
cookies.putAll(fContent.cookies.toMap())
|
||||||
|
|
||||||
|
val (fullString, key, v1, v2) = KWIK_PARAMS_RE.find(fContent.text.toString())!!.destructured
|
||||||
|
val decrypted = decrypt(fullString, key, v1.toInt(), v2.toInt())
|
||||||
|
val uri = KWIK_D_URL.find(decrypted)!!.destructured.component1()
|
||||||
|
val tok = KWIK_D_TOKEN.find(decrypted)!!.destructured.component1()
|
||||||
|
var content: khttp.responses.Response? = null
|
||||||
|
|
||||||
|
var code = 419
|
||||||
|
var tries = 0
|
||||||
|
|
||||||
|
while (code != 302 && tries < 20) {
|
||||||
|
content = khttp.post(
|
||||||
|
uri,
|
||||||
|
allowRedirects = false,
|
||||||
|
data=mapOf("_token" to tok),
|
||||||
|
headers=mapOf("referer" to fContent.url),
|
||||||
|
cookies=cookieStrToMap(fContent.headers.getValue("set-cookie").replace("path=/,", ""))
|
||||||
|
)
|
||||||
|
code = content.statusCode
|
||||||
|
++tries
|
||||||
|
}
|
||||||
|
if (tries > 19) {
|
||||||
|
throw Exception("Failed to extract the stream uri from kwik.")
|
||||||
|
}
|
||||||
|
return content?.headers?.getValue("location").toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractVideoLinks(episodeLink: String): List<ExtractorLink> {
|
||||||
|
var link = episodeLink
|
||||||
|
val headers = mapOf("referer" to "https://animepahe.com/")
|
||||||
|
|
||||||
|
if (link.contains("!!TRUE!!")) {
|
||||||
|
link = link.replace("!!TRUE!!", "")
|
||||||
|
} else {
|
||||||
|
val regex = """&ep=(\d+)!!FALSE!!""".toRegex()
|
||||||
|
val episodeNum = regex.find(link)?.destructured?.component1()?.toIntOrNull()
|
||||||
|
link = link.replace(regex, "")
|
||||||
|
|
||||||
|
|
||||||
|
val req = khttp.get(link, headers = headers)
|
||||||
|
val jsonResponse = req.let { mapper.readValue<AnimePaheAnimeData>(it.text) }
|
||||||
|
val ep = ((jsonResponse.data.map {
|
||||||
|
if (it.episode == episodeNum) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}).filterNotNull())[0]
|
||||||
|
link = "https://animepahe.com/api?m=links&id=${ep.animeId}&session=${ep.session}&p=kwik"
|
||||||
|
}
|
||||||
|
val req = khttp.get(link, headers = headers)
|
||||||
|
val data = mapper.readValue<AnimePaheEpisodeLoadLinks>(req.text)
|
||||||
|
|
||||||
|
val qualities = ArrayList<ExtractorLink>()
|
||||||
|
|
||||||
|
data.data.forEach {
|
||||||
|
it.entries.forEach { quality ->
|
||||||
|
qualities.add(
|
||||||
|
ExtractorLink(
|
||||||
|
"KWIK",
|
||||||
|
"KWIK - ${quality.key} [${quality.value.audio ?: "jpn"}]",
|
||||||
|
getStreamUrlFromKwik(quality.value.kwikAdfly),
|
||||||
|
"",
|
||||||
|
getQualityFromName(quality.key),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qualities
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
for (server in extractVideoLinks(data)) {
|
||||||
|
callback.invoke(server)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue