forked from recloudstream/cloudstream
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
2f69fffe87
18 changed files with 349 additions and 134 deletions
|
@ -36,7 +36,7 @@ android {
|
|||
targetSdkVersion 30
|
||||
|
||||
versionCode 45
|
||||
versionName "2.9.18"
|
||||
versionName "2.9.19"
|
||||
|
||||
resValue "string", "app_version",
|
||||
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"
|
||||
|
@ -96,8 +96,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha03'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha03'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha04'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha04'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
@ -126,6 +126,8 @@ dependencies {
|
|||
implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
|
||||
implementation 'com.google.android.exoplayer:extension-cast:2.16.1'
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:2.16.1"
|
||||
implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1'
|
||||
|
||||
//implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
|
||||
|
||||
// Bug reports
|
||||
|
@ -154,7 +156,6 @@ dependencies {
|
|||
// Networking
|
||||
implementation "com.squareup.okhttp3:okhttp:4.9.2"
|
||||
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1"
|
||||
implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1'
|
||||
|
||||
// Util to skip the URI file fuckery 🙏
|
||||
implementation "com.github.tachiyomiorg:unifile:17bec43"
|
||||
|
|
|
@ -197,7 +197,8 @@ class NineAnimeProvider : MainAPI() {
|
|||
)
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
val doc = app.get(url).document
|
||||
val validUrl = url.replace("https://9anime.to", mainUrl)
|
||||
val doc = app.get(validUrl).document
|
||||
val animeid = doc.selectFirst("div.player-wrapper.watchpage").attr("data-id") ?: return null
|
||||
val animeidencoded = encode(getVrf(animeid) ?: return null)
|
||||
val poster = doc.selectFirst("aside.main div.thumb div img").attr("src")
|
||||
|
@ -233,7 +234,7 @@ class NineAnimeProvider : MainAPI() {
|
|||
else null
|
||||
val tags = doc.select("div.info .meta .col1 div:contains(Genre) a").map { it.text() }
|
||||
|
||||
return newAnimeLoadResponse(title, url, tvType) {
|
||||
return newAnimeLoadResponse(title, validUrl, tvType) {
|
||||
this.posterUrl = poster
|
||||
this.plot = description
|
||||
this.recommendations = recommendations
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package com.lagradost.cloudstream3.metaproviders
|
||||
|
||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
|
||||
import com.lagradost.cloudstream3.utils.SyncUtil
|
||||
|
||||
object SyncRedirector {
|
||||
val syncApis = SyncApis
|
||||
|
||||
suspend fun redirect(url: String, preferredUrl: String): String {
|
||||
for (api in syncApis) {
|
||||
if (url.contains(api.mainUrl)) {
|
||||
val otherApi = when (api.name) {
|
||||
aniListApi.name -> "anilist"
|
||||
malApi.name -> "myanimelist"
|
||||
else -> return url
|
||||
}
|
||||
|
||||
return SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
|
||||
realUrl.contains(preferredUrl)
|
||||
} ?: run {
|
||||
throw ErrorLoadingException("Page does not exist on $preferredUrl")
|
||||
}
|
||||
}
|
||||
}
|
||||
return url
|
||||
}
|
||||
}
|
|
@ -4,13 +4,26 @@ import com.lagradost.cloudstream3.*
|
|||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.AniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi
|
||||
import com.lagradost.cloudstream3.utils.SyncUtil
|
||||
|
||||
// wont be implemented
|
||||
class MultiAnimeProvider : MainAPI() {
|
||||
override var name = "MultiAnime"
|
||||
override val lang = "en"
|
||||
override val usesWebView = true
|
||||
override val supportedTypes = setOf(TvType.Anime)
|
||||
private val syncApi = OAuth2API.aniListApi
|
||||
private val syncApi: SyncAPI = OAuth2API.aniListApi
|
||||
|
||||
private val syncUtilType by lazy {
|
||||
when (syncApi) {
|
||||
is AniListApi -> "anilist"
|
||||
is MALApi -> "myanimelist"
|
||||
else -> throw ErrorLoadingException("Invalid Api")
|
||||
}
|
||||
}
|
||||
|
||||
private val validApis by lazy {
|
||||
APIHolder.apis.filter {
|
||||
|
@ -32,13 +45,25 @@ class MultiAnimeProvider : MainAPI() {
|
|||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
return syncApi.getResult(url)?.let { res ->
|
||||
newAnimeLoadResponse(res.title!!, url, TvType.Anime) {
|
||||
val data = SyncUtil.getUrlsFromId(res.id, syncUtilType).apmap { url ->
|
||||
validApis.firstOrNull { api -> url.startsWith(api.mainUrl) }?.load(url)
|
||||
}.filterNotNull()
|
||||
|
||||
val type =
|
||||
if (data.any { it.type == TvType.AnimeMovie }) TvType.AnimeMovie else TvType.Anime
|
||||
|
||||
newAnimeLoadResponse(
|
||||
res.title ?: throw ErrorLoadingException("No Title found"),
|
||||
url,
|
||||
type
|
||||
) {
|
||||
posterUrl = res.posterUrl
|
||||
plot = res.synopsis
|
||||
tags = res.genres
|
||||
rating = res.publicScore
|
||||
addTrailer(res.trailerUrl)
|
||||
addAniListId(res.id.toIntOrNull())
|
||||
recommendations = res.recommendations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -218,31 +218,26 @@ class NginxProvider : MainAPI() {
|
|||
|
||||
if (isMovieType) {
|
||||
val movieName = nfoContent.select("title").text()
|
||||
|
||||
val posterUrl = mediaRootUrl + "poster.jpg"
|
||||
|
||||
return@mapNotNull MovieSearchResponse(
|
||||
return@mapNotNull newMovieSearchResponse(
|
||||
movieName,
|
||||
mediaRootUrl,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
posterUrl,
|
||||
null,
|
||||
)
|
||||
) {
|
||||
addPoster(posterUrl, authHeader)
|
||||
}
|
||||
} else { // tv serie
|
||||
val serieName = nfoContent.select("title").text()
|
||||
|
||||
val posterUrl = mediaRootUrl + "poster.jpg"
|
||||
|
||||
TvSeriesSearchResponse(
|
||||
newTvSeriesSearchResponse(
|
||||
serieName,
|
||||
nfoPath,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
posterUrl,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
) {
|
||||
addPoster(posterUrl, authHeader)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -605,14 +605,12 @@ open class SflixProvider : MainAPI() {
|
|||
M3u8Helper().m3u8Generation(M3u8Helper.M3u8Stream(this.file, null), true)
|
||||
.map { stream ->
|
||||
//println("stream: ${stream.quality} at ${stream.streamUrl}")
|
||||
val qualityString = if ((stream.quality ?: 0) == 0) label
|
||||
?: "" else "${stream.quality}p"
|
||||
ExtractorLink(
|
||||
caller.name,
|
||||
"${caller.name} $qualityString $name",
|
||||
"${caller.name} $name",
|
||||
stream.streamUrl,
|
||||
caller.mainUrl,
|
||||
getQualityFromName(stream.quality.toString()),
|
||||
getQualityFromName(stream.quality?.toString()),
|
||||
true,
|
||||
extractorData = extractorData
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import com.lagradost.cloudstream3.ActorData
|
||||
import com.lagradost.cloudstream3.ShowStatus
|
||||
import com.lagradost.cloudstream3.*
|
||||
|
||||
interface SyncAPI : OAuth2API {
|
||||
val icon: Int
|
||||
|
@ -24,13 +23,19 @@ interface SyncAPI : OAuth2API {
|
|||
|
||||
suspend fun search(name: String): List<SyncSearchResult>?
|
||||
|
||||
fun getIdFromUrl(url : String) : String
|
||||
|
||||
data class SyncSearchResult(
|
||||
val name: String,
|
||||
val syncApiName: String,
|
||||
val id: String,
|
||||
val url: String,
|
||||
val posterUrl: String?,
|
||||
)
|
||||
override val name: String,
|
||||
override val apiName: String,
|
||||
var syncId: String,
|
||||
override val url: String,
|
||||
override var posterUrl: String?,
|
||||
override var type: TvType? = null,
|
||||
override var quality: SearchQuality? = null,
|
||||
override var posterHeaders: Map<String, String>? = null,
|
||||
override var id: Int? = null,
|
||||
) : SearchResponse
|
||||
|
||||
data class SyncNextAiring(
|
||||
val episode: Int,
|
||||
|
|
|
@ -9,6 +9,7 @@ class SyncRepo(private val repo: SyncAPI) {
|
|||
val idPrefix = repo.idPrefix
|
||||
val name = repo.name
|
||||
val icon = repo.icon
|
||||
val mainUrl = repo.mainUrl
|
||||
|
||||
suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> {
|
||||
return safeApiCall { repo.score(id, status) }
|
||||
|
@ -29,4 +30,6 @@ class SyncRepo(private val repo: SyncAPI) {
|
|||
fun hasAccount() : Boolean {
|
||||
return normalSafeApiCall { repo.loginInfo() != null } ?: false
|
||||
}
|
||||
|
||||
fun getIdFromUrl(url : String) : String = repo.getIdFromUrl(url)
|
||||
}
|
|
@ -70,6 +70,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return user != null
|
||||
}
|
||||
|
||||
override fun getIdFromUrl(url : String): String {
|
||||
return url.removePrefix("$mainUrl/anime/").removeSuffix("/")
|
||||
}
|
||||
|
||||
private fun getUrlFromId(id: Int): String {
|
||||
return "$mainUrl/anime/$id"
|
||||
}
|
||||
|
||||
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult>? {
|
||||
val data = searchShows(name) ?: return null
|
||||
return data.data?.Page?.media?.map {
|
||||
|
@ -77,7 +85,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
it.title.romaji ?: return null,
|
||||
this.name,
|
||||
it.id.toString(),
|
||||
"$mainUrl/anime/${it.id}",
|
||||
getUrlFromId(it.id),
|
||||
it.bannerImage
|
||||
)
|
||||
}
|
||||
|
@ -126,7 +134,16 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
},
|
||||
publicScore = season.averageScore?.times(100),
|
||||
//recommendations = season.
|
||||
recommendations = season.recommendations?.edges?.mapNotNull { rec ->
|
||||
val recMedia = rec.node.mediaRecommendation
|
||||
SyncAPI.SyncSearchResult(
|
||||
name = recMedia.title?.userPreferred ?: return@mapNotNull null,
|
||||
this.name,
|
||||
recMedia.id?.toString() ?: return@mapNotNull null,
|
||||
getUrlFromId(recMedia.id),
|
||||
recMedia.coverImage?.large ?: recMedia.coverImage?.medium
|
||||
)
|
||||
}
|
||||
//TODO REST
|
||||
)
|
||||
}
|
||||
|
@ -396,6 +413,27 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
}
|
||||
recommendations {
|
||||
edges {
|
||||
node {
|
||||
mediaRecommendation {
|
||||
id
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
medium
|
||||
color
|
||||
}
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
userPreferred
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring
|
||||
episode
|
||||
|
@ -772,6 +810,21 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("trailer") val trailer: MediaTrailer?,
|
||||
@JsonProperty("description") val description: String?,
|
||||
@JsonProperty("characters") val characters: CharacterConnection?,
|
||||
@JsonProperty("recommendations") val recommendations: RecommendationConnection?,
|
||||
)
|
||||
|
||||
data class RecommendationConnection(
|
||||
@JsonProperty("edges") val edges: List<RecommendationEdge> = emptyList(),
|
||||
@JsonProperty("nodes") val nodes: List<Recommendation> = emptyList(),
|
||||
//@JsonProperty("pageInfo") val pageInfo: PageInfo,
|
||||
)
|
||||
|
||||
data class RecommendationEdge(
|
||||
//@JsonProperty("rating") val rating: Int,
|
||||
@JsonProperty("node") val node: Recommendation,
|
||||
)
|
||||
data class Recommendation(
|
||||
@JsonProperty("mediaRecommendation") val mediaRecommendation: SeasonMedia,
|
||||
)
|
||||
|
||||
data class CharacterName(
|
||||
|
@ -955,13 +1008,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("data") val data: LikeData?,
|
||||
)
|
||||
|
||||
data class Recommendation(
|
||||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("idMal") val idMal: Int?,
|
||||
@JsonProperty("poster") val poster: String?,
|
||||
@JsonProperty("averageScore") val averageScore: Int?
|
||||
)
|
||||
|
||||
data class AniListTitleHolder(
|
||||
@JsonProperty("title") val title: Title?,
|
||||
@JsonProperty("isFavourite") val isFavourite: Boolean?,
|
||||
|
|
|
@ -81,6 +81,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
override fun getIdFromUrl(url: String): String {
|
||||
return Regex("""/anime/((.*)/|(.*))""").find(url)!!.groupValues.first()
|
||||
}
|
||||
|
||||
override suspend fun score(id: String, status: SyncAPI.SyncStatus): Boolean {
|
||||
return setScoreRequest(
|
||||
id.toIntOrNull() ?: return false,
|
||||
|
@ -173,8 +177,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
private fun toSearchResult(node: Node?): SyncAPI.SyncSearchResult? {
|
||||
return SyncAPI.SyncSearchResult(
|
||||
name = node?.title ?: return null,
|
||||
syncApiName = this.name,
|
||||
id = node.id.toString(),
|
||||
apiName = this.name,
|
||||
syncId = node.id.toString(),
|
||||
url = "https://myanimelist.net/anime/${node.id}",
|
||||
posterUrl = node.main_picture?.large
|
||||
)
|
||||
|
|
|
@ -93,6 +93,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName
|
|||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
|
||||
import kotlinx.android.synthetic.main.fragment_result.*
|
||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||
import kotlinx.android.synthetic.main.result_recommendations.*
|
||||
import kotlinx.android.synthetic.main.result_sync.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -572,14 +573,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
|
||||
}
|
||||
|
||||
private fun setMalSync(id: Int?): Boolean {
|
||||
return syncModel.setMalId(id?.toString())
|
||||
}
|
||||
|
||||
private fun setAniListSync(id: Int?): Boolean {
|
||||
return syncModel.setAniListId(id?.toString())
|
||||
}
|
||||
|
||||
private fun setActors(actors: List<ActorData>?) {
|
||||
if (actors.isNullOrEmpty()) {
|
||||
result_cast_text?.isVisible = false
|
||||
|
@ -601,23 +594,47 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
}
|
||||
}
|
||||
|
||||
private fun setRecommendations(rec: List<SearchResponse>?) {
|
||||
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
||||
val isInvalid = rec.isNullOrEmpty()
|
||||
result_recommendations?.isGone = isInvalid
|
||||
result_recommendations_btt?.isGone = isInvalid
|
||||
result_recommendations_btt?.setOnClickListener {
|
||||
if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
|
||||
result_recommendations_btt?.nextFocusDownId = R.id.result_recommendations
|
||||
val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
|
||||
result_overlapping_panels?.openEndPanel()
|
||||
R.id.result_recommendations
|
||||
} else {
|
||||
result_recommendations_btt?.nextFocusDownId = R.id.result_description
|
||||
result_overlapping_panels?.closePanels()
|
||||
R.id.result_description
|
||||
}
|
||||
|
||||
result_recommendations_btt?.nextFocusDownId = nextFocusDown
|
||||
result_search?.nextFocusDownId = nextFocusDown
|
||||
result_open_in_browser?.nextFocusDownId = nextFocusDown
|
||||
result_share?.nextFocusDownId = nextFocusDown
|
||||
}
|
||||
result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
|
||||
|
||||
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
|
||||
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
|
||||
// very dirty selection
|
||||
result_recommendations_filter_button?.isVisible = apiNames.size > 1
|
||||
result_recommendations_filter_button?.text = matchAgainst
|
||||
result_recommendations_filter_button?.setOnClickListener { _ ->
|
||||
activity?.showBottomDialog(
|
||||
apiNames,
|
||||
apiNames.indexOf(matchAgainst),
|
||||
getString(R.string.home_change_provider_img_des), false, {}
|
||||
) {
|
||||
setRecommendations(rec, apiNames[it])
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
result_recommendations_filter_button?.isVisible = false
|
||||
}
|
||||
|
||||
result_recommendations?.post {
|
||||
rec?.let { list ->
|
||||
(result_recommendations?.adapter as SearchAdapter?)?.updateList(list)
|
||||
(result_recommendations?.adapter as SearchAdapter?)?.updateList(list.filter { it.apiName == matchAgainst })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1086,7 +1103,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
ACTION_PLAY_EPISODE_IN_PLAYER -> {
|
||||
viewModel.getGenerator(episodeClick.data)
|
||||
?.let { generator ->
|
||||
println("LANUCJ:::: $syncdata")
|
||||
activity?.navigate(
|
||||
R.id.global_to_navigation_player,
|
||||
GeneratorPlayer.newInstance(
|
||||
|
@ -1641,7 +1657,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
setDuration(d.duration)
|
||||
setYear(d.year)
|
||||
setRating(d.rating)
|
||||
setRecommendations(d.recommendations)
|
||||
setRecommendations(d.recommendations, null)
|
||||
setActors(d.actors)
|
||||
|
||||
if (SettingsFragment.accountEnabled) {
|
||||
|
@ -1950,7 +1966,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
}
|
||||
}
|
||||
|
||||
result_recommendations.adapter = recAdapter
|
||||
result_recommendations?.adapter = recAdapter
|
||||
|
||||
context?.let { ctx ->
|
||||
result_bookmark_button?.isVisible = ctx.isTvSettings()
|
||||
|
|
|
@ -11,6 +11,9 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromUrlNull
|
|||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider
|
||||
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
|
@ -127,6 +130,17 @@ class ResultViewModel : ViewModel() {
|
|||
addTrailer(meta.trailerUrl)
|
||||
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
|
||||
actors = actors ?: meta.actors
|
||||
|
||||
val realRecommendations = ArrayList<SearchResponse>()
|
||||
val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
|
||||
meta.recommendations?.forEach { rec ->
|
||||
apiNames.forEach { name ->
|
||||
realRecommendations.add(rec.copy(apiName = name))
|
||||
}
|
||||
}
|
||||
|
||||
recommendations = recommendations?.union(realRecommendations)?.toList()
|
||||
?: realRecommendations
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,10 +310,9 @@ class ResultViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
|
||||
_resultResponse.postValue(Resource.Loading(url))
|
||||
_publicEpisodes.postValue(Resource.Loading())
|
||||
_resultResponse.postValue(Resource.Loading(url))
|
||||
|
||||
_apiName.postValue(apiName)
|
||||
val api = getApiFromNameNull(apiName) ?: getApiFromUrlNull(url)
|
||||
if (api == null) {
|
||||
_resultResponse.postValue(
|
||||
|
@ -312,9 +325,31 @@ class ResultViewModel : ViewModel() {
|
|||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val validUrlResource = safeApiCall {
|
||||
SyncRedirector.redirect(
|
||||
url,
|
||||
api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
|
||||
.replace(GogoanimeProvider().mainUrl, "gogoanime")
|
||||
)
|
||||
}
|
||||
|
||||
if (validUrlResource !is Resource.Success) {
|
||||
if (validUrlResource is Resource.Failure) {
|
||||
_resultResponse.postValue(validUrlResource)
|
||||
}
|
||||
|
||||
return@launch
|
||||
}
|
||||
val validUrl = validUrlResource.value
|
||||
|
||||
_resultResponse.postValue(Resource.Loading(validUrl))
|
||||
|
||||
_apiName.postValue(apiName)
|
||||
|
||||
repo = APIRepository(api)
|
||||
|
||||
val data = repo?.load(url) ?: return@launch
|
||||
val data = repo?.load(validUrl) ?: return@launch
|
||||
|
||||
_resultResponse.postValue(data)
|
||||
|
||||
|
@ -331,7 +366,7 @@ class ResultViewModel : ViewModel() {
|
|||
mainId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
apiName,
|
||||
url,
|
||||
validUrl,
|
||||
d.type,
|
||||
d.name,
|
||||
d.posterUrl,
|
||||
|
|
|
@ -82,16 +82,16 @@ class SyncViewModel : ViewModel() {
|
|||
var isValid = false
|
||||
|
||||
map?.forEach { (prefix, id) ->
|
||||
isValid = isValid || addSync(prefix, id)
|
||||
isValid = addSync(prefix, id) || isValid
|
||||
}
|
||||
return isValid
|
||||
}
|
||||
|
||||
fun setMalId(id: String?): Boolean {
|
||||
private fun setMalId(id: String?): Boolean {
|
||||
return addSync(malApi.idPrefix, id ?: return false)
|
||||
}
|
||||
|
||||
fun setAniListId(id: String?): Boolean {
|
||||
private fun setAniListId(id: String?): Boolean {
|
||||
return addSync(aniListApi.idPrefix, id ?: return false)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import com.lagradost.cloudstream3.SearchQuality
|
|||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
|
||||
class SyncSearchViewModel {
|
||||
private val repos = OAuth2API.SyncApis
|
||||
|
@ -20,15 +19,4 @@ class SyncSearchViewModel {
|
|||
override var posterHeaders: Map<String, String>? = null,
|
||||
) : SearchResponse
|
||||
|
||||
private fun SyncAPI.SyncSearchResult.toSearchResponse(): SyncSearchResultSearchResponse {
|
||||
return SyncSearchResultSearchResponse(
|
||||
this.name,
|
||||
this.url,
|
||||
this.syncApiName,
|
||||
null,
|
||||
this.posterUrl,
|
||||
null, //this.id.hashCode()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ import com.google.android.gms.cast.framework.CastSession
|
|||
import com.google.android.gms.cast.framework.media.RemoteMediaClient
|
||||
import com.google.android.gms.common.api.PendingResult
|
||||
import com.google.android.gms.common.images.WebImage
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.MetadataHolder
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||
|
@ -64,7 +65,10 @@ object CastHelper {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
fun awaitLinks(pending: PendingResult<RemoteMediaClient.MediaChannelResult>?, callback: (Boolean) -> Unit) {
|
||||
fun awaitLinks(
|
||||
pending: PendingResult<RemoteMediaClient.MediaChannelResult>?,
|
||||
callback: (Boolean) -> Unit
|
||||
) {
|
||||
if (pending == null) return
|
||||
main {
|
||||
val res = withContext(Dispatchers.IO) { pending.await() }
|
||||
|
@ -90,6 +94,7 @@ object CastHelper {
|
|||
startIndex: Int? = null,
|
||||
startTime: Long? = null,
|
||||
): Boolean {
|
||||
try {
|
||||
if (this == null) return false
|
||||
if (episodes.isEmpty()) return false
|
||||
if (currentEpisodeIndex >= episodes.size) return false
|
||||
|
@ -97,7 +102,16 @@ object CastHelper {
|
|||
val epData = episodes[currentEpisodeIndex]
|
||||
|
||||
val holder =
|
||||
MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks, subtitles)
|
||||
MetadataHolder(
|
||||
apiName,
|
||||
isMovie,
|
||||
title,
|
||||
poster,
|
||||
currentEpisodeIndex,
|
||||
episodes,
|
||||
currentLinks,
|
||||
subtitles
|
||||
)
|
||||
|
||||
val index = if (startIndex == null || startIndex < 0) 0 else startIndex
|
||||
|
||||
|
@ -106,7 +120,8 @@ object CastHelper {
|
|||
|
||||
awaitLinks(
|
||||
this.remoteMediaClient?.load(
|
||||
MediaLoadRequestData.Builder().setMediaInfo(mediaItem).setCurrentTime(startTime ?: 0L).build()
|
||||
MediaLoadRequestData.Builder().setMediaInfo(mediaItem)
|
||||
.setCurrentTime(startTime ?: 0L).build()
|
||||
)
|
||||
) {
|
||||
if (currentLinks.size > index + 1)
|
||||
|
@ -124,5 +139,9 @@ object CastHelper {
|
|||
)
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ object SyncUtil {
|
|||
* valid sites are: Gogoanime, Twistmoe and 9anime*/
|
||||
private suspend fun getIdsFromSlug(
|
||||
slug: String,
|
||||
site: String = "GogoanimeGogoanime"
|
||||
site: String = "Gogoanime"
|
||||
): Pair<String?, String?>? {
|
||||
Log.i(TAG, "getIdsFromSlug $slug $site")
|
||||
try {
|
||||
|
@ -76,6 +76,28 @@ object SyncUtil {
|
|||
return null
|
||||
}
|
||||
|
||||
suspend fun getUrlsFromId(id: String, type: String = "anilist") : List<String> {
|
||||
val url =
|
||||
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
|
||||
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).mapped<SyncPage>()
|
||||
val pages = response.pages ?: return emptyList()
|
||||
return pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }
|
||||
}
|
||||
|
||||
data class SyncPage(
|
||||
@JsonProperty("Pages") val pages: SyncPages?,
|
||||
)
|
||||
|
||||
data class SyncPages(
|
||||
@JsonProperty("9anime") val nineanime: Map<String, ProviderPage> = emptyMap(),
|
||||
@JsonProperty("Gogoanime") val gogoanime: Map<String, ProviderPage> = emptyMap(),
|
||||
@JsonProperty("Twistmoe") val twistmoe: Map<String, ProviderPage> = emptyMap(),
|
||||
)
|
||||
|
||||
data class ProviderPage(
|
||||
@JsonProperty("url") val url: String?,
|
||||
)
|
||||
|
||||
data class MalSyncPage(
|
||||
@JsonProperty("identifier") val identifier: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
|
|
|
@ -180,17 +180,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end">
|
||||
|
||||
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
app:spanCount="3"
|
||||
android:id="@+id/result_recommendations"
|
||||
tools:listitem="@layout/search_result_grid"
|
||||
android:orientation="vertical" />
|
||||
<include layout="@layout/result_recommendations" />
|
||||
</FrameLayout>
|
||||
</com.discord.panels.OverlappingPanelsLayout>
|
||||
|
||||
|
|
37
app/src/main/res/layout/result_recommendations.xml
Normal file
37
app/src/main/res/layout/result_recommendations.xml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/BlackButton"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:text="GogoAnime"
|
||||
android:id="@+id/result_recommendations_filter_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/result_recommendations" />
|
||||
|
||||
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
app:spanCount="3"
|
||||
android:id="@+id/result_recommendations"
|
||||
tools:listitem="@layout/search_result_grid"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
Loading…
Reference in a new issue