From 2fe9d1741e74b9019d4e3b23b01f218ec399ce58 Mon Sep 17 00:00:00 2001 From: Claudemirovsky <63046606+Claudemirovsky@users.noreply.github.com> Date: Tue, 18 Apr 2023 07:39:59 -0300 Subject: [PATCH] KickAssAnime: Adapt to the current (beta) source (#1509) --- lib/cryptoaes/build.gradle.kts | 18 + .../tachiyomi/lib/cryptoaes/CryptoAES.kt | 154 ++++ .../tachiyomi/lib/cryptoaes/Deobfuscator.kt | 82 +++ src/en/kickassanime/AndroidManifest.xml | 24 +- src/en/kickassanime/build.gradle | 14 +- src/en/kickassanime/res/web_hi_res_512.png | Bin 37211 -> 0 bytes .../en/kickassanime/JSONUtil.java | 88 --- .../en/kickassanime/KickAssAnime.kt | 684 +++++------------- .../kickassanime/KickAssAnimeUrlActivity.kt | 41 ++ .../en/kickassanime/dto/KickAssAnimeDto.kt | 71 ++ .../extractors/GogoCdnExtractor.kt | 123 ---- .../extractors/KickAssAnimeExtractor.kt | 108 +++ .../en/kickassanime/extractors/PinkBird.kt | 45 -- 13 files changed, 668 insertions(+), 784 deletions(-) create mode 100644 lib/cryptoaes/build.gradle.kts create mode 100644 lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt create mode 100644 lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt delete mode 100644 src/en/kickassanime/res/web_hi_res_512.png delete mode 100644 src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/JSONUtil.java create mode 100644 src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnimeUrlActivity.kt create mode 100644 src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/dto/KickAssAnimeDto.kt delete mode 100644 src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/GogoCdnExtractor.kt create mode 100644 src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/KickAssAnimeExtractor.kt delete mode 100644 src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/PinkBird.kt diff --git a/lib/cryptoaes/build.gradle.kts b/lib/cryptoaes/build.gradle.kts new file mode 100644 index 000000000..af3736596 --- /dev/null +++ b/lib/cryptoaes/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + compileSdk = AndroidConfig.compileSdk + namespace = "eu.kanade.tachiyomi.lib.cryptoaes" + + defaultConfig { + minSdk = AndroidConfig.minSdk + targetSdk = AndroidConfig.targetSdk + } +} + +dependencies { + compileOnly(libs.kotlin.stdlib) +} diff --git a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt new file mode 100644 index 000000000..14a9057af --- /dev/null +++ b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt @@ -0,0 +1,154 @@ +package eu.kanade.tachiyomi.lib.cryptoaes + +/* + * Copyright (C) The Tachiyomi Open Source Project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// Thanks to Vlad on Stackoverflow: https://stackoverflow.com/a/63701411 + +import android.util.Base64 +import java.security.MessageDigest +import java.util.Arrays +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +/** + * Conforming with CryptoJS AES method + */ +@Suppress("unused", "FunctionName") +object CryptoAES { + + private const val KEY_SIZE = 256 + private const val IV_SIZE = 128 + private const val HASH_CIPHER = "AES/CBC/PKCS7PADDING" + private const val HASH_CIPHER_FALLBACK = "AES/CBC/PKCS5PADDING" + private const val AES = "AES" + private const val KDF_DIGEST = "MD5" + + /** + * Decrypt using CryptoJS defaults compatible method. + * Uses KDF equivalent to OpenSSL's EVP_BytesToKey function + * + * http://stackoverflow.com/a/29152379/4405051 + * @param cipherText base64 encoded ciphertext + * @param password passphrase + */ + fun decrypt(cipherText: String, password: String): String { + try { + val ctBytes = Base64.decode(cipherText, Base64.DEFAULT) + val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16) + val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) + val md5: MessageDigest = MessageDigest.getInstance("MD5") + val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5) + return decryptAES( + cipherTextBytes, + keyAndIV?.get(0) ?: ByteArray(32), + keyAndIV?.get(1) ?: ByteArray(16), + ) + } catch (e: Exception) { + return "" + } + } + + /** + * Decrypt using CryptoJS defaults compatible method. + * + * @param cipherText base64 encoded ciphertext + * @param keyBytes key as a bytearray + * @param ivBytes iv as a bytearray + */ + fun decrypt(cipherText: String, keyBytes: ByteArray, ivBytes: ByteArray): String { + return try { + val cipherTextBytes = Base64.decode(cipherText, Base64.DEFAULT) + decryptAES(cipherTextBytes, keyBytes, ivBytes) + } catch (e: Exception) { + "" + } + } + + /** + * Decrypt using CryptoJS defaults compatible method. + * + * @param cipherTextBytes encrypted text as a bytearray + * @param keyBytes key as a bytearray + * @param ivBytes iv as a bytearray + */ + private fun decryptAES(cipherTextBytes: ByteArray, keyBytes: ByteArray, ivBytes: ByteArray): String { + return try { + val cipher = try { + Cipher.getInstance(HASH_CIPHER) + } catch (e: Throwable) { Cipher.getInstance(HASH_CIPHER_FALLBACK) } + val keyS = SecretKeySpec(keyBytes, AES) + cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(ivBytes)) + cipher.doFinal(cipherTextBytes).toString(Charsets.UTF_8) + } catch (e: Exception) { + "" + } + } + + /** + * Generates a key and an initialization vector (IV) with the given salt and password. + * + * https://stackoverflow.com/a/41434590 + * This method is equivalent to OpenSSL's EVP_BytesToKey function + * (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c). + * By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data. + * + * @param keyLength the length of the generated key (in bytes) + * @param ivLength the length of the generated IV (in bytes) + * @param iterations the number of digestion rounds + * @param salt the salt data (8 bytes of data or `null`) + * @param password the password data (optional) + * @param md the message digest algorithm to use + * @return an two-element array with the generated key and IV + */ + private fun generateKeyAndIV(keyLength: Int, ivLength: Int, iterations: Int, salt: ByteArray, password: ByteArray, md: MessageDigest): Array? { + val digestLength = md.digestLength + val requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength + val generatedData = ByteArray(requiredLength) + var generatedLength = 0 + return try { + md.reset() + + // Repeat process until sufficient data has been generated + while (generatedLength < keyLength + ivLength) { + // Digest data (last digest if available, password data, salt if available) + if (generatedLength > 0) md.update(generatedData, generatedLength - digestLength, digestLength) + md.update(password) + md.update(salt, 0, 8) + md.digest(generatedData, generatedLength, digestLength) + + // additional rounds + for (i in 1 until iterations) { + md.update(generatedData, generatedLength, digestLength) + md.digest(generatedData, generatedLength, digestLength) + } + generatedLength += digestLength + } + + // Copy key and IV into separate byte arrays + val result = arrayOfNulls(2) + result[0] = generatedData.copyOfRange(0, keyLength) + if (ivLength > 0) result[1] = generatedData.copyOfRange(keyLength, keyLength + ivLength) + result + } catch (e: Exception) { + throw e + } finally { + // Clean out temporary data + Arrays.fill(generatedData, 0.toByte()) + } + } + + // Stolen from AnimixPlay(EN) / GogoCdnExtractor + fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } +} diff --git a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt new file mode 100644 index 000000000..8c584bc48 --- /dev/null +++ b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt @@ -0,0 +1,82 @@ +package eu.kanade.tachiyomi.lib.cryptoaes + +/* + * Copyright (C) The Tachiyomi Open Source Project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/** + * Helper class to deobfuscate JavaScript strings encoded in JSFuck style. + * + * More info on JSFuck found [here](https://en.wikipedia.org/wiki/JSFuck). + * + * Currently only supports Numeric and decimal ('.') characters + */ +object Deobfuscator { + fun deobfuscateJsPassword(inputString: String): String { + var idx = 0 + val brackets = listOf('[', '(') + var evaluatedString = StringBuilder() + while (idx < inputString.length) { + val chr = inputString[idx] + if (chr !in brackets) { + idx++ + continue + } + val closingIndex = getMatchingBracketIndex(idx, inputString) + if (chr == '[') { + val digit = calculateDigit(inputString.substring(idx, closingIndex)) + evaluatedString.append(digit) + } else { + evaluatedString.append('.') + if (inputString.getOrNull(closingIndex + 1) == '[') { + val skippingIndex = getMatchingBracketIndex(closingIndex + 1, inputString) + idx = skippingIndex + 1 + continue + } + } + idx = closingIndex + 1 + } + return evaluatedString.toString() + } + + private fun getMatchingBracketIndex(openingIndex: Int, inputString: String): Int { + val openingBracket = inputString[openingIndex] + val closingBracket = when (openingBracket) { + '[' -> ']' + else -> ')' + } + var counter = 0 + for (idx in openingIndex until inputString.length) { + if (inputString[idx] == openingBracket) counter++ + if (inputString[idx] == closingBracket) counter-- + + if (counter == 0) return idx // found matching bracket + if (counter < 0) return -1 // unbalanced brackets + } + return -1 // matching bracket not found + } + + private fun calculateDigit(inputSubString: String): Char { + /* 0 == '+[]' + 1 == '+!+[]' + 2 == '!+[]+!+[]' + 3 == '!+[]+!+[]+!+[]' + ... + therefore '!+[]' count equals the digit + if count equals 0, check for '+[]' just to be sure + */ + val digit = "\\!\\+\\[\\]".toRegex().findAll(inputSubString).count() // matches '!+[]' + if (digit == 0) { + if ("\\+\\[\\]".toRegex().findAll(inputSubString).count() == 1) { // matches '+[]' + return '0' + } + } else if (digit in 1..9) { + return digit.digitToChar() + } + return '-' // Illegal digit + } +} diff --git a/src/en/kickassanime/AndroidManifest.xml b/src/en/kickassanime/AndroidManifest.xml index acb4de356..5fa512c5c 100644 --- a/src/en/kickassanime/AndroidManifest.xml +++ b/src/en/kickassanime/AndroidManifest.xml @@ -1,2 +1,24 @@ - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/src/en/kickassanime/build.gradle b/src/en/kickassanime/build.gradle index c4caddb37..3f7e8338b 100644 --- a/src/en/kickassanime/build.gradle +++ b/src/en/kickassanime/build.gradle @@ -1,18 +1,18 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} ext { extName = 'KickAssAnime' pkgNameSuffix = 'en.kickassanime' extClass = '.KickAssAnime' - extVersionCode = 21 - libVersion = '13' + extVersionCode = 22 } dependencies { - implementation(project(':lib-streamsb-extractor')) - implementation(project(':lib-dood-extractor')) + implementation(project(":lib-cryptoaes")) } apply from: "$rootDir/common.gradle" diff --git a/src/en/kickassanime/res/web_hi_res_512.png b/src/en/kickassanime/res/web_hi_res_512.png deleted file mode 100644 index 0c386610920bf5b730f2920578ae88dda573da97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37211 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 0x10000) { - assert false : "Java stores as u16, so it should never give us a character that's bigger than 2 bytes. It literally can't."; - } else if(chx > 127) { - output.append(String.format("\\u%04x", chx)); - } else { - output.append(ch); - } - } - - return output.toString(); - } - - public static String unescape(String input) { - StringBuilder builder = new StringBuilder(); - - int i = 0; - while (i < input.length()) { - char delimiter = input.charAt(i); i++; // consume letter or backslash - - if(delimiter == '\\' && i < input.length()) { - - // consume first after backslash - char ch = input.charAt(i); i++; - - if(ch == '\\' || ch == '/' || ch == '"' || ch == '\'') { - builder.append(ch); - } - else if(ch == 'n') builder.append('\n'); - else if(ch == 'r') builder.append('\r'); - else if(ch == 't') builder.append('\t'); - else if(ch == 'b') builder.append('\b'); - else if(ch == 'f') builder.append('\f'); - else if(ch == 'u') { - - StringBuilder hex = new StringBuilder(); - - // expect 4 digits - if (i+4 > input.length()) { - throw new RuntimeException("Not enough unicode digits! "); - } - for (char x : input.substring(i, i + 4).toCharArray()) { - if(!Character.isLetterOrDigit(x)) { - throw new RuntimeException("Bad character in unicode escape."); - } - hex.append(Character.toLowerCase(x)); - } - i+=4; // consume those four digits. - - int code = Integer.parseInt(hex.toString(), 16); - builder.append((char) code); - } else { - throw new RuntimeException("Illegal escape sequence: \\"+ch); - } - } else { // it's not a backslash, or it's the last character. - builder.append(delimiter); - } - } - - return builder.toString(); - } -} diff --git a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt index e63c886c7..23fc79aab 100644 --- a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt +++ b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt @@ -2,514 +2,213 @@ package eu.kanade.tachiyomi.animeextension.en.kickassanime import android.app.Application import android.content.SharedPreferences -import android.net.Uri -import android.util.Base64 import androidx.preference.ListPreference import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.GogoCdnExtractor -import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.PinkBird +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.AnimeInfoDto +import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.EpisodeResponseDto +import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.PopularItemDto +import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.PopularResponseDto +import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.RecentsResponseDto +import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.ServersDto +import eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors.KickAssAnimeExtractor import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode -import eu.kanade.tachiyomi.animesource.model.Track import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource -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 eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.float -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.MediaType.Companion.toMediaType import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import org.jsoup.nodes.Document +import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.regex.Pattern -@ExperimentalSerializationApi class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() { override val name = "KickAssAnime" - override val baseUrl by lazy { - preferences.getString( - "preferred_domain", - "https://www2.kickassanime.ro", - )!! - } + override val baseUrl = "https://kaas.am" + + private val API_URL = "$baseUrl/api/show" override val lang = "en" - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - private val json: Json by injectLazy() + override val supportsLatest = true private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - companion object { - private val DateFormatter by lazy { - SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH) + private val json = Json { + ignoreUnknownKeys = true + } + + // ============================== Popular =============================== + override fun popularAnimeRequest(page: Int) = GET("$API_URL/popular?page=$page") + + override fun popularAnimeParse(response: Response): AnimesPage { + val data = response.parseAs() + val animes = data.result.map(::popularAnimeFromObject) + val page = response.request.url.queryParameter("page")?.toIntOrNull() ?: 0 + val hasNext = data.page_count > page + return AnimesPage(animes, hasNext) + } + + private fun popularAnimeFromObject(anime: PopularItemDto): SAnime { + return SAnime.create().apply { + val useEnglish = preferences.getBoolean(PREF_USE_ENGLISH_KEY, PREF_USE_ENGLISH_DEFAULT) + title = when { + anime.title_en.isNotBlank() && useEnglish -> anime.title_en + else -> anime.title + } + setUrlWithoutDomain("/${anime.slug}") + thumbnail_url = "$baseUrl/${anime.poster.url}" } } - // Add non working server names here - private val deadServers = listOf( - "BETASERVER1", - "BETASERVER3", - "DEVSTREAM", - "THETA-ORIGINAL-V4", - "KICKASSANIME1", - ) + // ============================== Episodes ============================== + private fun episodeListRequest(anime: SAnime, page: Int) = + GET("$API_URL/${anime.url}/episodes?page=$page&lang=ja-JP") - private val workingServers = arrayOf( - "StreamSB", "PINK-BIRD", "Doodstream", "MAVERICKKI", "BETA-SERVER", "DAILYMOTION", - "BETAPLAYER", "Vidstreaming", "SAPPHIRE-DUCK", "KICKASSANIMEV2", "ORIGINAL-QUALITY-V2", - ) + private fun getEpisodeResponse(anime: SAnime, page: Int): EpisodeResponseDto { + return client.newCall(episodeListRequest(anime, page)) + .execute() + .parseAs() + } - override fun popularAnimeRequest(page: Int): Request = - GET("$baseUrl/api/get_anime_list/all/$page") + override fun fetchEpisodeList(anime: SAnime): Observable> { + val first = getEpisodeResponse(anime, 1) + val items = buildList { + addAll(first.result) - override fun popularAnimeParse(response: Response): AnimesPage { - val responseObject = json.decodeFromString(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 - title = item.jsonObject["name"]!!.jsonPrimitive.content + first.pages.drop(1).forEachIndexed { index, _ -> + addAll(getEpisodeResponse(anime, index + 2).result) } } - return AnimesPage(animes, true) + + val episodes = items.map { + SEpisode.create().apply { + name = it.title + url = "${anime.url}/ep-${it.episode_string}-${it.slug}" + episode_number = it.episode_string.toFloatOrNull() ?: 0F + } + } + + return Observable.just(episodes.reversed()) } override fun episodeListParse(response: Response): List { - val data = getAppdata(response.asJsoup()) - val anime = data["anime"]!!.jsonObject - val episodeList = anime["episodes"]!!.jsonArray - return episodeList.map { item -> - SEpisode.create().apply { - url = item.jsonObject["slug"]!!.jsonPrimitive.content - episode_number = item.jsonObject["num"]!!.jsonPrimitive.float - name = item.jsonObject["epnum"]!!.jsonPrimitive.content - date_upload = parseDate(item.jsonObject["createddate"]!!.jsonPrimitive.content) - } - } + TODO("Not yet implemented") } - private fun parseDate(dateStr: String): Long { - return runCatching { DateFormatter.parse(dateStr)?.time } - .getOrNull() ?: 0L + // ============================ Video Links ============================= + override fun videoListRequest(episode: SEpisode): Request { + val url = API_URL + episode.url.replace("/ep-", "/episode/ep-") + return GET(url) } - override fun latestUpdatesParse(response: Response) = throw Exception("not used") - - override fun latestUpdatesRequest(page: Int) = throw Exception("not used") - override fun videoListParse(response: Response): List