en/slothanime dead
This commit is contained in:
parent
80ec94f022
commit
fd6d507ed2
8 changed files with 0 additions and 326 deletions
|
@ -1,7 +0,0 @@
|
|||
ext {
|
||||
extName = 'SlothAnime'
|
||||
extClass = '.SlothAnime'
|
||||
extVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
Before Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
|
@ -1,122 +0,0 @@
|
|||
package eu.kanade.tachiyomi.animeextension.en.slothanime
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
|
||||
open class UriPartFilter(
|
||||
name: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
defaultValue: String? = null,
|
||||
) : AnimeFilter.Select<String>(
|
||||
name,
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
vals.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0,
|
||||
) {
|
||||
fun getValue(): String {
|
||||
return vals[state].second
|
||||
}
|
||||
}
|
||||
|
||||
open class UriMultiSelectOption(name: String, val value: String) : AnimeFilter.CheckBox(name)
|
||||
|
||||
open class UriMultiSelectFilter(
|
||||
name: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Group<UriMultiSelectOption>(name, vals.map { UriMultiSelectOption(it.first, it.second) }) {
|
||||
fun getValues(): List<String> {
|
||||
return state.filter { it.state }.map { it.value }
|
||||
}
|
||||
}
|
||||
|
||||
open class UriMultiTriSelectOption(name: String, val value: String) : AnimeFilter.TriState(name)
|
||||
|
||||
open class UriMultiTriSelectFilter(
|
||||
name: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Group<UriMultiTriSelectOption>(name, vals.map { UriMultiTriSelectOption(it.first, it.second) }) {
|
||||
fun getIncluded(): List<String> {
|
||||
return state.filter { it.state == TriState.STATE_INCLUDE }.map { it.value }
|
||||
}
|
||||
|
||||
fun getExcluded(): List<String> {
|
||||
return state.filter { it.state == TriState.STATE_EXCLUDE }.map { it.value }
|
||||
}
|
||||
}
|
||||
|
||||
class GenreFilter : UriMultiTriSelectFilter(
|
||||
"Genre",
|
||||
arrayOf(
|
||||
Pair("Action", "action"),
|
||||
Pair("Adventure", "adventure"),
|
||||
Pair("Fantasy", "fantasy"),
|
||||
Pair("Martial Arts", "martial_arts"),
|
||||
Pair("Comedy", "comedy"),
|
||||
Pair("School", "school"),
|
||||
Pair("Slice of Life", "slice_of_life"),
|
||||
Pair("Military", "military"),
|
||||
Pair("Sci-Fi", "scifi"),
|
||||
Pair("Isekai", "isekai"),
|
||||
Pair("Kids", "kids"),
|
||||
Pair("Iyashikei", "iyashikei"),
|
||||
Pair("Horror", "horror"),
|
||||
Pair("Supernatural", "supernatural"),
|
||||
Pair("Avant Garde", "avant_garde"),
|
||||
Pair("Demons", "demons"),
|
||||
Pair("Gourmet", "gourmet"),
|
||||
Pair("Music", "music"),
|
||||
Pair("Drama", "drama"),
|
||||
Pair("Seinen", "seinen"),
|
||||
Pair("Ecchi", "ecchi"),
|
||||
Pair("Harem", "harem"),
|
||||
Pair("Romance", "romance"),
|
||||
Pair("Magic", "magic"),
|
||||
Pair("Mystery", "mystery"),
|
||||
Pair("Suspense", "suspense"),
|
||||
Pair("Parody", "parody"),
|
||||
Pair("Psychological", "psychological"),
|
||||
Pair("Super Power", "super_power"),
|
||||
Pair("Vampire", "vampire"),
|
||||
Pair("Shounen", "shounen"),
|
||||
Pair("Space", "space"),
|
||||
Pair("Mecha", "mecha"),
|
||||
Pair("Sports", "sports"),
|
||||
Pair("Shoujo", "shoujo"),
|
||||
Pair("Girls Love", "girls_love"),
|
||||
Pair("Josei", "josei"),
|
||||
Pair("Mahou Shoujo", "mahou_shoujo"),
|
||||
Pair("Thriller", "thriller"),
|
||||
Pair("Reverse Harem", "reverse_harem"),
|
||||
Pair("Boys Love", "boys_love"),
|
||||
Pair("Uncategorized", "uncategorized"),
|
||||
),
|
||||
)
|
||||
|
||||
class TypeFilter : UriMultiSelectFilter(
|
||||
"Type",
|
||||
arrayOf(
|
||||
Pair("ONA", "ona"),
|
||||
Pair("TV", "tv"),
|
||||
Pair("MOVIE", "movie"),
|
||||
Pair("SPECIAL", "special"),
|
||||
Pair("OVA", "ova"),
|
||||
Pair("MUSIC", "music"),
|
||||
),
|
||||
)
|
||||
|
||||
class StatusFilter : UriPartFilter(
|
||||
"Status",
|
||||
arrayOf(
|
||||
Pair("All", "2"),
|
||||
Pair("Completed", "1"),
|
||||
Pair("Releasing", "0"),
|
||||
),
|
||||
)
|
||||
|
||||
class SortFilter : UriPartFilter(
|
||||
"Sort",
|
||||
arrayOf(
|
||||
Pair("Most Watched", "viewed"),
|
||||
Pair("Scored", "scored"),
|
||||
Pair("Newest", "created_at"),
|
||||
Pair("Latest Update", "updated_at"),
|
||||
),
|
||||
)
|
|
@ -1,197 +0,0 @@
|
|||
package eu.kanade.tachiyomi.animeextension.en.slothanime
|
||||
|
||||
import android.util.Base64
|
||||
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.network.GET
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.math.floor
|
||||
|
||||
class SlothAnime : ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "SlothAnime"
|
||||
|
||||
override val baseUrl = "https://slothanime.com"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
val url = if (page > 1) {
|
||||
"$baseUrl/list/viewed?page=$page"
|
||||
} else {
|
||||
"$baseUrl/list/viewed"
|
||||
}
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = ".row > div > .anime-card-md"
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
|
||||
thumbnail_url = element.selectFirst("img")!!.imgAttr()
|
||||
with(element.selectFirst("a[href~=/anime]")!!) {
|
||||
title = text()
|
||||
setUrlWithoutDomain(attr("abs:href"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = ".pagination > .active ~ li:has(a)"
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val url = if (page > 1) {
|
||||
"$baseUrl/list/latest?page=$page"
|
||||
} else {
|
||||
"$baseUrl/list/latest"
|
||||
}
|
||||
|
||||
return GET(url, 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 genreFilter = filters.filterIsInstance<GenreFilter>().first()
|
||||
val typeFilter = filters.filterIsInstance<TypeFilter>().first()
|
||||
val statusFilter = filters.filterIsInstance<StatusFilter>().first()
|
||||
val sortFilter = filters.filterIsInstance<SortFilter>().first()
|
||||
|
||||
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
addPathSegment("search")
|
||||
addQueryParameter("q", query)
|
||||
genreFilter.getIncluded().forEachIndexed { idx, value ->
|
||||
addQueryParameter("genre[$idx]", value)
|
||||
}
|
||||
typeFilter.getValues().forEachIndexed { idx, value ->
|
||||
addQueryParameter("type[$idx]", value)
|
||||
}
|
||||
addQueryParameter("status", statusFilter.getValue())
|
||||
addQueryParameter("sort", sortFilter.getValue())
|
||||
genreFilter.getExcluded().forEachIndexed { idx, value ->
|
||||
addQueryParameter("ignore_genre[$idx]", value)
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
addQueryParameter("page", page.toString())
|
||||
}
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector(): String = popularAnimeSelector()
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||
|
||||
// ============================== Filters ===============================
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
GenreFilter(),
|
||||
TypeFilter(),
|
||||
StatusFilter(),
|
||||
SortFilter(),
|
||||
)
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
|
||||
title = document.selectFirst(".single-title > *:not(.single-altername)")!!.text()
|
||||
thumbnail_url = document.selectFirst(".single-cover > img")!!.imgAttr()
|
||||
description = document.selectFirst(".single-detail:has(span:contains(Description)) .more-content")?.text()
|
||||
genre = document.select(".single-tag > a.tag").joinToString { it.text() }
|
||||
author = document.select(".single-detail:has(span:contains(Studios)) .value a").joinToString { it.text() }
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
|
||||
override fun episodeListSelector() = ".list-episodes-container > a[class~=episode]"
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode = SEpisode.create().apply {
|
||||
setUrlWithoutDomain(element.attr("abs:href"))
|
||||
name = element.text()
|
||||
.replace(Regex("""^EP """), "Episode ")
|
||||
.replace(Regex("""^\d+""")) { m -> "Episode ${m.value}" }
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
|
||||
fun encryptAES(input: String, key: ByteArray, iv: ByteArray): String {
|
||||
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
|
||||
val secretKey = SecretKeySpec(key, "AES")
|
||||
val ivParameterSpec = IvParameterSpec(iv)
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
|
||||
val paddedInput = zeroPad(input)
|
||||
val encryptedBytes = cipher.doFinal(paddedInput.toByteArray(Charsets.UTF_8))
|
||||
return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
fun zeroPad(input: String): String {
|
||||
val blockSize = 16
|
||||
val padLength = blockSize - input.length % blockSize
|
||||
return input.padEnd(input.length + padLength, '\u0000')
|
||||
}
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
val key = String(Base64.decode(KEY, Base64.DEFAULT)).chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
val iv = String(Base64.decode(IV, Base64.DEFAULT)).chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
val time = floor(System.currentTimeMillis() / 1000.0)
|
||||
val vrf = encryptAES(time.toString(), key, iv)
|
||||
val id = episode.url.substringAfterLast("/")
|
||||
|
||||
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
addPathSegment("player-url")
|
||||
addPathSegment(id)
|
||||
addQueryParameter("vrf", vrf)
|
||||
}.build().toString()
|
||||
|
||||
val videoHeaders = headersBuilder().apply {
|
||||
add("Accept", "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5")
|
||||
add("Referer", baseUrl + episode.url)
|
||||
}.build()
|
||||
|
||||
return listOf(
|
||||
Video(url, "Video", url, videoHeaders),
|
||||
)
|
||||
}
|
||||
|
||||
override fun videoListSelector() = throw UnsupportedOperationException()
|
||||
|
||||
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
|
||||
|
||||
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private fun Element.imgAttr(): String = when {
|
||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
||||
hasAttr("data-src") -> attr("abs:data-src")
|
||||
else -> attr("abs:src")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY = "YWI0OWZkYjllYzE5M2I0YWQzYWFkMGVmMTU4N2Q2OGE0YmYxY2Y5YjJkMjA4YjRjYzIzMDYwZTkwNThiMjA0NA=="
|
||||
private const val IV = "NDI4MzEzNjcxMThiMzFmYjVhNTI1MTMzNTc0ZmJmNGI="
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue