Removed aniwave (#208)

Rip
This commit is contained in:
Hak 2024-09-03 09:33:50 +07:00 committed by GitHub
parent ffb39ec544
commit 6816676e4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 0 additions and 988 deletions

View file

@ -1,14 +0,0 @@
ext {
extName = 'Aniwave'
extClass = '.Aniwave'
extVersionCode = 75
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:vidsrc-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:streamtape-extractor'))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View file

@ -1,538 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.nineanime
import android.app.Application
import android.content.SharedPreferences
import android.webkit.URLUtil
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
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.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.vidsrcextractor.VidsrcExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
import eu.kanade.tachiyomi.util.parallelMapBlocking
import eu.kanade.tachiyomi.util.parseAs
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 Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Aniwave"
override val id: Long = 98855593379717478
override val baseUrl by lazy {
val customDomain = preferences.getString(PREF_CUSTOM_DOMAIN_KEY, null)
if (customDomain.isNullOrBlank()) {
preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!!
} else {
customDomain
}
}
override val lang = "en"
override val supportsLatest = true
private val utils by lazy { AniwaveUtils() }
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val refererHeaders = headers.newBuilder().apply {
add("Referer", "$baseUrl/")
}.build()
private val markFiller by lazy { preferences.getBoolean(PREF_MARK_FILLERS_KEY, PREF_MARK_FILLERS_DEFAULT) }
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filter?sort=trending&page=$page", refererHeaders)
override fun popularAnimeSelector(): String = "div.ani.items > div.item"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
element.select("a.name").let { a ->
setUrlWithoutDomain(a.attr("href").substringBefore("?"))
title = a.text()
}
thumbnail_url = element.select("div.poster img").attr("src")
}
override fun popularAnimeNextPageSelector(): String =
"nav > ul.pagination > li.active ~ li"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/filter?sort=recently_updated&page=$page", refererHeaders)
override fun latestUpdatesSelector(): String = popularAnimeSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filters = AniwaveFilters.getSearchParameters(filters)
val vrf = if (query.isNotBlank()) utils.vrfEncrypt(query) else ""
var url = "$baseUrl/filter?keyword=$query"
if (filters.genre.isNotBlank()) url += filters.genre
if (filters.country.isNotBlank()) url += filters.country
if (filters.season.isNotBlank()) url += filters.season
if (filters.year.isNotBlank()) url += filters.year
if (filters.type.isNotBlank()) url += filters.type
if (filters.status.isNotBlank()) url += filters.status
if (filters.language.isNotBlank()) url += filters.language
if (filters.rating.isNotBlank()) url += filters.rating
return GET("$url&sort=${filters.sort}&page=$page&vrf=$vrf", refererHeaders)
}
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = AniwaveFilters.FILTER_LIST
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
val newDocument = resolveSearchAnime(anime, document)
anime.apply {
title = newDocument.select("h1.title").text()
genre = newDocument.select("div:contains(Genre) > span > a").joinToString { it.text() }
description = newDocument.select("div.synopsis > div.shorting > div.content").text()
author = newDocument.select("div:contains(Studio) > span > a").text()
status = parseStatus(newDocument.select("div:contains(Status) > span").text())
val altName = "Other name(s): "
newDocument.select("h1.title").attr("data-jp").let {
if (it.isNotBlank()) {
description = when {
description.isNullOrBlank() -> altName + it
else -> description + "\n\n$altName" + it
}
}
}
}
return anime
}
// ============================== Episodes ==============================
override fun episodeListRequest(anime: SAnime): Request {
val response = client.newCall(GET(baseUrl + anime.url)).execute()
var document = response.asJsoup()
document = resolveSearchAnime(anime, document)
val id = document.selectFirst("div[data-id]")?.attr("data-id") ?: throw Exception("ID not found")
val vrf = utils.vrfEncrypt(id)
val listHeaders = headers.newBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01")
add("Referer", baseUrl + anime.url)
add("X-Requested-With", "XMLHttpRequest")
}.build()
return GET("$baseUrl/ajax/episode/list/$id?vrf=$vrf#${anime.url}", listHeaders)
}
override fun episodeListSelector() = "div.episodes ul > li > a"
override fun episodeListParse(response: Response): List<SEpisode> {
val animeUrl = response.request.url.fragment!!
val document = response.parseAs<ResultResponse>().toDocument()
val episodeElements = document.select(episodeListSelector())
return episodeElements.parallelMapBlocking { episodeFromElements(it, animeUrl) }.reversed()
}
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
private fun episodeFromElements(element: Element, url: String): SEpisode {
val title = element.parent()?.attr("title") ?: ""
val epNum = element.attr("data-num")
val ids = element.attr("data-ids")
val sub = if (element.attr("data-sub").toInt().toBoolean()) "Sub" else ""
val dub = if (element.attr("data-dub").toInt().toBoolean()) "Dub" else ""
val softSub = if (SOFTSUB_REGEX.find(title) != null) "SoftSub" else ""
val extraInfo = if (element.hasClass("filler") && markFiller) {
" • Filler Episode"
} else {
""
}
val name = element.parent()?.select("span.d-title")?.text().orEmpty()
val namePrefix = "Episode $epNum"
return SEpisode.create().apply {
this.name = "Episode $epNum" +
if (name.isNotEmpty() && name != namePrefix) ": $name" else ""
this.url = "$ids&epurl=$url/ep-$epNum"
episode_number = epNum.toFloat()
date_upload = RELEASE_REGEX.find(title)?.let {
parseDate(it.groupValues[1])
} ?: 0L
scanlator = arrayOf(sub, softSub, dub).filter(String::isNotBlank).joinToString(", ") + extraInfo
}
}
// ============================ Video Links =============================
override fun videoListRequest(episode: SEpisode): Request {
val ids = episode.url.substringBefore("&")
val vrf = utils.vrfEncrypt(ids)
val url = "/ajax/server/list/$ids?vrf=$vrf"
val epurl = episode.url.substringAfter("epurl=")
val listHeaders = headers.newBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01")
add("Referer", baseUrl + epurl)
add("X-Requested-With", "XMLHttpRequest")
}.build()
return GET("$baseUrl$url#$epurl", listHeaders)
}
data class VideoData(
val type: String,
val serverId: String,
val serverName: String,
)
override fun videoListParse(response: Response): List<Video> {
val epurl = response.request.url.fragment!!
val document = response.parseAs<ResultResponse>().toDocument()
val hosterSelection = getHosters()
val typeSelection = preferences.getStringSet(PREF_TYPE_TOGGLE_KEY, PREF_TYPES_TOGGLE_DEFAULT)!!
return document.select("div.servers > div").parallelFlatMapBlocking { elem ->
val type = elem.attr("data-type").replaceFirstChar { it.uppercase() }
elem.select("li").mapNotNull { serverElement ->
val serverId = serverElement.attr("data-link-id")
val serverName = serverElement.text().lowercase()
if (hosterSelection.contains(serverName, true).not()) return@mapNotNull null
if (typeSelection.contains(type, true).not()) return@mapNotNull null
VideoData(type, serverId, serverName)
}
}
.parallelFlatMapBlocking { extractVideo(it, epurl) }
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// ============================= Utilities ==============================
private val vidsrcExtractor by lazy { VidsrcExtractor(client, headers) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private fun extractVideo(server: VideoData, epUrl: String): List<Video> {
val vrf = utils.vrfEncrypt(server.serverId)
val listHeaders = headers.newBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01")
add("Referer", baseUrl + epUrl)
add("X-Requested-With", "XMLHttpRequest")
}.build()
val response = client.newCall(
GET("$baseUrl/ajax/server/${server.serverId}?vrf=$vrf", listHeaders),
).execute()
if (response.code != 200) return emptyList()
return runCatching {
val parsed = response.parseAs<ServerResponse>()
val embedLink = utils.vrfDecrypt(parsed.result.url)
when (server.serverName) {
"vidstream" -> vidsrcExtractor.videosFromUrl(embedLink, "Vidstream", server.type)
"megaf" -> vidsrcExtractor.videosFromUrl(embedLink, "MegaF", server.type)
"moonf" -> filemoonExtractor.videosFromUrl(embedLink, "MoonF - ${server.type} - ")
"streamtape" -> streamtapeExtractor.videoFromUrl(embedLink, "StreamTape - ${server.type}")?.let(::listOf) ?: emptyList()
"mp4u" -> mp4uploadExtractor.videosFromUrl(embedLink, headers, suffix = " - ${server.type}")
else -> emptyList()
}
}.getOrElse { emptyList() }
}
private fun Int.toBoolean() = this == 1
private fun Set<String>.contains(s: String, ignoreCase: Boolean): Boolean {
return any { it.equals(s, ignoreCase) }
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareByDescending<Video> { it.quality.contains(lang) }
.thenByDescending { it.quality.contains(quality) }
.thenByDescending { it.quality.contains(server, true) },
)
}
@Synchronized
private fun parseDate(dateStr: String): Long {
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
.getOrNull() ?: 0L
}
private fun parseStatus(statusString: String): Int {
return when (statusString) {
"Releasing" -> SAnime.ONGOING
"Completed" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
private fun resolveSearchAnime(anime: SAnime, document: Document): Document {
if (document.location().startsWith("$baseUrl/filter?keyword=")) { // redirected to search
val element = document.selectFirst(searchAnimeSelector())
val foundAnimePath = element?.selectFirst("a[href]")?.attr("href")
?: throw Exception("Search element not found (resolveSearch)")
anime.url = foundAnimePath // probably doesn't work as intended
return client.newCall(GET(baseUrl + foundAnimePath)).execute().asJsoup()
}
return document
}
private fun getHosters(): Set<String> {
val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
var invalidRecord = false
hosterSelection.forEach { str ->
val index = HOSTERS_NAMES.indexOf(str)
if (index == -1) {
invalidRecord = true
}
}
// found invalid record, reset to defaults
if (invalidRecord) {
preferences.edit().putStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT).apply()
return PREF_HOSTER_DEFAULT.toSet()
}
return hosterSelection.toSet()
}
companion object {
private val SOFTSUB_REGEX by lazy { Regex("""\bsoftsub\b""", RegexOption.IGNORE_CASE) }
private val RELEASE_REGEX by lazy { Regex("""Release: (\d+\/\d+\/\d+ \d+:\d+)""") }
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy/MM/dd HH:mm", Locale.ENGLISH)
}
private const val PREF_DOMAIN_KEY = "preferred_domain"
private const val PREF_DOMAIN_DEFAULT = "https://aniwave.to"
private const val PREF_CUSTOM_DOMAIN_KEY = "custom_domain"
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private const val PREF_LANG_KEY = "preferred_language"
private const val PREF_LANG_DEFAULT = "Sub"
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Vidstream"
private const val PREF_MARK_FILLERS_KEY = "mark_fillers"
private const val PREF_MARK_FILLERS_DEFAULT = true
private const val PREF_HOSTER_KEY = "hoster_selection"
private val HOSTERS = arrayOf(
"Vidstream",
"Megaf",
"MoonF",
"StreamTape",
"MP4u",
)
private val HOSTERS_NAMES = arrayOf(
"vidstream",
"megaf",
"moonf",
"streamtape",
"mp4u",
)
private val PREF_HOSTER_DEFAULT = HOSTERS_NAMES.toSet()
private const val PREF_TYPE_TOGGLE_KEY = "type_selection"
private val TYPES = arrayOf("Sub", "Softsub", "Dub")
private val PREF_TYPES_TOGGLE_DEFAULT = TYPES.toSet()
}
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
// validate hosters preferences and if invalid reset
try {
getHosters()
} catch (e: Exception) {
Toast.makeText(screen.context, e.toString(), Toast.LENGTH_LONG).show()
}
ListPreference(screen.context).apply {
key = PREF_DOMAIN_KEY
title = "Preferred domain"
entries = arrayOf("aniwave.to", "aniwavetv.to (unofficial)")
entryValues = arrayOf("https://aniwave.to", "https://aniwavetv.to")
setDefaultValue(PREF_DOMAIN_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
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_LANG_KEY
title = "Preferred Type"
entries = TYPES
entryValues = TYPES
setDefaultValue(PREF_LANG_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_NAMES
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)
SwitchPreferenceCompat(screen.context).apply {
key = PREF_MARK_FILLERS_KEY
title = "Mark filler episodes"
setDefaultValue(PREF_MARK_FILLERS_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show()
preferences.edit().putBoolean(key, newValue as Boolean).commit()
}
}.also(screen::addPreference)
MultiSelectListPreference(screen.context).apply {
key = PREF_HOSTER_KEY
title = "Enable/Disable Hosts"
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)
MultiSelectListPreference(screen.context).apply {
key = PREF_TYPE_TOGGLE_KEY
title = "Enable/Disable Types"
entries = TYPES
entryValues = TYPES
setDefaultValue(PREF_TYPES_TOGGLE_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
@Suppress("UNCHECKED_CAST")
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}.also(screen::addPreference)
EditTextPreference(screen.context).apply {
key = PREF_CUSTOM_DOMAIN_KEY
title = "Custom domain"
setDefaultValue(null)
val currentValue = preferences.getString(PREF_CUSTOM_DOMAIN_KEY, null)
summary = if (currentValue.isNullOrBlank()) {
"Custom domain of your choosing"
} else {
"Domain: \"$currentValue\". \nLeave blank to disable. Overrides any domain preferences!"
}
setOnPreferenceChangeListener { _, newValue ->
val newDomain = newValue as String
if (newDomain.isBlank() || URLUtil.isValidUrl(newDomain)) {
summary = "Restart to apply changes"
Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, newDomain).apply()
true
} else {
Toast.makeText(screen.context, "Invalid url. Url example: https://aniwave.to", Toast.LENGTH_LONG).show()
false
}
}
}.also(screen::addPreference)
}
}

View file

@ -1,49 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.nineanime
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
@Serializable
data class ServerResponse(
val result: Result,
) {
@Serializable
data class Result(
val url: 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,
)
}
}
@Serializable
data class ResultResponse(
val result: String,
) {
fun toDocument(): Document {
return Jsoup.parseBodyFragment(result)
}
}

View file

@ -1,270 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.nineanime
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AniwaveFilters {
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, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return this.filterIsInstance<R>().joinToString("") {
(it as QueryPartFilter).toQueryPart()
}
}
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"
}
}
}
class SortFilter : QueryPartFilter("Sort order", NineAnimeFiltersData.SORT)
class GenreFilter : CheckBoxFilterList(
"Genre",
NineAnimeFiltersData.GENRE.map { CheckBoxVal(it.first, false) },
)
class CountryFilter : CheckBoxFilterList(
"Country",
NineAnimeFiltersData.COUNTRY.map { CheckBoxVal(it.first, false) },
)
class SeasonFilter : CheckBoxFilterList(
"Season",
NineAnimeFiltersData.SEASON.map { CheckBoxVal(it.first, false) },
)
class YearFilter : CheckBoxFilterList(
"Year",
NineAnimeFiltersData.YEAR.map { CheckBoxVal(it.first, false) },
)
class TypeFilter : CheckBoxFilterList(
"Type",
NineAnimeFiltersData.TYPE.map { CheckBoxVal(it.first, false) },
)
class StatusFilter : CheckBoxFilterList(
"Status",
NineAnimeFiltersData.STATUS.map { CheckBoxVal(it.first, false) },
)
class LanguageFilter : CheckBoxFilterList(
"Language",
NineAnimeFiltersData.LANGUAGE.map { CheckBoxVal(it.first, false) },
)
class RatingFilter : CheckBoxFilterList(
"Rating",
NineAnimeFiltersData.RATING.map { CheckBoxVal(it.first, false) },
)
val FILTER_LIST get() = AnimeFilterList(
SortFilter(),
AnimeFilter.Separator(),
GenreFilter(),
CountryFilter(),
SeasonFilter(),
YearFilter(),
TypeFilter(),
StatusFilter(),
LanguageFilter(),
RatingFilter(),
)
data class FilterSearchParams(
val sort: String = "",
val genre: String = "",
val country: String = "",
val season: String = "",
val year: String = "",
val type: String = "",
val status: String = "",
val language: String = "",
val rating: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<SortFilter>(),
filters.parseCheckbox<GenreFilter>(NineAnimeFiltersData.GENRE, "genre"),
filters.parseCheckbox<CountryFilter>(NineAnimeFiltersData.COUNTRY, "country"),
filters.parseCheckbox<SeasonFilter>(NineAnimeFiltersData.SEASON, "season"),
filters.parseCheckbox<YearFilter>(NineAnimeFiltersData.YEAR, "year"),
filters.parseCheckbox<TypeFilter>(NineAnimeFiltersData.TYPE, "type"),
filters.parseCheckbox<StatusFilter>(NineAnimeFiltersData.STATUS, "status"),
filters.parseCheckbox<LanguageFilter>(NineAnimeFiltersData.LANGUAGE, "language"),
filters.parseCheckbox<RatingFilter>(NineAnimeFiltersData.RATING, "rating"),
)
}
private object NineAnimeFiltersData {
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("MAL scores", "mal_scores"),
Pair("Most watched", "most_watched"),
Pair("Most favourited", "most_favourited"),
Pair("Number of episodes", "number_of_episodes"),
)
val GENRE = arrayOf(
Pair("Action", "1"),
Pair("Adventure", "2"),
Pair("Avant Garde", "2262888"),
Pair("Boys Love", "2262603"),
Pair("Comedy", "4"),
Pair("Demons", "4424081"),
Pair("Drama", "7"),
Pair("Ecchi", "8"),
Pair("Fantasy", "9"),
Pair("Girls Love", "2263743"),
Pair("Gourmet", "2263289"),
Pair("Harem", "11"),
Pair("Horror", "14"),
Pair("Isekai", "3457284"),
Pair("Iyashikei", "4398552"),
Pair("Josei", "15"),
Pair("Kids", "16"),
Pair("Magic", "4424082"),
Pair("Mahou Shoujo", "3457321"),
Pair("Martial Arts", "18"),
Pair("Mecha", "19"),
Pair("Military", "20"),
Pair("Music", "21"),
Pair("Mystery", "22"),
Pair("Parody", "23"),
Pair("Psychological", "25"),
Pair("Reverse Harem", "4398403"),
Pair("Romance", "26"),
Pair("School", "28"),
Pair("Sci-Fi", "29"),
Pair("Seinen", "30"),
Pair("Shoujo", "31"),
Pair("Shounen", "33"),
Pair("Slice of Life", "35"),
Pair("Space", "36"),
Pair("Sports", "37"),
Pair("Super Power", "38"),
Pair("Supernatural", "39"),
Pair("Suspense", "2262590"),
Pair("Thriller", "40"),
Pair("Vampire", "41"),
)
val COUNTRY = arrayOf(
Pair("China", "120823"),
Pair("Japan", "120822"),
)
val SEASON = arrayOf(
Pair("Fall", "fall"),
Pair("Summer", "summer"),
Pair("Spring", "spring"),
Pair("Winter", "winter"),
Pair("Unknown", "unknown"),
)
val YEAR = 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 TYPE = arrayOf(
Pair("Movie", "movie"),
Pair("TV", "tv"),
Pair("OVA", "ova"),
Pair("ONA", "ona"),
Pair("Special", "special"),
Pair("Music", "music"),
)
val STATUS = arrayOf(
Pair("Not Yet Aired", "info"),
Pair("Releasing", "releasing"),
Pair("Completed", "completed"),
)
val LANGUAGE = arrayOf(
Pair("Sub and Dub", "subdub"),
Pair("Sub", "sub"),
Pair("Dub", "dub"),
)
val RATING = arrayOf(
Pair("G - All Ages", "g"),
Pair("PG - Children", "pg"),
Pair("PG 13 - Teens 13 and Older", "pg_13"),
Pair("R - 17+, Violence & Profanity", "r"),
Pair("R+ - Profanity & Mild Nudity", "r+"),
Pair("Rx - Hentai", "rx"),
)
}
}

