updated anime API for dub/sub

This commit is contained in:
LagradOst 2021-11-02 16:09:29 +01:00
parent 32a0658611
commit 2f9c76d9b5
13 changed files with 280 additions and 262 deletions

View file

@ -255,7 +255,8 @@ fun sortSubs(urls: List<SubtitleFile>): List<SubtitleFile> {
/** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */
fun imdbUrlToId(url: String): String? { fun imdbUrlToId(url: String): String? {
return Regex("/title/(tt[0-9]*)").find(url)?.groupValues?.get(1) ?: Regex("tt[0-9]{5,}").find(url)?.groupValues?.get(0) return Regex("/title/(tt[0-9]*)").find(url)?.groupValues?.get(1)
?: Regex("tt[0-9]{5,}").find(url)?.groupValues?.get(0)
} }
fun imdbUrlToIdNullable(url: String?): String? { fun imdbUrlToIdNullable(url: String?): String? {
@ -390,12 +391,12 @@ fun LoadResponse?.isAnimeBased(): Boolean {
data class AnimeEpisode( data class AnimeEpisode(
val url: String, val url: String,
val name: String? = null, var name: String? = null,
val posterUrl: String? = null, var posterUrl: String? = null,
val date: String? = null, var date: String? = null,
val rating: Int? = null, var rating: Int? = null,
val descript: String? = null, var description: String? = null,
val episode: Int? = null, var episode: Int? = null,
) )
data class TorrentLoadResponse( data class TorrentLoadResponse(
@ -416,32 +417,47 @@ data class TorrentLoadResponse(
) : LoadResponse ) : LoadResponse
data class AnimeLoadResponse( data class AnimeLoadResponse(
val engName: String?, var engName: String? = null,
val japName: String?, var japName: String? = null,
override val name: String, override val name: String,
override val url: String, override val url: String,
override val apiName: String, override val apiName: String,
override val type: TvType, override val type: TvType,
override val posterUrl: String?, override var posterUrl: String? = null,
override val year: Int?, override var year: Int? = null,
val dubEpisodes: List<AnimeEpisode>?, var episodes: HashMap<DubStatus, List<AnimeEpisode>> = hashMapOf(),
val subEpisodes: List<AnimeEpisode>?, var showStatus: ShowStatus? = null,
val showStatus: ShowStatus?,
override val plot: String?, override var plot: String? = null,
override val tags: List<String>? = null, override var tags: List<String>? = null,
val synonyms: List<String>? = null, var synonyms: List<String>? = null,
val malId: Int? = null, var malId: Int? = null,
val anilistId: Int? = null, var anilistId: Int? = null,
override val rating: Int? = null, override var rating: Int? = null,
override val duration: String? = null, override var duration: String? = null,
override val trailerUrl: String? = null, override var trailerUrl: String? = null,
override val recommendations: List<SearchResponse>? = null, override var recommendations: List<SearchResponse>? = null,
) : LoadResponse ) : LoadResponse
fun AnimeLoadResponse.addEpisodes(status : DubStatus, episodes : List<AnimeEpisode>?) {
if(episodes == null) return
this.episodes[status] = episodes
}
fun MainAPI.newAnimeLoadResponse(
name: String,
url: String,
type: TvType,
initializer: AnimeLoadResponse.() -> Unit = { }
): AnimeLoadResponse {
val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type)
builder.initializer()
return builder
}
data class MovieLoadResponse( data class MovieLoadResponse(
override val name: String, override val name: String,
override val url: String, override val url: String,
@ -449,11 +465,11 @@ data class MovieLoadResponse(
override val type: TvType, override val type: TvType,
val dataUrl: String, val dataUrl: String,
override val posterUrl: String?, override val posterUrl: String? = null,
override val year: Int?, override val year: Int? = null,
override val plot: String?, override val plot: String? = null,
val imdbId: String?, val imdbId: String? = null,
override val rating: Int? = null, override val rating: Int? = null,
override val tags: List<String>? = null, override val tags: List<String>? = null,
override val duration: String? = null, override val duration: String? = null,
@ -462,9 +478,9 @@ data class MovieLoadResponse(
) : LoadResponse ) : LoadResponse
data class TvSeriesEpisode( data class TvSeriesEpisode(
val name: String?, val name: String? = null,
val season: Int?, val season: Int? = null,
val episode: Int?, val episode: Int? = null,
val data: String, val data: String,
val posterUrl: String? = null, val posterUrl: String? = null,
val date: String? = null, val date: String? = null,
@ -479,12 +495,12 @@ data class TvSeriesLoadResponse(
override val type: TvType, override val type: TvType,
val episodes: List<TvSeriesEpisode>, val episodes: List<TvSeriesEpisode>,
override val posterUrl: String?, override val posterUrl: String? = null,
override val year: Int?, override val year: Int? = null,
override val plot: String?, override val plot: String? = null,
val showStatus: ShowStatus?, val showStatus: ShowStatus? = null,
val imdbId: String?, val imdbId: String? = null,
override val rating: Int? = null, override val rating: Int? = null,
override val tags: List<String>? = null, override val tags: List<String>? = null,
override val duration: String? = null, override val duration: String? = null,

View file

@ -1,21 +1,19 @@
package com.lagradost.cloudstream3.animeproviders package com.lagradost.cloudstream3.animeproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.network.get
import com.lagradost.cloudstream3.network.text
import com.lagradost.cloudstream3.utils.ExtractorLink
import org.jsoup.Jsoup
import java.util.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import kotlin.collections.ArrayList import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.network.get
import com.lagradost.cloudstream3.network.text
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.getQualityFromName
import org.jsoup.Jsoup
import org.mozilla.javascript.Context import org.mozilla.javascript.Context
import org.mozilla.javascript.Scriptable import org.mozilla.javascript.Scriptable
import java.net.URI import java.net.URI
import java.net.URLDecoder import java.net.URLDecoder
import com.lagradost.cloudstream3.utils.getQualityFromName import java.util.*
class AllAnimeProvider : MainAPI() { class AllAnimeProvider : MainAPI() {
@ -41,17 +39,17 @@ class AllAnimeProvider : MainAPI() {
override val supportedTypes: Set<TvType> override val supportedTypes: Set<TvType>
get() = setOf(TvType.Anime, TvType.AnimeMovie) get() = setOf(TvType.Anime, TvType.AnimeMovie)
private data class Data ( private data class Data(
@JsonProperty("shows") val shows: Shows @JsonProperty("shows") val shows: Shows
) )
private data class Shows ( private data class Shows(
@JsonProperty("pageInfo") val pageInfo: PageInfo, @JsonProperty("pageInfo") val pageInfo: PageInfo,
@JsonProperty("edges") val edges: List<Edges>, @JsonProperty("edges") val edges: List<Edges>,
@JsonProperty("__typename") val _typename: String @JsonProperty("__typename") val _typename: String
) )
private data class Edges ( private data class Edges(
@JsonProperty("_id") val Id: String?, @JsonProperty("_id") val Id: String?,
@JsonProperty("name") val name: String, @JsonProperty("name") val name: String,
@JsonProperty("englishName") val englishName: String?, @JsonProperty("englishName") val englishName: String?,
@ -68,34 +66,35 @@ class AllAnimeProvider : MainAPI() {
@JsonProperty("status") val status: String?, @JsonProperty("status") val status: String?,
) )
private data class AvailableEpisodes ( private data class AvailableEpisodes(
@JsonProperty("sub") val sub: Int, @JsonProperty("sub") val sub: Int,
@JsonProperty("dub") val dub: Int, @JsonProperty("dub") val dub: Int,
@JsonProperty("raw") val raw: Int @JsonProperty("raw") val raw: Int
) )
private data class AiredStart ( private data class AiredStart(
@JsonProperty("year") val year: Int, @JsonProperty("year") val year: Int,
@JsonProperty("month") val month: Int, @JsonProperty("month") val month: Int,
@JsonProperty("date") val date: Int @JsonProperty("date") val date: Int
) )
private data class Season ( private data class Season(
@JsonProperty("quarter") val quarter: String, @JsonProperty("quarter") val quarter: String,
@JsonProperty("year") val year: Int @JsonProperty("year") val year: Int
) )
private data class PageInfo ( private data class PageInfo(
@JsonProperty("total") val total: Int, @JsonProperty("total") val total: Int,
@JsonProperty("__typename") val _typename: String @JsonProperty("__typename") val _typename: String
) )
private data class AllAnimeQuery ( private data class AllAnimeQuery(
@JsonProperty("data") val data: Data @JsonProperty("data") val data: Data
) )
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): ArrayList<SearchResponse> {
val link = """$mainUrl/graphql?variables=%7B%22search%22%3A%7B%22allowAdult%22%3Afalse%2C%22query%22%3A%22$query%22%7D%2C%22limit%22%3A26%2C%22page%22%3A1%2C%22translationType%22%3A%22sub%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%229343797cc3d9e3f444e2d3b7db9a84d759b816a4d84512ea72d079f85bb96e98%22%7D%7D""" val link =
"""$mainUrl/graphql?variables=%7B%22search%22%3A%7B%22allowAdult%22%3Afalse%2C%22query%22%3A%22$query%22%7D%2C%22limit%22%3A26%2C%22page%22%3A1%2C%22translationType%22%3A%22sub%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%229343797cc3d9e3f444e2d3b7db9a84d759b816a4d84512ea72d079f85bb96e98%22%7D%7D"""
var res = get(link).text var res = get(link).text
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) { if (res.contains("PERSISTED_QUERY_NOT_FOUND")) {
res = get(link).text res = get(link).text
@ -116,15 +115,15 @@ class AllAnimeProvider : MainAPI() {
TvType.Anime, TvType.Anime,
it.thumbnail, it.thumbnail,
it.airedStart?.year, it.airedStart?.year,
EnumSet.of(DubStatus.Subbed), //, DubStatus.Dubbed), EnumSet.of(DubStatus.Subbed, DubStatus.Dubbed),
it.englishName, it.englishName,
null, //it.availableEpisodes?.dub, it.availableEpisodes?.dub,
it.availableEpisodes?.sub it.availableEpisodes?.sub
) )
}) })
} }
private data class AvailableEpisodesDetail ( private data class AvailableEpisodesDetail(
@JsonProperty("sub") val sub: List<String>, @JsonProperty("sub") val sub: List<String>,
@JsonProperty("dub") val dub: List<String>, @JsonProperty("dub") val dub: List<String>,
@JsonProperty("raw") val raw: List<String> @JsonProperty("raw") val raw: List<String>
@ -137,7 +136,6 @@ class AllAnimeProvider : MainAPI() {
rhino.optimizationLevel = -1 rhino.optimizationLevel = -1
val scope: Scriptable = rhino.initStandardObjects() val scope: Scriptable = rhino.initStandardObjects()
val html = get(url).text val html = get(url).text
val soup = Jsoup.parse(html) val soup = Jsoup.parse(html)
@ -161,43 +159,28 @@ class AllAnimeProvider : MainAPI() {
val episodes = showData.availableEpisodes.let { val episodes = showData.availableEpisodes.let {
if (it == null) return@let Pair(null, null) if (it == null) return@let Pair(null, null)
Pair(if (it.sub != 0) ArrayList((1 .. it.sub).map { epNum -> Pair(if (it.sub != 0) ArrayList((1..it.sub).map { epNum ->
AnimeEpisode( AnimeEpisode(
"$mainUrl/anime/${showData.Id}/episodes/sub/$epNum", "$mainUrl/anime/${showData.Id}/episodes/sub/$epNum", episode = epNum
null,
null,
null,
null,
null,
epNum
) )
}) else null, if (it.dub != 0) ArrayList((1 .. it.dub).map { epNum -> }) else null, if (it.dub != 0) ArrayList((1..it.dub).map { epNum ->
AnimeEpisode( AnimeEpisode(
"$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", "$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", episode = epNum
null,
null,
null,
null,
null,
epNum
) )
}) else null) }) else null)
} }
return AnimeLoadResponse( return newAnimeLoadResponse(title, url, TvType.Anime) {
null, posterUrl = poster
null, year = showData.airedStart?.year
title,
url, addEpisodes(DubStatus.Subbed, episodes.first)
this.name, addEpisodes(DubStatus.Dubbed, episodes.second)
TvType.Anime,
poster, showStatus = getStatus(showData.status.toString())
showData.airedStart?.year,
null, // no dub, because there is no way to switch from dub to sub //episodes.second, plot = description?.replace(Regex("""<(.*?)>"""), "")
episodes.first, }
getStatus(showData.status.toString()),
description?.replace(Regex("""\<(.*?)\>"""), "")
)
} }
private val embedBlackList = listOf( private val embedBlackList = listOf(
@ -216,7 +199,7 @@ class AllAnimeProvider : MainAPI() {
return true return true
} }
} else { } else {
if (url.contains(it as String)) { if (url.contains(it)) {
return true return true
} }
} }
@ -232,14 +215,14 @@ class AllAnimeProvider : MainAPI() {
return out return out
} }
private data class Links ( private data class Links(
@JsonProperty("link") val link: String, @JsonProperty("link") val link: String,
@JsonProperty("hls") val hls: Boolean?, @JsonProperty("hls") val hls: Boolean?,
@JsonProperty("resolutionStr") val resolutionStr: String, @JsonProperty("resolutionStr") val resolutionStr: String,
@JsonProperty("src") val src: String? @JsonProperty("src") val src: String?
) )
private data class AllAnimeVideoApiResponse ( private data class AllAnimeVideoApiResponse(
@JsonProperty("links") val links: List<Links> @JsonProperty("links") val links: List<Links>
) )
@ -273,7 +256,8 @@ class AllAnimeProvider : MainAPI() {
val html = get(data).text val html = get(data).text
val sources = Regex("""sourceUrl[:=]"(.+?)"""").findAll(html).toList().map { URLDecoder.decode(it.destructured.component1().sanitize(), "UTF-8") } val sources = Regex("""sourceUrl[:=]"(.+?)"""").findAll(html).toList()
.map { URLDecoder.decode(it.destructured.component1().sanitize(), "UTF-8") }
sources.forEach { sources.forEach {
var link = it var link = it
if (URI(link).isAbsolute || link.startsWith("//")) { if (URI(link).isAbsolute || link.startsWith("//")) {
@ -305,16 +289,26 @@ class AllAnimeProvider : MainAPI() {
val links = mapper.readValue<AllAnimeVideoApiResponse>(response.text).links val links = mapper.readValue<AllAnimeVideoApiResponse>(response.text).links
links.forEach { server -> links.forEach { server ->
if (server.hls != null && server.hls) { if (server.hls != null && server.hls) {
getM3u8Qualities(server.link, "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(server.link).path), server.resolutionStr).forEach(callback) getM3u8Qualities(
} else {
callback(ExtractorLink(
"AllAnime - " + URI(server.link).host,
server.resolutionStr,
server.link, server.link,
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(server.link).path), "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
getQualityFromName("1080"), server.link
false ).path),
)) server.resolutionStr
).forEach(callback)
} else {
callback(
ExtractorLink(
"AllAnime - " + URI(server.link).host,
server.resolutionStr,
server.link,
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
server.link
).path),
getQualityFromName("1080"),
false
)
)
} }
} }
} }

View file

@ -76,21 +76,15 @@ class AnimeFlickProvider : MainAPI() {
AnimeEpisode(link, name) AnimeEpisode(link, name)
}.reversed() }.reversed()
return AnimeLoadResponse( return newAnimeLoadResponse(title, url, getType(title)) {
title, posterUrl = poster
null, this.year = year
title,
url, addEpisodes(DubStatus.Subbed, episodes)
this.name,
getType(title), plot = description
poster, tags = genres
year, }
null,
episodes,
null,
description,
genres
)
} }
override fun loadLinks( override fun loadLinks(

View file

@ -293,30 +293,26 @@ class AnimePaheProvider : MainAPI() {
} }
} }
AnimeLoadResponse( newAnimeLoadResponse(animeTitle, url, getType(tvType.toString())) {
animeTitle, engName = animeTitle
japTitle, japName = japTitle
animeTitle,
url, this.posterUrl = poster
this.name, this.year = year
getType(tvType.toString()),
poster, addEpisodes(DubStatus.Subbed, episodes)
year, this.showStatus = status
null, plot = synopsis
episodes, tags = if (!doc.select(".anime-genre > ul a").isEmpty()) {
status,
synopsis,
if (!doc.select(".anime-genre > ul a").isEmpty()) {
ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() }) ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() })
} else { } else {
null null
}, }
ArrayList(),
malId, this.malId = malId
anilistId, this.anilistId = anilistId
null, this.trailerUrl = trailer
trailer }
)
} }
} }
@ -325,7 +321,6 @@ class AnimePaheProvider : MainAPI() {
return s?.toIntOrNull() != null return s?.toIntOrNull() != null
} }
private fun cookieStrToMap(cookie: String): Map<String, String> { private fun cookieStrToMap(cookie: String): Map<String, String> {
val cookies = mutableMapOf<String, String>() val cookies = mutableMapOf<String, String>()
for (string in cookie.split("; ")) { for (string in cookie.split("; ")) {
@ -347,7 +342,6 @@ class AnimePaheProvider : MainAPI() {
val slice2 = characterMap.slice(0 until s2) val slice2 = characterMap.slice(0 until s2)
var acc: Long = 0 var acc: Long = 0
for ((n, i) in content.reversed().withIndex()) { for ((n, i) in content.reversed().withIndex()) {
acc += (when (isNumber("$i")) { acc += (when (isNumber("$i")) {
true -> "$i".toLong() true -> "$i".toLong()

View file

@ -109,7 +109,7 @@ class DubbedAnimeProvider : MainAPI() {
HomePageList("Trending", parseDocumentTrending(trendingUrl)), HomePageList("Trending", parseDocumentTrending(trendingUrl)),
HomePageList("Recently Added", parseDocument(recentlyAddedUrl)), HomePageList("Recently Added", parseDocument(recentlyAddedUrl)),
HomePageList("Recent Releases", parseDocument(lastEpisodeUrl, true)), HomePageList("Recent Releases", parseDocument(lastEpisodeUrl, true)),
// HomePageList("All", parseDocument(allUrl)) // HomePageList("All", parseDocument(allUrl))
) )
return HomePageResponse(listItems) return HomePageResponse(listItems)
@ -268,20 +268,12 @@ class DubbedAnimeProvider : MainAPI() {
} }
val img = fixUrl(document.select("div.fkimgs > img").attr("src")) val img = fixUrl(document.select("div.fkimgs > img").attr("src"))
return AnimeLoadResponse( return newAnimeLoadResponse(title, url, TvType.Anime) {
null, posterUrl = img
null, this.year = year
title, addEpisodes(DubStatus.Dubbed, episodes)
url, plot = descript
this.name, }
TvType.Anime,
img,
year,
ArrayList(episodes),
null,
null,
descript,
)
} }
} }
} }

View file

@ -185,24 +185,18 @@ class GogoanimeProvider : MainAPI() {
"Episode " + it.selectFirst(".name").text().replace("EP", "").trim() "Episode " + it.selectFirst(".name").text().replace("EP", "").trim()
) )
}.reversed() }.reversed()
return AnimeLoadResponse(
title, return newAnimeLoadResponse(title, link, getType(type.toString())) {
nativeName, japName = nativeName
title, engName = title
link, posterUrl = poster
this.name, this.year = year
getType(type.toString()), addEpisodes(DubStatus.Subbed, episodes) // TODO CHECK
poster, plot = description
year, tags = genre
null,
episodes, showStatus = getStatus(status.toString())
getStatus(status.toString()), }
description,
ArrayList(genre),
null,
null,
null,
)
} }
private fun extractVideos(uri: String): List<ExtractorLink> { private fun extractVideos(uri: String): List<ExtractorLink> {

View file

@ -109,23 +109,13 @@ class KawaiifuProvider : MainAPI() {
} }
val poster = soup.selectFirst("a.thumb > img").attr("src") val poster = soup.selectFirst("a.thumb > img").attr("src")
return newAnimeLoadResponse(title, url, TvType.Anime) {
return AnimeLoadResponse( this.year = year
title, posterUrl = poster
null, addEpisodes(DubStatus.Subbed, episodes)
title, plot = description
url, this.tags = tags
this.name, }
TvType.Anime,
poster,
year,
null,
episodes,
ShowStatus.Ongoing,
description,
ArrayList(tags),
ArrayList()
)
} }
override fun loadLinks( override fun loadLinks(

View file

@ -268,24 +268,19 @@ class TenshiProvider : MainAPI() {
val synonyms = val synonyms =
document.select("li.synonym.meta-data > div.info-box > span.value").map { it?.text()?.trim().toString() } document.select("li.synonym.meta-data > div.info-box > span.value").map { it?.text()?.trim().toString() }
return AnimeLoadResponse( return newAnimeLoadResponse(canonicalTitle,url,getType(type ?: "")) {
englishTitle, engName = englishTitle
japaneseTitle, japName = japaneseTitle
canonicalTitle,
url, posterUrl = poster
this.name, this.year = year.toIntOrNull()
getType(type ?: ""),
poster, addEpisodes(DubStatus.Subbed,episodes)
year.toIntOrNull(), showStatus = status
null, tags = genre
episodes, this.synonyms = synonyms
status, plot = synopsis
synopsis, }
ArrayList(genre),
ArrayList(synonyms),
null,
null,
)
} }

View file

@ -208,22 +208,16 @@ class WcoProvider : MainAPI() {
val genre = document.select("div.elements div.row > div:nth-child(1) > div.row-line:nth-child(5) > a") val genre = document.select("div.elements div.row > div:nth-child(1) > div.row-line:nth-child(5) > a")
.map { it?.text()?.trim().toString() } .map { it?.text()?.trim().toString() }
return AnimeLoadResponse( return newAnimeLoadResponse(canonicalTitle,url,getType(type ?: "")) {
canonicalTitle, japName = japaneseTitle
japaneseTitle, engName = canonicalTitle
canonicalTitle, posterUrl = poster
url, this.year = year
this.name, addEpisodes(if(isDubbed) DubStatus.Dubbed else DubStatus.Subbed,episodes)
getType(type ?: ""), showStatus = status
poster, plot = synopsis
year, tags = genre
if (isDubbed) episodes else null, }
if (!isDubbed) episodes else null,
status,
synopsis,
ArrayList(genre),
ArrayList(),
)
} }
override fun loadLinks( override fun loadLinks(

View file

@ -230,21 +230,17 @@ class ZoroProvider : MainAPI() {
it.selectFirst(".ssli-order")?.text()?.toIntOrNull() it.selectFirst(".ssli-order")?.text()?.toIntOrNull()
) )
} }
return AnimeLoadResponse(
title, return newAnimeLoadResponse(title, url, TvType.Anime) {
japaneseTitle, japName = japaneseTitle
title, engName = title
url, posterUrl = poster
this.name, this.year = year
TvType.Anime, addEpisodes(DubStatus.Subbed, episodes)
poster, showStatus = status
year, plot = description
null, this.tags = tags
episodes, }
status,
description,
tags,
)
} }
private fun getM3u8FromRapidCloud(url: String): String { private fun getM3u8FromRapidCloud(url: String): String {

View file

@ -235,7 +235,7 @@ class ResultFragment : Fragment() {
private var currentId: Int? = null private var currentId: Int? = null
private var currentIsMovie: Boolean? = null private var currentIsMovie: Boolean? = null
private var episodeRanges: List<String>? = null private var episodeRanges: List<String>? = null
private var dubRange: Set<DubStatus>? = null
var url: String? = null var url: String? = null
private fun fromIndexToSeasonText(selection: Int?): String { private fun fromIndexToSeasonText(selection: Int?): String {
@ -681,7 +681,6 @@ class ResultFragment : Fragment() {
} }
outputFile.writeText(text) outputFile.writeText(text)
val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT) val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
vlcIntent.setPackage(VLC_PACKAGE) vlcIntent.setPackage(VLC_PACKAGE)
@ -858,6 +857,25 @@ class ResultFragment : Fragment() {
} }
} }
observe(viewModel.dubStatus) { status ->
result_dub_select?.text = status.toString()
}
observe(viewModel.dubSubSelections) { range ->
dubRange = range
result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE
}
result_dub_select.setOnClickListener {
val ranges = dubRange
if (ranges != null) {
it.popupMenuNoIconsAndNoStringRes(ranges.map { status -> Pair(status.ordinal, status.toString()) }
.toList()) {
viewModel.changeDubStatus(requireContext(), DubStatus.values()[itemId])
}
}
}
observe(viewModel.selectedRange) { range -> observe(viewModel.selectedRange) { range ->
result_episode_select?.text = range result_episode_select?.text = range
} }
@ -962,8 +980,10 @@ class ResultFragment : Fragment() {
if (metadataInfoArray.size > 0) { if (metadataInfoArray.size > 0) {
result_metadata.visibility = VISIBLE result_metadata.visibility = VISIBLE
val text = SpannableStringBuilder() val text = SpannableStringBuilder()
val grayColor = ctx.colorFromAttribute(R.attr.grayTextColor) //ContextCompat.getColor(requireContext(), R.color.grayTextColor) val grayColor =
val textColor = ctx.colorFromAttribute(R.attr.textColor) //ContextCompat.getColor(requireContext(), R.color.textColor) ctx.colorFromAttribute(R.attr.grayTextColor) //ContextCompat.getColor(requireContext(), R.color.grayTextColor)
val textColor =
ctx.colorFromAttribute(R.attr.textColor) //ContextCompat.getColor(requireContext(), R.color.textColor)
for (meta in metadataInfoArray) { for (meta in metadataInfoArray) {
text.color(grayColor) { append(getString(meta.first) + ": ") } text.color(grayColor) { append(getString(meta.first) + ": ") }
.color(textColor) { append("${meta.second}\n") } .color(textColor) { append("${meta.second}\n") }

View file

@ -55,13 +55,20 @@ class ResultViewModel : ViewModel() {
val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes
val publicEpisodesCount: LiveData<Int> get() = _publicEpisodesCount val publicEpisodesCount: LiveData<Int> get() = _publicEpisodesCount
private val dubStatus: MutableLiveData<DubStatus> = MutableLiveData() val dubStatus: MutableLiveData<DubStatus> get() = _dubStatus
private val _dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
private val page: MutableLiveData<LoadResponse> = MutableLiveData() private val page: MutableLiveData<LoadResponse> = MutableLiveData()
val id: MutableLiveData<Int> = MutableLiveData() val id: MutableLiveData<Int> = MutableLiveData()
val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2) val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData() val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
val dubSubSelections: MutableLiveData<Set<DubStatus>> get() = _dubSubSelections
private val _dubSubSelections: MutableLiveData<Set<DubStatus>> = MutableLiveData()
val dubSubEpisodes: MutableLiveData<Map<DubStatus, List<ResultEpisode>>?> get() = _dubSubEpisodes
private val _dubSubEpisodes: MutableLiveData<Map<DubStatus, List<ResultEpisode>>?> = MutableLiveData()
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData() private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData()
val watchStatus: LiveData<WatchType> get() = _watchStatus val watchStatus: LiveData<WatchType> get() = _watchStatus
@ -110,7 +117,7 @@ class ResultViewModel : ViewModel() {
val seasons = seasonTypes.toList().map { it.first }.sortedBy { it } val seasons = seasonTypes.toList().map { it.first }.sortedBy { it }
seasonSelections.postValue(seasons) seasonSelections.postValue(seasons)
if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS
_publicEpisodes.postValue(Resource.Success( ArrayList())) _publicEpisodes.postValue(Resource.Success(ArrayList()))
return return
} }
@ -160,7 +167,7 @@ class ResultViewModel : ViewModel() {
selectedRange.postValue(allRange) selectedRange.postValue(allRange)
} }
_publicEpisodes.postValue(Resource.Success( currentList)) _publicEpisodes.postValue(Resource.Success(currentList))
} }
fun changeSeason(context: Context, selection: Int?) { fun changeSeason(context: Context, selection: Int?) {
@ -171,6 +178,13 @@ class ResultViewModel : ViewModel() {
filterEpisodes(context, _episodes.value, null, range) filterEpisodes(context, _episodes.value, null, range)
} }
fun changeDubStatus(context: Context, status: DubStatus?) {
dubSubEpisodes.value?.get(status)?.let { episodes ->
dubStatus.postValue(status)
updateEpisodes(context, null, episodes, null)
}
}
private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) { private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) {
_episodes.postValue(list) _episodes.postValue(list)
val set = HashMap<Int, Int>() val set = HashMap<Int, Int>()
@ -237,7 +251,7 @@ class ResultViewModel : ViewModel() {
return name return name
} }
fun load(context: Context, url: String, apiName: String, showFillers : Boolean) = viewModelScope.launch { fun load(context: Context, url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
_resultResponse.postValue(Resource.Loading(url)) _resultResponse.postValue(Resource.Loading(url))
_publicEpisodes.postValue(Resource.Loading()) _publicEpisodes.postValue(Resource.Loading())
@ -273,16 +287,22 @@ class ResultViewModel : ViewModel() {
when (d) { when (d) {
is AnimeLoadResponse -> { is AnimeLoadResponse -> {
val isDub = d.dubEpisodes != null && d.dubEpisodes.isNotEmpty() //TODO context.getKey<>() isdub
dubStatus.postValue(if (isDub) DubStatus.Dubbed else DubStatus.Subbed)
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes) val isDub =
d.episodes.containsKey(DubStatus.Dubbed) && !d.episodes[DubStatus.Dubbed].isNullOrEmpty()
val dubStatus = if (isDub) DubStatus.Dubbed else DubStatus.Subbed
_dubStatus.postValue(dubStatus)
val fillerEpisodes = if(showFillers) safeApiCall { getFillerEpisodes(d.name) } else null _dubSubSelections.postValue(d.episodes.keys)
val fillerEpisodes = if (showFillers) safeApiCall { getFillerEpisodes(d.name) } else null
if (dataList != null) { // TODO dub and sub at the same time var idIndex = mainId
val res = d.episodes.map { ep ->
val episodes = ArrayList<ResultEpisode>() val episodes = ArrayList<ResultEpisode>()
for ((index, i) in dataList.withIndex()) { for ((index, i) in ep.value.withIndex()) {
idIndex++
val episode = i.episode ?: (index + 1) val episode = i.episode ?: (index + 1)
episodes.add( episodes.add(
context.buildResultEpisode( context.buildResultEpisode(
@ -292,17 +312,22 @@ class ResultViewModel : ViewModel() {
null, // TODO FIX SEASON null, // TODO FIX SEASON
i.url, i.url,
apiName, apiName,
(mainId + index + 1), idIndex,
index, index,
i.rating, i.rating,
i.descript, i.description,
if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let { if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let {
it.contains(episode) && it[episode] == true it.contains(episode) && it[episode] == true
} } ?: false else false,
?: false else false,
) )
) )
} }
Pair(ep.key, episodes)
}.toMap()
_dubSubEpisodes.postValue(res)
res[dubStatus]?.let { episodes ->
updateEpisodes(context, mainId, episodes, -1) updateEpisodes(context, mainId, episodes, -1)
} }
} }
@ -366,11 +391,10 @@ class ResultViewModel : ViewModel() {
), -1 ), -1
) )
} }
} }
} }
else -> { else -> {
// nothing
} }
} }
} }

View file

@ -431,8 +431,23 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
style="@style/MultiSelectButton" style="@style/MultiSelectButton"
> />
</com.google.android.material.button.MaterialButton>
<com.google.android.material.button.MaterialButton
tools:visibility="visible"
tools:text="Dubbed"
android:nextFocusUp="@id/result_descript"
android:nextFocusRight="@id/result_season_button"
android:nextFocusLeft="@id/result_season_button"
android:nextFocusDown="@id/result_episodes"
android:id="@+id/result_dub_select"
android:visibility="gone"
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
style="@style/MultiSelectButton"
/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"