diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 43101393..fce227e8 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -57,7 +57,7 @@ jobs: # ./.github/scripts/bump-versions.py ${{ steps.modified-libs.outputs.all_changed_files }} - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@a494d935f4b56874c4a5a87d19af7afcf3a163d0 # v2 + uses: gradle/actions/wrapper-validation@v4 - name: Get number of modules run: | diff --git a/lib-multisrc/anilist/build.gradle.kts b/lib-multisrc/anilist/build.gradle.kts index e2f11e9c..6e70fd15 100644 --- a/lib-multisrc/anilist/build.gradle.kts +++ b/lib-multisrc/anilist/build.gradle.kts @@ -2,4 +2,4 @@ plugins { id("lib-multisrc") } -baseVersionCode = 3 +baseVersionCode = 4 diff --git a/lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListFilters.kt b/lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListFilters.kt index 688d64e4..83b7b6dd 100644 --- a/lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListFilters.kt +++ b/lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListFilters.kt @@ -110,6 +110,8 @@ object AniListFilters { val YEAR_LIST = arrayOf( Pair("", ""), - Pair("Action", "action"), - Pair("Adult Cast", "adult-cast"), - Pair("Adventure", "adventure"), - Pair("Anthropomorphic", "anthropomorphic"), - Pair("Avant Garde", "avant-garde"), - Pair("Boys Love", "shounen-ai"), - Pair("Cars", "cars"), - Pair("CGDCT", "cgdct"), - Pair("Childcare", "childcare"), - Pair("Comedy", "comedy"), - Pair("Comic", "comic"), - Pair("Crime", "crime"), - Pair("Crossdressing", "crossdressing"), - Pair("Delinquents", "delinquents"), - Pair("Dementia", "dementia"), - Pair("Demons", "demons"), - Pair("Detective", "detective"), - Pair("Drama", "drama"), - Pair("Dub", "dub"), - Pair("Ecchi", "ecchi"), - Pair("Erotica", "erotica"), - Pair("Family", "family"), - Pair("Fantasy", "fantasy"), - Pair("Gag Humor", "gag-humor"), - Pair("Game", "game"), - Pair("Gender Bender", "gender-bender"), - Pair("Gore", "gore"), - Pair("Gourmet", "gourmet"), - Pair("Harem", "harem"), - Pair("Hentai", "hentai"), - Pair("High Stakes Game", "high-stakes-game"), - Pair("Historical", "historical"), - Pair("Horror", "horror"), - Pair("Isekai", "isekai"), - Pair("Iyashikei", "iyashikei"), - Pair("Josei", "josei"), - Pair("Kids", "kids"), - Pair("Magic", "magic"), - Pair("Magical Sex Shift", "magical-sex-shift"), - Pair("Mahou Shoujo", "mahou-shoujo"), - Pair("Martial Arts", "martial-arts"), - Pair("Mecha", "mecha"), - Pair("Medical", "medical"), - Pair("Military", "military"), - Pair("Music", "music"), - Pair("Mystery", "mystery"), - Pair("Mythology", "mythology"), - Pair("Organized Crime", "organized-crime"), - Pair("Parody", "parody"), - Pair("Performing Arts", "performing-arts"), - Pair("Pets", "pets"), - Pair("Police", "police"), - Pair("Psychological", "psychological"), - Pair("Racing", "racing"), - Pair("Reincarnation", "reincarnation"), - Pair("Romance", "romance"), - Pair("Romantic Subtext", "romantic-subtext"), - Pair("Samurai", "samurai"), - Pair("School", "school"), - Pair("Sci-Fi", "sci-fi"), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shoujo Ai", "shoujo-ai"), - Pair("Shounen", "shounen"), - Pair("Showbiz", "showbiz"), - Pair("Slice of Life", "slice-of-life"), - Pair("Space", "space"), - Pair("Sports", "sports"), - Pair("Strategy Game", "strategy-game"), - Pair("Super Power", "super-power"), - Pair("Supernatural", "supernatural"), - Pair("Survival", "survival"), - Pair("Suspense", "suspense"), - Pair("Team Sports", "team-sports"), - Pair("Thriller", "thriller"), - Pair("Time Travel", "time-travel"), - Pair("Vampire", "vampire"), - Pair("Visual Arts", "visual-arts"), - Pair("Work Life", "work-life"), - Pair("Workplace", "workplace"), - Pair("Yaoi", "yaoi"), - Pair("Yuri", "yuri"), - ) - - val RECENT_LIST = arrayOf( - Pair("", ""), - Pair("Latest season", "new-season.html"), - Pair("Spring 2024", "sub-category/spring-2024-anime"), - Pair("Winter 2024", "sub-category/winter-2024-anime"), - Pair("Fall 2023", "sub-category/fall-2023-anime"), - Pair("Summer 2023", "sub-category/summer-2023-anime"), - Pair("Spring 2023", "sub-category/spring-2023-anime"), - Pair("Winter 2023", "sub-category/winter-2023-anime"), - Pair("Fall 2022", "sub-category/fall-2022-anime"), - Pair("Summer 2022", "sub-category/summer-2022-anime"), - Pair("Spring 2022", "sub-category/spring-2022-anime"), - Pair("Winter 2022", "sub-category/winter-2022-anime"), - Pair("Fall 2021", "sub-category/fall-2021-anime"), - Pair("Summer 2021", "sub-category/summer-2021-anime"), - Pair("Spring 2021", "sub-category/spring-2021-anime"), - Pair("Winter 2021", "sub-category/winter-2021-anime"), - Pair("Fall 2020", "sub-category/fall-2020-anime"), - Pair("Summer 2020", "sub-category/summer-2020-anime"), - Pair("Spring 2020", "sub-category/spring-2020-anime"), - Pair("Winter 2020", "sub-category/winter-2020-anime"), - Pair("Fall 2019", "sub-category/fall-2019-anime"), - Pair("Summer 2019", "sub-category/summer-2019-anime"), - Pair("Spring 2019", "sub-category/spring-2019-anime"), - Pair("Winter 2019", "sub-category/winter-2019-anime"), - Pair("Fall 2018", "sub-category/fall-2018-anime"), - Pair("Summer 2018", "sub-category/summer-2018-anime"), - Pair("Spring 2018", "sub-category/spring-2018-anime"), - Pair("Winter 2018", "sub-category/winter-2018-anime"), - Pair("Fall 2017", "sub-category/fall-2017-anime"), - Pair("Summer 2017", "sub-category/summer-2017-anime"), - Pair("Spring 2017", "sub-category/spring-2017-anime"), - Pair("Winter 2017", "sub-category/winter-2017-anime"), - Pair("Fall 2016", "sub-category/fall-2016-anime"), - Pair("Summer 2016", "sub-category/summer-2016-anime"), - Pair("Spring 2016", "sub-category/spring-2016-anime"), - Pair("Winter 2016", "sub-category/winter-2016-anime"), - Pair("Fall 2015", "sub-category/fall-2015-anime"), - Pair("Summer 2015", "sub-category/summer-2015-anime"), - Pair("Spring 2015", "sub-category/spring-2015-anime"), - Pair("Winter 2015", "sub-category/winter-2015-anime"), - Pair("Fall 2014", "sub-category/fall-2014-anime"), - Pair("Summer 2014", "sub-category/summer-2014-anime"), - Pair("Spring 2014", "sub-category/spring-2014-anime"), - Pair("Winter 2014", "sub-category/winter-2014-anime"), - ) - } -} diff --git a/src/en/jpfilms/build.gradle b/src/en/jpfilms/build.gradle new file mode 100644 index 00000000..4d0d4e22 --- /dev/null +++ b/src/en/jpfilms/build.gradle @@ -0,0 +1,12 @@ +ext { + extName = 'JPFilms' + extClass = '.JPFilms' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(":lib:playlist-utils")) +} diff --git a/src/en/jpfilms/res/mipmap-hdpi/ic_launcher.png b/src/en/jpfilms/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..80d4f02b Binary files /dev/null and b/src/en/jpfilms/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/jpfilms/res/mipmap-mdpi/ic_launcher.png b/src/en/jpfilms/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..4738b546 Binary files /dev/null and b/src/en/jpfilms/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/jpfilms/res/mipmap-xhdpi/ic_launcher.png b/src/en/jpfilms/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..f8eeb7f4 Binary files /dev/null and b/src/en/jpfilms/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/jpfilms/res/mipmap-xxhdpi/ic_launcher.png b/src/en/jpfilms/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..8f193b70 Binary files /dev/null and b/src/en/jpfilms/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/jpfilms/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/jpfilms/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..b43b57de Binary files /dev/null and b/src/en/jpfilms/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/jpfilms/src/eu/kanade/tachiyomi/animeextension/en/jpfilms/JPFilms.kt b/src/en/jpfilms/src/eu/kanade/tachiyomi/animeextension/en/jpfilms/JPFilms.kt new file mode 100644 index 00000000..b3434690 --- /dev/null +++ b/src/en/jpfilms/src/eu/kanade/tachiyomi/animeextension/en/jpfilms/JPFilms.kt @@ -0,0 +1,406 @@ +package eu.kanade.tachiyomi.animeextension.en.jpfilms + +import android.app.Application +import android.content.SharedPreferences +import android.util.Log +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +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.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class JPFilms : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + override val name = "JPFilms" + override val baseUrl = "https://jp-films.com" + override val lang = "en" + override val supportsLatest = true + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + // ============================== Popular Anime ============================== + override fun popularAnimeSelector(): String = + "div.item" + + override fun popularAnimeRequest(page: Int): Request = GET("https://jp-films.com/wp-content/themes/halimmovies/halim-ajax.php?action=halim_get_popular_post&showpost=50&type=all") + + override fun popularAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.setUrlWithoutDomain(element.select("a").attr("href")) + anime.title = element.select("h3.title").text() + anime.thumbnail_url = element.selectFirst("img")?.attr("abs:data-src") + Log.d("JPFilmsDebug", "Thumbnail URL: ${anime.thumbnail_url}") + return anime + } + + override fun popularAnimeNextPageSelector(): String? = null + + // ============================== Latest Anime ============================== + override fun latestUpdatesSelector(): String = + "#ajax-vertical-widget-movie > div.item, " + + "#ajax-vertical-widget-tv_series > div.item" + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl) + + override fun latestUpdatesFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.setUrlWithoutDomain(element.select("a").attr("href")) + anime.title = element.select("h3.title").text() + anime.thumbnail_url = element.select("img").attr("data-src") + Log.d("JPFilmsDebug", "Poster: ${anime.thumbnail_url}") + return anime + } + + override fun latestUpdatesNextPageSelector(): String? = null + + // ============================== Search Anime ============================== + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val searchQuery = query.replace(" ", "+") + return GET("$baseUrl/?s=$searchQuery", headers) + } + + override fun searchAnimeSelector(): String = "#main-contents > section > div.halim_box > article" + + override fun searchAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.setUrlWithoutDomain(element.select("a.halim-thumb").attr("href")) + anime.title = element.select("a.halim-thumb").attr("title") + anime.thumbnail_url = element.select("img").attr("data-src") + Log.d("JPFilmsDebug", "Poster: ${anime.thumbnail_url}") + return anime + } + + override fun searchAnimeNextPageSelector(): String? = null + + // ============================== Anime Details ============================== + override suspend fun getAnimeDetails(anime: SAnime): SAnime { + val document = client.newCall(GET(baseUrl + anime.url, headers)).execute().asJsoup() + anime.title = document.select("h1.entry-title").text() + anime.genre = document.select("p.category a").joinToString(", ") { it.text() } + anime.description = document.select("#content > div > div.entry-content.htmlwrap.clearfix > div.video-item.halim-entry-box article p").text() + anime.thumbnail_url = document.select("#content > div > div.halim-movie-wrapper.tpl-2 > div > div.movie-poster.col-md-4 > img").attr("data-src") + anime.author = "forsyth47" + return anime + } + + override fun animeDetailsParse(document: Document): SAnime = throw UnsupportedOperationException() + + // ============================== Episode List ============================== + + @Serializable + data class JsonLdData( + @SerialName("@type") val type: String? = null, + ) + + override fun episodeListSelector(): String { + throw UnsupportedOperationException("Not used because we override episodeListParse.") + } + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + + // Extract JSON-LD data to determine if it's a Movie or TVSeries + val jsonLdScript = document.selectFirst("script[type=application/ld+json]:not(.rank-math-schema)")?.data() + Log.d("JPFilmsDebug", "JSON-LD Script: $jsonLdScript") + + val jsonLdData = json.decodeFromString(jsonLdScript ?: "{}") + Log.d("JPFilmsDebug", "JSON-LD Data: $jsonLdData") + + val isMovie = jsonLdData.type == "Movie" + Log.d("JPFilmsDebug", "Type: ${if (isMovie) "Movie" else "TVSeries"}") + + val serverAvailable = document.select("#halim-list-server > ul > li") + Log.d("JPFilmsDebug", "Server Available: $serverAvailable") + + var freeServerFound: Boolean = false + val episodeContainerSelector = run { + freeServerFound = false + var selectedContainer: String? = null + + // Iterate through each server div + for (serverDiv in serverAvailable) { + Log.d("JPFilmsDebug", "Server Div: $serverDiv") + + // Log the title of the current server div + val title = serverDiv.select("li > a").text() + Log.d("JPFilmsDebug", "Server Div Title: $title") + + // Check if the current server contains a
  • with a title containing "FREE" + val hasFreeServer = title.contains("FREE") + Log.d("JPFilmsDebug", "Has Free Server: $hasFreeServer") + + if (hasFreeServer) { + // Mark that a FREE server was found + freeServerFound = true + Log.d("JPFilmsDebug", "FREE Server Found") + + // Select this server's container + selectedContainer = "${serverDiv.select("a").attr("href")} > div > ul" + break // Exit the loop once a FREE server is found + } else if (!freeServerFound) { + // If no FREE server is found yet, select the first available server + selectedContainer = "${serverDiv.select("a").attr("href")} > div > ul" + } + } + + // Return the selected container or an empty string if none is found + selectedContainer ?: "" + } + Log.d("JPFilmsDebug", "Episode Container Selector: $episodeContainerSelector") + + // Extract all
  • elements from the selected container + val episodeElements = document.select("$episodeContainerSelector > li") + Log.d("JPFilmsDebug", "Episode Elements: $episodeElements") + + return episodeElements.map { element -> + SEpisode.create().apply { + // Get the href attribute from either the anchor tag or the span tag + var href = if (element.select("a").hasAttr("href")) { + element.select("a").attr("href") + } else { + element.select("span").attr("data-href") + } + if (!freeServerFound) { + href = "$href?svid=2" + } + setUrlWithoutDomain(href) + Log.d("JPFilmsDebug", "Episode URL: $href") + + // Determine if the episode belongs to a FREE or VIP server + val isFreeServer = element.select("a").attr("title").contains("FREE") || + element.select("span").text().contains("FREE") + val serverPrefix = if (isFreeServer) "[FREE] " else "[VIP] " + + // Use the title attribute of the anchor tag as the episode name + name = serverPrefix + ( + element.select("a").attr("title").ifEmpty { + element.select("span").text() + } + ) + Log.d("JPFilmsDebug", "Episode Name: $name") + + // Generate an episode number based on the text content + episode_number = element.text() + .filter { it.isDigit() } + .toFloatOrNull() ?: 1F + Log.d("JPFilmsDebug", "Episode Number: $episode_number") + } + }.reversed() + } + + override fun episodeFromElement(element: Element): SEpisode { + throw UnsupportedOperationException("Not used because we override episodeListParse.") + } + + // ============================== Video List ============================== + + // Define the JSON serializer + private val json = Json { ignoreUnknownKeys = true } + + override fun videoListParse(response: Response): List
  • with a title containing "FREE" + val hasFreeServer = title.contains("FREE") + Log.d("JPFilmsDebug", "Has Free Server: $hasFreeServer") + + if (hasFreeServer) { + // Mark that a FREE server was found + freeServerFound = true + Log.d("JPFilmsDebug", "FREE Server Found") + + // Select this server's container + selectedContainer = "${serverDiv.select("a").attr("href")} > div > ul" + break // Exit the loop once a FREE server is found + } else if (!freeServerFound) { + // If no FREE server is found yet, select the first available server + selectedContainer = "${serverDiv.select("a").attr("href")} > div > ul" + } + } + + // Return the selected container or an empty string if none is found + selectedContainer ?: "" + } + + val episodeElements = document.select("$episodeContainerSelector > li") + Log.d("JPFilmsDebug", "Episode Elements: $episodeElements") + + val targetEpisodeElement = episodeElements.firstOrNull { element -> + element.select("span").attr("data-episode-slug") == episodeSlug + } ?: run { + Log.e("JPFilmsDebug", "No matching episode element found for slug: $episodeSlug") + return emptyList() // Exit early if no matching element is found + } + + // Extract the server ID from the target
  • element + val serverId = targetEpisodeElement.select("span").attr("data-server").toIntOrNull() ?: 0 + + // Debugging: Log the extracted server ID + Log.d("JPFilmsDebug", "Extracted Server ID: $serverId") + + // First attempt with server_id=serverId and no subsvId + var subsvId: String? = null + val playerUrl1 = getPlayerUrl(serverId = serverId, subsvId = subsvId) + val (_, hlsUrl1) = fetchAndParsePlayerResponse(playerUrl1) + + // Retry with subsvId=2 if the first attempt fails + val hlsUrl = if (hlsUrl1.isEmpty()) { + subsvId = "2" + val playerUrl2 = getPlayerUrl(serverId = serverId, subsvId = subsvId) + val (_, hlsUrl2) = fetchAndParsePlayerResponse(playerUrl2) + hlsUrl2 + } else { + hlsUrl1 + } + + // Return the video list if the HLS URL is found, otherwise return an empty list + return if (hlsUrl.isNotEmpty()) { + PlaylistUtils(client).extractFromHls(hlsUrl, referer = baseUrl) + } else { + emptyList() + } + } + + // Data classes for JSON parsing + @Serializable + data class PlayerResponse( + val data: PlayerData? = null, + ) + + @Serializable + data class PlayerData( + val status: Boolean? = null, + val sources: String? = null, + ) + + private fun extractPostId(document: Document): String { + val bodyClass = document.select("body").attr("class") + return Regex("postid-(\\d+)").find(bodyClass)?.groupValues?.get(1) ?: "" + } + + override fun videoListSelector(): String = throw UnsupportedOperationException() + + override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException() + + override fun videoUrlParse(document: Document): String = throw UnsupportedOperationException() + + // ============================== ToDo ============================== + // Plan to add option to change between original title and translated title + // Plan to add backup server too. + // ============================== Preferences ============================== + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + key = Companion.PREF_TITLE_STYLE_KEY + title = "Preferred Title Style" + entries = arrayOf("Original", "Translated") + entryValues = arrayOf("original", "translated") + setDefaultValue("translated") + summary = "%s" + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit().putString(key, newValue as String).commit() + } + }.also(screen::addPreference) + } + + private val SharedPreferences.titleStyle + get() = getString(Companion.PREF_TITLE_STYLE_KEY, "translated")!! + + companion object { + private const val PREF_TITLE_STYLE_KEY = "preferred_title_style" + } +} diff --git a/src/en/kaido/build.gradle b/src/en/kaido/build.gradle index 6bcd6429..534ef8dc 100644 --- a/src/en/kaido/build.gradle +++ b/src/en/kaido/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Kaido' themePkg = 'zorotheme' baseUrl = 'https://kaido.to' - overrideVersionCode = 8 + overrideVersionCode = 9 } apply from: "$rootDir/common.gradle" \ No newline at end of file diff --git a/src/en/kickassanime/build.gradle b/src/en/kickassanime/build.gradle index fd3ddc54..d8c21bab 100644 --- a/src/en/kickassanime/build.gradle +++ b/src/en/kickassanime/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'KickAssAnime' extClass = '.KickAssAnime' - extVersionCode = 45 + extVersionCode = 48 } apply from: "$rootDir/common.gradle" 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 ec74afc7..84788210 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 @@ -338,8 +338,8 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() { private const val PREF_DOMAIN_KEY = "preferred_domain" private const val PREF_DOMAIN_TITLE = "Preferred domain (requires app restart)" - private const val PREF_DOMAIN_DEFAULT = "https://kaas.to" - private val PREF_DOMAIN_ENTRIES = arrayOf("kaas.to", "kaas.ro", "kickassanimes.io", "www1.kickassanime.mx") + private const val PREF_DOMAIN_DEFAULT = "https://kaa.mx" + private val PREF_DOMAIN_ENTRIES = arrayOf("kaa.mx", "kaas.ro", "kaas.to", "kickassanimes.io", "www1.kickassanime.mx") private val PREF_DOMAIN_ENTRY_VALUES = PREF_DOMAIN_ENTRIES.map { "https://$it" }.toTypedArray() private const val PREF_HOSTER_KEY = "hoster_selection" diff --git a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnimeFilters.kt b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnimeFilters.kt index 81a06bbf..bbd39bfc 100644 --- a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnimeFilters.kt +++ b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnimeFilters.kt @@ -270,6 +270,7 @@ object KickAssAnimeFilters { Pair("2022", "2022"), Pair("2023", "2023"), Pair("2024", "2024"), + Pair("2025", "2025"), ) val STATUS = arrayOf( diff --git a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/KickAssAnimeExtractor.kt b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/KickAssAnimeExtractor.kt index 0fc5d08c..d256986b 100644 --- a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/KickAssAnimeExtractor.kt +++ b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/extractors/KickAssAnimeExtractor.kt @@ -18,6 +18,8 @@ class KickAssAnimeExtractor( private val json: Json, private val headers: Headers, ) { + private val playlistUtils by lazy { PlaylistUtils(client, headers) } + fun videosFromUrl(url: String, name: String): List