View file

@ -1,117 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.nineanime
import android.util.Base64
import java.net.URLDecoder
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
class AniwaveUtils {
fun vrfEncrypt(input: String): String {
var vrf = input
ORDER.sortedBy {
it.first
}.forEach { item ->
when (item.second) {
"exchange" -> vrf = exchange(vrf, item.third)
"rc4" -> vrf = rc4Encrypt(item.third.get(0), vrf)
"reverse" -> vrf = vrf.reversed()
"base64" -> vrf = Base64.encode(vrf.toByteArray(), Base64.URL_SAFE or Base64.NO_WRAP).toString(Charsets.UTF_8)
else -> {}
}
}
return java.net.URLEncoder.encode(vrf, "utf-8")
}
fun vrfDecrypt(input: String): String {
var vrf = input
ORDER.sortedByDescending {
it.first
}.forEach { item ->
when (item.second) {
"exchange" -> vrf = exchange(vrf, item.third.reversed())
"rc4" -> vrf = rc4Decrypt(item.third.get(0), vrf)
"reverse" -> vrf = vrf.reversed()
"base64" -> vrf = Base64.decode(vrf, Base64.URL_SAFE).toString(Charsets.UTF_8)
else -> {}
}
}
return URLDecoder.decode(vrf, "utf-8")
}
private fun rc4Encrypt(key: String, input: String): String {
val rc4Key = SecretKeySpec(key.toByteArray(), "RC4")
val cipher = Cipher.getInstance("RC4")
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
var output = cipher.doFinal(input.toByteArray())
output = Base64.encode(output, Base64.URL_SAFE or Base64.NO_WRAP)
return output.toString(Charsets.UTF_8)
}
private fun rc4Decrypt(key: String, input: String): String {
var vrf = input.toByteArray()
vrf = Base64.decode(vrf, Base64.URL_SAFE)
val rc4Key = SecretKeySpec(key.toByteArray(), "RC4")
val cipher = Cipher.getInstance("RC4")
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
vrf = cipher.doFinal(vrf)
return vrf.toString(Charsets.UTF_8)
}
private fun exchange(input: String, keys: List<String>): String {
val key1 = keys.get(0)
val key2 = keys.get(1)
return input.map { i ->
val index = key1.indexOf(i)
if (index != -1) {
key2[index]
} else {
i
}
}.joinToString("")
}
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(-2, -4, -5, 6, 2, -3, 3, 6)[i % 8]
vrf[i] = vrf[i].plus(shift).toByte()
}
return vrf
}
companion object {
private val EXCHANGE_KEY_1 = listOf("AP6GeR8H0lwUz1", "UAz8Gwl10P6ReH")
private val KEY_1 = "ItFKjuWokn4ZpB"
private val KEY_2 = "fOyt97QWFB3"
private val EXCHANGE_KEY_2 = listOf("1majSlPQd2M5", "da1l2jSmP5QM")
private val EXCHANGE_KEY_3 = listOf("CPYvHj09Au3", "0jHA9CPYu3v")
private val KEY_3 = "736y1uTJpBLUX"
private val ORDER = listOf(
Triple(1, "exchange", EXCHANGE_KEY_1),
Triple(2, "rc4", listOf(KEY_1)),
Triple(3, "rc4", listOf(KEY_2)),
Triple(4, "exchange", EXCHANGE_KEY_2),
Triple(5, "exchange", EXCHANGE_KEY_3),
Triple(5, "reverse", emptyList()),
Triple(6, "rc4", listOf(KEY_3)),
Triple(7, "base64", emptyList()),
)
}
}