diff --git a/src/en/dramacool/build.gradle b/src/en/dramacool/build.gradle deleted file mode 100644 index 950c5ea8..00000000 --- a/src/en/dramacool/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -ext { - extName = 'DramaCool' - extClass = '.DramaCool' - extVersionCode = 54 -} - -apply from: "$rootDir/common.gradle" - -dependencies { - implementation(project(':lib:streamwish-extractor')) - implementation(project(':lib:streamtape-extractor')) - implementation(project(':lib:dood-extractor')) -} \ No newline at end of file diff --git a/src/en/dramacool/res/mipmap-hdpi/ic_launcher.png b/src/en/dramacool/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 386283c6..00000000 Binary files a/src/en/dramacool/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dramacool/res/mipmap-mdpi/ic_launcher.png b/src/en/dramacool/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3d81dfbc..00000000 Binary files a/src/en/dramacool/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dramacool/res/mipmap-xhdpi/ic_launcher.png b/src/en/dramacool/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d0126fee..00000000 Binary files a/src/en/dramacool/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dramacool/res/mipmap-xxhdpi/ic_launcher.png b/src/en/dramacool/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9ab42d45..00000000 Binary files a/src/en/dramacool/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dramacool/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/dramacool/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6d99bf28..00000000 Binary files a/src/en/dramacool/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dramacool/src/eu/kanade/tachiyomi/animeextension/en/dramacool/DramaCool.kt b/src/en/dramacool/src/eu/kanade/tachiyomi/animeextension/en/dramacool/DramaCool.kt deleted file mode 100644 index 4ec49eb0..00000000 --- a/src/en/dramacool/src/eu/kanade/tachiyomi/animeextension/en/dramacool/DramaCool.kt +++ /dev/null @@ -1,202 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.dramacool - -import android.app.Application -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.doodextractor.DoodExtractor -import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor -import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.util.asJsoup -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 -import java.text.SimpleDateFormat -import java.util.Locale - -class DramaCool : ConfigurableAnimeSource, ParsedAnimeHttpSource() { - - override val name = "DramaCool" - - // TODO: Check frequency of url changes to potentially - // add back overridable baseurl preference - override val baseUrl = "https://asianc.co/" - - override val lang = "en" - - override val supportsLatest = true - - private val preferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - // ============================== Popular =============================== - override fun popularAnimeRequest(page: Int) = GET("$baseUrl/most-popular-drama?page=$page") // page/$page - - override fun popularAnimeSelector() = "ul.list-episode-item li a" - - override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.selectFirst("img")?.attr("data-original")?.replace(" ", "%20") - title = element.selectFirst("h3")?.text() ?: "Serie" - } - - override fun popularAnimeNextPageSelector() = "li.next a" - - // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/recently-added?page=$page") - - override fun latestUpdatesSelector() = "ul.switch-block a" - - override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() - - // =============================== Search =============================== - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) = - GET("$baseUrl/search?keyword=$query&page=$page") - - override fun searchAnimeSelector() = popularAnimeSelector() - - override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element) - - override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector() - - // =========================== Anime Details ============================ - override fun animeDetailsRequest(anime: SAnime): Request { - if (anime.url.contains("-episode-") && anime.url.endsWith(".html")) { - val doc = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() - anime.setUrlWithoutDomain(doc.selectFirst("div.category a")!!.attr("href")) - } - return GET(baseUrl + anime.url) - } - - override fun animeDetailsParse(document: Document) = SAnime.create().apply { - document.selectFirst("div.img img")!!.run { - title = attr("alt") - thumbnail_url = absUrl("src") - } - - with(document.selectFirst("div.info")!!) { - description = select("p:contains(Description) ~ p:not(:has(span))").eachText() - .joinToString("\n") - .takeUnless(String::isBlank) - author = selectFirst("p:contains(Original Network:) > a")?.text() - genre = select("p:contains(Genre:) > a").joinToString { it.text() }.takeUnless(String::isBlank) - status = parseStatus(selectFirst("p:contains(Status) a")?.text()) - } - } - - // ============================== Episodes ============================== - override fun episodeListSelector() = "ul.all-episode li a" - - override fun episodeFromElement(element: Element) = SEpisode.create().apply { - setUrlWithoutDomain(element.attr("href")) - val epNum = element.selectFirst("h3")!!.text().substringAfterLast("Episode ") - val type = element.selectFirst("span.type")?.text() ?: "RAW" - name = "$type: Episode $epNum".trimEnd() - episode_number = when { - epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F - else -> 1F - } - date_upload = element.selectFirst("span.time")?.text().orEmpty().toDate() - } - - // ============================ Video Links ============================= - override fun videoListSelector() = "ul.list-server-items li" - - override fun videoListParse(response: Response): List<Video> { - val document = response.asJsoup() - val iframeUrl = document.selectFirst("iframe")?.absUrl("src") ?: return emptyList() - val iframeDoc = client.newCall(GET(iframeUrl)).execute().asJsoup() - - return iframeDoc.select(videoListSelector()).flatMap(::videosFromElement) - } - - private val doodExtractor by lazy { DoodExtractor(client) } - private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) } - private val streamtapeExtractor by lazy { StreamTapeExtractor(client) } - - // TODO: Create a extractor for the "Standard server" thingie. - // it'll require Synchrony or something similar, but synchrony is too slow >:( - private fun videosFromElement(element: Element): List<Video> { - val url = element.attr("data-video") - return runCatching { - when { - url.contains("dood") -> doodExtractor.videosFromUrl(url) - url.contains("dwish") -> streamwishExtractor.videosFromUrl(url) - url.contains("streamtape") -> streamtapeExtractor.videosFromUrl(url) - else -> emptyList() - } - }.getOrElse { emptyList() } - } - - override fun videoFromElement(element: Element) = throw UnsupportedOperationException() - - override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() - - // ============================== Settings ============================== - override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - key = PREF_QUALITY_KEY - title = PREF_QUALITY_TITLE - entries = PREF_QUALITY_ENTRIES - entryValues = PREF_QUALITY_VALUES - setDefaultValue(PREF_QUALITY_DEFAULT) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(key, entry).commit() - } - }.also(screen::addPreference) - } - - // ============================= Utilities ============================== - override fun List<Video>.sort(): List<Video> { - val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! - - return sortedWith( - compareBy( - { it.quality.contains(quality) }, - { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, - ), - ).reversed() - } - - private fun parseStatus(statusString: String?): Int { - return when (statusString) { - "Ongoing" -> SAnime.ONGOING - "Completed" -> SAnime.COMPLETED - else -> SAnime.UNKNOWN - } - } - - private fun String.toDate(): Long { - return runCatching { DATE_FORMATTER.parse(trim())?.time } - .getOrNull() ?: 0L - } - - companion object { - private val DATE_FORMATTER by lazy { - SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH) - } - - private const val PREF_QUALITY_KEY = "preferred_quality" - private const val PREF_QUALITY_TITLE = "Preferred quality" - private const val PREF_QUALITY_DEFAULT = "1080" - private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p", "Doodstream", "StreamTape") - private val PREF_QUALITY_VALUES = PREF_QUALITY_ENTRIES - } -} diff --git a/src/en/fmovies/build.gradle b/src/en/fmovies/build.gradle deleted file mode 100644 index 3d5bb63a..00000000 --- a/src/en/fmovies/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -ext { - extName = 'FMovies' - extClass = '.FMovies' - extVersionCode = 26 -} - -apply from: "$rootDir/common.gradle" - -dependencies { - implementation(project(':lib:vidsrc-extractor')) - implementation(project(':lib:filemoon-extractor')) - implementation(project(':lib:streamtape-extractor')) -} diff --git a/src/en/fmovies/res/mipmap-hdpi/ic_launcher.png b/src/en/fmovies/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 20e890a4..00000000 Binary files a/src/en/fmovies/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/fmovies/res/mipmap-mdpi/ic_launcher.png b/src/en/fmovies/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 341c5611..00000000 Binary files a/src/en/fmovies/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/fmovies/res/mipmap-xhdpi/ic_launcher.png b/src/en/fmovies/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7732fbf3..00000000 Binary files a/src/en/fmovies/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/fmovies/res/mipmap-xxhdpi/ic_launcher.png b/src/en/fmovies/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fede27d7..00000000 Binary files a/src/en/fmovies/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/fmovies/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/fmovies/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 62f3def9..00000000 Binary files a/src/en/fmovies/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt deleted file mode 100644 index 9e062a7e..00000000 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt +++ /dev/null @@ -1,368 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.fmovies - -import android.app.Application -import android.content.SharedPreferences -import androidx.preference.ListPreference -import androidx.preference.MultiSelectListPreference -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.Track -import eu.kanade.tachiyomi.animesource.model.Video -import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource -import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor -import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor -import eu.kanade.tachiyomi.lib.vidsrcextractor.VidsrcExtractor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.await -import eu.kanade.tachiyomi.network.awaitSuccess -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.parallelCatchingFlatMap -import eu.kanade.tachiyomi.util.parseAs -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy - -class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { - - override val name = "FMovies" - - override val baseUrl = "https://fmovies24.to" - - override val lang = "en" - - override val supportsLatest = true - - private val json: Json by injectLazy() - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val utils by lazy { FmoviesUtils(client, headers) } - - // ============================== Popular =============================== - - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/trending${page.toPageQuery()}", headers) - - override fun popularAnimeSelector(): String = "div.items > div.item" - - override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply { - element.selectFirst("div.meta a")!!.let { a -> - title = a.text() - setUrlWithoutDomain(a.attr("abs:href")) - } - - thumbnail_url = element.select("div.poster img").attr("data-src") - } - - override fun popularAnimeNextPageSelector(): String = "ul.pagination > li.active + li" - - // =============================== Latest =============================== - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/filter?keyword=&sort=recently_updated${page.toPageQuery(false)}", headers) - - override fun latestUpdatesSelector(): String = popularAnimeSelector() - - override fun latestUpdatesFromElement(element: Element): SAnime = popularAnimeFromElement(element) - - override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() - - // =============================== Search =============================== - - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { - val params = FMoviesFilters.getSearchParameters(filters) - - return GET("$baseUrl/filter?keyword=$query${params.filter}${page.toPageQuery(false)}", headers) - } - - override fun searchAnimeSelector(): String = popularAnimeSelector() - - override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) - - override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() - - // ============================== Filters =============================== - - override fun getFilterList(): AnimeFilterList = FMoviesFilters.FILTER_LIST - - // =========================== Anime Details ============================ - - override fun animeDetailsParse(document: Document): SAnime { - val info = document.selectFirst("section#w-info > div.info")!! - val detail = info.selectFirst("div.detail") - - val descElement = info.selectFirst("div.description") - val desc = descElement?.selectFirst("div[data-name=full]")?.ownText() ?: descElement?.ownText() ?: "" - val extraInfo = detail?.select("> div")?.joinToString("\n") { it.text() } ?: "" - - val mediaTitle = info.selectFirst("h1.name")!!.text() - val mediaDetail = utils.getDetail(mediaTitle) - - return SAnime.create().apply { - title = mediaTitle - status = when (mediaDetail?.status) { - "Ended", "Released" -> SAnime.COMPLETED - "In Production" -> SAnime.LICENSED - "Canceled" -> SAnime.CANCELLED - "Returning Series" -> { - mediaDetail.nextEpisode?.let { SAnime.ONGOING } ?: SAnime.ON_HIATUS - } - else -> SAnime.UNKNOWN - } - thumbnail_url = document.selectFirst("section#w-info > div.poster img")!!.attr("src") - description = buildString { - appendLine(desc.ifBlank { mediaDetail?.overview }) - appendLine() - mediaDetail?.nextEpisode?.let { - appendLine("Next: Ep ${it.epNumber} - ${it.name}") - appendLine("Air Date: ${it.airDate}") - appendLine() - } - appendLine(extraInfo) - } - genre = detail?.let { dtl -> - dtl.select("> div:has(> div:contains(Genre:)) span").joinToString { it.text() } - } - author = detail?.let { dtl -> - dtl.select("> div:has(> div:contains(Production:)) span").joinToString { it.text() } - } - } - } - - // ============================== Episodes ============================== - - override fun episodeListRequest(anime: SAnime): Request { - val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() - .selectFirst("div[data-id]")!!.attr("data-id") - - val vrf = utils.vrfEncrypt(id) - val vrfHeaders = headers.newBuilder().apply { - add("Accept", "application/json, text/javascript, */*; q=0.01") - add("Host", baseUrl.toHttpUrl().host) - add("Referer", baseUrl + anime.url) - add("X-Requested-With", "XMLHttpRequest") - }.build() - - return GET("$baseUrl/ajax/episode/list/$id?vrf=$vrf", headers = vrfHeaders) - } - - override fun episodeListParse(response: Response): List<SEpisode> { - val document = Jsoup.parse( - response.parseAs<AjaxResponse>().result, - ) - val episodeList = mutableListOf<SEpisode>() - val seasons = document.select("div.body > ul.episodes") - seasons.forEach { season -> - val seasonPrefix = if (seasons.size > 1) { - "Season ${season.attr("data-season")} " - } else { - "" - } - - season.select("li").forEach { ep -> - episodeList.add( - SEpisode.create().apply { - name = "$seasonPrefix${ep.text().trim()}".replace("Episode ", "Ep. ") - - ep.selectFirst("a")!!.let { a -> - episode_number = a.attr("data-num").toFloatOrNull() ?: 0F - url = json.encodeToString( - EpisodeInfo( - id = a.attr("data-id"), - url = "$baseUrl${a.attr("href")}", - ), - ) - } - }, - ) - } - } - - return episodeList.reversed() - } - - override fun episodeListSelector() = throw UnsupportedOperationException() - - override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException() - - // ============================ Video Links ============================= - - override suspend fun getVideoList(episode: SEpisode): List<Video> { - return client.newCall(videoListRequest(episode)) - .awaitSuccess() - .let { response -> - videoListParse(response, episode).sort() - } - } - - override fun videoListRequest(episode: SEpisode): Request { - val data = json.decodeFromString<EpisodeInfo>(episode.url) - val vrf = utils.vrfEncrypt(data.id) - - val vrfHeaders = headers.newBuilder() - .add("Accept", "application/json, text/javascript, */*; q=0.01") - .add("Host", baseUrl.toHttpUrl().host) - .add("Referer", data.url) - .add("X-Requested-With", "XMLHttpRequest") - .build() - - return GET("$baseUrl/ajax/server/list/${data.id}?vrf=$vrf", headers = vrfHeaders) - } - - private val vidsrcExtractor by lazy { VidsrcExtractor(client, headers) } - private val filemoonExtractor by lazy { FilemoonExtractor(client) } - private val streamtapeExtractor by lazy { StreamTapeExtractor(client) } - - private suspend fun videoListParse(response: Response, episode: SEpisode): List<Video> { - val data = json.decodeFromString<EpisodeInfo>(episode.url) - val document = Jsoup.parse( - response.parseAs<AjaxResponse>().result, - ) - val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!! - - return document.select("ul.servers > li.server").parallelCatchingFlatMap { server -> - val name = server.text().trim() - if (!hosterSelection.contains(name)) return@parallelCatchingFlatMap emptyList() - - // Get decrypted url - val vrf = utils.vrfEncrypt(server.attr("data-link-id")) - - val vrfHeaders = headers.newBuilder() - .add("Accept", "application/json, text/javascript, */*; q=0.01") - .add("Host", baseUrl.toHttpUrl().host) - .add("Referer", data.url) - .add("X-Requested-With", "XMLHttpRequest") - .build() - val encrypted = client.newCall( - GET("$baseUrl/ajax/server/${server.attr("data-link-id")}?vrf=$vrf", headers = vrfHeaders), - ).await().parseAs<AjaxServerResponse>().result.url - - val decrypted = utils.vrfDecrypt(encrypted) - when (name) { - "Vidplay", "MyCloud" -> { - val subs = client.newCall( - GET("$baseUrl/ajax/episode/subtitles/${data.id}"), - ).execute().toTracks() - vidsrcExtractor.videosFromUrl(decrypted, name, subtitleList = subs) - } - "Filemoon" -> filemoonExtractor.videosFromUrl(decrypted, headers = headers) - "Streamtape" -> { - val subtitleList = decrypted.toHttpUrl().queryParameter("sub.info")?.let { - client.newCall(GET(it, headers)).await().toTracks() - } ?: emptyList() - - streamtapeExtractor.videoFromUrl(decrypted, subtitleList = subtitleList)?.let(::listOf) ?: emptyList() - } - else -> emptyList() - } - } - } - - override fun videoListSelector() = throw UnsupportedOperationException() - - override fun videoFromElement(element: Element) = throw UnsupportedOperationException() - - override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() - - // ============================= Utilities ============================== - - override fun List<Video>.sort(): List<Video> { - val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! - val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!! - - return this.sortedWith( - compareBy( - { it.quality.contains(server) }, - { it.quality.contains(quality) }, - { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, - ), - ).reversed() - } - - private fun Int.toPageQuery(first: Boolean = true): String { - return if (this == 1) "" else "${if (first) "?" else "&"}page=$this" - } - - private fun Response.toTracks(): List<Track> = parseAs<List<FMoviesSubs>>() - .map { t -> - Track(t.file, t.label) - } - - companion object { - private val HOSTERS = arrayOf( - "Vidplay", - "MyCloud", - "Filemoon", - "Streamtape", - ) - - private const val PREF_QUALITY_KEY = "preferred_quality" - private const val PREF_QUALITY_DEFAULT = "1080" - - private const val PREF_SERVER_KEY = "preferred_server" - private const val PREF_SERVER_DEFAULT = "Vidplay" - - private const val PREF_HOSTER_KEY = "hoster_selection" - private val PREF_HOSTER_DEFAULT = setOf("Vidplay", "Filemoon") - } - // ============================== Settings ============================== - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - key = PREF_QUALITY_KEY - title = "Preferred quality" - entries = arrayOf("1080p", "720p", "480p", "360p") - entryValues = arrayOf("1080", "720", "480", "360") - setDefaultValue(PREF_QUALITY_DEFAULT) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(key, entry).commit() - } - }.also(screen::addPreference) - - ListPreference(screen.context).apply { - key = PREF_SERVER_KEY - title = "Preferred server" - entries = HOSTERS - entryValues = HOSTERS - setDefaultValue(PREF_SERVER_DEFAULT) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(key, entry).commit() - } - }.also(screen::addPreference) - - MultiSelectListPreference(screen.context).apply { - key = PREF_HOSTER_KEY - title = "Enable/Disable Hosts" - entries = HOSTERS - entryValues = HOSTERS - setDefaultValue(PREF_HOSTER_DEFAULT) - - setOnPreferenceChangeListener { _, newValue -> - @Suppress("UNCHECKED_CAST") - preferences.edit().putStringSet(key, newValue as Set<String>).commit() - } - }.also(screen::addPreference) - } -} diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt deleted file mode 100644 index f6f964a8..00000000 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt +++ /dev/null @@ -1,54 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.fmovies - -import kotlinx.serialization.Serializable - -@Serializable -data class AjaxResponse( - val result: String, -) - -@Serializable -data class AjaxServerResponse( - val result: UrlObject, -) { - @Serializable - data class UrlObject( - val url: String, - ) -} - -@Serializable -data class EpisodeInfo( - val id: String, - val url: String, -) - -@Serializable -data class FMoviesSubs( - val file: String, - val label: String, -) - -@Serializable -data class MediaResponseBody( - val status: Int, - val result: Result, -) { - @Serializable - data class Result( - val sources: ArrayList<Source>, - val tracks: ArrayList<SubTrack> = ArrayList(), - ) { - @Serializable - data class Source( - val file: String, - ) - - @Serializable - data class SubTrack( - val file: String, - val label: String = "", - val kind: String, - ) - } -} diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesFilters.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesFilters.kt deleted file mode 100644 index 19c3a036..00000000 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesFilters.kt +++ /dev/null @@ -1,304 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.fmovies - -import eu.kanade.tachiyomi.animesource.model.AnimeFilter -import eu.kanade.tachiyomi.animesource.model.AnimeFilterList - -object FMoviesFilters { - - open class QueryPartFilter( - displayName: String, - val vals: Array<Pair<String, String>>, - ) : AnimeFilter.Select<String>( - displayName, - vals.map { it.first }.toTypedArray(), - ) { - fun toQueryPart(name: String) = "&$name=${vals[state].second}" - } - - open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values) - - private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state) - - open class TriStateFilterList(name: String, values: List<TriFilter>) : AnimeFilter.Group<AnimeFilter.TriState>(name, values) - - class TriFilter(name: String, val value: String) : AnimeFilter.TriState(name) - - private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String { - return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name) - } - - private inline fun <reified R> AnimeFilterList.getFirst(): R { - return this.filterIsInstance<R>().first() - } - - private inline fun <reified R> AnimeFilterList.parseCheckbox( - options: Array<Pair<String, String>>, - name: String, - ): String { - return (this.getFirst<R>() as CheckBoxFilterList).state - .mapNotNull { checkbox -> - if (checkbox.state) { - options.find { it.first == checkbox.name }!!.second - } else { - null - } - }.joinToString("&$name[]=").let { - if (it.isBlank()) { - "" - } else { - "&$name[]=$it" - } - } - } - - private inline fun <reified R> AnimeFilterList.parseTriFilter( - options: Array<Pair<String, String>>, - name: String, - ): String { - return (this.getFirst<R>() as TriStateFilterList).state - .mapNotNull { checkbox -> - if (checkbox.state != AnimeFilter.TriState.STATE_IGNORE) { - (if (checkbox.state == AnimeFilter.TriState.STATE_EXCLUDE) "-" else "") + options.find { it.first == checkbox.name }!!.second - } else { - null - } - }.joinToString("&$name[]=").let { - if (it.isBlank()) { - "" - } else { - "&$name[]=$it" - } - } - } - - class TypesFilter : CheckBoxFilterList( - "Type", - FMoviesFiltersData.TYPES.map { CheckBoxVal(it.first, false) }, - ) - - class GenresFilter : TriStateFilterList( - "Genre", - FMoviesFiltersData.GENRES.map { TriFilter(it.first, it.second) }, - ) - - class CountriesFilter : CheckBoxFilterList( - "Country", - FMoviesFiltersData.COUNTRIES.map { CheckBoxVal(it.first, false) }, - ) - - class YearsFilter : CheckBoxFilterList( - "Year", - FMoviesFiltersData.YEARS.map { CheckBoxVal(it.first, false) }, - ) - - class RatingsFilter : CheckBoxFilterList( - "Rating", - FMoviesFiltersData.RATINGS.map { CheckBoxVal(it.first, false) }, - ) - - class QualitiesFilter : CheckBoxFilterList( - "Quality", - FMoviesFiltersData.QUALITIES.map { CheckBoxVal(it.first, false) }, - ) - - class SortFilter : QueryPartFilter("Sort", FMoviesFiltersData.SORT) - - val FILTER_LIST get() = AnimeFilterList( - TypesFilter(), - GenresFilter(), - CountriesFilter(), - YearsFilter(), - RatingsFilter(), - QualitiesFilter(), - SortFilter(), - ) - - data class FilterSearchParams( - val filter: String = "", - ) - - internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { - if (filters.isEmpty()) return FilterSearchParams() - - return FilterSearchParams( - filters.parseCheckbox<TypesFilter>(FMoviesFiltersData.TYPES, "type") + - filters.parseTriFilter<GenresFilter>(FMoviesFiltersData.GENRES, "genre") + - filters.parseCheckbox<CountriesFilter>(FMoviesFiltersData.COUNTRIES, "country") + - filters.parseCheckbox<YearsFilter>(FMoviesFiltersData.YEARS, "year") + - filters.parseCheckbox<RatingsFilter>(FMoviesFiltersData.RATINGS, "rating") + - filters.parseCheckbox<QualitiesFilter>(FMoviesFiltersData.QUALITIES, "quality") + - filters.asQueryPart<SortFilter>("sort"), - ) - } - - private object FMoviesFiltersData { - val TYPES = arrayOf( - Pair("Movie", "movie"), - Pair("TV-Shows", "tv"), - ) - - val GENRES = arrayOf( - Pair("Action", "25"), - Pair("Adult", "1068691"), - Pair("Adventure", "17"), - Pair("Animation", "10"), - Pair("Biography", "215"), - Pair("Comedy", "14"), - Pair("Costume", "1693"), - Pair("Crime", "26"), - Pair("Documentary", "131"), - Pair("Drama", "1"), - Pair("Family", "43"), - Pair("Fantasy", "31"), - Pair("Film-Noir", "1068395"), - Pair("Game-Show", "212"), - Pair("History", "47"), - Pair("Horror", "74"), - Pair("Kungfu", "248"), - Pair("Music", "199"), - Pair("Musical", "1066604"), - Pair("Mystery", "64"), - Pair("News", "1066549"), - Pair("Reality", "1123750"), - Pair("Reality-TV", "4"), - Pair("Romance", "23"), - Pair("Sci-Fi", "15"), - Pair("Short", "1066916"), - Pair("Sport", "44"), - Pair("Talk", "1124002"), - Pair("Talk-Show", "1067786"), - Pair("Thriller", "7"), - Pair("TV Movie", "1123752"), - Pair("TV Show", "139"), - Pair("War", "58"), - Pair("Western", "28"), - ) - - val COUNTRIES = arrayOf( - Pair("Argentina", "181863"), - Pair("Australia", "181851"), - Pair("Austria", "181882"), - Pair("Belgium", "181849"), - Pair("Brazil", "181867"), - Pair("Canada", "181861"), - Pair("China", "108"), - Pair("Czech Republic", "181859"), - Pair("Denmark", "181855"), - Pair("Finland", "181877"), - Pair("France", "11"), - Pair("Germany", "1025332"), - Pair("Hong Kong", "2630"), - Pair("Hungary", "181876"), - Pair("India", "34"), - Pair("Ireland", "181862"), - Pair("Israel", "181887"), - Pair("Italy", "181857"), - Pair("Japan", "36"), - Pair("Luxembourg", "181878"), - Pair("Mexico", "181852"), - Pair("Netherlands", "181848"), - Pair("New Zealand", "181847"), - Pair("Norway", "181901"), - Pair("Philippines", "1025339"), - Pair("Poland", "181880"), - Pair("Romania", "181895"), - Pair("Russia", "181860"), - Pair("South Africa", "181850"), - Pair("South Korea", "1025429"), - Pair("Spain", "181871"), - Pair("Sweden", "181883"), - Pair("Switzerland", "181869"), - Pair("Thailand", "94"), - Pair("Turkey", "1025379"), - Pair("United Kingdom", "8"), - Pair("United States", "2"), - ) - - val YEARS = arrayOf( - Pair("2024", "2024"), - Pair("2023", "2023"), - Pair("2022", "2022"), - Pair("2021", "2021"), - Pair("2020", "2020"), - Pair("2019", "2019"), - Pair("2018", "2018"), - Pair("2017", "2017"), - Pair("2016", "2016"), - Pair("2015", "2015"), - Pair("2014", "2014"), - Pair("2013", "2013"), - Pair("2012", "2012"), - Pair("2011", "2011"), - Pair("2010", "2010"), - Pair("2009", "2009"), - Pair("2008", "2008"), - Pair("2007", "2007"), - Pair("2006", "2006"), - Pair("2005", "2005"), - Pair("2004", "2004"), - Pair("2003", "2003"), - Pair("2000s", "2000s"), - Pair("1990s", "1990s"), - Pair("1980s", "1980s"), - Pair("1970s", "1970s"), - Pair("1960s", "1960s"), - Pair("1950s", "1950s"), - Pair("1940s", "1940s"), - Pair("1930s", "1930s"), - Pair("1920s", "1920s"), - Pair("1910s", "1910s"), - ) - - val RATINGS = arrayOf( - Pair("12", "12"), - Pair("13+", "13+"), - Pair("16+", "16+"), - Pair("18", "18"), - Pair("18+", "18+"), - Pair("AO", "AO"), - Pair("C", "C"), - Pair("E", "E"), - Pair("G", "G"), - Pair("GP", "GP"), - Pair("M", "M"), - Pair("M/PG", "M/PG"), - Pair("MA-13", "MA-13"), - Pair("MA-17", "MA-17"), - Pair("NC-17", "NC-17"), - Pair("PG", "PG"), - Pair("PG-13", "PG-13"), - Pair("R", "R"), - Pair("TV_MA", "TV_MA"), - Pair("TV-13", "TV-13"), - Pair("TV-14", "TV-14"), - Pair("TV-G", "TV-G"), - Pair("TV-MA", "TV-MA"), - Pair("TV-PG", "TV-PG"), - Pair("TV-Y", "TV-Y"), - Pair("TV-Y7", "TV-Y7"), - Pair("TV-Y7-FV", "TV-Y7-FV"), - Pair("X", "X"), - ) - - val QUALITIES = arrayOf( - Pair("HD", "HD"), - Pair("HDRip", "HDRip"), - Pair("SD", "SD"), - Pair("TS", "TS"), - Pair("CAM", "CAM"), - ) - - val SORT = arrayOf( - Pair("Most relevance", "most_relevance"), - Pair("Recently updated", "recently_updated"), - Pair("Recently added", "recently_added"), - Pair("Release date", "release_date"), - Pair("Trending", "trending"), - Pair("Name A-Z", "title_az"), - Pair("Scores", "scores"), - Pair("IMDb", "imdb"), - Pair("Most watched", "most_watched"), - Pair("Most favourited", "most_favourited"), - ) - } -} diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt deleted file mode 100644 index 31b7a95c..00000000 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt +++ /dev/null @@ -1,140 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.fmovies - -import android.util.Base64 -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.parseAs -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import java.net.URLDecoder -import javax.crypto.Cipher -import javax.crypto.spec.SecretKeySpec - -class FmoviesUtils(private val client: OkHttpClient, private val headers: Headers) { - - // ===================== Media Detail ================================ - - private val tmdbURL = "https://api.themoviedb.org/3".toHttpUrl() - - private val seez = "https://seez.su" - - private val apiKey by lazy { - val jsUrl = client.newCall(GET(seez, headers)).execute().asJsoup() - .select("script[defer][src]")[1].attr("abs:src") - - val jsBody = client.newCall(GET(jsUrl, headers)).execute().use { it.body.string() } - Regex("""f="(\w{20,})"""").find(jsBody)!!.groupValues[1] - } - - private val apiHeaders = headers.newBuilder().apply { - add("Accept", "application/json, text/javascript, */*; q=0.01") - add("Host", "api.themoviedb.org") - add("Origin", seez) - add("Referer", "$seez/") - }.build() - - fun getDetail(mediaTitle: String): TmdbDetailsResponse? = - runCatching { - val searchUrl = tmdbURL.newBuilder().apply { - addPathSegment("search") - addPathSegment("multi") - addQueryParameter("query", mediaTitle) - addQueryParameter("api_key", apiKey) - }.build().toString() - val searchResp = client.newCall(GET(searchUrl, headers = apiHeaders)) - .execute() - .parseAs<TmdbResponse>() - - val media = searchResp.results.first() - - val detailUrl = tmdbURL.newBuilder().apply { - addPathSegment(media.mediaType) - addPathSegment(media.id.toString()) - addQueryParameter("api_key", apiKey) - }.build().toString() - client.newCall(GET(detailUrl, headers = apiHeaders)) - .execute() - .parseAs<TmdbDetailsResponse>() - }.getOrNull() - - // ===================== Encryption ================================ - fun vrfEncrypt(input: String): String { - val rc4Key = SecretKeySpec("Ij4aiaQXgluXQRs6".toByteArray(), "RC4") - val cipher = Cipher.getInstance("RC4") - cipher.init(Cipher.ENCRYPT_MODE, rc4Key, cipher.parameters) - - var vrf = cipher.doFinal(input.toByteArray()) - vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) -// vrf = rot13(vrf) - vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) - vrf.reverse() - vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) - vrf = vrfShift(vrf) - val stringVrf = vrf.toString(Charsets.UTF_8) - return java.net.URLEncoder.encode(stringVrf, "utf-8") - } - - fun vrfDecrypt(input: String): String { - var vrf = input.toByteArray() - vrf = Base64.decode(vrf, Base64.URL_SAFE) - - val rc4Key = SecretKeySpec("8z5Ag5wgagfsOuhz".toByteArray(), "RC4") - val cipher = Cipher.getInstance("RC4") - cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) - vrf = cipher.doFinal(vrf) - - return URLDecoder.decode(vrf.toString(Charsets.UTF_8), "utf-8") - } - - private fun rot13(vrf: ByteArray): ByteArray { - for (i in vrf.indices) { - val byte = vrf[i] - if (byte in 'A'.code..'Z'.code) { - vrf[i] = ((byte - 'A'.code + 13) % 26 + 'A'.code).toByte() - } else if (byte in 'a'.code..'z'.code) { - vrf[i] = ((byte - 'a'.code + 13) % 26 + 'a'.code).toByte() - } - } - return vrf - } - - private fun vrfShift(vrf: ByteArray): ByteArray { - for (i in vrf.indices) { - val shift = arrayOf(4, 3, -2, 5, 2, -4, -4, 2)[i % 8] - vrf[i] = vrf[i].plus(shift).toByte() - } - return vrf - } -} - -@Serializable -data class TmdbResponse( - val results: List<TmdbResult>, -) { - @Serializable - data class TmdbResult( - val id: Int, - @SerialName("media_type") - val mediaType: String = "tv", - ) -} - -@Serializable -data class TmdbDetailsResponse( - val status: String, - val overview: String? = null, - @SerialName("next_episode_to_air") - val nextEpisode: NextEpisode? = null, -) { - @Serializable - data class NextEpisode( - val name: String? = "", - @SerialName("episode_number") - val epNumber: Int, - @SerialName("air_date") - val airDate: String, - ) -} diff --git a/src/en/gogoanime/build.gradle b/src/en/gogoanime/build.gradle deleted file mode 100644 index 9ac6d8eb..00000000 --- a/src/en/gogoanime/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -ext { - extName = 'Gogoanime' - extClass = '.GogoAnime' - extVersionCode = 87 -} - -apply from: "$rootDir/common.gradle" - -dependencies { - implementation(project(':lib:streamwish-extractor')) - implementation(project(':lib:mp4upload-extractor')) - implementation(project(':lib:dood-extractor')) - implementation(project(':lib:gogostream-extractor')) -} \ No newline at end of file diff --git a/src/en/gogoanime/res/mipmap-hdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9e4d2f6b..00000000 Binary files a/src/en/gogoanime/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gogoanime/res/mipmap-mdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1bd68c8c..00000000 Binary files a/src/en/gogoanime/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gogoanime/res/mipmap-xhdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4f2d6d97..00000000 Binary files a/src/en/gogoanime/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gogoanime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1a6056f7..00000000 Binary files a/src/en/gogoanime/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gogoanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 67e41341..00000000 Binary files a/src/en/gogoanime/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt deleted file mode 100644 index b05f103c..00000000 --- a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt +++ /dev/null @@ -1,299 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.gogoanime - -import android.app.Application -import androidx.preference.ListPreference -import androidx.preference.MultiSelectListPreference -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.doodextractor.DoodExtractor -import eu.kanade.tachiyomi.lib.gogostreamextractor.GogoStreamExtractor -import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor -import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking -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 GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { - - override val name = "Gogoanime" - - // TODO: Check frequency of url changes to potentially - // add back overridable baseurl preference - override val baseUrl = "https://anitaku.to" - - override val lang = "en" - - override val supportsLatest = true - - override fun headersBuilder() = super.headersBuilder() - .add("Origin", baseUrl) - .add("Referer", "$baseUrl/") - - private val preferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - // ============================== Popular =============================== - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page", headers) - - override fun popularAnimeSelector(): String = "div.img a" - - override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply { - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.selectFirst("img")!!.attr("src") - title = element.attr("title") - } - - override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" - - // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/home.html?page=$page", headers) - - override fun latestUpdatesSelector(): String = "div.img a" - - override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { - thumbnail_url = element.selectFirst("img")?.attr("src") - title = element.attr("title") - val slug = element.attr("href").substringAfter(baseUrl) - .trimStart('/') - .substringBefore("-episode-") - setUrlWithoutDomain("/category/$slug") - } - - override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() - - // =============================== Search =============================== - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { - val params = GogoAnimeFilters.getSearchParameters(filters) - - return when { - params.genre.isNotEmpty() -> GET("$baseUrl/genre/${params.genre}?page=$page", headers) - params.recent.isNotEmpty() -> GET("$AJAX_URL/page-recent-release.html?page=$page&type=${params.recent}", headers) - params.season.isNotEmpty() -> GET("$baseUrl/${params.season}?page=$page", headers) - else -> GET("$baseUrl/filter.html?keyword=$query&${params.filter}&page=$page", headers) - } - } - - override fun searchAnimeSelector(): String = popularAnimeSelector() - - override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) - - override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() - - // ============================== Filters =============================== - override fun getFilterList(): AnimeFilterList = GogoAnimeFilters.FILTER_LIST - - // =========================== Anime Details ============================ - override fun animeDetailsParse(document: Document): SAnime { - val infoDocument = document.selectFirst("div.anime-info a[href]")?.let { - client.newCall(GET(it.absUrl("href"), headers)).execute().asJsoup() - } ?: document - - return SAnime.create().apply { - title = infoDocument.selectFirst("div.anime_info_body_bg > h1")!!.text() - genre = infoDocument.getInfo("Genre:") - status = parseStatus(infoDocument.getInfo("Status:").orEmpty()) - - description = buildString { - val summary = infoDocument.selectFirst("div.anime_info_body_bg > div.description") - append(summary?.text()) - - // add alternative name to anime description - infoDocument.getInfo("Other name:")?.also { - if (isNotBlank()) append("\n\n") - append("Other name(s): $it") - } - } - } - } - - // ============================== Episodes ============================== - private fun episodesRequest(totalEpisodes: String, id: String): List<SEpisode> { - val request = GET("$AJAX_URL/load-list-episode?ep_start=0&ep_end=$totalEpisodes&id=$id", headers) - val epResponse = client.newCall(request).execute() - val document = epResponse.asJsoup() - return document.select("a").map(::episodeFromElement) - } - - override fun episodeListParse(response: Response): List<SEpisode> { - val document = response.asJsoup() - val totalEpisodes = document.select(episodeListSelector()).last()!!.attr("ep_end") - val id = document.select("input#movie_id").attr("value") - return episodesRequest(totalEpisodes, id) - } - - override fun episodeListSelector() = "ul#episode_page li a" - - override fun episodeFromElement(element: Element): SEpisode { - val ep = element.selectFirst("div.name")!!.ownText().substringAfter(" ") - return SEpisode.create().apply { - setUrlWithoutDomain(element.attr("abs:href")) - episode_number = ep.toFloat() - name = "Episode $ep" - } - } - - // ============================ Video Links ============================= - private val gogoExtractor by lazy { GogoStreamExtractor(client) } - private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) } - private val doodExtractor by lazy { DoodExtractor(client) } - private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) } - - override fun videoListParse(response: Response): List<Video> { - val document = response.asJsoup() - val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!! - - return document.select("div.anime_muti_link > ul > li").parallelCatchingFlatMapBlocking { server -> - val className = server.className() - if (!hosterSelection.contains(className)) return@parallelCatchingFlatMapBlocking emptyList() - val serverUrl = server.selectFirst("a") - ?.attr("abs:data-video") - ?: return@parallelCatchingFlatMapBlocking emptyList() - - getHosterVideos(className, serverUrl) - } - } - - private fun getHosterVideos(className: String, serverUrl: String): List<Video> { - return when (className) { - "anime", "vidcdn" -> gogoExtractor.videosFromUrl(serverUrl) - "streamwish" -> streamwishExtractor.videosFromUrl(serverUrl) - "doodstream" -> doodExtractor.videosFromUrl(serverUrl) - "mp4upload" -> mp4uploadExtractor.videosFromUrl(serverUrl, headers) - "filelions" -> { - streamwishExtractor.videosFromUrl(serverUrl, videoNameGen = { quality -> "FileLions - $quality" }) - } - else -> emptyList() - } - } - - override fun videoListSelector() = throw UnsupportedOperationException() - - override fun videoFromElement(element: Element) = throw UnsupportedOperationException() - - override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() - - // ============================= Utilities ============================== - private fun Document.getInfo(text: String): String? { - val base = selectFirst("p.type:has(span:containsOwn($text))") ?: return null - return base.select("a").eachText().joinToString("") - .ifBlank { base.ownText() } - .takeUnless(String::isBlank) - } - - override fun List<Video>.sort(): List<Video> { - val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! - val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!! - - return sortedWith( - compareBy( - { it.quality.contains(quality) }, - { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, - { it.quality.contains(server) }, - ), - ).reversed() - } - - private fun parseStatus(statusString: String): Int { - return when (statusString) { - "Ongoing" -> SAnime.ONGOING - "Completed" -> SAnime.COMPLETED - else -> SAnime.UNKNOWN - } - } - - companion object { - private const val AJAX_URL = "https://ajax.gogocdn.net/ajax" - - private val HOSTERS = arrayOf( - "Gogostream", - "Vidstreaming", - "Doodstream", - "StreamWish", - "Mp4upload", - "FileLions", - ) - private val HOSTERS_NAMES = arrayOf( - // Names that appears in the gogo html - "vidcdn", - "anime", - "doodstream", - "streamwish", - "mp4upload", - "filelions", - ) - - private const val PREF_QUALITY_KEY = "preferred_quality" - private const val PREF_QUALITY_TITLE = "Preferred quality" - private const val PREF_QUALITY_DEFAULT = "1080" - private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p") - private val PREF_QUALITY_VALUES = arrayOf("1080", "720", "480", "360") - - private const val PREF_SERVER_KEY = "preferred_server" - private const val PREF_SERVER_TITLE = "Preferred server" - private const val PREF_SERVER_DEFAULT = "Gogostream" - - private const val PREF_HOSTER_KEY = "hoster_selection" - private const val PREF_HOSTER_TITLE = "Enable/Disable Hosts" - private val PREF_HOSTER_DEFAULT = HOSTERS_NAMES.toSet() - } - - // ============================== Settings ============================== - override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - key = PREF_QUALITY_KEY - title = PREF_QUALITY_TITLE - entries = PREF_QUALITY_ENTRIES - entryValues = PREF_QUALITY_VALUES - setDefaultValue(PREF_QUALITY_DEFAULT) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(key, entry).commit() - } - }.also(screen::addPreference) - - ListPreference(screen.context).apply { - key = PREF_SERVER_KEY - title = PREF_SERVER_TITLE - entries = HOSTERS - entryValues = HOSTERS - setDefaultValue(PREF_SERVER_DEFAULT) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(key, entry).commit() - } - }.also(screen::addPreference) - - MultiSelectListPreference(screen.context).apply { - key = PREF_HOSTER_KEY - title = PREF_HOSTER_TITLE - entries = HOSTERS - entryValues = HOSTERS_NAMES - setDefaultValue(PREF_HOSTER_DEFAULT) - - setOnPreferenceChangeListener { _, newValue -> - @Suppress("UNCHECKED_CAST") - preferences.edit().putStringSet(key, newValue as Set<String>).commit() - } - }.also(screen::addPreference) - } -} diff --git a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnimeFilters.kt b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnimeFilters.kt deleted file mode 100644 index a72b81a3..00000000 --- a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnimeFilters.kt +++ /dev/null @@ -1,414 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.gogoanime - -import eu.kanade.tachiyomi.animesource.model.AnimeFilter -import eu.kanade.tachiyomi.animesource.model.AnimeFilterList - -object GogoAnimeFilters { - open class QueryPartFilter( - displayName: String, - val vals: Array<Pair<String, String>>, - ) : AnimeFilter.Select<String>( - displayName, - vals.map { it.first }.toTypedArray(), - ) { - fun toQueryPart() = vals[state].second - } - - open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) : - AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) }) - - private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state) - - private inline fun <reified R> AnimeFilterList.asQueryPart(): String { - return (getFirst<R>() as QueryPartFilter).toQueryPart() - } - - private inline fun <reified R> AnimeFilterList.getFirst(): R { - return first { it is R } as R - } - - private inline fun <reified R> AnimeFilterList.parseCheckbox( - options: Array<Pair<String, String>>, - name: String, - ): String { - return (getFirst<R>() as CheckBoxFilterList).state - .filter { it.state } - .map { checkbox -> options.find { it.first == checkbox.name }!!.second } - .filter(String::isNotBlank) - .joinToString("&") { "$name[]=$it" } - } - - class GenreSearchFilter : CheckBoxFilterList("Genre", GogoAnimeFiltersData.GENRE_SEARCH_LIST) - class CountrySearchFilter : CheckBoxFilterList("Country", GogoAnimeFiltersData.COUNTRY_SEARCH_LIST) - class SeasonSearchFilter : CheckBoxFilterList("Season", GogoAnimeFiltersData.SEASON_SEARCH_LIST) - class YearSearchFilter : CheckBoxFilterList("Year", GogoAnimeFiltersData.YEAR_SEARCH_LIST) - class LanguageSearchFilter : CheckBoxFilterList("Language", GogoAnimeFiltersData.LANGUAGE_SEARCH_LIST) - class TypeSearchFilter : CheckBoxFilterList("Type", GogoAnimeFiltersData.TYPE_SEARCH_LIST) - class StatusSearchFilter : CheckBoxFilterList("Status", GogoAnimeFiltersData.STATUS_SEARCH_LIST) - class SortSearchFilter : QueryPartFilter("Sort by", GogoAnimeFiltersData.SORT_SEARCH_LIST) - - class GenreFilter : QueryPartFilter("Genre", GogoAnimeFiltersData.GENRE_LIST) - class RecentFilter : QueryPartFilter("Recent episodes", GogoAnimeFiltersData.RECENT_LIST) - class SeasonFilter : QueryPartFilter("Season", GogoAnimeFiltersData.SEASON_LIST) - - val FILTER_LIST get() = AnimeFilterList( - AnimeFilter.Header("Advanced search"), - GenreSearchFilter(), - CountrySearchFilter(), - SeasonSearchFilter(), - YearSearchFilter(), - LanguageSearchFilter(), - TypeSearchFilter(), - StatusSearchFilter(), - SortSearchFilter(), - AnimeFilter.Separator(), - AnimeFilter.Header("Select sub-page"), - AnimeFilter.Header("Note: Ignores search & other filters"), - GenreFilter(), - RecentFilter(), - SeasonFilter(), - ) - - data class FilterSearchParams( - val filter: String = "", - val genre: String = "", - val recent: String = "", - val season: String = "", - ) - - internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { - if (filters.isEmpty()) return FilterSearchParams() - - val filter = buildList { - add(filters.parseCheckbox<GenreSearchFilter>(GogoAnimeFiltersData.GENRE_SEARCH_LIST, "genre")) - add(filters.parseCheckbox<CountrySearchFilter>(GogoAnimeFiltersData.GENRE_SEARCH_LIST, "country")) - add(filters.parseCheckbox<SeasonSearchFilter>(GogoAnimeFiltersData.SEASON_SEARCH_LIST, "season")) - add(filters.parseCheckbox<YearSearchFilter>(GogoAnimeFiltersData.YEAR_SEARCH_LIST, "year")) - add(filters.parseCheckbox<LanguageSearchFilter>(GogoAnimeFiltersData.LANGUAGE_SEARCH_LIST, "language")) - add(filters.parseCheckbox<TypeSearchFilter>(GogoAnimeFiltersData.TYPE_SEARCH_LIST, "type")) - add(filters.parseCheckbox<StatusSearchFilter>(GogoAnimeFiltersData.STATUS_SEARCH_LIST, "status")) - add("sort=${filters.asQueryPart<SortSearchFilter>()}") - }.filter(String::isNotBlank).joinToString("&") - - return FilterSearchParams( - filter, - filters.asQueryPart<GenreFilter>(), - filters.asQueryPart<RecentFilter>(), - filters.asQueryPart<SeasonFilter>(), - ) - } - - private object GogoAnimeFiltersData { - // copy($("div.cls_genre ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val GENRE_SEARCH_LIST = arrayOf( - 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"), - ) - - // copy($("div.cls_genre ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val COUNTRY_SEARCH_LIST = arrayOf( - Pair("China", "5"), - Pair("Japan", "2"), - ) - - // copy($("div.cls_season ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val SEASON_SEARCH_LIST = arrayOf( - Pair("Fall", "fall"), - Pair("Summer", "summer"), - Pair("Spring", "spring"), - Pair("Winter", "winter"), - ) - - // copy($("div.cls_year ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val YEAR_SEARCH_LIST = arrayOf( - Pair("2024", "2024"), - Pair("2023", "2023"), - Pair("2022", "2022"), - Pair("2021", "2021"), - Pair("2020", "2020"), - Pair("2019", "2019"), - Pair("2018", "2018"), - Pair("2017", "2017"), - Pair("2016", "2016"), - Pair("2015", "2015"), - Pair("2014", "2014"), - Pair("2013", "2013"), - Pair("2012", "2012"), - Pair("2011", "2011"), - Pair("2010", "2010"), - Pair("2009", "2009"), - Pair("2008", "2008"), - Pair("2007", "2007"), - Pair("2006", "2006"), - Pair("2005", "2005"), - Pair("2004", "2004"), - Pair("2003", "2003"), - Pair("2002", "2002"), - Pair("2001", "2001"), - Pair("2000", "2000"), - Pair("1999", "1999"), - ) - - // copy($("div.cls_lang ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val LANGUAGE_SEARCH_LIST = arrayOf( - Pair("Sub & Dub", "subdub"), - Pair("Sub", "sub"), - Pair("Dub", "dub"), - ) - - // copy($("div.cls_type ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val TYPE_SEARCH_LIST = arrayOf( - Pair("Movie", "3"), - Pair("TV", "1"), - Pair("OVA", "26"), - Pair("ONA", "30"), - Pair("Special", "2"), - Pair("Music", "32"), - ) - - // copy($("div.cls_status ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val STATUS_SEARCH_LIST = arrayOf( - Pair("Not Yet Aired", "Upcoming"), - Pair("Ongoing", "Ongoing"), - Pair("Completed", "Completed"), - ) - - // copy($("div.cls_sort ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n')) - // on /filter.html - val SORT_SEARCH_LIST = arrayOf( - Pair("Name A-Z", "title_az"), - Pair("Recently updated", "recently_updated"), - Pair("Recently added", "recently_added"), - Pair("Release date", "release_date"), - ) - - // copy($("div.dropdown-menu > a.dropdown-item").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).attr('href').trim().slice(18)}")`).get().join(',\n')) - // on / - val GENRE_LIST = arrayOf( - Pair("<select>", ""), - 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("<select>", ""), - Pair("Recent Release", "1"), - Pair("Recent Dub", "2"), - Pair("Recent Chinese", "3"), - ) - - val SEASON_LIST = arrayOf( - Pair("<select>", ""), - 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"), - ) - } -}