feat(en/animedao): Remove extension (#1938)

This commit is contained in:
Secozzi
2023-07-21 10:02:46 +00:00
committed by GitHub
parent f56cb917da
commit 2b97788200
11 changed files with 0 additions and 850 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -1,22 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'AnimeDao'
pkgNameSuffix = 'en.animedao'
extClass = '.AnimeDao'
extVersionCode = 13
libVersion = '13'
}
dependencies {
implementation(project(':lib-mixdrop-extractor'))
implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation(project(':lib-dood-extractor'))
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -1,404 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.animedao
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.animeextension.en.animedao.extractors.VidstreamingExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
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.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class AnimeDao : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeDao"
override val baseUrl = "https://animedao.to"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animelist/popular")
override fun popularAnimeSelector(): String = "div.container > div.row > div.col-md-6"
override fun popularAnimeFromElement(element: Element): SAnime {
val thumbnailUrl = element.selectFirst("img")!!.attr("data-src")
return SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
thumbnail_url = if (thumbnailUrl.contains(baseUrl.toHttpUrl().host)) {
thumbnailUrl
} else {
baseUrl + thumbnailUrl
}
title = element.selectFirst("span.animename")!!.text()
}
}
override fun popularAnimeNextPageSelector(): String? = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl)
override fun latestUpdatesSelector(): String = "div#latest-tab-pane > div.row > div.col-md-6"
override fun latestUpdatesFromElement(element: Element): SAnime {
val thumbnailUrl = element.selectFirst("img")!!.attr("data-src")
return SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a.animeparent")!!.attr("href"))
thumbnail_url = if (thumbnailUrl.contains(baseUrl.toHttpUrl().host)) {
thumbnailUrl
} else {
baseUrl + thumbnailUrl
}
title = element.selectFirst("span.animename")!!.text()
}
}
override fun latestUpdatesNextPageSelector(): String? = popularAnimeNextPageSelector()
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used")
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
val params = AnimeDaoFilters.getSearchParameters(filters)
return client.newCall(searchAnimeRequest(page, query, params))
.asObservableSuccess()
.map { response ->
searchAnimeParse(response)
}
}
private fun searchAnimeRequest(page: Int, query: String, filters: AnimeDaoFilters.FilterSearchParams): Request {
return if (query.isNotBlank()) {
val cleanQuery = query.replace(" ", "+")
GET("$baseUrl/search/?search=$cleanQuery", headers = headers)
} else {
var url = "$baseUrl/animelist/".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("status[]=", filters.status)
.addQueryParameter("order[]=", filters.order)
.build().toString()
if (filters.genre.isNotBlank()) url += "&${filters.genre}"
if (filters.rating.isNotBlank()) url += "&${filters.rating}"
if (filters.letter.isNotBlank()) url += "&${filters.letter}"
if (filters.year.isNotBlank()) url += "&${filters.year}"
if (filters.score.isNotBlank()) url += "&${filters.score}"
url += "&page=$page"
GET(url, headers = headers)
}
}
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val selector = if (response.request.url.encodedPath.startsWith("/animelist/")) {
searchAnimeSelectorFilter()
} else {
searchAnimeSelector()
}
val animes = document.select(selector).map { element ->
searchAnimeFromElement(element)
}
val hasNextPage = searchAnimeNextPageSelector().let { selector ->
document.select(selector).first()
} != null
return AnimesPage(animes, hasNextPage)
}
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
private fun searchAnimeSelectorFilter(): String = "div.container div.col-12 > div.row > div.col-md-6"
override fun searchAnimeNextPageSelector(): String = "ul.pagination > li.page-item:has(i.fa-arrow-right):not(.disabled)"
// ============================== FILTERS ===============================
override fun getFilterList(): AnimeFilterList = AnimeDaoFilters.FILTER_LIST
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
val thumbnailUrl = document.selectFirst("div.card-body img")!!.attr("data-src")
val moreInfo = document.select("div.card-body table > tbody > tr").joinToString("\n") { it.text() }
return SAnime.create().apply {
title = document.selectFirst("div.card-body h2")!!.text()
thumbnail_url = if (thumbnailUrl.contains(baseUrl.toHttpUrl().host)) {
thumbnailUrl
} else {
baseUrl + thumbnailUrl
}
status = document.selectFirst("div.card-body table > tbody > tr:has(>td:contains(Status)) td:not(:contains(Status))")?.let {
parseStatus(it.text())
} ?: SAnime.UNKNOWN
description = (document.selectFirst("div.card-body div:has(>b:contains(Description))")?.ownText() ?: "") + "\n\n$moreInfo"
genre = document.select("div.card-body table > tbody > tr:has(>td:contains(Genres)) td > a").joinToString(", ") { it.text() }
}
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
return if (preferences.getBoolean(PREF_EPISODE_SORT_KEY, PREF_EPISODE_SORT_DEFAULT)) {
super.episodeListParse(response).sortedWith(
compareBy(
{ it.episode_number },
{ it.name },
),
).reversed()
} else {
super.episodeListParse(response)
}
}
override fun episodeListSelector(): String = "div#episodes-tab-pane > div.row > div > div.card"
override fun episodeFromElement(element: Element): SEpisode {
val episodeName = element.selectFirst("span.animename")!!.text()
val episodeTitle = element.selectFirst("div.animetitle")?.text() ?: ""
return SEpisode.create().apply {
name = "$episodeName $episodeTitle"
episode_number = if (episodeName.contains("Episode ", true)) {
episodeName.substringAfter("Episode ").substringBefore(" ").toFloatOrNull() ?: 0F
} else { 0F }
if (element.selectFirst("span.filler") != null && preferences.getBoolean(PREF_MARK_FILLERS_KEY, PREF_MARK_FILLERS_DEFAULT)) {
scanlator = "Filler Episode"
}
date_upload = element.selectFirst("span.date")?.let { parseDate(it.text()) } ?: 0L
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
}
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val serverList = mutableListOf<Server>()
val script = document.selectFirst("script:containsData(videowrapper)")!!.data()
val frameRegex = """function (\w+).*?iframe src=\"(.*?)\"""".toRegex()
frameRegex.findAll(script).forEach {
val redirected = client.newCall(GET(baseUrl + it.groupValues[2])).execute().request.url.toString()
serverList.add(
Server(
redirected,
it.groupValues[1],
),
)
}
// Get videos
videoList.addAll(
serverList.parallelMap { server ->
runCatching {
val prefix = "${server.name} - "
val url = server.url
when {
url.contains("streamtape") -> {
val video = StreamTapeExtractor(client).videoFromUrl(url, server.name)
if (video == null) {
emptyList()
} else {
listOf(video)
}
}
url.contains("streamsb") -> {
StreamSBExtractor(client).videosFromUrl(url, headers = headers, prefix = prefix)
}
url.contains("vidstreaming") -> {
VidstreamingExtractor(client, json).videosFromUrl(url, prefix = prefix)
}
url.contains("mixdrop") -> {
MixDropExtractor(client).videoFromUrl(url, prefix = prefix)
}
url.contains("https://dood") -> {
DoodExtractor(client).videosFromUrl(url, quality = server.name)
}
url.contains("mp4upload") -> {
Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix)
}
else -> null
}
}.getOrNull()
}.filterNotNull().flatten(),
)
return videoList
}
override fun videoFromElement(element: Element): Video = throw Exception("Not Used")
override fun videoListSelector(): String = throw Exception("Not Used")
override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
// ============================= 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(quality) },
{ it.quality.contains(server) },
),
).reversed()
}
data class Server(
val url: String,
val name: String,
)
private fun parseDate(dateStr: String): Long {
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
.getOrNull() ?: 0L
}
private fun parseStatus(statusString: String): Int {
return when (statusString) {
"Ongoing" -> SAnime.ONGOING
"Completed" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("d MMMM yyyy", Locale.ENGLISH)
}
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 = "vstream"
private const val PREF_EPISODE_SORT_KEY = "preferred_episode_sorting"
private const val PREF_EPISODE_SORT_DEFAULT = true
private const val PREF_MARK_FILLERS_KEY = "mark_fillers"
private const val PREF_MARK_FILLERS_DEFAULT = true
}
// ============================== 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 = arrayOf("Vidstreaming", "Vidstreaming2", "Vidstreaming3", "Mixdrop", "StreamSB", "Streamtape", "Vidstreaming4", "Doodstream")
entryValues = arrayOf("vstream", "src2", "src", "mixdrop", "streamsb", "streamtape", "vplayer", "doodstream")
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_EPISODE_SORT_KEY
title = "Attempt episode sorting"
summary = """AnimeDao displays the episodes in either ascending or descending order,
| enable to attempt order or disable to set same as website.
""".trimMargin()
setDefaultValue(PREF_EPISODE_SORT_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
val new = newValue as Boolean
preferences.edit().putBoolean(key, new).commit()
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = PREF_MARK_FILLERS_KEY
title = "Mark filler episodes"
setDefaultValue(PREF_MARK_FILLERS_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putBoolean(key, newValue as Boolean).commit()
}
}.also(screen::addPreference)
}
}

