Add Extension: AllAnime (#1149)
* Add Extractor * Use lib extractors * Use lib okru extractor
This commit is contained in:
2
src/en/allanime/AndroidManifest.xml
Normal file
2
src/en/allanime/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.animeextension" />
|
21
src/en/allanime/build.gradle
Normal file
21
src/en/allanime/build.gradle
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'AllAnime'
|
||||||
|
pkgNameSuffix = 'en.allanime'
|
||||||
|
extClass = '.AllAnime'
|
||||||
|
extVersionCode = 1
|
||||||
|
libVersion = '13'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(':lib-streamsb-extractor'))
|
||||||
|
implementation(project(':lib-dood-extractor'))
|
||||||
|
implementation(project(':lib-okru-extractor'))
|
||||||
|
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/en/allanime/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/en/allanime/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
src/en/allanime/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/en/allanime/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
src/en/allanime/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/en/allanime/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
src/en/allanime/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/en/allanime/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
src/en/allanime/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/en/allanime/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
src/en/allanime/res/web_hi_res_512.png
Normal file
BIN
src/en/allanime/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
@ -0,0 +1,486 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.MultiSelectListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.allanime.extractors.AllAnimeExtractor
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.allanime.extractors.Mp4uploadExtractor
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.allanime.extractors.StreamlareExtractor
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.allanime.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.AnimeHttpSource
|
||||||
|
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class AllAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||||
|
|
||||||
|
override val name = "AllAnime"
|
||||||
|
|
||||||
|
override val baseUrl = "https://allanime.site"
|
||||||
|
|
||||||
|
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 {
|
||||||
|
val variables = """{"type":"anime","size":30,"dateRange":7,"page":$page,"allowAdult":false,"allowUnknown":false}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"6f6fe5663e3e9ea60bdfa693f878499badab83e7f18b56acdba5f8e8662002aa"}}"""
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
return GET("$baseUrl/allanimeapi?variables=$variables&extensions=$extensions", headers = headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||||
|
val parsed = json.decodeFromString<PopularResult>(response.body!!.string())
|
||||||
|
val animeList = mutableListOf<SAnime>()
|
||||||
|
|
||||||
|
val titleStyle = preferences.getString("preferred_title_style", "romaji")!!
|
||||||
|
|
||||||
|
parsed.data.queryPopular.recommendations.forEach {
|
||||||
|
if (it.anyCard != null) {
|
||||||
|
animeList.add(
|
||||||
|
SAnime.create().apply {
|
||||||
|
title = when (titleStyle) {
|
||||||
|
"romaji" -> it.anyCard.name
|
||||||
|
"eng" -> it.anyCard.englishName ?: it.anyCard.name
|
||||||
|
else -> it.anyCard.nativeName ?: it.anyCard.name
|
||||||
|
}
|
||||||
|
thumbnail_url = it.anyCard.thumbnail
|
||||||
|
url = it.anyCard._id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimesPage(animeList, animeList.size == 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Latest ===============================
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val variables = """{"search":{"allowAdult":false,"allowUnknown":false},"limit":26,"page":$page,"translationType":"${preferences.getString("preferred_sub", "sub")!!}","countryOrigin":"ALL"}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"9c7a8bc1e095a34f2972699e8105f7aaf9082c6e1ccd56eab99c2f1a971152c6"}}"""
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
return GET("$baseUrl/allanimeapi?variables=$variables&extensions=$extensions", headers = headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||||
|
return ParseAnime(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Search ===============================
|
||||||
|
|
||||||
|
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||||
|
val params = AllAnimeFilters.getSearchParameters(filters)
|
||||||
|
return client.newCall(searchAnimeRequest(page, query, params))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
searchAnimeParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("not used")
|
||||||
|
|
||||||
|
private fun searchAnimeRequest(page: Int, query: String, filters: AllAnimeFilters.FilterSearchParams): Request {
|
||||||
|
return if (query.isNotEmpty()) {
|
||||||
|
val variables = """{"search":{"query":"$query","allowAdult":false,"allowUnknown":false},"limit":26,"page":$page,"translationType":"${preferences.getString("preferred_sub", "sub")!!}","countryOrigin":"ALL"}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"9c7a8bc1e095a34f2972699e8105f7aaf9082c6e1ccd56eab99c2f1a971152c6"}}"""
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
GET("$baseUrl/allanimeapi?variables=$variables&extensions=$extensions", headers = headers)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val seasonString = if (filters.season == "all") "" else ""","season":"${filters.season}""""
|
||||||
|
val yearString = if (filters.releaseYear == "all") "" else ""","year":${filters.releaseYear}"""
|
||||||
|
val genresString = if (filters.genres == "all") "" else ""","genres":${filters.genres},"excludeGenres":[]"""
|
||||||
|
val typesString = if (filters.types == "all") "" else ""","types":${filters.types}"""
|
||||||
|
val sortByString = if (filters.sortBy == "update") "" else ""","sortBy":"${filters.sortBy}""""
|
||||||
|
|
||||||
|
var variables = """{"search":{"allowAdult":false,"allowUnknown":false$seasonString$yearString$genresString$typesString$sortByString"""
|
||||||
|
variables += """},"limit":26,"page":$page,"translationType":"${preferences.getString("preferred_sub", "sub")!!}","countryOrigin":"${filters.origin}"}"""
|
||||||
|
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"9c7a8bc1e095a34f2972699e8105f7aaf9082c6e1ccd56eab99c2f1a971152c6"}}"""
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
GET("$baseUrl/allanimeapi?variables=$variables&extensions=$extensions", headers = headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||||
|
return ParseAnime(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList(): AnimeFilterList = AllAnimeFilters.filterList
|
||||||
|
|
||||||
|
// =========================== Anime Details ============================
|
||||||
|
|
||||||
|
override fun animeDetailsRequest(anime: SAnime): Request {
|
||||||
|
val variables = """{"_id":"${anime.url}"}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"afcdaedfd46f36448916b5f7db84d2bdbb72fded428ad8755179a03845c57b96"}}"""
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
return GET("$baseUrl/allanimeapi?variables=$variables&extensions=$extensions", headers = headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun animeDetailsParse(response: Response): SAnime {
|
||||||
|
val show = json.decodeFromString<SeriesResult>(response.body!!.string()).data.show
|
||||||
|
val anime = SAnime.create()
|
||||||
|
|
||||||
|
anime.title = show.name
|
||||||
|
|
||||||
|
anime.description = Jsoup.parse(
|
||||||
|
show.description?.replace("<br>", "br2n") ?: ""
|
||||||
|
).text().replace("br2n", "\n") + "\n\n"
|
||||||
|
anime.description += "Type: ${show.type ?: "Unknown"}"
|
||||||
|
anime.description += "\nAired: ${show.season?.quarter ?: "-"} ${show.season?.year ?: "-"}"
|
||||||
|
anime.description += "\nScore: ${show.score ?: "-"}★"
|
||||||
|
|
||||||
|
anime.genre = show.genres?.joinToString(separator = ", ") ?: ""
|
||||||
|
anime.status = parseStatus(show.status)
|
||||||
|
if (show.studios?.isNotEmpty() == true) {
|
||||||
|
anime.author = show.studios.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
return anime
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Episodes ==============================
|
||||||
|
|
||||||
|
override fun episodeListRequest(anime: SAnime): Request {
|
||||||
|
val variables = """{"_id":"${anime.url}"}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"afcdaedfd46f36448916b5f7db84d2bdbb72fded428ad8755179a03845c57b96"}}"""
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
return GET("$baseUrl/allanimeapi?variables=$variables&extensions=$extensions", headers = headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
|
val medias = json.decodeFromString<SeriesResult>(response.body!!.string())
|
||||||
|
val episodeList = mutableListOf<SEpisode>()
|
||||||
|
|
||||||
|
val subOrDub = preferences.getString("preferred_sub", "sub")!!
|
||||||
|
|
||||||
|
if (subOrDub == "sub") {
|
||||||
|
for (ep in medias.data.show.availableEpisodesDetail.sub!!) {
|
||||||
|
val episode = SEpisode.create()
|
||||||
|
episode.episode_number = ep.toFloatOrNull() ?: 0F
|
||||||
|
val numName = ep.toIntOrNull() ?: (ep.toFloatOrNull() ?: "1")
|
||||||
|
episode.name = "Episode $numName (sub)"
|
||||||
|
|
||||||
|
val variables = """{"showId":"${medias.data.show._id}","translationType":"sub","episodeString":"$ep"}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"3933a4a68bc80c46e25b7b8b3f563df1416b7b583595e5e5bfc67c01bd791df8"}}"""
|
||||||
|
episode.setUrlWithoutDomain("/allanimeapi?variables=$variables&extensions=$extensions")
|
||||||
|
episodeList.add(episode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (ep in medias.data.show.availableEpisodesDetail.dub!!) {
|
||||||
|
val episode = SEpisode.create()
|
||||||
|
episode.episode_number = ep.toFloatOrNull() ?: 0F
|
||||||
|
val numName = ep.toIntOrNull() ?: (ep.toFloatOrNull() ?: "1")
|
||||||
|
episode.name = "Episode $numName (dub)"
|
||||||
|
|
||||||
|
val variables = """{"showId":"${medias.data.show._id}","translationType":"dub","episodeString":"$ep"}"""
|
||||||
|
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"3933a4a68bc80c46e25b7b8b3f563df1416b7b583595e5e5bfc67c01bd791df8"}}"""
|
||||||
|
episode.setUrlWithoutDomain("/allanimeapi?variables=$variables&extensions=$extensions")
|
||||||
|
episodeList.add(episode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodeList
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ Video Links =============================
|
||||||
|
|
||||||
|
override fun videoListRequest(episode: SEpisode): Request {
|
||||||
|
val headers = headers.newBuilder()
|
||||||
|
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0")
|
||||||
|
.build()
|
||||||
|
return GET(baseUrl + episode.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
|
val body = response.body!!.string()
|
||||||
|
|
||||||
|
val videoJson = json.decodeFromString<EpisodeResult>(body)
|
||||||
|
val videoList = mutableListOf<Pair<Video, Float>>()
|
||||||
|
|
||||||
|
val altHosterSelection = preferences.getStringSet(
|
||||||
|
"alt_hoster_selection",
|
||||||
|
setOf("player", "vidstreaming", "okru", "mp4upload", "streamlare", "doodstream")
|
||||||
|
)!!
|
||||||
|
|
||||||
|
val hosterSelection = preferences.getStringSet(
|
||||||
|
"hoster_selection",
|
||||||
|
setOf("default", "luf-mp4", "si-hls", "s-mp4", "ac-hls")
|
||||||
|
)!!
|
||||||
|
|
||||||
|
for (video in videoJson.data.episode.sourceUrls) {
|
||||||
|
when {
|
||||||
|
video.sourceUrl.startsWith("/apivtwo/") && (
|
||||||
|
(hosterSelection.contains("default") && video.sourceName.lowercase().contains("default")) ||
|
||||||
|
(hosterSelection.contains("luf-mp4") && video.sourceName.lowercase().contains("luf-mp4")) ||
|
||||||
|
(hosterSelection.contains("si-hls") && video.sourceName.lowercase().contains("si-hls")) ||
|
||||||
|
(hosterSelection.contains("s-mp4") && video.sourceName.lowercase().contains("s-mp4")) ||
|
||||||
|
(hosterSelection.contains("ac-hls") && video.sourceName.lowercase().contains("ac-hls")) ||
|
||||||
|
(hosterSelection.contains("uv-mp4") && video.sourceName.lowercase().contains("uv-mp4")) ||
|
||||||
|
(hosterSelection.contains("pn-hls") && video.sourceName.lowercase().contains("pn-hls"))
|
||||||
|
) -> {
|
||||||
|
val extractor = AllAnimeExtractor(client)
|
||||||
|
val videos = runCatching {
|
||||||
|
extractor.videoFromUrl(video.sourceUrl, video.sourceName)
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("player") && video.type == "player" -> {
|
||||||
|
videoList.add(
|
||||||
|
Pair(
|
||||||
|
Video(
|
||||||
|
video.sourceUrl,
|
||||||
|
"Original (player ${video.sourceName})",
|
||||||
|
video.sourceUrl
|
||||||
|
),
|
||||||
|
video.priority
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("streamsb") && video.sourceUrl.contains("streamsb") -> {
|
||||||
|
val extractor = StreamSBExtractor(client)
|
||||||
|
val videos = runCatching {
|
||||||
|
extractor.videosFromUrl(video.sourceUrl, headers)
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("vidstreaming") && (video.sourceUrl.contains("vidstreaming") || video.sourceUrl.contains("https://gogo")) -> {
|
||||||
|
val extractor = VidstreamingExtractor(client, json)
|
||||||
|
val videos = runCatching {
|
||||||
|
extractor.videosFromUrl(video.sourceUrl.replace(Regex("^//"), "https://"))
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("doodstream") && video.sourceUrl.contains("dood") -> {
|
||||||
|
val extractor = DoodExtractor(client)
|
||||||
|
val videos = runCatching {
|
||||||
|
extractor.videosFromUrl(video.sourceUrl)
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("okru") && video.sourceUrl.contains("ok.ru") -> {
|
||||||
|
val extractor = OkruExtractor(client)
|
||||||
|
val videos = runCatching {
|
||||||
|
extractor.videosFromUrl(video.sourceUrl)
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("mp4upload") && video.sourceUrl.contains("mp4upload.com") -> {
|
||||||
|
val headers = headers.newBuilder().set("referer", "https://mp4upload.com/").build()
|
||||||
|
val videos = runCatching {
|
||||||
|
Mp4uploadExtractor(client).getVideoFromUrl(video.sourceUrl, headers)
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altHosterSelection.contains("streamlare") && video.sourceUrl.contains("streamlare.com") -> {
|
||||||
|
val extractor = StreamlareExtractor(client)
|
||||||
|
val videos = runCatching {
|
||||||
|
extractor.videosFromUrl(video.sourceUrl)
|
||||||
|
}.getOrNull() ?: emptyList()
|
||||||
|
for (v in videos) {
|
||||||
|
videoList.add(Pair(v, video.priority))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prioritySort(videoList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================= Utilities ==============================
|
||||||
|
|
||||||
|
private fun prioritySort(pList: List<Pair<Video, Float>>): List<Video> {
|
||||||
|
val prefServer = preferences.getString("preferred_server", "site_default")!!
|
||||||
|
val quality = preferences.getString("preferred_quality", "1080")!!
|
||||||
|
val subOrDub = preferences.getString("preferred_sub", "sub")!!
|
||||||
|
|
||||||
|
return pList.sortedWith(
|
||||||
|
compareBy(
|
||||||
|
{ if (prefServer == "site_default") it.second else it.first.quality.lowercase().contains(prefServer) },
|
||||||
|
{ it.first.quality.lowercase().contains(quality) },
|
||||||
|
{ it.first.quality.lowercase().contains(subOrDub) }
|
||||||
|
)
|
||||||
|
).reversed().map { t -> t.first }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStatus(string: String?): Int {
|
||||||
|
return when (string) {
|
||||||
|
"Releasing" -> SAnime.ONGOING
|
||||||
|
"Finished" -> SAnime.COMPLETED
|
||||||
|
"Not Yet Released" -> SAnime.ONGOING
|
||||||
|
else -> SAnime.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ParseAnime(response: Response): AnimesPage {
|
||||||
|
val parsed = json.decodeFromString<SearchResult>(response.body!!.string())
|
||||||
|
val titleStyle = preferences.getString("preferred_title_style", "romaji")!!
|
||||||
|
|
||||||
|
val animeList = parsed.data.shows.edges.map { ani ->
|
||||||
|
SAnime.create().apply {
|
||||||
|
title = when (titleStyle) {
|
||||||
|
"romaji" -> ani.name
|
||||||
|
"eng" -> ani.englishName ?: ani.name
|
||||||
|
else -> ani.nativeName ?: ani.name
|
||||||
|
}
|
||||||
|
thumbnail_url = ani.thumbnail
|
||||||
|
url = ani._id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimesPage(animeList, animeList.size == 26)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
val serverPref = ListPreference(screen.context).apply {
|
||||||
|
key = "preferred_server"
|
||||||
|
title = "Preferred Video Server"
|
||||||
|
entries = arrayOf("Site Default", "Luf-mp4", "Vid-mp4", "Yt-mp4", "Ok.ru", "Mp4upload", "Sl-mp4", "Uv-mp4", "S-mp4", "Ac-Hls", "Default")
|
||||||
|
entryValues = arrayOf("site_default", "luf-mp4", "vid-mp4", "yt-mp4", "okru", "mp4upload", "sl-mp4", "uv-mp4", "s-mp4", "ac-hls", "default")
|
||||||
|
setDefaultValue("site_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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val hostSelection = MultiSelectListPreference(screen.context).apply {
|
||||||
|
key = "hoster_selection"
|
||||||
|
title = "Enable/Disable Hosts"
|
||||||
|
entries = arrayOf("Default", "Luf-mp4", "Si-Hls", "S-mp4", "Ac-Hls", "Uv-mp4", "Pn-Hls")
|
||||||
|
entryValues = arrayOf("default", "luf-mp4", "si-hls", "s-mp4", "ac-hls", "uv-mp4", "pn-hls")
|
||||||
|
setDefaultValue(setOf("default", "luf-mp4", "si-hls", "s-mp4", "ac-hls"))
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val altHostSelection = MultiSelectListPreference(screen.context).apply {
|
||||||
|
key = "alt_hoster_selection"
|
||||||
|
title = "Enable/Disable Alternative Hosts"
|
||||||
|
entries = arrayOf("Direct Player", "Vidstreaming/Gogo", "Ok.ru", "Mp4upload.com", "Streamlare.com", "StreamSB", "Doodstream")
|
||||||
|
entryValues = arrayOf("player", "vidstreaming", "okru", "mp4upload", "streamlare", "streamsb", "doodstream")
|
||||||
|
setDefaultValue(setOf("player", "vidstreaming", "okru", "mp4upload", "streamlare", "doodstream"))
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val videoQualityPref = ListPreference(screen.context).apply {
|
||||||
|
key = "preferred_quality"
|
||||||
|
title = "Preferred quality"
|
||||||
|
entries = arrayOf("1080p", "720p", "480p", "360p", "240p", "80p")
|
||||||
|
entryValues = arrayOf("1080", "720", "480", "360", "240", "80")
|
||||||
|
setDefaultValue("1080")
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val titleStylePref = ListPreference(screen.context).apply {
|
||||||
|
key = "preferred_title_style"
|
||||||
|
title = "Preferred Title Style"
|
||||||
|
entries = arrayOf("Romaji", "English", "Native")
|
||||||
|
entryValues = arrayOf("romaji", "eng", "native")
|
||||||
|
setDefaultValue("romaji")
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val subPref = ListPreference(screen.context).apply {
|
||||||
|
key = "preferred_sub"
|
||||||
|
title = "Prefer subs or dubs?"
|
||||||
|
entries = arrayOf("Subs", "Dubs")
|
||||||
|
entryValues = arrayOf("sub", "dub")
|
||||||
|
setDefaultValue("sub")
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.addPreference(serverPref)
|
||||||
|
screen.addPreference(hostSelection)
|
||||||
|
screen.addPreference(altHostSelection)
|
||||||
|
screen.addPreference(videoQualityPref)
|
||||||
|
screen.addPreference(titleStylePref)
|
||||||
|
screen.addPreference(subPref)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
|
||||||
|
object AllAnimeFilters {
|
||||||
|
|
||||||
|
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.getFirst<R>() 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>>
|
||||||
|
): String {
|
||||||
|
return (this.getFirst<R>() as CheckBoxFilterList).state
|
||||||
|
.mapNotNull { checkbox ->
|
||||||
|
if (checkbox.state)
|
||||||
|
options.find { it.first == checkbox.name }!!.second
|
||||||
|
else null
|
||||||
|
}.joinToString("\",\"").let {
|
||||||
|
if (it.isBlank()) "all"
|
||||||
|
else "[\"$it\"]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OriginFilter : QueryPartFilter("Origin", AllAnimeFiltersData.origin)
|
||||||
|
class SeasonFilter : QueryPartFilter("Season", AllAnimeFiltersData.seasons)
|
||||||
|
class ReleaseYearFilter : QueryPartFilter("Released at", AllAnimeFiltersData.years)
|
||||||
|
class SortByFilter : QueryPartFilter("Sort By", AllAnimeFiltersData.sortBy)
|
||||||
|
|
||||||
|
class TypesFilter : CheckBoxFilterList(
|
||||||
|
"Types",
|
||||||
|
AllAnimeFiltersData.types.map { CheckBoxVal(it.first, false) }
|
||||||
|
)
|
||||||
|
|
||||||
|
class GenresFilter : CheckBoxFilterList(
|
||||||
|
"Genres",
|
||||||
|
AllAnimeFiltersData.genres.map { CheckBoxVal(it.first, false) }
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = AnimeFilterList(
|
||||||
|
OriginFilter(),
|
||||||
|
SeasonFilter(),
|
||||||
|
ReleaseYearFilter(),
|
||||||
|
SortByFilter(),
|
||||||
|
AnimeFilter.Separator(),
|
||||||
|
TypesFilter(),
|
||||||
|
GenresFilter()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FilterSearchParams(
|
||||||
|
val origin: String = "",
|
||||||
|
val season: String = "",
|
||||||
|
val releaseYear: String = "",
|
||||||
|
val sortBy: String = "",
|
||||||
|
val types: String = "",
|
||||||
|
val genres: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||||
|
if (filters.isEmpty()) return FilterSearchParams()
|
||||||
|
|
||||||
|
return FilterSearchParams(
|
||||||
|
filters.asQueryPart<OriginFilter>(),
|
||||||
|
filters.asQueryPart<SeasonFilter>(),
|
||||||
|
filters.asQueryPart<ReleaseYearFilter>(),
|
||||||
|
filters.asQueryPart<SortByFilter>(),
|
||||||
|
filters.parseCheckbox<TypesFilter>(AllAnimeFiltersData.types),
|
||||||
|
filters.parseCheckbox<GenresFilter>(AllAnimeFiltersData.genres),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private object AllAnimeFiltersData {
|
||||||
|
val all = Pair("All", "all")
|
||||||
|
|
||||||
|
val origin = arrayOf(
|
||||||
|
Pair("All", "ALL"),
|
||||||
|
Pair("Japan", "JP"),
|
||||||
|
Pair("China", "CN"),
|
||||||
|
Pair("Korea", "KR")
|
||||||
|
)
|
||||||
|
|
||||||
|
val seasons = arrayOf(
|
||||||
|
all,
|
||||||
|
Pair("Winter", "Winter"),
|
||||||
|
Pair("Spring", "Spring"),
|
||||||
|
Pair("Summer", "Summer"),
|
||||||
|
Pair("Fall", "Fall")
|
||||||
|
)
|
||||||
|
|
||||||
|
val years = arrayOf(
|
||||||
|
all,
|
||||||
|
Pair("2024", "2024"),
|
||||||
|
Pair("2023", "2023"),
|
||||||
|
Pair("2022", "2022"),
|
||||||
|
Pair("2021", "2021"),
|
||||||
|
Pair("2020", "2020"),
|
||||||
|
Pair("2019", "2019"),
|
||||||
|
Pair("2018", "2018"),
|
||||||
|
Pair("2017", "2017"),
|
||||||
|
Pair("2016", "2016"),
|
||||||
|
Pair("2015", "2015"),
|
||||||
|
Pair("2014", "2014"),
|
||||||
|
Pair("2013", "2013"),
|
||||||
|
Pair("2012", "2012"),
|
||||||
|
Pair("2011", "2011"),
|
||||||
|
Pair("2010", "2010"),
|
||||||
|
Pair("2009", "2009"),
|
||||||
|
Pair("2008", "2008"),
|
||||||
|
Pair("2007", "2007"),
|
||||||
|
Pair("2006", "2006"),
|
||||||
|
Pair("2005", "2005"),
|
||||||
|
Pair("2004", "2004"),
|
||||||
|
Pair("2003", "2003"),
|
||||||
|
Pair("2002", "2002"),
|
||||||
|
Pair("2001", "2001"),
|
||||||
|
Pair("2000", "2000"),
|
||||||
|
Pair("1999", "1999"),
|
||||||
|
Pair("1998", "1998"),
|
||||||
|
Pair("1997", "1997"),
|
||||||
|
Pair("1996", "1996"),
|
||||||
|
Pair("1995", "1995"),
|
||||||
|
Pair("1994", "1994"),
|
||||||
|
Pair("1993", "1993"),
|
||||||
|
Pair("1992", "1992"),
|
||||||
|
Pair("1991", "1991"),
|
||||||
|
Pair("1990", "1990"),
|
||||||
|
Pair("1989", "1989"),
|
||||||
|
Pair("1988", "1988"),
|
||||||
|
Pair("1987", "1987"),
|
||||||
|
Pair("1986", "1986"),
|
||||||
|
Pair("1985", "1985"),
|
||||||
|
Pair("1984", "1984"),
|
||||||
|
Pair("1983", "1983"),
|
||||||
|
Pair("1982", "1982"),
|
||||||
|
Pair("1981", "1981"),
|
||||||
|
Pair("1980", "1980"),
|
||||||
|
Pair("1979", "1979"),
|
||||||
|
Pair("1978", "1978"),
|
||||||
|
Pair("1977", "1977"),
|
||||||
|
Pair("1976", "1976"),
|
||||||
|
Pair("1975", "1975")
|
||||||
|
)
|
||||||
|
|
||||||
|
val sortBy = arrayOf(
|
||||||
|
Pair("Update", "update"),
|
||||||
|
Pair("Name Asc", "Name_ASC"),
|
||||||
|
Pair("Name Desc", "Name_DESC"),
|
||||||
|
Pair("Ratings", "Top")
|
||||||
|
)
|
||||||
|
|
||||||
|
val types = arrayOf(
|
||||||
|
Pair("Movie", "Movie"),
|
||||||
|
Pair("ONA", "ONA"),
|
||||||
|
Pair("OVA", "OVA"),
|
||||||
|
Pair("Special", "Special"),
|
||||||
|
Pair("TV", "TV"),
|
||||||
|
Pair("Unknown", "Unknown"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val genres = arrayOf(
|
||||||
|
Pair("Action", "Action"),
|
||||||
|
Pair("Adventure", "Adventure"),
|
||||||
|
Pair("Cars", "Cars"),
|
||||||
|
Pair("Comedy", "Comedy"),
|
||||||
|
Pair("Dementia", "Dementia"),
|
||||||
|
Pair("Demons", "Demons"),
|
||||||
|
Pair("Drama", "Drama"),
|
||||||
|
Pair("Ecchi", "Ecchi"),
|
||||||
|
Pair("Fantasy", "Fantasy"),
|
||||||
|
Pair("Game", "Game"),
|
||||||
|
Pair("Harem", "Harem"),
|
||||||
|
Pair("Historical", "Historical"),
|
||||||
|
Pair("Horror", "Horror"),
|
||||||
|
Pair("Isekai", "Isekai"),
|
||||||
|
Pair("Josei", "Josei"),
|
||||||
|
Pair("Kids", "Kids"),
|
||||||
|
Pair("Magic", "Magic"),
|
||||||
|
Pair("Martial Arts", "Martial Arts"),
|
||||||
|
Pair("Mecha", "Mecha"),
|
||||||
|
Pair("Military", "Military"),
|
||||||
|
Pair("Music", "Music"),
|
||||||
|
Pair("Mystery", "Mystery"),
|
||||||
|
Pair("Parody", "Parody"),
|
||||||
|
Pair("Police", "Police"),
|
||||||
|
Pair("Psychological", "Psychological"),
|
||||||
|
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("Super Power", "Super Power"),
|
||||||
|
Pair("Supernatural", "Supernatural"),
|
||||||
|
Pair("Thriller", "Thriller"),
|
||||||
|
Pair("Unknown", "Unknown"),
|
||||||
|
Pair("Vampire", "Vampire"),
|
||||||
|
Pair("Yaoi", "Yaoi"),
|
||||||
|
Pair("Yuri", "Yuri")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PopularResult(
|
||||||
|
val data: PopularResultData
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class PopularResultData(
|
||||||
|
val queryPopular: QueryPopularData
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class QueryPopularData(
|
||||||
|
val recommendations: List<Recommendation>
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Recommendation(
|
||||||
|
val anyCard: Card? = null
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Card(
|
||||||
|
val _id: String,
|
||||||
|
val name: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val englishName: String? = null,
|
||||||
|
val nativeName: String? = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchResult(
|
||||||
|
val data: SearchResultData
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class SearchResultData(
|
||||||
|
val shows: SearchResultShows
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class SearchResultShows(
|
||||||
|
val edges: List<SearchResultEdge>
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class SearchResultEdge(
|
||||||
|
val _id: String,
|
||||||
|
val name: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val englishName: String? = null,
|
||||||
|
val nativeName: String? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SeriesResult(
|
||||||
|
val data: DataShow
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class DataShow(
|
||||||
|
val show: SeriesShows
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class SeriesShows(
|
||||||
|
val _id: String,
|
||||||
|
val name: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val genres: List<String>? = null,
|
||||||
|
val studios: List<String>? = null,
|
||||||
|
val season: AirSeason? = null,
|
||||||
|
val status: String? = null,
|
||||||
|
val score: Float? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val availableEpisodesDetail: AvailableEps
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class AvailableEps(
|
||||||
|
val sub: List<String>? = null,
|
||||||
|
val dub: List<String>? = null,
|
||||||
|
)
|
||||||
|
@Serializable
|
||||||
|
data class AirSeason(
|
||||||
|
val quarter: String,
|
||||||
|
val year: Int
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeResult(
|
||||||
|
val data: DataEpisode
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class DataEpisode(
|
||||||
|
val episode: Episode
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Episode(
|
||||||
|
val sourceUrls: List<SourceUrl>
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class SourceUrl(
|
||||||
|
val sourceUrl: String,
|
||||||
|
val type: String,
|
||||||
|
val sourceName: String,
|
||||||
|
val priority: Float = 0F
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime.extractors
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Track
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoLink(
|
||||||
|
val links: List<Link>
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Link(
|
||||||
|
val link: String,
|
||||||
|
val hls: Boolean? = null,
|
||||||
|
val mp4: Boolean? = null,
|
||||||
|
val resolutionStr: String,
|
||||||
|
val subtitles: List<Subtitles>? = null
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Subtitles(
|
||||||
|
val lang: String,
|
||||||
|
val src: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AllAnimeExtractor(private val client: OkHttpClient) {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
fun videoFromUrl(url: String, name: String): List<Video> {
|
||||||
|
val videoList = mutableListOf<Video>()
|
||||||
|
|
||||||
|
val resp = client.newCall(
|
||||||
|
GET("https://blog.allanime.pro" + url.replace("/clock?", "/clock.json?"))
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
if (resp.code != 200) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = resp.body!!.string()
|
||||||
|
val linkJson = json.decodeFromString<VideoLink>(body)
|
||||||
|
|
||||||
|
for (link in linkJson.links) {
|
||||||
|
val subtitles = mutableListOf<Track>()
|
||||||
|
if (!link.subtitles.isNullOrEmpty()) {
|
||||||
|
try {
|
||||||
|
for (sub in link.subtitles) {
|
||||||
|
subtitles.add(Track(sub.src, sub.lang))
|
||||||
|
}
|
||||||
|
} catch (_: Error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link.mp4 == true) {
|
||||||
|
try {
|
||||||
|
videoList.add(
|
||||||
|
Video(
|
||||||
|
link.link,
|
||||||
|
"Original ($name - ${link.resolutionStr})",
|
||||||
|
link.link,
|
||||||
|
subtitleTracks = subtitles
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (_: Error) {
|
||||||
|
videoList.add(
|
||||||
|
Video(
|
||||||
|
link.link,
|
||||||
|
"Original ($name - ${link.resolutionStr})",
|
||||||
|
link.link
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (link.hls == true) {
|
||||||
|
val newClient = OkHttpClient()
|
||||||
|
val resp = runCatching {
|
||||||
|
newClient.newCall(
|
||||||
|
GET(link.link, headers = Headers.headersOf("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0"))
|
||||||
|
).execute()
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (resp != null && resp.code == 200) {
|
||||||
|
val masterPlaylist = resp.body!!.string()
|
||||||
|
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
|
||||||
|
.forEach {
|
||||||
|
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p ($name - ${link.resolutionStr})"
|
||||||
|
var videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||||
|
|
||||||
|
if (!videoUrl.startsWith("http")) {
|
||||||
|
videoUrl = resp.request.url.toString().substringBeforeLast("/") + "/$videoUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subtitles))
|
||||||
|
} catch (_: Error) {
|
||||||
|
videoList.add(Video(videoUrl, quality, videoUrl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return videoList
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime.extractors
|
||||||
|
|
||||||
|
import dev.datlag.jsunpacker.JsUnpacker
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
class Mp4uploadExtractor(private val client: OkHttpClient) {
|
||||||
|
fun getVideoFromUrl(url: String, headers: Headers): List<Video> {
|
||||||
|
val body = client.newCall(GET(url, headers = headers)).execute().body!!.string()
|
||||||
|
|
||||||
|
val packed = body.substringAfter("<script type='text/javascript'>eval(function(p,a,c,k,e,d)")
|
||||||
|
.substringBefore("</script>")
|
||||||
|
val unpacked = JsUnpacker.unpackAndCombine("eval(function(p,a,c,k,e,d)" + packed) ?: return emptyList()
|
||||||
|
val videoUrl = unpacked.substringAfter("player.src(\"").substringBefore("\");")
|
||||||
|
return listOf(
|
||||||
|
Video(videoUrl, "Original (Mp4upload)", videoUrl, headers = Headers.headersOf("Referer", "https://www.mp4upload.com/"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime.extractors
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
|
class StreamlareExtractor(private val client: OkHttpClient) {
|
||||||
|
fun videosFromUrl(url: String): List<Video> {
|
||||||
|
val id = url.split("/").last()
|
||||||
|
val videoList = mutableListOf<Video>()
|
||||||
|
val playlist = client.newCall(
|
||||||
|
POST(
|
||||||
|
"https://slwatch.co/api/video/stream/get",
|
||||||
|
body = "{\"id\":\"$id\"}"
|
||||||
|
.toRequestBody("application/json".toMediaType())
|
||||||
|
)
|
||||||
|
).execute().body!!.string()
|
||||||
|
|
||||||
|
playlist.substringAfter("\"label\":\"").split("\"label\":\"").forEach {
|
||||||
|
val quality = it.substringAfter("\"label\":\"").substringBefore("\",") + " (Sl-mp4)"
|
||||||
|
val token = it.substringAfter("\"file\":\"https:\\/\\/larecontent.com\\/video?token=")
|
||||||
|
.substringBefore("\",")
|
||||||
|
val response = client.newCall(POST("https://larecontent.com/video?token=$token")).execute()
|
||||||
|
val videoUrl = response.request.url.toString()
|
||||||
|
videoList.add(Video(videoUrl, quality, videoUrl))
|
||||||
|
}
|
||||||
|
return videoList
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.en.allanime.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): List<Video> {
|
||||||
|
try {
|
||||||
|
val document = client.newCall(GET(serverUrl)).execute().asJsoup()
|
||||||
|
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 = serverUrl.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, 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", serverUrl)
|
||||||
|
if (label == "auto") autoList.add(
|
||||||
|
Video(
|
||||||
|
fileURL,
|
||||||
|
label + qualitySuffix,
|
||||||
|
fileURL,
|
||||||
|
headers = videoHeaders
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else videoList.add(Video(fileURL, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user