Add Extension: AllAnime (#1149)

* Add Extractor

* Use lib extractors

* Use lib okru extractor
This commit is contained in:
Secozzi
2023-01-10 14:13:40 +01:00
committed by GitHub
parent 7911e39681
commit eb9a0ca0ab
15 changed files with 1128 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.animeextension" />

View 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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -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)
}
}

View File

@ -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")
)
}
}

View File

@ -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
)
}
}
}

View File

@ -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
}
}

View File

@ -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/"))
)
}
}

View File

@ -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
}
}

View File

@ -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)
}
}
}