View File

@ -1,261 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.animedao
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimeDaoFilters {
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 GenreFilter : CheckBoxFilterList(
"Genre",
AnimeDaoFiltersData.GENRE.map { CheckBoxVal(it.first, false) },
)
class RatingFilter : CheckBoxFilterList(
"Rating",
AnimeDaoFiltersData.RATING.map { CheckBoxVal(it.first, false) },
)
class LetterFilter : CheckBoxFilterList(
"Letter",
AnimeDaoFiltersData.LETTER.map { CheckBoxVal(it.first, false) },
)
class YearFilter : CheckBoxFilterList(
"Year",
AnimeDaoFiltersData.YEAR.map { CheckBoxVal(it.first, false) },
)
class StatusFilter : QueryPartFilter("Status", AnimeDaoFiltersData.STATUS)
class ScoreFilter : CheckBoxFilterList(
"Score",
AnimeDaoFiltersData.SCORE.map { CheckBoxVal(it.first, false) },
)
class OrderFilter : QueryPartFilter("Order", AnimeDaoFiltersData.ORDER)
val FILTER_LIST get() = AnimeFilterList(
GenreFilter(),
RatingFilter(),
LetterFilter(),
YearFilter(),
ScoreFilter(),
AnimeFilter.Separator(),
StatusFilter(),
OrderFilter(),
)
data class FilterSearchParams(
val genre: String = "",
val rating: String = "",
val letter: String = "",
val year: String = "",
val status: String = "",
val score: String = "",
val order: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.parseCheckbox<GenreFilter>(AnimeDaoFiltersData.GENRE, "genres"),
filters.parseCheckbox<RatingFilter>(AnimeDaoFiltersData.RATING, "ratings"),
filters.parseCheckbox<LetterFilter>(AnimeDaoFiltersData.LETTER, "letters"),
filters.parseCheckbox<YearFilter>(AnimeDaoFiltersData.YEAR, "years"),
filters.asQueryPart<StatusFilter>(),
filters.parseCheckbox<ScoreFilter>(AnimeDaoFiltersData.SCORE, "score"),
filters.asQueryPart<OrderFilter>(),
)
}
private object AnimeDaoFiltersData {
val GENRE = arrayOf(
Pair("Action", "Action"),
Pair("Adventure", "Adventure"),
Pair("Chinese", "Chinese"),
Pair("Comedy", "Comedy"),
Pair("Detective", "Detective"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Gourmet", "Gourmet"),
Pair("Harem", "Harem"),
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("Martial Arts", "Martial Arts"),
Pair("Mecha", "Mecha"),
Pair("Military", "Military"),
Pair("Music", "Music"),
Pair("Mystery", "Mystery"),
Pair("Mythology", "Mythology"),
Pair("Parody", "Parody"),
Pair("Psychological", "Psychological"),
Pair("Racing", "Racing"),
Pair("Reincarnation", "Reincarnation"),
Pair("Romance", "Romance"),
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("Shounen Ai", "Shounen Ai"),
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("Time Travel", "Time Travel"),
Pair("Vampire", "Vampire"),
Pair("Video Game", "Video Game"),
)
val RATING = arrayOf(
Pair("G - All Ages", "all-ages"),
Pair("PG - Children", "children"),
Pair("PG-13 - Teens 13 or older", "pg13"),
Pair("R - 17+ (violence & profanity)", "r17"),
Pair("R+ - Mild Nudity", "rplus"),
)
val LETTER = arrayOf(
Pair("A", "A"),
Pair("B", "B"),
Pair("C", "C"),
Pair("D", "D"),
Pair("E", "E"),
Pair("F", "F"),
Pair("G", "G"),
Pair("H", "H"),
Pair("I", "I"),
Pair("J", "J"),
Pair("K", "K"),
Pair("L", "L"),
Pair("M", "M"),
Pair("N", "N"),
Pair("O", "O"),
Pair("P", "P"),
Pair("Q", "Q"),
Pair("R", "R"),
Pair("S", "S"),
Pair("T", "T"),
Pair("U", "U"),
Pair("V", "V"),
Pair("W", "W"),
Pair("X", "X"),
Pair("Y", "Y"),
Pair("Z", "Z"),
)
val YEAR = arrayOf(
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("1990 - 1999", "1990"),
Pair("1980 - 1989", "1980"),
Pair("1970 - 1979", "1970"),
Pair("1960 - 1969", "1960"),
)
val STATUS = arrayOf(
Pair("Status", ""),
Pair("Ongoing", "Ongoing"),
Pair("Completed", "Completed"),
)
val SCORE = arrayOf(
Pair("Outstanding (9+)", "outstanding"),
Pair("Excellent (8+)", "excellent"),
Pair("Very Good (7+)", "verygood"),
Pair("Good (6+)", "good"),
Pair("Average (5+)", "average"),
Pair("Poor (4+)", "poor"),
Pair("Bad (3+)", "bad"),
Pair("Dont Watch (2+)", "dontwatch"),
)
val ORDER = arrayOf(
Pair("Order", ""),
Pair("Name A -> Z", "nameaz"),
Pair("Name Z -> A", "nameza"),
Pair("Date New -> Old", "datenewold"),
Pair("Date Old -> New", "dateoldnew"),
Pair("Score", "score"),
Pair("Most Watched", "mostwatched"),
)
}
}

