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