diff --git a/Kickassanime/build.gradle.kts b/Kickassanime/build.gradle.kts
new file mode 100644
index 00000000..e4f3d602
--- /dev/null
+++ b/Kickassanime/build.gradle.kts
@@ -0,0 +1,27 @@
+// use an integer for version numbers
+version = 1
+
+
+cloudstream {
+ language = "en"
+ // All of these properties are optional, you can safely remove them
+
+ // description = "Lorem Ipsum"
+ authors = listOf("Hexated")
+
+ /**
+ * Status int as the following:
+ * 0: Down
+ * 1: Ok
+ * 2: Slow
+ * 3: Beta only
+ * */
+ status = 1 // will be 3 if unspecified
+ tvTypes = listOf(
+ "AnimeMovie",
+ "Anime",
+ "OVA",
+ )
+
+ iconUrl = "https://www.google.com/s2/favicons?domain=www2.kickassanime.ro&sz=%size%"
+}
\ No newline at end of file
diff --git a/Kickassanime/src/main/AndroidManifest.xml b/Kickassanime/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..874740e3
--- /dev/null
+++ b/Kickassanime/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/Kickassanime/src/main/kotlin/com/hexated/GogoExtractor.kt b/Kickassanime/src/main/kotlin/com/hexated/GogoExtractor.kt
new file mode 100644
index 00000000..ed19fd25
--- /dev/null
+++ b/Kickassanime/src/main/kotlin/com/hexated/GogoExtractor.kt
@@ -0,0 +1,166 @@
+package com.hexated
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.base64Decode
+import com.lagradost.cloudstream3.base64DecodeArray
+import com.lagradost.cloudstream3.base64Encode
+import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
+import com.lagradost.cloudstream3.mvvm.safeApiCall
+import com.lagradost.cloudstream3.utils.AppUtils
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.M3u8Helper
+import com.lagradost.cloudstream3.utils.getQualityFromName
+import org.jsoup.nodes.Document
+import java.net.URI
+import javax.crypto.Cipher
+import javax.crypto.spec.IvParameterSpec
+import javax.crypto.spec.SecretKeySpec
+
+object GogoExtractor {
+
+ /**
+ * @param id base64Decode(show_id) + IV
+ * @return the encryption key
+ * */
+ private fun getKey(id: String): String? {
+ return normalSafeApiCall {
+ id.map {
+ it.code.toString(16)
+ }.joinToString("").substring(0, 32)
+ }
+ }
+
+ // https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt#L60
+ // No Licence on the function
+ private fun cryptoHandler(
+ string: String,
+ iv: String,
+ secretKeyString: String,
+ encrypt: Boolean = true
+ ): String {
+ //println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string")
+ val ivParameterSpec = IvParameterSpec(iv.toByteArray())
+ val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES")
+ val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
+ return if (!encrypt) {
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)
+ String(cipher.doFinal(base64DecodeArray(string)))
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
+ base64Encode(cipher.doFinal(string.toByteArray()))
+ }
+ }
+
+ /**
+ * @param iframeUrl something like https://gogoplay4.com/streaming.php?id=XXXXXX
+ * @param mainApiName used for ExtractorLink names and source
+ * @param iv secret iv from site, required non-null if isUsingAdaptiveKeys is off
+ * @param secretKey secret key for decryption from site, required non-null if isUsingAdaptiveKeys is off
+ * @param secretDecryptKey secret key to decrypt the response json, required non-null if isUsingAdaptiveKeys is off
+ * @param isUsingAdaptiveKeys generates keys from IV and ID, see getKey()
+ * @param isUsingAdaptiveData generate encrypt-ajax data based on $("script[data-name='episode']")[0].dataset.value
+ * */
+ suspend fun extractVidstream(
+ iframeUrl: String,
+ mainApiName: String,
+ callback: (ExtractorLink) -> Unit,
+ iv: String?,
+ secretKey: String?,
+ secretDecryptKey: String?,
+ // This could be removed, but i prefer it verbose
+ isUsingAdaptiveKeys: Boolean,
+ isUsingAdaptiveData: Boolean,
+ // If you don't want to re-fetch the document
+ iframeDocument: Document? = null
+ ) = safeApiCall {
+ // https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt
+ // No Licence on the following code
+ // Also modified of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/extractors/GogoCdnExtractor.kt
+ // License on the code above https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
+
+ if ((iv == null || secretKey == null || secretDecryptKey == null) && !isUsingAdaptiveKeys)
+ return@safeApiCall
+
+ val id = Regex("id=([^&]+)").find(iframeUrl)!!.value.removePrefix("id=")
+
+ var document: Document? = iframeDocument
+ val foundIv =
+ iv ?: (document ?: app.get(iframeUrl).document.also { document = it })
+ .select("""div.wrapper[class*=container]""")
+ .attr("class").split("-").lastOrNull() ?: return@safeApiCall
+ val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall
+ val foundDecryptKey = secretDecryptKey ?: foundKey
+
+ val uri = URI(iframeUrl)
+ val mainUrl = "https://" + uri.host
+
+ val encryptedId = cryptoHandler(id, foundIv, foundKey)
+ val encryptRequestData = if (isUsingAdaptiveData) {
+ // Only fetch the document if necessary
+ val realDocument = document ?: app.get(iframeUrl).document
+ val dataEncrypted =
+ realDocument.select("script[data-name='episode']").attr("data-value")
+ val headers = cryptoHandler(dataEncrypted, foundIv, foundKey, false)
+ "id=$encryptedId&alias=$id&" + headers.substringAfter("&")
+ } else {
+ "id=$encryptedId&alias=$id"
+ }
+
+ val jsonResponse =
+ app.get(
+ "$mainUrl/encrypt-ajax.php?$encryptRequestData",
+ headers = mapOf("X-Requested-With" to "XMLHttpRequest")
+ )
+ val dataencrypted =
+ jsonResponse.text.substringAfter("{\"data\":\"").substringBefore("\"}")
+ val datadecrypted = cryptoHandler(dataencrypted, foundIv, foundDecryptKey, false)
+ val sources = AppUtils.parseJson(datadecrypted)
+
+ suspend fun invokeGogoSource(
+ source: GogoSource,
+ sourceCallback: (ExtractorLink) -> Unit
+ ) {
+ if (source.file.contains(".m3u8")) {
+ M3u8Helper.generateM3u8(
+ mainApiName,
+ source.file,
+ mainUrl,
+ headers = mapOf("Origin" to "https://plyr.link")
+ ).forEach(sourceCallback)
+ } else {
+ sourceCallback.invoke(
+ ExtractorLink(
+ mainApiName,
+ mainApiName,
+ source.file,
+ mainUrl,
+ getQualityFromName(source.label),
+ )
+ )
+ }
+ }
+
+ sources.source?.forEach {
+ invokeGogoSource(it, callback)
+ }
+ sources.sourceBk?.forEach {
+ invokeGogoSource(it, callback)
+ }
+ }
+
+ data class GogoSources(
+ @JsonProperty("source") val source: List?,
+ @JsonProperty("sourceBk") val sourceBk: List?,
+ //val track: List,
+ //val advertising: List,
+ //val linkiframe: String
+ )
+
+ data class GogoSource(
+ @JsonProperty("file") val file: String,
+ @JsonProperty("label") val label: String?,
+ @JsonProperty("type") val type: String?,
+ @JsonProperty("default") val default: String? = null
+ )
+}
diff --git a/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt b/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt
new file mode 100644
index 00000000..07f8e10f
--- /dev/null
+++ b/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt
@@ -0,0 +1,462 @@
+package com.hexated
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
+import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
+import com.lagradost.cloudstream3.utils.*
+import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
+import java.net.URI
+import java.net.URLDecoder
+
+class Kickassanime : MainAPI() {
+ override var mainUrl = "https://www2.kickassanime.ro"
+ override var name = "Kickassanime"
+ override val hasMainPage = true
+ override var lang = "en"
+ override val hasDownloadSupport = true
+
+ override val supportedTypes = setOf(
+ TvType.Anime,
+ TvType.AnimeMovie,
+ TvType.OVA
+ )
+
+ companion object {
+ private const val kaast = "https://kaast1.com"
+ fun getType(t: String): TvType {
+ return when {
+ t.contains("Ova", true) -> TvType.OVA
+ t.contains("Movie", true) -> TvType.AnimeMovie
+ else -> TvType.Anime
+ }
+ }
+
+ fun getStatus(t: String): ShowStatus {
+ return when (t) {
+ "Finished Airing" -> ShowStatus.Completed
+ "Currently Airing" -> ShowStatus.Ongoing
+ else -> ShowStatus.Completed
+ }
+ }
+ }
+
+ override val mainPage = mainPageOf(
+ "$mainUrl/api/get_anime_list/sub/" to "Sub",
+ "$mainUrl/api/get_anime_list/dub/" to "Dub",
+ )
+
+ override suspend fun getMainPage(
+ page: Int,
+ request: MainPageRequest
+ ): HomePageResponse {
+ val home = app.get(request.data + page).parsedSafe()?.data?.mapNotNull { media ->
+ media.toSearchResponse()
+ } ?: throw ErrorLoadingException()
+ return newHomePageResponse(request.name, home)
+ }
+
+ private fun getProperAnimeLink(uri: String): String {
+ return when {
+ uri.contains("/episode") -> fixUrl(uri.substringBeforeLast("/"))
+ else -> fixUrl(uri)
+ }
+ }
+
+ private fun Animes.toSearchResponse(): AnimeSearchResponse? {
+ val href = getProperAnimeLink(this.slug ?: return null)
+ val title = this.name ?: return null
+ val posterUrl = getImageUrl(this.poster)
+ val episode = this.episode?.toIntOrNull()
+ val isDub = this.name.contains("(Dub)")
+
+ return newAnimeSearchResponse(title, href, TvType.Anime) {
+ this.posterUrl = posterUrl
+ addDubStatus(isDub, episode)
+ }
+ }
+
+ override suspend fun search(query: String): List {
+ val document = app.get("$mainUrl/search?q=$query").document
+ val data = document.selectFirst("script:containsData(appData)")?.data()
+ ?.substringAfter("\"animes\":[")?.substringBefore("],")
+
+ return tryParseJson>("[$data]")?.mapNotNull { media -> media.toSearchResponse() }
+ ?: throw ErrorLoadingException()
+
+ }
+
+ override suspend fun load(url: String): LoadResponse? {
+ val document = app.get(url).document
+
+ val res = document.selectFirst("script:containsData(appData)")?.data()
+ ?.substringAfter("\"anime\":{")?.substringBefore("},\"wkl\"")?.let {
+ tryParseJson("{$it}")
+ } ?: throw ErrorLoadingException()
+
+ val title = res.name ?: return null
+ val trackerTitle = res.en_title.orEmpty().ifEmpty { res.name }.fixTitle()
+ val poster = getImageUrl(res.image)
+ val tags = res.genres?.map { it.name ?: return null }
+ val year = res.startdate?.substringBefore("-")?.toIntOrNull()
+ val status = getStatus(res.status ?: return null)
+ val description = res.description
+
+ val episodes = res.episodes?.mapNotNull { eps ->
+ Episode(fixUrl(eps.slug ?: return@mapNotNull null), episode = eps.num?.toIntOrNull())
+ }?.reversed() ?: emptyList()
+
+ val type = res.type?.substringBefore(",")?.trim()?.let {
+ when (it) {
+ "TV Series" -> "tv"
+ "Ova" -> "ova"
+ "Movie" -> "movie"
+ else -> "tv"
+ }
+ } ?: if (episodes.size == 1) "movie" else "tv"
+
+ val (malId, anilistId, image, cover) = getTracker(trackerTitle, type, year)
+
+ return newAnimeLoadResponse(title, url, getType(type)) {
+ engName = title
+ posterUrl = image ?: poster
+ backgroundPosterUrl = cover ?: image ?: poster
+ this.year = year
+ addEpisodes(DubStatus.Subbed, episodes)
+ showStatus = status
+ plot = description
+ this.tags = tags
+ addMalId(malId)
+ addAniListId(anilistId?.toIntOrNull())
+ }
+
+ }
+
+ override suspend fun loadLinks(
+ data: String,
+ isCasting: Boolean,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ): Boolean {
+
+ val document = app.get(data).document
+ val sources = document.selectFirst("script:containsData(appData)")?.data()?.let {
+ tryParseJson("{${Regex("(\"episode\":.*),\"wkl").find(it)?.groupValues?.get(1)}}")
+ }?.let { server ->
+ listOf(
+ server.episode?.link1,
+ server.ext_servers?.find { it.name == "Vidstreaming" }?.link
+ )
+ }?.filterNotNull()
+
+ sources?.flatMap {
+ httpsify(it).fixIframe()
+ }?.apmap { (name, iframe) ->
+ val sourceName = fixTitle(name ?: this.name)
+ val link = httpsify(iframe ?: return@apmap null)
+ when {
+ name?.contains(Regex("(?i)(KICKASSANIMEV2|ORIGINAL-QUALITY-V2|BETA-SERVER)")) == true -> {
+ invokeAlpha(sourceName, link, callback)
+ }
+ name?.contains(Regex("(?i)(BETAPLAYER)")) == true -> {
+ invokeBeta(sourceName, link, callback)
+ }
+ name?.contains(Regex("(?i)(MAVERICKKI)")) == true -> {
+ invokeMave(sourceName, link, subtitleCallback, callback)
+ }
+ name?.contains(Regex("(?i)(gogo)")) == true -> {
+ invokeGogo(link, subtitleCallback, callback)
+ }
+ else -> {}
+ }
+ }
+
+ return true
+ }
+
+ private suspend fun invokeAlpha(
+ name: String,
+ url: String? = null,
+ callback: (ExtractorLink) -> Unit
+ ) {
+ val fixUrl = url?.replace(Regex("(player|embed)\\.php"), "pref.php")
+ app.get(
+ fixUrl ?: return,
+ referer = kaast
+ ).document.selectFirst("script:containsData(Base64.decode)")?.data()
+ ?.substringAfter("Base64.decode(\"")?.substringBefore("\")")?.let { base64Decode(it) }
+ ?.substringAfter("sources: [")?.substringBefore("],")
+ ?.let { tryParseJson>("[$it]") }?.map {
+ callback.invoke(
+ ExtractorLink(
+ name,
+ name,
+ it.file ?: return@map null,
+ url,
+ getQualityFromName(it.label)
+ )
+ )
+ }
+ }
+
+ private suspend fun invokeBeta(
+ name: String,
+ url: String? = null,
+ callback: (ExtractorLink) -> Unit
+ ) {
+ app.get(
+ url ?: return,
+ referer = kaast
+ ).document.selectFirst("script:containsData(JSON.parse)")?.data()
+ ?.substringAfter("JSON.parse('")?.substringBeforeLast("')")
+ ?.let { tryParseJson>(it) }?.map {
+ callback.invoke(
+ ExtractorLink(
+ name,
+ name,
+ it.file ?: return@map null,
+ getBaseUrl(url),
+ getQualityFromName(it.label)
+ )
+ )
+ }
+ }
+
+ private suspend fun invokeMave(
+ name: String,
+ url: String? = null,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit,
+ ) {
+ val fixUrl = url?.replace("/embed/", "/api/source/") ?: return
+ val base = getBaseUrl(url)
+ val data = app.get(fixUrl, referer = url).parsedSafe()
+
+ M3u8Helper.generateM3u8(
+ name,
+ fixUrl(data?.hls ?: return, base),
+ url
+ ).forEach(callback)
+
+ data.subtitles?.map { sub ->
+ subtitleCallback.invoke(
+ SubtitleFile(
+ sub.name ?: "",
+ fixUrl(sub.src ?: return@map null, base)
+ )
+ )
+ }
+
+ }
+
+ private suspend fun invokeGogo(
+ link: String,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ) {
+ val iframe = app.get(link)
+ val iframeDoc = iframe.document
+ argamap({
+ iframeDoc.select(".list-server-items > .linkserver")
+ .forEach { element ->
+ val status = element.attr("data-status") ?: return@forEach
+ if (status != "1") return@forEach
+ val extractorData = element.attr("data-video") ?: return@forEach
+ loadExtractor(extractorData, iframe.url, subtitleCallback, callback)
+ }
+ }, {
+ val iv = "3134003223491201"
+ val secretKey = "37911490979715163134003223491201"
+ val secretDecryptKey = "54674138327930866480207815084989"
+ GogoExtractor.extractVidstream(
+ iframe.url,
+ "Gogoanime",
+ callback,
+ iv,
+ secretKey,
+ secretDecryptKey,
+ isUsingAdaptiveKeys = false,
+ isUsingAdaptiveData = true,
+ iframeDocument = iframeDoc
+ )
+ })
+ }
+
+ private suspend fun String.fixIframe(): List> {
+ return when {
+ this.startsWith("$kaast/dust/") -> {
+ val document = app.get(this).document
+ document.selectFirst("script:containsData(sources =)")?.data()
+ ?.substringAfter("sources = [")?.substringBefore("];")?.let {
+ tryParseJson>("[$it]")?.map { source ->
+ source.name to source.src
+ }
+ } ?: emptyList()
+ }
+ this.startsWith("$kaast/axplayer/") -> {
+ val source = decode(
+ this.substringAfter("&data=").substringBefore("&vref=")
+ )
+ listOf(URI(source).host.substringBefore(".") to source)
+ }
+ else -> {
+ emptyList()
+ }
+ }
+ }
+
+ private fun decode(input: String): String =
+ URLDecoder.decode(input, "utf-8").replace(" ", "%20")
+
+ private fun String.fixTitle(): String {
+ return this.replace("(Dub)", "").replace("(Uncensored)", "").trim()
+ }
+
+ private fun getImageUrl(link: String?): String? {
+ if (link == null) return null
+ return if (link.startsWith(mainUrl)) link else "$mainUrl/uploads/$link"
+ }
+
+ private fun getBaseUrl(url: String): String {
+ return URI(url).let {
+ "${it.scheme}://${it.host}"
+ }
+ }
+
+ private fun fixUrl(url: String, domain: String): String {
+ if (url.startsWith("http")) {
+ return url
+ }
+ if (url.isEmpty()) {
+ return ""
+ }
+
+ val startsWithNoHttp = url.startsWith("//")
+ if (startsWithNoHttp) {
+ return "https:$url"
+ } else {
+ if (url.startsWith('/')) {
+ return domain + url
+ }
+ return "$domain/$url"
+ }
+ }
+
+ private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker {
+ val res = app.get("https://api.consumet.org/meta/anilist/$title")
+ .parsedSafe()?.results?.find { media ->
+ (media.title?.english.equals(title, true) || media.title?.romaji.equals(
+ title,
+ true
+ )) || (media.type.equals(type, true) && media.releaseDate == year)
+ }
+ return Tracker(res?.malId, res?.aniId, res?.image, res?.cover)
+ }
+
+ data class Tracker(
+ val malId: Int? = null,
+ val aniId: String? = null,
+ val image: String? = null,
+ val cover: String? = null,
+ )
+
+ data class Title(
+ @JsonProperty("romaji") val romaji: String? = null,
+ @JsonProperty("english") val english: String? = null,
+ )
+
+ data class Results(
+ @JsonProperty("id") val aniId: String? = null,
+ @JsonProperty("malId") val malId: Int? = null,
+ @JsonProperty("title") val title: Title? = null,
+ @JsonProperty("releaseDate") val releaseDate: Int? = null,
+ @JsonProperty("type") val type: String? = null,
+ @JsonProperty("image") val image: String? = null,
+ @JsonProperty("cover") val cover: String? = null,
+ )
+
+ data class AniSearch(
+ @JsonProperty("results") val results: ArrayList? = arrayListOf(),
+ )
+
+ data class Genres(
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("slug") val slug: String? = null,
+ )
+
+ data class Episodes(
+ @JsonProperty("epnum") val epnum: String? = null,
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("slug") val slug: String? = null,
+ @JsonProperty("createddate") val createddate: String? = null,
+ @JsonProperty("num") val num: String? = null,
+ )
+
+ data class DetailAnime(
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("en_title") val en_title: String? = null,
+ @JsonProperty("slug") val slug: String? = null,
+ @JsonProperty("description") val description: String? = null,
+ @JsonProperty("status") val status: String? = null,
+ @JsonProperty("image") val image: String? = null,
+ @JsonProperty("startdate") val startdate: String? = null,
+ @JsonProperty("broadcast_day") val broadcast_day: String? = null,
+ @JsonProperty("broadcast_time") val broadcast_time: String? = null,
+ @JsonProperty("type") val type: String? = null,
+ @JsonProperty("episodes") val episodes: ArrayList? = null,
+ @JsonProperty("genres") val genres: ArrayList? = null,
+ )
+
+ data class Animes(
+ @JsonProperty("episode") val episode: String? = null,
+ @JsonProperty("slug") val slug: String? = null,
+ @JsonProperty("type") val type: String? = null,
+ @JsonProperty("episode_date") val episode_date: String? = null,
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("poster") val poster: String? = null,
+ )
+
+ data class Responses(
+ @JsonProperty("data") val data: ArrayList? = arrayListOf(),
+ )
+
+ data class Iframe(
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("src") val src: String? = null,
+ )
+
+ data class ExtServers(
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("link") val link: String? = null,
+ )
+
+ data class Eps(
+ @JsonProperty("link1") val link1: String? = null,
+ )
+
+ data class Resources(
+ @JsonProperty("episode") val episode: Eps? = null,
+ @JsonProperty("ext_servers") val ext_servers: ArrayList? = arrayListOf(),
+ )
+
+ data class BetaSources(
+ @JsonProperty("file") val file: String? = null,
+ @JsonProperty("label") val label: String? = null,
+ )
+
+ data class AlphaSources(
+ @JsonProperty("file") val file: String? = null,
+ @JsonProperty("label") val label: String? = null,
+ )
+
+ data class MaveSubtitles(
+ @JsonProperty("name") val name: String? = null,
+ @JsonProperty("src") val src: String? = null,
+ )
+
+ data class MaveSources(
+ @JsonProperty("hls") val hls: String? = null,
+ @JsonProperty("subtitles") val subtitles: ArrayList? = null,
+ )
+
+}
\ No newline at end of file
diff --git a/Kickassanime/src/main/kotlin/com/hexated/KickassanimePlugin.kt b/Kickassanime/src/main/kotlin/com/hexated/KickassanimePlugin.kt
new file mode 100644
index 00000000..5565d18d
--- /dev/null
+++ b/Kickassanime/src/main/kotlin/com/hexated/KickassanimePlugin.kt
@@ -0,0 +1,14 @@
+
+package com.hexated
+
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+import android.content.Context
+
+@CloudstreamPlugin
+class KickassanimePlugin: Plugin() {
+ override fun load(context: Context) {
+ // All providers should be added in this manner. Please don't edit the providers list directly.
+ registerMainAPI(Kickassanime())
+ }
+}
\ No newline at end of file
diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt
index 78515e68..6884be58 100644
--- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt
+++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt
@@ -1858,8 +1858,9 @@ object SoraExtractor : SoraStream() {
} else {
"$rStreamAPI/Shows/$id/$season/$episode.mp4"
}
+ val referer = "https://remotestre.am/"
- if (!app.get(url).isSuccessful) return
+ if (!app.get(url, referer = referer).isSuccessful) return
delay(4000)
callback.invoke(
@@ -1867,7 +1868,7 @@ object SoraExtractor : SoraStream() {
"RStream",
"RStream",
url,
- "https://remotestre.am/",
+ referer,
Qualities.P720.value
)
)