View File

@ -1,161 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.animedao.extractors
import android.util.Base64
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import java.lang.Exception
import java.util.Locale
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@ExperimentalSerializationApi
class VidstreamingExtractor(private val client: OkHttpClient, private val json: Json) {
fun videosFromUrl(serverUrl: String, prefix: String = ""): List<Video> {
try {
var document = client.newCall(GET(serverUrl)).execute().asJsoup()
var newUrl = serverUrl
if (serverUrl.contains("/embedded/") && document.selectFirst("body")!!.childrenSize() == 1) {
newUrl = document.selectFirst("iframe")!!.attr("src")
document = client.newCall(
GET(newUrl),
).execute().asJsoup()
}
val script = document.selectFirst("script:containsData(playerInstance)")
if (script != null) {
val url = script.data().substringAfter("\"file\": '").substringBefore("'")
val headers = Headers.headersOf(
"Accept",
"*/*",
"Host",
url.toHttpUrl().host,
"Origin",
"https://${newUrl.toHttpUrl().host}",
)
val videoList = mutableListOf<Video>()
val masterPlaylist = client.newCall(GET(url, headers = headers)).execute().body.string()
if (masterPlaylist.contains("#EXT-X-STREAM-INF:")) {
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
.split("#EXT-X-STREAM-INF:").forEach {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",").substringBefore("\n") + "p"
var videoUrl = it.substringAfter("\n").substringBefore("\n")
if (!videoUrl.startsWith("http")) {
videoUrl = url.substringBeforeLast("/") + "/$videoUrl"
}
videoList.add(Video(videoUrl, "$prefix$quality (Vid-mp4 - Vidstreaming)", videoUrl))
}
} else {
videoList.add(Video(url, "$prefix (Vid-mp4 - Vidstreaming)", url))
}
return videoList
}
val iv = document.select("div.wrapper")
.attr("class").substringAfter("container-")
.filter { it.isDigit() }.toByteArray()
val secretKey = document.select("body[class]")
.attr("class").substringAfter("container-")
.filter { it.isDigit() }.toByteArray()
val decryptionKey = document.select("div.videocontent")
.attr("class").substringAfter("videocontent-")
.filter { it.isDigit() }.toByteArray()
val encryptAjaxParams = cryptoHandler(
document.select("script[data-value]")
.attr("data-value"),
iv,
secretKey,
false,
).substringAfter("&")
val httpUrl = newUrl.toHttpUrl()
val host = "https://" + httpUrl.host + "/"
val id = httpUrl.queryParameter("id") ?: throw Exception("error getting id")
val encryptedId = cryptoHandler(id, iv, secretKey)
val token = httpUrl.queryParameter("token")
val qualitySuffix = if (token != null) " (Vid-mp4 - Gogostream)" else " (Vid-mp4 - Vidstreaming)"
val jsonResponse = client.newCall(
GET(
"${host}encrypt-ajax.php?id=$encryptedId&$encryptAjaxParams&alias=$id",
Headers.headersOf(
"X-Requested-With",
"XMLHttpRequest",
),
),
).execute().body.string()
val data = json.decodeFromString<JsonObject>(jsonResponse)["data"]!!.jsonPrimitive.content
val decryptedData = cryptoHandler(data, iv, decryptionKey, false)
val videoList = mutableListOf<Video>()
val autoList = mutableListOf<Video>()
val array = json.decodeFromString<JsonObject>(decryptedData)["source"]!!.jsonArray
if (array.size == 1 && array[0].jsonObject["type"]!!.jsonPrimitive.content == "hls") {
val fileURL = array[0].jsonObject["file"].toString().trim('"')
val masterPlaylist = client.newCall(GET(fileURL)).execute().body.string()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:")
.split("#EXT-X-STREAM-INF:").forEach {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",").substringBefore("\n") + "p"
var videoUrl = it.substringAfter("\n").substringBefore("\n")
if (!videoUrl.startsWith("http")) {
videoUrl = fileURL.substringBeforeLast("/") + "/$videoUrl"
}
videoList.add(Video(videoUrl, prefix + quality + qualitySuffix, videoUrl))
}
} else {
array.forEach {
val label = it.jsonObject["label"].toString().lowercase(Locale.ROOT)
.trim('"').replace(" ", "")
val fileURL = it.jsonObject["file"].toString().trim('"')
val videoHeaders = Headers.headersOf("Referer", newUrl)
if (label == "auto") {
autoList.add(
Video(
fileURL,
prefix + label + qualitySuffix,
fileURL,
headers = videoHeaders,
),
)
} else {
videoList.add(Video(fileURL, prefix + label + qualitySuffix, fileURL, headers = videoHeaders))
}
}
}
return videoList.sortedByDescending {
it.quality.substringBefore(qualitySuffix).substringBefore("p").toIntOrNull() ?: -1
} + autoList
} catch (e: Exception) {
return emptyList()
}
}
private fun cryptoHandler(
string: String,
iv: ByteArray,
secretKeyString: ByteArray,
encrypt: Boolean = true,
): String {
val ivParameterSpec = IvParameterSpec(iv)
val secretKey = SecretKeySpec(secretKeyString, "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
return if (!encrypt) {
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)
String(cipher.doFinal(Base64.decode(string, Base64.DEFAULT)))
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
Base64.encodeToString(cipher.doFinal(string.toByteArray()), Base64.NO_WRAP)
}
}
}