Removed aniwave #208
11 changed files with 0 additions and 988 deletions
|
@ -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 |
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue