Fix #550 and Improve KwikExtractor Decrypt Function (#551)

* Improve KwikExtractor Decrypt Function

- Optimize and improve performance of KwikExtractor decrypt functionality

* Fix issue #550

* Update version
This commit is contained in:
CursedSheep 2025-01-18 00:47:35 +08:00 committed by GitHub
parent 970d14a93c
commit b4e9c0d3a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 23 additions and 69 deletions

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'AnimePahe' extName = 'AnimePahe'
extClass = '.AnimePahe' extClass = '.AnimePahe'
extVersionCode = 27 extVersionCode = 28
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -19,9 +19,6 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parseAs import eu.kanade.tachiyomi.util.parseAs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
@ -67,16 +64,8 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
* @see episodeListRequest * @see episodeListRequest
*/ */
override fun animeDetailsRequest(anime: SAnime): Request { override fun animeDetailsRequest(anime: SAnime): Request {
val animeId = anime.getId() val session = anime.getSession()
// We're using coroutines here to run it inside another thread and return GET("$baseUrl/anime/$session")
// prevent android.os.NetworkOnMainThreadException when trying to open
// webview or share it.
val session = runBlocking {
withContext(Dispatchers.IO) {
fetchSession(anime.title, animeId)
}
}
return GET("$baseUrl/anime/$session?anime_id=$animeId")
} }
override fun animeDetailsParse(response: Response): SAnime { override fun animeDetailsParse(response: Response): SAnime {
@ -106,8 +95,8 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
SAnime.create().apply { SAnime.create().apply {
title = anime.title title = anime.title
thumbnail_url = anime.snapshot thumbnail_url = anime.snapshot
val animeId = anime.id val sessionId = anime.anime_session
setUrlWithoutDomain("/anime/?anime_id=$animeId") setUrlWithoutDomain("/anime/?session_id=$sessionId")
artist = anime.fansub artist = anime.fansub
} }
} }
@ -124,8 +113,8 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
SAnime.create().apply { SAnime.create().apply {
title = anime.title title = anime.title
thumbnail_url = anime.poster thumbnail_url = anime.poster
val animeId = anime.id val sessionId = anime.session
setUrlWithoutDomain("/anime/?anime_id=$animeId") setUrlWithoutDomain("/anime/?session_id=$sessionId")
} }
} }
return AnimesPage(animeList, false) return AnimesPage(animeList, false)
@ -146,7 +135,7 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
* @see animeDetailsRequest * @see animeDetailsRequest
*/ */
override fun episodeListRequest(anime: SAnime): Request { override fun episodeListRequest(anime: SAnime): Request {
val session = fetchSession(anime.title, anime.getId()) val session = anime.getSession()
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")
} }
@ -310,15 +299,6 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun fetchSession(title: String, animeId: String): String {
return client.newCall(GET("$baseUrl/api?m=search&q=$title"))
.execute()
.body.string()
.substringAfter("\"id\":$animeId")
.substringAfter("\"session\":\"")
.substringBefore("\"")
}
private fun parseStatus(statusString: String): Int { private fun parseStatus(statusString: String): Int {
return when (statusString) { return when (statusString) {
"Currently Airing" -> SAnime.ONGOING "Currently Airing" -> SAnime.ONGOING
@ -327,7 +307,7 @@ class AnimePahe : ConfigurableAnimeSource, AnimeHttpSource() {
} }
} }
private fun SAnime.getId() = url.substringAfterLast("?anime_id=").substringBefore("\"") private fun SAnime.getSession() = url.substringAfterLast("?session_id=").substringBefore("\"")
private fun String.toDate(): Long { private fun String.toDate(): Long {
return runCatching { return runCatching {

View file

@ -35,7 +35,6 @@ import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import kotlin.math.pow
class KwikExtractor(private val client: OkHttpClient) { class KwikExtractor(private val client: OkHttpClient) {
private var cookies: String = "" private var cookies: String = ""
@ -108,52 +107,25 @@ class KwikExtractor(private val client: OkHttpClient) {
} }
private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String { private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String {
var r = "" val keyIndexMap = key.withIndex().associate { it.value to it.index }
val sb = StringBuilder()
var i = 0 var i = 0
val toFind = key[v2]
while (i < fullString.length) { while (i < fullString.length) {
var s = "" val nextIndex = fullString.indexOf(toFind, i)
val decodedCharStr = buildString {
while (fullString[i] != key[v2]) { for (j in i until nextIndex) {
s += fullString[i] append(keyIndexMap[fullString[j]] ?: -1)
++i }
} }
var j = 0
while (j < key.length) { i = nextIndex + 1
s = s.replace(key[j].toString(), j.toString())
++j
}
r += (getString(s, v2).toInt() - v1).toChar()
++i
}
return r
}
private fun getString(content: String, s1: Int): String { val decodedChar = (decodedCharStr.toInt(v2) - v1).toChar()
val s2 = 10 sb.append(decodedChar)
val characterMap = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
val slice2 = characterMap.slice(0 until s2)
var acc: Long = 0
for ((n, i) in content.reversed().withIndex()) {
acc += when (isNumber("$i")) {
true -> "$i".toLong()
false -> 0L
} * s1.toDouble().pow(n.toDouble()).toInt()
} }
var k = "" return sb.toString()
while (acc > 0) {
k = slice2[(acc % s2).toInt()] + k
acc = (acc - (acc % s2)) / s2
}
return when (k != "") {
true -> k
false -> "0"
}
} }
} }

View file

@ -23,6 +23,7 @@ data class LatestAnimeDto(
@SerialName("anime_id") @SerialName("anime_id")
val id: Int, val id: Int,
val fansub: String, val fansub: String,
val anime_session: String,
) )
@Serializable @Serializable
@ -30,6 +31,7 @@ data class SearchResultDto(
val title: String, val title: String,
val poster: String, val poster: String,
val id: Int, val id: Int,
val session: String,
) )
@Serializable @Serializable