Fix animepahe (#1280)

This commit is contained in:
Secozzi
2023-02-15 22:30:51 +01:00
committed by GitHub
parent c08cbf1d57
commit 308c97431c
3 changed files with 79 additions and 53 deletions

View File

@ -1,15 +1,17 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'AnimePahe' extName = 'AnimePahe'
pkgNameSuffix = 'en.animepahe' pkgNameSuffix = 'en.animepahe'
extClass = '.AnimePahe' extClass = '.AnimePahe'
extVersionCode = 17 extVersionCode = 18
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
ext.coroutinesVersion = "1.4.3" ext.coroutinesVersion = "1.4.3"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
} }

View File

@ -13,13 +13,15 @@ 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.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.float import kotlinx.serialization.json.float
import kotlinx.serialization.json.int import kotlinx.serialization.json.int
@ -30,6 +32,7 @@ import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -56,7 +59,8 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
override fun animeDetailsRequest(anime: SAnime): Request { override fun animeDetailsRequest(anime: SAnime): Request {
val animeId = anime.url.substringAfterLast("?anime_id=") val parsed = json.decodeFromString<LinkData>(anime.url)
val animeId = parsed.url.substringAfterLast("?anime_id=")
val session = getSession(anime.title, animeId) val session = getSession(anime.title, animeId)
return GET("$baseUrl/anime/$session?anime_id=$animeId") return GET("$baseUrl/anime/$session?anime_id=$animeId")
} }
@ -111,7 +115,12 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
val anime = SAnime.create() val anime = SAnime.create()
anime.title = item.jsonObject["title"]!!.jsonPrimitive.content anime.title = item.jsonObject["title"]!!.jsonPrimitive.content
val animeId = item.jsonObject["id"]!!.jsonPrimitive.int val animeId = item.jsonObject["id"]!!.jsonPrimitive.int
anime.setUrlWithoutDomain("$baseUrl/anime/?anime_id=$animeId") anime.setUrlWithoutDomain(
LinkData(
"$baseUrl/anime/?anime_id=$animeId",
item.jsonObject["session"]!!.jsonPrimitive.content
).toJsonString()
)
animeList.add(anime) animeList.add(anime)
} }
return AnimesPage(animeList, false) return AnimesPage(animeList, false)
@ -136,7 +145,12 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
val anime = SAnime.create() val anime = SAnime.create()
anime.title = item.jsonObject["anime_title"]!!.jsonPrimitive.content anime.title = item.jsonObject["anime_title"]!!.jsonPrimitive.content
val animeId = item.jsonObject["anime_id"]!!.jsonPrimitive.int val animeId = item.jsonObject["anime_id"]!!.jsonPrimitive.int
anime.setUrlWithoutDomain("$baseUrl/anime/?anime_id=$animeId") anime.setUrlWithoutDomain(
LinkData(
"$baseUrl/anime/?anime_id=$animeId",
item.jsonObject["anime_session"]!!.jsonPrimitive.content
).toJsonString()
)
anime.artist = item.jsonObject["fansub"]!!.jsonPrimitive.content anime.artist = item.jsonObject["fansub"]!!.jsonPrimitive.content
animeList.add(anime) animeList.add(anime)
} }
@ -151,17 +165,33 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
} }
} }
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
val parsed = json.decodeFromString<LinkData>(anime.url)
return if (anime.status != SAnime.LICENSED) {
client.newCall(episodeListRequest(anime))
.asObservableSuccess()
.map { response ->
episodeListParse(response, parsed.animeSession)
}
} else {
Observable.error(Exception("Licensed - No episodes to show"))
}
}
override fun episodeListRequest(anime: SAnime): Request { override fun episodeListRequest(anime: SAnime): Request {
val animeId = anime.url.substringAfterLast("?anime_id=") val parsed = json.decodeFromString<LinkData>(anime.url)
val animeId = parsed.url.substringAfterLast("?anime_id=")
val session = getSession(anime.title, animeId) val session = getSession(anime.title, animeId)
return GET("$baseUrl/api?m=release&id=$session&sort=episode_desc&page=1") return GET("$baseUrl/api?m=release&id=$session&sort=episode_desc&page=1")
} }
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> = throw Exception("Not used")
return recursivePages(response)
private fun episodeListParse(response: Response, animeSession: String): List<SEpisode> {
return recursivePages(response, animeSession)
} }
private fun parseEpisodePage(jsonLine: String?): MutableList<SEpisode> { private fun parseEpisodePage(jsonLine: String?, animeSession: String): MutableList<SEpisode> {
val jsonData = jsonLine ?: return mutableListOf() val jsonData = jsonLine ?: return mutableListOf()
val jObject = json.decodeFromString<JsonObject>(jsonData) val jObject = json.decodeFromString<JsonObject>(jsonData)
val array = jObject["data"]!!.jsonArray val array = jObject["data"]!!.jsonArray
@ -171,9 +201,8 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
val episode = SEpisode.create() val episode = SEpisode.create()
episode.date_upload = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) episode.date_upload = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
.parse(itemO["created_at"]!!.jsonPrimitive.content)!!.time .parse(itemO["created_at"]!!.jsonPrimitive.content)!!.time
val animeId = itemO["anime_id"]!!.jsonPrimitive.int
val session = itemO["session"]!!.jsonPrimitive.content val session = itemO["session"]!!.jsonPrimitive.content
episode.setUrlWithoutDomain("$baseUrl/api?m=links&id=$animeId&session=$session&p=kwik") episode.setUrlWithoutDomain("$baseUrl/play/$animeSession/$session")
val epNum = itemO["episode"]!!.jsonPrimitive.float val epNum = itemO["episode"]!!.jsonPrimitive.float
episode.episode_number = epNum episode.episode_number = epNum
val epNumString = if (epNum % 1F == 0F) epNum.toInt().toString() else epNum.toString() val epNumString = if (epNum % 1F == 0F) epNum.toInt().toString() else epNum.toString()
@ -183,16 +212,16 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
return episodeList return episodeList
} }
private fun recursivePages(response: Response): List<SEpisode> { private fun recursivePages(response: Response, animeSession: String): List<SEpisode> {
val responseString = response.body!!.string() val responseString = response.body!!.string()
val jObject = json.decodeFromString<JsonObject>(responseString) val jObject = json.decodeFromString<JsonObject>(responseString)
val lastPage = jObject["last_page"]!!.jsonPrimitive.int val lastPage = jObject["last_page"]!!.jsonPrimitive.int
val page = jObject["current_page"]!!.jsonPrimitive.int val page = jObject["current_page"]!!.jsonPrimitive.int
val hasNextPage = page < lastPage val hasNextPage = page < lastPage
val returnList = parseEpisodePage(responseString) val returnList = parseEpisodePage(responseString, animeSession)
if (hasNextPage) { if (hasNextPage) {
val nextPage = nextPageRequest(response.request.url.toString(), page + 1) val nextPage = nextPageRequest(response.request.url.toString(), page + 1)
returnList += recursivePages(nextPage) returnList += recursivePages(nextPage, animeSession)
} }
return returnList return returnList
} }
@ -203,18 +232,17 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val array = json.decodeFromString<JsonObject>(response.body!!.string()) val document = response.asJsoup()
.jsonObject["data"]!!.jsonArray val videoList = mutableListOf<Video>()
val videos = mutableListOf<Video>()
for (item in array) { document.select("div#resolutionMenu > button").forEachIndexed { index, btn ->
val quality = item.jsonObject.keys.first() val kwikLink = btn.attr("data-src")
val paheWinLink = item.jsonObject[quality]!!.jsonObject["kwik_pahewin"]!!.jsonPrimitive.content val quality = btn.text()
val kwikLink = item.jsonObject[quality]!!.jsonObject["kwik"]!!.jsonPrimitive.content val paheWinLink = document.select("div#pickDownload > a")[index].attr("href")
val audio = item.jsonObject[quality]!!.jsonObject["audio"]!! videoList.add(getVideo(paheWinLink, kwikLink, quality))
val qualityString = if (audio is JsonNull) "${quality}p" else "${quality}p (" + audio.jsonPrimitive.content + " audio)"
videos.add(getVideo(paheWinLink, kwikLink, qualityString))
} }
return videos
return videoList
} }
private fun getVideo(paheUrl: String, kwikUrl: String, quality: String): Video { private fun getVideo(paheUrl: String, kwikUrl: String, quality: String): Video {
@ -233,27 +261,14 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val subPreference = preferences.getString("preferred_sub", "jpn")!! val subPreference = preferences.getString("preferred_sub", "jpn")!!
val quality = preferences.getString("preferred_quality", "1080")!! val quality = preferences.getString("preferred_quality", "1080")!!
val qualityList = mutableListOf<Video>() val shouldEndWithEng = (subPreference == "eng")
val newList = mutableListOf<Video>()
var preferred = 0 return this.sortedWith(
for (video in this.reversed()) { compareBy(
if (video.quality.contains(quality)) { { it.quality.contains(quality) },
qualityList.add(preferred, video) { it.quality.endsWith("eng", true) == shouldEndWithEng },
preferred++ )
} else { ).reversed()
qualityList.add(video)
}
}
preferred = 0
for (video in qualityList) {
if (video.quality.contains(subPreference)) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -319,4 +334,14 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
screen.addPreference(subPref) screen.addPreference(subPref)
screen.addPreference(linkPref) screen.addPreference(linkPref)
} }
@Serializable
data class LinkData(
val url: String,
val animeSession: String
)
private fun LinkData.toJsonString(): String {
return json.encodeToString(this)
}
} }

View File

@ -27,8 +27,10 @@ SOFTWARE.
package eu.kanade.tachiyomi.animeextension.en.animepahe package eu.kanade.tachiyomi.animeextension.en.animepahe
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -48,13 +50,10 @@ class KwikExtractor(private val client: OkHttpClient) {
fun getHlsStreamUrl(kwikUrl: String, referer: String): String { fun getHlsStreamUrl(kwikUrl: String, referer: String): String {
val eContent = client.newCall(GET(kwikUrl, Headers.headersOf("referer", referer))) val eContent = client.newCall(GET(kwikUrl, Headers.headersOf("referer", referer)))
.execute().body!!.string() .execute().asJsoup()
val substring = eContent.substringAfterLast("m3u8|uwu|").substringBefore("'") val script = eContent.selectFirst("script:containsData(eval\\(function)").data().substringAfterLast("eval(function(")
val urlParts = substring.split("|").reversed() val unpacked = JsUnpacker.unpackAndCombine("eval(function($script")!!
assert(urlParts.lastIndex == 8) return unpacked.substringAfter("const source=\\'").substringBefore("\\';")
return urlParts[0] + "://" + urlParts[1] + "-" + urlParts[2] + "." + urlParts[3] + "." +
urlParts[4] + "." + urlParts[5] + "/" + urlParts[6] + "/" + urlParts[7] + "/" +
urlParts[8] + "/uwu.m3u8"
} }
fun getStreamUrlFromKwik(paheUrl: String): String { fun getStreamUrlFromKwik(paheUrl: String): String {