diff --git a/src/de/kool/AndroidManifest.xml b/src/de/kool/AndroidManifest.xml new file mode 100644 index 000000000..acb4de356 --- /dev/null +++ b/src/de/kool/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/de/kool/build.gradle b/src/de/kool/build.gradle new file mode 100644 index 000000000..834f8da29 --- /dev/null +++ b/src/de/kool/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Kool' + pkgNameSuffix = 'de.kool' + extClass = '.Kool' + extVersionCode = 1 + libVersion = '13' +} + +dependencies { + implementation(project(':lib-streamtape-extractor')) + implementation(project(':lib-voe-extractor')) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/de/kool/res/mipmap-hdpi/ic_launcher.png b/src/de/kool/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..7647a8852 Binary files /dev/null and b/src/de/kool/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/de/kool/res/mipmap-hdpi/ic_launcher_background.png b/src/de/kool/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 000000000..2d07511b8 Binary files /dev/null and b/src/de/kool/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/src/de/kool/res/mipmap-hdpi/ic_launcher_foreground.png b/src/de/kool/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..259233d76 Binary files /dev/null and b/src/de/kool/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src/de/kool/res/mipmap-hdpi/ic_launcher_monochrome.png b/src/de/kool/res/mipmap-hdpi/ic_launcher_monochrome.png new file mode 100644 index 000000000..259233d76 Binary files /dev/null and b/src/de/kool/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/src/de/kool/res/mipmap-mdpi/ic_launcher.png b/src/de/kool/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..af84b36c6 Binary files /dev/null and b/src/de/kool/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/de/kool/res/mipmap-mdpi/ic_launcher_background.png b/src/de/kool/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 000000000..09b41700c Binary files /dev/null and b/src/de/kool/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/src/de/kool/res/mipmap-mdpi/ic_launcher_foreground.png b/src/de/kool/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..360f5be2e Binary files /dev/null and b/src/de/kool/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src/de/kool/res/mipmap-mdpi/ic_launcher_monochrome.png b/src/de/kool/res/mipmap-mdpi/ic_launcher_monochrome.png new file mode 100644 index 000000000..360f5be2e Binary files /dev/null and b/src/de/kool/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/src/de/kool/res/mipmap-xhdpi/ic_launcher.png b/src/de/kool/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..78785c9b1 Binary files /dev/null and b/src/de/kool/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/de/kool/res/mipmap-xhdpi/ic_launcher_background.png b/src/de/kool/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 000000000..ea00baaf9 Binary files /dev/null and b/src/de/kool/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/src/de/kool/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/de/kool/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..de15043d3 Binary files /dev/null and b/src/de/kool/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src/de/kool/res/mipmap-xhdpi/ic_launcher_monochrome.png b/src/de/kool/res/mipmap-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 000000000..de15043d3 Binary files /dev/null and b/src/de/kool/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/src/de/kool/res/mipmap-xxhdpi/ic_launcher.png b/src/de/kool/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..a025dc39f Binary files /dev/null and b/src/de/kool/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/de/kool/res/mipmap-xxhdpi/ic_launcher_background.png b/src/de/kool/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 000000000..adcd3c25e Binary files /dev/null and b/src/de/kool/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/src/de/kool/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/de/kool/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..902dd5b9d Binary files /dev/null and b/src/de/kool/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src/de/kool/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/src/de/kool/res/mipmap-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 000000000..902dd5b9d Binary files /dev/null and b/src/de/kool/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/src/de/kool/res/mipmap-xxxhdpi/ic_launcher.png b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..ca631eb1f Binary files /dev/null and b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_background.png b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 000000000..08eb24e0c Binary files /dev/null and b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..7046d7998 Binary files /dev/null and b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 000000000..7046d7998 Binary files /dev/null and b/src/de/kool/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/src/de/kool/src/eu/kanade/tachiyomi/animeextension/de/kool/Kool.kt b/src/de/kool/src/eu/kanade/tachiyomi/animeextension/de/kool/Kool.kt new file mode 100644 index 000000000..8f2ccf8ee --- /dev/null +++ b/src/de/kool/src/eu/kanade/tachiyomi/animeextension/de/kool/Kool.kt @@ -0,0 +1,759 @@ +package eu.kanade.tachiyomi.animeextension.de.kool + +import android.app.Application +import android.content.SharedPreferences +import android.util.Log +import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.de.kool.extractors.FilemoonExtractor +import eu.kanade.tachiyomi.animeextension.de.movie4k.extractors.VidozaExtractor +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +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.Video +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor +import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor +import eu.kanade.tachiyomi.network.POST +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.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Headers +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class Kool : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "Kool" + + override val baseUrl = "https://www.kool.to" + + override val lang = "de" + + override val supportsLatest = false + + override val client: OkHttpClient = network.client + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private val json = Json { + isLenient = true + ignoreUnknownKeys = true + } + + private fun mxhub(): String { + val mhubjson = client.newCall( + POST( + "https://www.dezor.net/api/app/ping", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "accept", "application/json"), + body = + """ + { + "reason": "ping", + "locale": "de", + "theme": "dark", + "metadata": { + "device": { + "type": "Tablet", + "brand": "google", + "model": "Pixel 5", + "name": "Pixel 5", + "uniqueId": "17623a364c1eab4b" + }, + "os": { + "name": "android", + "version": "12", + "abis": [ + "x86_64", + "arm64-v8a", + "x86", + "armeabi-v7a", + "armeabi" + ], + "host": "2e977b6bc000001" + }, + "app": { + "platform": "android", + "version": "1.1.2", + "buildId": "97245000", + "engine": "hbc85", + "signatures": [ + "43c308d52a6d51a07092ecd410963f26baae6a0e47d57fd718663a55e3d2d5e4" + ], + "installer": "com.android.vending" + }, + "version": { + "package": "net.dezor.browser", + "binary": "1.1.2", + "js": "1.1.2" + } + }, + "appFocusTime": 120169, + "playDuration": 0, + "devMode": true, + "hasMhub": true, + "castConnected": false, + "package": "net.dezor.browser", + "version": "1.1.2", + "process": "app", + "firstAppStart": 1677833802384, + "lastAppStart": 1677833802384, + "ipLocation": { + "ip": "0.0.0.0", + "country": "DE", + "city": "Berlin" + }, + "adblockEnabled": false, + "proxy": { + "supported": true, + "enabled": false + } + } + """.toRequestBody("application/json".toMediaType()), + ), + ).execute().body.string() + val jsonData = json.decodeFromString(mhubjson) + return jsonData["mhub"]!!.jsonPrimitive.content + } + + override fun popularAnimeRequest(page: Int): Request { + val mhub = mxhub() + val tpage = page - 1 + return POST( + "$baseUrl/kool/mediahubmx-catalog.json", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "catalogId": "tmdb.movie", + "id": "movie/popular", + "adult": false, + "search": "", + "sort": "popularity", + "filter": {}, + "cursor": + ${ + if (tpage == 0) { + "null" + } else if (tpage == 1){ + 8 + } else { + tpage * 8 - (tpage - 1) + } + }, + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } + + override fun popularAnimeParse(response: Response): AnimesPage { + val responseString = response.body.string() + return parsePopularAnimeJson(responseString) + } + + private fun parsePopularAnimeJson(jsonLine: String?): AnimesPage { + val jsonData = jsonLine ?: return AnimesPage(emptyList(), false) + val jObject = json.decodeFromString(jsonData) + val popularcursor = jObject.jsonObject["nextCursor"]?.jsonPrimitive?.content.toString() + val hasNextPage: Boolean = !popularcursor.contains("null") + val array = jObject["items"]!!.jsonArray + val animeList = mutableListOf() + for (item in array) { + val anime = SAnime.create() + anime.title = item.jsonObject["name"]!!.jsonPrimitive.content + val animeId = item.jsonObject["ids"]!!.jsonObject["tmdb_id"]!!.jsonPrimitive.content + val type = item.jsonObject["type"]!!.jsonPrimitive.content + if (type == "iptv") { + val url = item.jsonObject["url"]!!.jsonPrimitive.content + anime.setUrlWithoutDomain(url) + } else { + anime.setUrlWithoutDomain("$baseUrl/data/watch/?_id=$animeId&type=$type") + } + + anime.thumbnail_url = item.jsonObject["images"]!!.jsonObject["poster"]?.jsonPrimitive?.content ?: item.jsonObject["images"]!!.jsonObject["backdrop"]?.jsonPrimitive?.content + animeList.add(anime) + } + return AnimesPage(animeList, hasNextPage) + } + + // episodes + + override fun episodeListRequest(anime: SAnime): Request { + val mhub = mxhub() + if (anime.url.substringAfter("&type=") == "movie") { + return POST( + "$baseUrl/kool-cluster/mediahubmx-source.json?id=${anime.url.substringAfter("?_id=").substringBefore("&type")}&name=${anime.title}&type=movie", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "type": "${anime.url.substringAfter("&type=")}", + "ids": { + "tmdb_id": "${anime.url.substringAfter("?_id=").substringBefore("&type")}" + }, + "name": "${anime.title}", + "episode": {}, + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } else if (anime.url.substringAfter("&type=") == "series") { + return POST( + "$baseUrl/kool/mediahubmx-item.json?id=${anime.url.substringAfter("?_id=").substringBefore("&type")}&name=${anime.title}&type=series", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "type": "${anime.url.substringAfter("&type=")}", + "ids": { + "tmdb_id": "${anime.url.substringAfter("?_id=").substringBefore("&type")}" + }, + "name": "${anime.title}", + "episode": {}, + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } else { + return POST( + "$baseUrl/kool-cluster/mediahubmx-resolve.json?url=${anime.url}&type=tv", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "url": "$baseUrl${anime.url}", + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } + } + + override fun episodeListParse(response: Response): List { + val responseString = response.body.string() + val url = response.request.url.toString() + val type = url.substringAfter("&type=") + if (type == "movie") { + return parseMoviePage(url) + } else if (type == "series") { + return parseEpisodePage(responseString, url) + } else { + return parseTvPage(url) + } + } + + private fun parseMoviePage(url: String): List { + val episodeList = mutableListOf() + val episode = SEpisode.create() + episode.name = "Film" + episode.episode_number = 1F + episode.setUrlWithoutDomain(url) + episodeList.add(episode) + return episodeList.reversed() + } + + private fun parseEpisodePage(jsonLine: String?, url: String): List { + val jsonData = jsonLine ?: return mutableListOf() + val jObject = json.decodeFromString(jsonData) + val episodeList = mutableListOf() + val array = jObject["episodes"]!!.jsonArray + for (item in array) { + val episode = SEpisode.create() + val id = item.jsonObject["ids"]!!.jsonObject["tmdb_episode_id"]!!.jsonPrimitive.content + episode.name = "Staffel ${item.jsonObject["season"]!!.jsonPrimitive.int} " + + "Folge ${item.jsonObject["episode"]!!.jsonPrimitive.int} : " + item.jsonObject["name"]!!.jsonPrimitive.content + episode.episode_number = item.jsonObject["episode"]!!.jsonPrimitive.float + episode.setUrlWithoutDomain("$url&epid=$id&season=${item.jsonObject["season"]!!.jsonPrimitive.int}&ep=${item.jsonObject["episode"]!!.jsonPrimitive.int}&epname=${item.jsonObject["name"]!!.jsonPrimitive.content}") + episodeList.add(episode) + } + return episodeList.reversed() + } + + private fun parseTvPage(url: String): List { + val episodeList = mutableListOf() + val episode = SEpisode.create() + episode.name = "TV" + episode.episode_number = 1F + episode.setUrlWithoutDomain(url) + episodeList.add(episode) + return episodeList.reversed() + } + + // Video Extractor + + override fun videoListRequest(episode: SEpisode): Request { + val mhub = mxhub() + val id = episode.url.substringAfter("?id=").substringBefore("&name=") + val name = java.net.URLDecoder.decode(episode.url.substringAfter("&name=").substringBefore("&type="), "utf-8") + val type = episode.url.substringAfter("&type=").substringBefore("&epid") + if (type == "movie") { + return POST( + "$baseUrl/kool-cluster/mediahubmx-source.json", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "type": "$type", + "ids": { + "tmdb_id": "$id" + }, + "name": "$name", + "episode": {}, + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } else if (type == "series") { + return POST( + "$baseUrl/kool-cluster/mediahubmx-source.json", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "type": "$type", + "ids": { + "tmdb_id": "$id" + }, + "name": "$name", + "episode": { + "name": "${episode.url.substringAfter("&epname=")}", + "ids": { + "tmdb_episode_id": "${episode.url.substringAfter("&epid=").substringBefore("&season=")}" + }, + "season": ${episode.url.substringAfter("&season=").substringBefore("&ep=")}, + "episode": ${episode.url.substringAfter("&ep=").substringBefore("&epname=")} + }, + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } else { + return POST( + "$baseUrl/kool-cluster/mediahubmx-resolve.json", + headers = Headers.headersOf("content-type", "application/json; charset=utf-8", "mediahubmx-signature", mhub, "user-agent", "MediaHubMX/2"), + body = + """ + { + "language": "de", + "region": "DE", + "url": "$baseUrl${episode.url.substringAfter("?url=").substringBefore("&type=")}", + "clientVersion": "1.1.3" + } + """.toRequestBody("application/json".toMediaType()), + ) + } + } + + override fun videoListParse(response: Response): List