forked from AlmightyHak/extensions-source
Initial commit
This commit is contained in:
commit
98ed7e8839
2263 changed files with 108711 additions and 0 deletions
16
src/en/allanime/build.gradle
Normal file
16
src/en/allanime/build.gradle
Normal file
|
@ -0,0 +1,16 @@
|
|||
ext {
|
||||
extName = 'AllAnime'
|
||||
extClass = '.AllAnime'
|
||||
extVersionCode = 31
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:streamlare-extractor'))
|
||||
implementation(project(':lib:mp4upload-extractor'))
|
||||
implementation(project(':lib:dood-extractor'))
|
||||
implementation(project(':lib:okru-extractor'))
|
||||
implementation(project(':lib:gogostream-extractor'))
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
}
|
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 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,667 @@
|
|||
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.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.gogostreamextractor.GogoStreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.util.parallelCatchingFlatMap
|
||||
import eu.kanade.tachiyomi.util.parseAs
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlinx.serialization.json.putJsonObject
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
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 by lazy { preferences.baseUrl }
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
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 data = buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
put("type", "anime")
|
||||
put("size", PAGE_SIZE)
|
||||
put("dateRange", 7)
|
||||
put("page", page)
|
||||
}
|
||||
put("query", POPULAR_QUERY)
|
||||
}
|
||||
return buildPost(data)
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = response.parseAs<PopularResult>()
|
||||
|
||||
val animeList = parsed.data.queryPopular.recommendations.filter { it.anyCard != null }.map {
|
||||
SAnime.create().apply {
|
||||
title = when (preferences.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}<&sep>${it.anyCard.slugTime ?: ""}<&sep>${it.anyCard.name.slugify()}"
|
||||
}
|
||||
}
|
||||
|
||||
return AnimesPage(animeList, animeList.size == PAGE_SIZE)
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val data = buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
putJsonObject("search") {
|
||||
put("allowAdult", false)
|
||||
put("allowUnknown", false)
|
||||
}
|
||||
put("limit", PAGE_SIZE)
|
||||
put("page", page)
|
||||
put("translationType", preferences.subPref)
|
||||
put("countryOrigin", "ALL")
|
||||
}
|
||||
put("query", SEARCH_QUERY)
|
||||
}
|
||||
return buildPost(data)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage = parseAnime(response)
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val filters = AllAnimeFilters.getSearchParameters(filters)
|
||||
|
||||
return if (query.isNotEmpty()) {
|
||||
val data = buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
putJsonObject("search") {
|
||||
put("query", query)
|
||||
put("allowAdult", false)
|
||||
put("allowUnknown", false)
|
||||
}
|
||||
put("limit", PAGE_SIZE)
|
||||
put("page", page)
|
||||
put("translationType", preferences.subPref)
|
||||
put("countryOrigin", "ALL")
|
||||
}
|
||||
put("query", SEARCH_QUERY)
|
||||
}
|
||||
buildPost(data)
|
||||
} else {
|
||||
val data = buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
putJsonObject("search") {
|
||||
put("allowAdult", false)
|
||||
put("allowUnknown", false)
|
||||
if (filters.season != "all") put("season", filters.season)
|
||||
if (filters.releaseYear != "all") put("year", filters.releaseYear.toInt())
|
||||
if (filters.genres != "all") {
|
||||
put("genres", json.decodeFromString(filters.genres))
|
||||
put("excludeGenres", buildJsonArray { })
|
||||
}
|
||||
if (filters.types != "all") put("types", json.decodeFromString(filters.types))
|
||||
if (filters.sortBy != "update") put("sortBy", filters.sortBy)
|
||||
}
|
||||
put("limit", PAGE_SIZE)
|
||||
put("page", page)
|
||||
put("translationType", preferences.subPref)
|
||||
put("countryOrigin", filters.origin)
|
||||
}
|
||||
put("query", SEARCH_QUERY)
|
||||
}
|
||||
buildPost(data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = parseAnime(response)
|
||||
|
||||
// ============================== Filters ===============================
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AllAnimeFilters.FILTER_LIST
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
|
||||
override fun animeDetailsRequest(anime: SAnime): Request {
|
||||
val data = buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
put("_id", anime.url.split("<&sep>").first())
|
||||
}
|
||||
put("query", DETAILS_QUERY)
|
||||
}
|
||||
return buildPost(data)
|
||||
}
|
||||
|
||||
override fun getAnimeUrl(anime: SAnime): String {
|
||||
val (id, time, slug) = anime.url.split("<&sep>")
|
||||
val slugTime = if (time.isNotEmpty()) "-st-$time" else time
|
||||
val siteUrl = preferences.siteUrl
|
||||
|
||||
return "$siteUrl/anime/$id/$slug$slugTime"
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val show = response.parseAs<DetailsResult>().data.show
|
||||
|
||||
return SAnime.create().apply {
|
||||
genre = show.genres?.joinToString(separator = ", ") ?: ""
|
||||
status = parseStatus(show.status)
|
||||
author = show.studios?.firstOrNull()
|
||||
description = buildString {
|
||||
append(
|
||||
Jsoup.parseBodyFragment(
|
||||
show.description?.replace("<br>", "br2n") ?: "",
|
||||
).text().replace("br2n", "\n"),
|
||||
)
|
||||
append("\n\n")
|
||||
append("Type: ${show.type ?: "Unknown"}")
|
||||
append("\nAired: ${show.season?.quarter ?: "-"} ${show.season?.year ?: "-"}")
|
||||
append("\nScore: ${show.score ?: "-"}★")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request {
|
||||
val data = buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
put("_id", anime.url.split("<&sep>").first())
|
||||
}
|
||||
put("query", EPISODES_QUERY)
|
||||
}
|
||||
return buildPost(data)
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val subPref = preferences.subPref
|
||||
val medias = response.parseAs<SeriesResult>()
|
||||
|
||||
val episodesDetail = if (subPref == "sub") {
|
||||
medias.data.show.availableEpisodesDetail.sub!!
|
||||
} else {
|
||||
medias.data.show.availableEpisodesDetail.dub!!
|
||||
}
|
||||
|
||||
return episodesDetail.map { ep ->
|
||||
val numName = ep.toIntOrNull() ?: (ep.toFloatOrNull() ?: "1")
|
||||
|
||||
SEpisode.create().apply {
|
||||
episode_number = ep.toFloatOrNull() ?: 0F
|
||||
name = "Episode $numName ($subPref)"
|
||||
url = json.encodeToString(
|
||||
buildJsonObject {
|
||||
putJsonObject("variables") {
|
||||
put("showId", medias.data.show._id)
|
||||
put("translationType", subPref)
|
||||
put("episodeString", ep)
|
||||
}
|
||||
put("query", STREAMS_QUERY)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
|
||||
override fun videoListRequest(episode: SEpisode): Request {
|
||||
val payload = episode.url
|
||||
.toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||
|
||||
val siteUrl = preferences.siteUrl
|
||||
val postHeaders = headers.newBuilder().apply {
|
||||
add("Accept", "*/*")
|
||||
add("Content-Length", payload.contentLength().toString())
|
||||
add("Content-Type", payload.contentType().toString())
|
||||
add("Host", baseUrl.toHttpUrl().host)
|
||||
add("Origin", siteUrl)
|
||||
add("Referer", "$baseUrl/")
|
||||
}.build()
|
||||
|
||||
return POST("$baseUrl/api", headers = postHeaders, body = payload)
|
||||
}
|
||||
|
||||
private val allAnimeExtractor by lazy { AllAnimeExtractor(client, headers, preferences.siteUrl) }
|
||||
private val gogoStreamExtractor by lazy { GogoStreamExtractor(client) }
|
||||
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
|
||||
private val streamlareExtractor by lazy { StreamlareExtractor(client) }
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
val response = client.newCall(videoListRequest(episode)).await()
|
||||
|
||||
val videoJson = response.parseAs<EpisodeResult>()
|
||||
val videoList = mutableListOf<Pair<Video, Float>>()
|
||||
val serverList = mutableListOf<Server>()
|
||||
|
||||
val hosterSelection = preferences.getHosters
|
||||
val altHosterSelection = preferences.getAltHosters
|
||||
|
||||
// list of alternative hosters
|
||||
val mappings = listOf(
|
||||
"vidstreaming" to listOf("vidstreaming", "https://gogo", "playgo1.cc", "playtaku"),
|
||||
"doodstream" to listOf("dood"),
|
||||
"okru" to listOf("ok.ru"),
|
||||
"mp4upload" to listOf("mp4upload.com"),
|
||||
"streamlare" to listOf("streamlare.com"),
|
||||
)
|
||||
|
||||
videoJson.data.episode.sourceUrls.forEach { video ->
|
||||
val videoUrl = video.sourceUrl.decryptSource()
|
||||
|
||||
val matchingMapping = mappings.firstOrNull { (altHoster, urlMatches) ->
|
||||
altHosterSelection.contains(altHoster) && videoUrl.containsAny(urlMatches)
|
||||
}
|
||||
|
||||
when {
|
||||
videoUrl.startsWith("/apivtwo/") && INTERAL_HOSTER_NAMES.any {
|
||||
Regex("""\b${it.lowercase()}\b""").find(video.sourceName.lowercase()) != null &&
|
||||
hosterSelection.contains(it.lowercase())
|
||||
} -> {
|
||||
serverList.add(Server(videoUrl, "internal ${video.sourceName}", video.priority))
|
||||
}
|
||||
altHosterSelection.contains("player") && video.type == "player" -> {
|
||||
serverList.add(Server(videoUrl, "player@${video.sourceName}", video.priority))
|
||||
}
|
||||
matchingMapping != null -> {
|
||||
serverList.add(Server(videoUrl, matchingMapping.first, video.priority))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
videoList.addAll(
|
||||
serverList.parallelCatchingFlatMap { server ->
|
||||
val sName = server.sourceName
|
||||
when {
|
||||
sName.startsWith("internal ") -> {
|
||||
allAnimeExtractor.videoFromUrl(server.sourceUrl, server.sourceName)
|
||||
}
|
||||
sName.startsWith("player@") -> {
|
||||
val endPoint = client.newCall(GET("${preferences.siteUrl}/getVersion")).await()
|
||||
.parseAs<AllAnimeExtractor.VersionResponse>()
|
||||
.episodeIframeHead
|
||||
|
||||
val videoHeaders = headers.newBuilder().apply {
|
||||
add("Accept", "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5")
|
||||
add("Host", server.sourceUrl.toHttpUrl().host)
|
||||
add("Referer", "$endPoint/")
|
||||
}.build()
|
||||
|
||||
listOf(
|
||||
Video(
|
||||
server.sourceUrl,
|
||||
"Original (player ${server.sourceName.substringAfter("player@")})",
|
||||
server.sourceUrl,
|
||||
headers = videoHeaders,
|
||||
),
|
||||
)
|
||||
}
|
||||
sName == "vidstreaming" -> {
|
||||
gogoStreamExtractor.videosFromUrl(server.sourceUrl.replace(Regex("^//"), "https://"))
|
||||
}
|
||||
sName == "dood" -> {
|
||||
doodExtractor.videosFromUrl(server.sourceUrl)
|
||||
}
|
||||
sName == "okru" -> {
|
||||
okruExtractor.videosFromUrl(server.sourceUrl)
|
||||
}
|
||||
sName == "mp4upload" -> {
|
||||
mp4uploadExtractor.videosFromUrl(server.sourceUrl, headers)
|
||||
}
|
||||
sName == "streamlare" -> {
|
||||
streamlareExtractor.videosFromUrl(server.sourceUrl)
|
||||
}
|
||||
else -> emptyList()
|
||||
}.let { it.map { v -> Pair(v, server.priority) } }
|
||||
},
|
||||
)
|
||||
|
||||
return prioritySort(videoList)
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private fun String.decryptSource(): String {
|
||||
return if (this.startsWith("-")) {
|
||||
this.substringAfterLast('-').chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray().map {
|
||||
(it.toInt() xor 56).toChar()
|
||||
}.joinToString("")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun prioritySort(pList: List<Pair<Video, Float>>): List<Video> {
|
||||
val prefServer = preferences.prefServer
|
||||
val quality = preferences.quality
|
||||
val subPref = preferences.subPref
|
||||
|
||||
return pList.sortedWith(
|
||||
compareBy(
|
||||
{ if (prefServer == "site_default") it.second else it.first.quality.contains(prefServer, true) },
|
||||
{ it.first.quality.contains(quality, true) },
|
||||
{ it.first.quality.contains(subPref, true) },
|
||||
),
|
||||
).reversed().map { t -> t.first }
|
||||
}
|
||||
|
||||
private fun buildPost(dataObject: JsonObject): Request {
|
||||
val payload = json.encodeToString(dataObject)
|
||||
.toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||
|
||||
val siteUrl = preferences.siteUrl
|
||||
val postHeaders = headers.newBuilder().apply {
|
||||
add("Accept", "*/*")
|
||||
add("Content-Length", payload.contentLength().toString())
|
||||
add("Content-Type", payload.contentType().toString())
|
||||
add("Host", baseUrl.toHttpUrl().host)
|
||||
add("Origin", siteUrl)
|
||||
add("Referer", "$baseUrl/")
|
||||
}.build()
|
||||
|
||||
return POST("$baseUrl/api", headers = postHeaders, body = payload)
|
||||
}
|
||||
|
||||
data class Server(
|
||||
val sourceUrl: String,
|
||||
val sourceName: String,
|
||||
val priority: Float,
|
||||
)
|
||||
|
||||
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 String.slugify(): String {
|
||||
return this.replace("""[^a-zA-Z0-9]""".toRegex(), "-")
|
||||
.replace("""-{2,}""".toRegex(), "-")
|
||||
.lowercase()
|
||||
}
|
||||
|
||||
private fun parseAnime(response: Response): AnimesPage {
|
||||
val parsed = response.parseAs<SearchResult>()
|
||||
|
||||
val animeList = parsed.data.shows.edges.map { ani ->
|
||||
SAnime.create().apply {
|
||||
title = when (preferences.titleStyle) {
|
||||
"romaji" -> ani.name
|
||||
"eng" -> ani.englishName ?: ani.name
|
||||
else -> ani.nativeName ?: ani.name
|
||||
}
|
||||
thumbnail_url = ani.thumbnail
|
||||
url = "${ani._id}<&sep>${ani.slugTime ?: ""}<&sep>${ani.name.slugify()}"
|
||||
}
|
||||
}
|
||||
|
||||
return AnimesPage(animeList, animeList.size == PAGE_SIZE)
|
||||
}
|
||||
|
||||
private fun String.containsAny(keywords: List<String>): Boolean {
|
||||
return keywords.any { this.contains(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PAGE_SIZE = 26 // number of items to retrieve when calling API
|
||||
private val INTERAL_HOSTER_NAMES = arrayOf(
|
||||
"Default", "Ac", "Ak", "Kir", "Rab", "Luf-mp4",
|
||||
"Si-Hls", "S-mp4", "Ac-Hls", "Uv-mp4", "Pn-Hls",
|
||||
)
|
||||
|
||||
private val ALT_HOSTER_NAMES = arrayOf(
|
||||
"player",
|
||||
"vidstreaming",
|
||||
"okru",
|
||||
"mp4upload",
|
||||
"streamlare",
|
||||
"doodstream",
|
||||
)
|
||||
|
||||
private const val PREF_SITE_DOMAIN_KEY = "preferred_site_domain"
|
||||
private const val PREF_SITE_DOMAIN_DEFAULT = "https://allanime.to"
|
||||
|
||||
private const val PREF_DOMAIN_KEY = "preferred_domain"
|
||||
private const val PREF_DOMAIN_DEFAULT = "https://api.allanime.day"
|
||||
|
||||
private const val PREF_SERVER_KEY = "preferred_server"
|
||||
private val PREF_SERVER_ENTRIES = arrayOf("Site Default") +
|
||||
INTERAL_HOSTER_NAMES.sliceArray(1 until INTERAL_HOSTER_NAMES.size) +
|
||||
ALT_HOSTER_NAMES
|
||||
private val PREF_SERVER_ENTRY_VALUES = arrayOf("site_default") +
|
||||
INTERAL_HOSTER_NAMES.sliceArray(1 until INTERAL_HOSTER_NAMES.size).map {
|
||||
it.lowercase()
|
||||
}.toTypedArray() +
|
||||
ALT_HOSTER_NAMES
|
||||
private const val PREF_SERVER_DEFAULT = "site_default"
|
||||
|
||||
private const val PREF_HOSTER_KEY = "hoster_selection"
|
||||
private val PREF_HOSTER_ENTRY_VALUES = INTERAL_HOSTER_NAMES.map {
|
||||
it.lowercase()
|
||||
}.toTypedArray()
|
||||
private val PREF_HOSTER_DEFAULT = setOf("default", "ac", "ak", "kir", "luf-mp4", "si-hls", "s-mp4", "ac-hls")
|
||||
|
||||
private const val PREF_ALT_HOSTER_KEY = "alt_hoster_selection"
|
||||
|
||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||
private val PREF_QUALITY_ENTRIES = arrayOf(
|
||||
"2160p",
|
||||
"1440p",
|
||||
"1080p",
|
||||
"720p",
|
||||
"480p",
|
||||
"360p",
|
||||
"240p",
|
||||
"80p",
|
||||
)
|
||||
private val PREF_QUALITY_ENTRY_VALUES = PREF_QUALITY_ENTRIES.map {
|
||||
it.substringBefore("p")
|
||||
}.toTypedArray()
|
||||
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||
|
||||
private const val PREF_TITLE_STYLE_KEY = "preferred_title_style"
|
||||
private const val PREF_TITLE_STYLE_DEFAULT = "romaji"
|
||||
|
||||
private const val PREF_SUB_KEY = "preferred_sub"
|
||||
private const val PREF_SUB_DEFAULT = "sub"
|
||||
}
|
||||
|
||||
// ============================== Settings ==============================
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SITE_DOMAIN_KEY
|
||||
title = "Preferred domain for site (requires app restart)"
|
||||
entries = arrayOf("allmanga.to")
|
||||
entryValues = arrayOf("https://allmanga.to")
|
||||
setDefaultValue(PREF_SITE_DOMAIN_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_DOMAIN_KEY
|
||||
title = "Preferred domain (requires app restart)"
|
||||
entries = arrayOf("api.allanime.day")
|
||||
entryValues = arrayOf("https://api.allanime.day")
|
||||
setDefaultValue(PREF_DOMAIN_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 Video Server"
|
||||
entries = PREF_SERVER_ENTRIES
|
||||
entryValues = PREF_SERVER_ENTRY_VALUES
|
||||
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)
|
||||
|
||||
MultiSelectListPreference(screen.context).apply {
|
||||
key = PREF_HOSTER_KEY
|
||||
title = "Enable/Disable Hosts"
|
||||
entries = INTERAL_HOSTER_NAMES
|
||||
entryValues = PREF_HOSTER_ENTRY_VALUES
|
||||
setDefaultValue(PREF_HOSTER_DEFAULT)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
MultiSelectListPreference(screen.context).apply {
|
||||
key = PREF_ALT_HOSTER_KEY
|
||||
title = "Enable/Disable Alternative Hosts"
|
||||
entries = ALT_HOSTER_NAMES
|
||||
entryValues = ALT_HOSTER_NAMES
|
||||
setDefaultValue(ALT_HOSTER_NAMES.toSet())
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_QUALITY_KEY
|
||||
title = "Preferred quality"
|
||||
entries = PREF_QUALITY_ENTRIES
|
||||
entryValues = PREF_QUALITY_ENTRY_VALUES
|
||||
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_TITLE_STYLE_KEY
|
||||
title = "Preferred Title Style"
|
||||
entries = arrayOf("Romaji", "English", "Native")
|
||||
entryValues = arrayOf("romaji", "eng", "native")
|
||||
setDefaultValue(PREF_TITLE_STYLE_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_SUB_KEY
|
||||
title = "Prefer subs or dubs?"
|
||||
entries = arrayOf("Subs", "Dubs")
|
||||
entryValues = arrayOf("sub", "dub")
|
||||
setDefaultValue(PREF_SUB_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)
|
||||
}
|
||||
|
||||
private val SharedPreferences.subPref
|
||||
get() = getString(PREF_SUB_KEY, PREF_SUB_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.baseUrl
|
||||
get() = getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.siteUrl
|
||||
get() = getString(PREF_SITE_DOMAIN_KEY, PREF_SITE_DOMAIN_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.titleStyle
|
||||
get() = getString(PREF_TITLE_STYLE_KEY, PREF_TITLE_STYLE_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.quality
|
||||
get() = getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.prefServer
|
||||
get() = getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.getHosters
|
||||
get() = getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
|
||||
|
||||
private val SharedPreferences.getAltHosters
|
||||
get() = getStringSet(PREF_ALT_HOSTER_KEY, ALT_HOSTER_NAMES.toSet())!!
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
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,
|
||||
val slugTime: 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,
|
||||
val slugTime: String? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class DetailsResult(
|
||||
val data: DataShow,
|
||||
) {
|
||||
@Serializable
|
||||
data class DataShow(
|
||||
val show: SeriesShows,
|
||||
) {
|
||||
@Serializable
|
||||
data class SeriesShows(
|
||||
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,
|
||||
) {
|
||||
@Serializable
|
||||
data class AirSeason(
|
||||
val quarter: String,
|
||||
val year: Int,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SeriesResult(
|
||||
val data: DataShow,
|
||||
) {
|
||||
@Serializable
|
||||
data class DataShow(
|
||||
val show: SeriesShows,
|
||||
) {
|
||||
@Serializable
|
||||
data class SeriesShows(
|
||||
val _id: String,
|
||||
val availableEpisodesDetail: AvailableEps,
|
||||
) {
|
||||
@Serializable
|
||||
data class AvailableEps(
|
||||
val sub: List<String>? = null,
|
||||
val dub: List<String>? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,231 @@
|
|||
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.SORT_BY)
|
||||
|
||||
class TypesFilter : CheckBoxFilterList(
|
||||
"Types",
|
||||
AllAnimeFiltersData.TYPES.map { CheckBoxVal(it.first, false) },
|
||||
)
|
||||
|
||||
class GenresFilter : CheckBoxFilterList(
|
||||
"Genres",
|
||||
AllAnimeFiltersData.GENRES.map { CheckBoxVal(it.first, false) },
|
||||
)
|
||||
|
||||
val FILTER_LIST get() = 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 SORT_BY = 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,119 @@
|
|||
package eu.kanade.tachiyomi.animeextension.en.allanime
|
||||
|
||||
fun buildQuery(queryAction: () -> String): String {
|
||||
return queryAction()
|
||||
.trimIndent()
|
||||
.replace("%", "$")
|
||||
}
|
||||
|
||||
val POPULAR_QUERY: String = buildQuery {
|
||||
"""
|
||||
query(
|
||||
%type: VaildPopularTypeEnumType!
|
||||
%size: Int!
|
||||
%page: Int
|
||||
%dateRange: Int
|
||||
) {
|
||||
queryPopular(
|
||||
type: %type
|
||||
size: %size
|
||||
dateRange: %dateRange
|
||||
page: %page
|
||||
) {
|
||||
total
|
||||
recommendations {
|
||||
anyCard {
|
||||
_id
|
||||
name
|
||||
thumbnail
|
||||
englishName
|
||||
nativeName
|
||||
slugTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
val SEARCH_QUERY: String = buildQuery {
|
||||
"""
|
||||
query(
|
||||
%search: SearchInput
|
||||
%limit: Int
|
||||
%page: Int
|
||||
%translationType: VaildTranslationTypeEnumType
|
||||
%countryOrigin: VaildCountryOriginEnumType
|
||||
) {
|
||||
shows(
|
||||
search: %search
|
||||
limit: %limit
|
||||
page: %page
|
||||
translationType: %translationType
|
||||
countryOrigin: %countryOrigin
|
||||
) {
|
||||
pageInfo {
|
||||
total
|
||||
}
|
||||
edges {
|
||||
_id
|
||||
name
|
||||
thumbnail
|
||||
englishName
|
||||
nativeName
|
||||
slugTime
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
val DETAILS_QUERY = buildQuery {
|
||||
"""
|
||||
query (%_id: String!) {
|
||||
show(
|
||||
_id: %_id
|
||||
) {
|
||||
thumbnail
|
||||
description
|
||||
type
|
||||
season
|
||||
score
|
||||
genres
|
||||
status
|
||||
studios
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
val EPISODES_QUERY = buildQuery {
|
||||
"""
|
||||
query (%_id: String!) {
|
||||
show(
|
||||
_id: %_id
|
||||
) {
|
||||
_id
|
||||
availableEpisodesDetail
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
val STREAMS_QUERY = buildQuery {
|
||||
"""
|
||||
query(
|
||||
%showId: String!,
|
||||
%translationType: VaildTranslationTypeEnumType!,
|
||||
%episodeString: String!
|
||||
) {
|
||||
episode(
|
||||
showId: %showId
|
||||
translationType: %translationType
|
||||
episodeString: %episodeString
|
||||
) {
|
||||
sourceUrls
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
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.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
|
||||
class AllAnimeExtractor(private val client: OkHttpClient, private val headers: Headers, private val siteUrl: String) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private fun bytesIntoHumanReadable(bytes: Long): String {
|
||||
val kilobyte: Long = 1000
|
||||
val megabyte = kilobyte * 1000
|
||||
val gigabyte = megabyte * 1000
|
||||
val terabyte = gigabyte * 1000
|
||||
return if (bytes in 0 until kilobyte) {
|
||||
"$bytes b/s"
|
||||
} else if (bytes in kilobyte until megabyte) {
|
||||
(bytes / kilobyte).toString() + " kb/s"
|
||||
} else if (bytes in megabyte until gigabyte) {
|
||||
(bytes / megabyte).toString() + " mb/s"
|
||||
} else if (bytes in gigabyte until terabyte) {
|
||||
(bytes / gigabyte).toString() + " gb/s"
|
||||
} else if (bytes >= terabyte) {
|
||||
(bytes / terabyte).toString() + " tb/s"
|
||||
} else {
|
||||
"$bytes bits/s"
|
||||
}
|
||||
}
|
||||
|
||||
fun videoFromUrl(url: String, name: String): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val endPoint = json.decodeFromString<VersionResponse>(
|
||||
client.newCall(GET("$siteUrl/getVersion")).execute().body.string(),
|
||||
).episodeIframeHead
|
||||
|
||||
val resp = client.newCall(
|
||||
GET(endPoint + 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()) {
|
||||
subtitles.addAll(
|
||||
link.subtitles.map { sub ->
|
||||
val label = if (sub.label != null) {
|
||||
" - ${sub.label}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
Track(sub.src, Locale(sub.lang).displayLanguage + label)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (link.mp4 == true) {
|
||||
videoList.add(
|
||||
Video(
|
||||
link.link,
|
||||
"Original ($name - ${link.resolutionStr})",
|
||||
link.link,
|
||||
subtitleTracks = subtitles,
|
||||
),
|
||||
)
|
||||
} else if (link.hls == true) {
|
||||
val newClient = OkHttpClient()
|
||||
|
||||
val masterHeaders = headers.newBuilder()
|
||||
.add("Accept", "*/*")
|
||||
.add("Host", link.link.toHttpUrl().host)
|
||||
.add("Origin", endPoint)
|
||||
.add("Referer", "$endPoint/")
|
||||
.build()
|
||||
|
||||
val resp = runCatching {
|
||||
newClient.newCall(
|
||||
GET(link.link, headers = masterHeaders),
|
||||
).execute()
|
||||
}.getOrNull()
|
||||
|
||||
if (resp != null && resp.code == 200) {
|
||||
val masterPlaylist = resp.body.string()
|
||||
|
||||
val audioList = mutableListOf<Track>()
|
||||
if (masterPlaylist.contains("#EXT-X-MEDIA:TYPE=AUDIO")) {
|
||||
val audioInfo = masterPlaylist.substringAfter("#EXT-X-MEDIA:TYPE=AUDIO")
|
||||
.substringBefore("\n")
|
||||
val language = audioInfo.substringAfter("NAME=\"").substringBefore("\"")
|
||||
val url = audioInfo.substringAfter("URI=\"").substringBefore("\"")
|
||||
audioList.add(
|
||||
Track(url, language),
|
||||
)
|
||||
}
|
||||
|
||||
if (!masterPlaylist.contains("#EXT-X-STREAM-INF:")) {
|
||||
return if (audioList.isEmpty()) {
|
||||
listOf(Video(link.link, "$name - ${link.resolutionStr}", link.link, subtitleTracks = subtitles, headers = masterHeaders))
|
||||
} else {
|
||||
listOf(Video(link.link, "$name - ${link.resolutionStr}", link.link, subtitleTracks = subtitles, audioTracks = audioList, headers = masterHeaders))
|
||||
}
|
||||
}
|
||||
|
||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
|
||||
.forEach {
|
||||
val bandwidth = if (it.contains("AVERAGE-BANDWIDTH")) {
|
||||
" " + bytesIntoHumanReadable(it.substringAfter("AVERAGE-BANDWIDTH=").substringBefore(",").toLong())
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p$bandwidth ($name - ${link.resolutionStr})"
|
||||
var videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
|
||||
if (!videoUrl.startsWith("http")) {
|
||||
videoUrl = resp.request.url.toString().substringBeforeLast("/") + "/$videoUrl"
|
||||
}
|
||||
|
||||
val plHeaders = headers.newBuilder()
|
||||
.add("Accept", "*/*")
|
||||
.add("Host", videoUrl.toHttpUrl().host)
|
||||
.add("Origin", endPoint)
|
||||
.add("Referer", "$endPoint/")
|
||||
.build()
|
||||
|
||||
if (audioList.isEmpty()) {
|
||||
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subtitles, headers = plHeaders))
|
||||
} else {
|
||||
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subtitles, audioTracks = audioList, headers = plHeaders))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (link.crIframe == true) {
|
||||
link.portData!!.streams.forEach {
|
||||
if (it.format == "adaptive_dash") {
|
||||
videoList.add(
|
||||
Video(
|
||||
it.url,
|
||||
"Original (AC - Dash${if (it.hardsub_lang.isEmpty()) "" else " - Hardsub: ${it.hardsub_lang}"})",
|
||||
it.url,
|
||||
subtitleTracks = subtitles,
|
||||
),
|
||||
)
|
||||
} else if (it.format == "adaptive_hls") {
|
||||
val resp = runCatching {
|
||||
client.newCall(
|
||||
GET(it.url, 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 { t ->
|
||||
val quality = t.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p (AC - HLS${if (it.hardsub_lang.isEmpty()) "" else " - Hardsub: ${it.hardsub_lang}"})"
|
||||
var videoUrl = t.substringAfter("\n").substringBefore("\n")
|
||||
|
||||
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subtitles))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (link.dash == true) {
|
||||
val audioList = link.rawUrls?.audios?.map {
|
||||
Track(it.url, bytesIntoHumanReadable(it.bandwidth))
|
||||
}
|
||||
val videos = link.rawUrls?.vids?.map {
|
||||
if (audioList == null) {
|
||||
Video(it.url, "$name - ${it.height} ${bytesIntoHumanReadable(it.bandwidth)}", it.url, subtitleTracks = subtitles)
|
||||
} else {
|
||||
Video(it.url, "$name - ${it.height} ${bytesIntoHumanReadable(it.bandwidth)}", it.url, audioTracks = audioList, subtitleTracks = subtitles)
|
||||
}
|
||||
}
|
||||
if (videos != null) {
|
||||
videoList.addAll(videos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return videoList
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class VersionResponse(
|
||||
val episodeIframeHead: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VideoLink(
|
||||
val links: List<Link>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Link(
|
||||
val link: String,
|
||||
val hls: Boolean? = null,
|
||||
val mp4: Boolean? = null,
|
||||
val dash: Boolean? = null,
|
||||
val crIframe: Boolean? = null,
|
||||
val resolutionStr: String,
|
||||
val subtitles: List<Subtitles>? = null,
|
||||
val rawUrls: RawUrl? = null,
|
||||
val portData: Stream? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class Subtitles(
|
||||
val lang: String,
|
||||
val src: String,
|
||||
val label: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Stream(
|
||||
val streams: List<StreamObject>,
|
||||
) {
|
||||
@Serializable
|
||||
data class StreamObject(
|
||||
val format: String,
|
||||
val url: String,
|
||||
val audio_lang: String,
|
||||
val hardsub_lang: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RawUrl(
|
||||
val vids: List<DashStreamObject>? = null,
|
||||
val audios: List<DashStreamObject>? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class DashStreamObject(
|
||||
val bandwidth: Long,
|
||||
val height: Int,
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue