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 |         targetSdkVersion 30 | ||||||
| 
 | 
 | ||||||
|         versionCode 45 |         versionCode 45 | ||||||
|         versionName "2.9.18" |         versionName "2.9.19" | ||||||
| 
 | 
 | ||||||
|         resValue "string", "app_version", |         resValue "string", "app_version", | ||||||
|                 "${defaultConfig.versionName}${versionNameSuffix ?: ""}" |                 "${defaultConfig.versionName}${versionNameSuffix ?: ""}" | ||||||
|  | @ -96,8 +96,8 @@ dependencies { | ||||||
|     implementation 'androidx.appcompat:appcompat:1.4.1' |     implementation 'androidx.appcompat:appcompat:1.4.1' | ||||||
|     implementation 'com.google.android.material:material:1.5.0' |     implementation 'com.google.android.material:material:1.5.0' | ||||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.3' |     implementation 'androidx.constraintlayout:constraintlayout:2.1.3' | ||||||
|     implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha03' |     implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha04' | ||||||
|     implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha03' |     implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha04' | ||||||
|     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' |     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' | ||||||
|     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' |     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' | ||||||
|     testImplementation 'junit:junit:4.13.2' |     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:exoplayer:2.16.1' | ||||||
|     implementation 'com.google.android.exoplayer:extension-cast: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-mediasession:2.16.1" | ||||||
|  |     implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1' | ||||||
|  | 
 | ||||||
|     //implementation "com.google.android.exoplayer:extension-leanback:2.14.0" |     //implementation "com.google.android.exoplayer:extension-leanback:2.14.0" | ||||||
| 
 | 
 | ||||||
|     // Bug reports |     // Bug reports | ||||||
|  | @ -154,7 +156,6 @@ dependencies { | ||||||
|     // Networking |     // Networking | ||||||
|     implementation "com.squareup.okhttp3:okhttp:4.9.2" |     implementation "com.squareup.okhttp3:okhttp:4.9.2" | ||||||
|     implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1" |     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 🙏 |     // Util to skip the URI file fuckery 🙏 | ||||||
|     implementation "com.github.tachiyomiorg:unifile:17bec43" |     implementation "com.github.tachiyomiorg:unifile:17bec43" | ||||||
|  |  | ||||||
|  | @ -197,7 +197,8 @@ class NineAnimeProvider : MainAPI() { | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     override suspend fun load(url: String): LoadResponse? { |     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 animeid = doc.selectFirst("div.player-wrapper.watchpage").attr("data-id") ?: return null | ||||||
|         val animeidencoded = encode(getVrf(animeid) ?: return null) |         val animeidencoded = encode(getVrf(animeid) ?: return null) | ||||||
|         val poster = doc.selectFirst("aside.main div.thumb div img").attr("src") |         val poster = doc.selectFirst("aside.main div.thumb div img").attr("src") | ||||||
|  | @ -233,7 +234,7 @@ class NineAnimeProvider : MainAPI() { | ||||||
|             else null |             else null | ||||||
|         val tags = doc.select("div.info .meta .col1 div:contains(Genre) a").map { it.text() } |         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.posterUrl = poster | ||||||
|             this.plot = description |             this.plot = description | ||||||
|             this.recommendations = recommendations |             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.addAniListId | ||||||
| import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer | import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2API | 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() { | class MultiAnimeProvider : MainAPI() { | ||||||
|     override var name = "MultiAnime" |     override var name = "MultiAnime" | ||||||
|     override val lang = "en" |     override val lang = "en" | ||||||
|     override val usesWebView = true |     override val usesWebView = true | ||||||
|     override val supportedTypes = setOf(TvType.Anime) |     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 { |     private val validApis by lazy { | ||||||
|         APIHolder.apis.filter { |         APIHolder.apis.filter { | ||||||
|  | @ -32,13 +45,25 @@ class MultiAnimeProvider : MainAPI() { | ||||||
| 
 | 
 | ||||||
|     override suspend fun load(url: String): LoadResponse? { |     override suspend fun load(url: String): LoadResponse? { | ||||||
|         return syncApi.getResult(url)?.let { res -> |         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 |                 posterUrl = res.posterUrl | ||||||
|                 plot = res.synopsis |                 plot = res.synopsis | ||||||
|                 tags = res.genres |                 tags = res.genres | ||||||
|                 rating = res.publicScore |                 rating = res.publicScore | ||||||
|                 addTrailer(res.trailerUrl) |                 addTrailer(res.trailerUrl) | ||||||
|                 addAniListId(res.id.toIntOrNull()) |                 addAniListId(res.id.toIntOrNull()) | ||||||
|  |                 recommendations = res.recommendations | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -218,31 +218,26 @@ class NginxProvider : MainAPI() { | ||||||
| 
 | 
 | ||||||
|                             if (isMovieType) { |                             if (isMovieType) { | ||||||
|                                 val movieName = nfoContent.select("title").text() |                                 val movieName = nfoContent.select("title").text() | ||||||
| 
 |  | ||||||
|                                 val posterUrl = mediaRootUrl + "poster.jpg" |                                 val posterUrl = mediaRootUrl + "poster.jpg" | ||||||
| 
 |                                 return@mapNotNull newMovieSearchResponse( | ||||||
|                                 return@mapNotNull MovieSearchResponse( |  | ||||||
|                                     movieName, |                                     movieName, | ||||||
|                                     mediaRootUrl, |                                     mediaRootUrl, | ||||||
|                                     this.name, |  | ||||||
|                                     TvType.Movie, |                                     TvType.Movie, | ||||||
|                                     posterUrl, |                                 ) { | ||||||
|                                     null, |                                     addPoster(posterUrl, authHeader) | ||||||
|                                 ) |                                 } | ||||||
|                             } else {  // tv serie |                             } else {  // tv serie | ||||||
|                                 val serieName = nfoContent.select("title").text() |                                 val serieName = nfoContent.select("title").text() | ||||||
| 
 | 
 | ||||||
|                                 val posterUrl = mediaRootUrl + "poster.jpg" |                                 val posterUrl = mediaRootUrl + "poster.jpg" | ||||||
| 
 | 
 | ||||||
|                                 TvSeriesSearchResponse( |                                 newTvSeriesSearchResponse( | ||||||
|                                     serieName, |                                     serieName, | ||||||
|                                     nfoPath, |                                     nfoPath, | ||||||
|                                     this.name, |  | ||||||
|                                     TvType.TvSeries, |                                     TvType.TvSeries, | ||||||
|                                     posterUrl, |                                 ) { | ||||||
|                                     null, |                                     addPoster(posterUrl, authHeader) | ||||||
|                                     null, |                                 } | ||||||
|                                 ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|  | @ -605,14 +605,12 @@ open class SflixProvider : MainAPI() { | ||||||
|                     M3u8Helper().m3u8Generation(M3u8Helper.M3u8Stream(this.file, null), true) |                     M3u8Helper().m3u8Generation(M3u8Helper.M3u8Stream(this.file, null), true) | ||||||
|                         .map { stream -> |                         .map { stream -> | ||||||
|                             //println("stream: ${stream.quality} at ${stream.streamUrl}") |                             //println("stream: ${stream.quality} at ${stream.streamUrl}") | ||||||
|                             val qualityString = if ((stream.quality ?: 0) == 0) label |  | ||||||
|                                 ?: "" else "${stream.quality}p" |  | ||||||
|                             ExtractorLink( |                             ExtractorLink( | ||||||
|                                 caller.name, |                                 caller.name, | ||||||
|                                 "${caller.name} $qualityString $name", |                                 "${caller.name} $name", | ||||||
|                                 stream.streamUrl, |                                 stream.streamUrl, | ||||||
|                                 caller.mainUrl, |                                 caller.mainUrl, | ||||||
|                                 getQualityFromName(stream.quality.toString()), |                                 getQualityFromName(stream.quality?.toString()), | ||||||
|                                 true, |                                 true, | ||||||
|                                 extractorData = extractorData |                                 extractorData = extractorData | ||||||
|                             ) |                             ) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.syncproviders | package com.lagradost.cloudstream3.syncproviders | ||||||
| 
 | 
 | ||||||
| import com.lagradost.cloudstream3.ActorData | import com.lagradost.cloudstream3.* | ||||||
| import com.lagradost.cloudstream3.ShowStatus |  | ||||||
| 
 | 
 | ||||||
| interface SyncAPI : OAuth2API { | interface SyncAPI : OAuth2API { | ||||||
|     val icon: Int |     val icon: Int | ||||||
|  | @ -24,13 +23,19 @@ interface SyncAPI : OAuth2API { | ||||||
| 
 | 
 | ||||||
|     suspend fun search(name: String): List<SyncSearchResult>? |     suspend fun search(name: String): List<SyncSearchResult>? | ||||||
| 
 | 
 | ||||||
|  |     fun getIdFromUrl(url : String) : String | ||||||
|  | 
 | ||||||
|     data class SyncSearchResult( |     data class SyncSearchResult( | ||||||
|         val name: String, |         override val name: String, | ||||||
|         val syncApiName: String, |         override val apiName: String, | ||||||
|         val id: String, |         var syncId: String, | ||||||
|         val url: String, |         override val url: String, | ||||||
|         val posterUrl: 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( |     data class SyncNextAiring( | ||||||
|         val episode: Int, |         val episode: Int, | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ class SyncRepo(private val repo: SyncAPI) { | ||||||
|     val idPrefix = repo.idPrefix |     val idPrefix = repo.idPrefix | ||||||
|     val name = repo.name |     val name = repo.name | ||||||
|     val icon = repo.icon |     val icon = repo.icon | ||||||
|  |     val mainUrl = repo.mainUrl | ||||||
| 
 | 
 | ||||||
|     suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> { |     suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> { | ||||||
|         return safeApiCall { repo.score(id, status) } |         return safeApiCall { repo.score(id, status) } | ||||||
|  | @ -29,4 +30,6 @@ class SyncRepo(private val repo: SyncAPI) { | ||||||
|     fun hasAccount() : Boolean { |     fun hasAccount() : Boolean { | ||||||
|         return normalSafeApiCall { repo.loginInfo() != null } ?: false |         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 |         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>? { |     override suspend fun search(name: String): List<SyncAPI.SyncSearchResult>? { | ||||||
|         val data = searchShows(name) ?: return null |         val data = searchShows(name) ?: return null | ||||||
|         return data.data?.Page?.media?.map { |         return data.data?.Page?.media?.map { | ||||||
|  | @ -77,7 +85,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                 it.title.romaji ?: return null, |                 it.title.romaji ?: return null, | ||||||
|                 this.name, |                 this.name, | ||||||
|                 it.id.toString(), |                 it.id.toString(), | ||||||
|                 "$mainUrl/anime/${it.id}", |                 getUrlFromId(it.id), | ||||||
|                 it.bannerImage |                 it.bannerImage | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  | @ -126,7 +134,16 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                 ) |                 ) | ||||||
|             }, |             }, | ||||||
|             publicScore = season.averageScore?.times(100), |             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 |             //TODO REST | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | @ -335,10 +352,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                            color |                            color | ||||||
|                        } |                        } | ||||||
|                        title { |                        title { | ||||||
|                             romaji |                            romaji | ||||||
|                             english |                            english | ||||||
|                             native |                            native | ||||||
|                             userPreferred |                            userPreferred | ||||||
|                        } |                        } | ||||||
|                        duration |                        duration | ||||||
|                        episodes |                        episodes | ||||||
|  | @ -382,23 +399,44 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                            thumbnail |                            thumbnail | ||||||
|                        } |                        } | ||||||
|                        relations { |                        relations { | ||||||
|                             edges { |                            edges { | ||||||
|                                  id |                                 id | ||||||
|                                  relationType(version: 2) |                                 relationType(version: 2) | ||||||
|                                  node { |                                 node { | ||||||
|                                       id |                                      id | ||||||
|                                       coverImage { |                                      coverImage { | ||||||
|                                           extraLarge |                                          extraLarge | ||||||
|                                           large |                                          large | ||||||
|                                           medium |                                          medium | ||||||
|                                           color |                                          color | ||||||
|                                       } |                                      } | ||||||
|                                  } |                                 } | ||||||
|                             } |                            } | ||||||
|  |                        } | ||||||
|  |                        recommendations { | ||||||
|  |                            edges { | ||||||
|  |                                node { | ||||||
|  |                                    mediaRecommendation { | ||||||
|  |                                        id | ||||||
|  |                                        coverImage { | ||||||
|  |                                            extraLarge | ||||||
|  |                                            large | ||||||
|  |                                            medium | ||||||
|  |                                            color | ||||||
|  |                                        } | ||||||
|  |                                        title { | ||||||
|  |                                            romaji | ||||||
|  |                                            english | ||||||
|  |                                            native | ||||||
|  |                                            userPreferred | ||||||
|  |                                        } | ||||||
|  |                                    } | ||||||
|  |                                } | ||||||
|  |                            } | ||||||
|                        } |                        } | ||||||
|                        nextAiringEpisode { |                        nextAiringEpisode { | ||||||
|                             timeUntilAiring |                            timeUntilAiring | ||||||
|                             episode |                            episode | ||||||
|                        } |                        } | ||||||
|                        format |                        format | ||||||
|                    } |                    } | ||||||
|  | @ -772,6 +810,21 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         @JsonProperty("trailer") val trailer: MediaTrailer?, |         @JsonProperty("trailer") val trailer: MediaTrailer?, | ||||||
|         @JsonProperty("description") val description: String?, |         @JsonProperty("description") val description: String?, | ||||||
|         @JsonProperty("characters") val characters: CharacterConnection?, |         @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( |     data class CharacterName( | ||||||
|  | @ -955,13 +1008,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         @JsonProperty("data") val data: LikeData?, |         @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( |     data class AniListTitleHolder( | ||||||
|         @JsonProperty("title") val title: Title?, |         @JsonProperty("title") val title: Title?, | ||||||
|         @JsonProperty("isFavourite") val isFavourite: Boolean?, |         @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 { |     override suspend fun score(id: String, status: SyncAPI.SyncStatus): Boolean { | ||||||
|         return setScoreRequest( |         return setScoreRequest( | ||||||
|             id.toIntOrNull() ?: return false, |             id.toIntOrNull() ?: return false, | ||||||
|  | @ -173,8 +177,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|     private fun toSearchResult(node: Node?): SyncAPI.SyncSearchResult? { |     private fun toSearchResult(node: Node?): SyncAPI.SyncSearchResult? { | ||||||
|         return SyncAPI.SyncSearchResult( |         return SyncAPI.SyncSearchResult( | ||||||
|             name = node?.title ?: return null, |             name = node?.title ?: return null, | ||||||
|             syncApiName = this.name, |             apiName = this.name, | ||||||
|             id = node.id.toString(), |             syncId = node.id.toString(), | ||||||
|             url = "https://myanimelist.net/anime/${node.id}", |             url = "https://myanimelist.net/anime/${node.id}", | ||||||
|             posterUrl = node.main_picture?.large |             posterUrl = node.main_picture?.large | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -93,6 +93,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName | ||||||
| import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename | import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename | ||||||
| import kotlinx.android.synthetic.main.fragment_result.* | import kotlinx.android.synthetic.main.fragment_result.* | ||||||
| import kotlinx.android.synthetic.main.fragment_result_swipe.* | import kotlinx.android.synthetic.main.fragment_result_swipe.* | ||||||
|  | import kotlinx.android.synthetic.main.result_recommendations.* | ||||||
| import kotlinx.android.synthetic.main.result_sync.* | import kotlinx.android.synthetic.main.result_sync.* | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.Job | import kotlinx.coroutines.Job | ||||||
|  | @ -572,14 +573,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|         setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f)) |         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>?) { |     private fun setActors(actors: List<ActorData>?) { | ||||||
|         if (actors.isNullOrEmpty()) { |         if (actors.isNullOrEmpty()) { | ||||||
|             result_cast_text?.isVisible = false |             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() |         val isInvalid = rec.isNullOrEmpty() | ||||||
|         result_recommendations?.isGone = isInvalid |         result_recommendations?.isGone = isInvalid | ||||||
|         result_recommendations_btt?.isGone = isInvalid |         result_recommendations_btt?.isGone = isInvalid | ||||||
|         result_recommendations_btt?.setOnClickListener { |         result_recommendations_btt?.setOnClickListener { | ||||||
|             if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) { |             val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) { | ||||||
|                 result_recommendations_btt?.nextFocusDownId = R.id.result_recommendations |  | ||||||
|                 result_overlapping_panels?.openEndPanel() |                 result_overlapping_panels?.openEndPanel() | ||||||
|  |                 R.id.result_recommendations | ||||||
|             } else { |             } else { | ||||||
|                 result_recommendations_btt?.nextFocusDownId = R.id.result_description |  | ||||||
|                 result_overlapping_panels?.closePanels() |                 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) |         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 { |         result_recommendations?.post { | ||||||
|             rec?.let { list -> |             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 -> { |                 ACTION_PLAY_EPISODE_IN_PLAYER -> { | ||||||
|                     viewModel.getGenerator(episodeClick.data) |                     viewModel.getGenerator(episodeClick.data) | ||||||
|                         ?.let { generator -> |                         ?.let { generator -> | ||||||
|                             println("LANUCJ:::: $syncdata") |  | ||||||
|                             activity?.navigate( |                             activity?.navigate( | ||||||
|                                 R.id.global_to_navigation_player, |                                 R.id.global_to_navigation_player, | ||||||
|                                 GeneratorPlayer.newInstance( |                                 GeneratorPlayer.newInstance( | ||||||
|  | @ -1641,7 +1657,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                     setDuration(d.duration) |                     setDuration(d.duration) | ||||||
|                     setYear(d.year) |                     setYear(d.year) | ||||||
|                     setRating(d.rating) |                     setRating(d.rating) | ||||||
|                     setRecommendations(d.recommendations) |                     setRecommendations(d.recommendations, null) | ||||||
|                     setActors(d.actors) |                     setActors(d.actors) | ||||||
| 
 | 
 | ||||||
|                     if (SettingsFragment.accountEnabled) { |                     if (SettingsFragment.accountEnabled) { | ||||||
|  | @ -1950,7 +1966,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         result_recommendations.adapter = recAdapter |         result_recommendations?.adapter = recAdapter | ||||||
| 
 | 
 | ||||||
|         context?.let { ctx -> |         context?.let { ctx -> | ||||||
|             result_bookmark_button?.isVisible = ctx.isTvSettings() |             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.APIHolder.getId | ||||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.setKey | import com.lagradost.cloudstream3.AcraApplication.Companion.setKey | ||||||
| import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer | 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.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.safeApiCall | import com.lagradost.cloudstream3.mvvm.safeApiCall | ||||||
| import com.lagradost.cloudstream3.syncproviders.SyncAPI | import com.lagradost.cloudstream3.syncproviders.SyncAPI | ||||||
|  | @ -127,6 +130,17 @@ class ResultViewModel : ViewModel() { | ||||||
|             addTrailer(meta.trailerUrl) |             addTrailer(meta.trailerUrl) | ||||||
|             posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl |             posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl | ||||||
|             actors = actors ?: meta.actors |             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 { |     fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch { | ||||||
|         _resultResponse.postValue(Resource.Loading(url)) |  | ||||||
|         _publicEpisodes.postValue(Resource.Loading()) |         _publicEpisodes.postValue(Resource.Loading()) | ||||||
|  |         _resultResponse.postValue(Resource.Loading(url)) | ||||||
| 
 | 
 | ||||||
|         _apiName.postValue(apiName) |  | ||||||
|         val api = getApiFromNameNull(apiName) ?: getApiFromUrlNull(url) |         val api = getApiFromNameNull(apiName) ?: getApiFromUrlNull(url) | ||||||
|         if (api == null) { |         if (api == null) { | ||||||
|             _resultResponse.postValue( |             _resultResponse.postValue( | ||||||
|  | @ -312,9 +325,31 @@ class ResultViewModel : ViewModel() { | ||||||
|             ) |             ) | ||||||
|             return@launch |             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) |         repo = APIRepository(api) | ||||||
| 
 | 
 | ||||||
|         val data = repo?.load(url) ?: return@launch |         val data = repo?.load(validUrl) ?: return@launch | ||||||
| 
 | 
 | ||||||
|         _resultResponse.postValue(data) |         _resultResponse.postValue(data) | ||||||
| 
 | 
 | ||||||
|  | @ -331,7 +366,7 @@ class ResultViewModel : ViewModel() { | ||||||
|                     mainId.toString(), |                     mainId.toString(), | ||||||
|                     VideoDownloadHelper.DownloadHeaderCached( |                     VideoDownloadHelper.DownloadHeaderCached( | ||||||
|                         apiName, |                         apiName, | ||||||
|                         url, |                         validUrl, | ||||||
|                         d.type, |                         d.type, | ||||||
|                         d.name, |                         d.name, | ||||||
|                         d.posterUrl, |                         d.posterUrl, | ||||||
|  |  | ||||||
|  | @ -82,16 +82,16 @@ class SyncViewModel : ViewModel() { | ||||||
|         var isValid = false |         var isValid = false | ||||||
| 
 | 
 | ||||||
|         map?.forEach { (prefix, id) -> |         map?.forEach { (prefix, id) -> | ||||||
|             isValid = isValid || addSync(prefix, id) |             isValid = addSync(prefix, id) || isValid | ||||||
|         } |         } | ||||||
|         return isValid |         return isValid | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun setMalId(id: String?): Boolean { |     private fun setMalId(id: String?): Boolean { | ||||||
|         return addSync(malApi.idPrefix, id ?: return false) |         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) |         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.SearchResponse | ||||||
| import com.lagradost.cloudstream3.TvType | import com.lagradost.cloudstream3.TvType | ||||||
| import com.lagradost.cloudstream3.syncproviders.OAuth2API | import com.lagradost.cloudstream3.syncproviders.OAuth2API | ||||||
| import com.lagradost.cloudstream3.syncproviders.SyncAPI |  | ||||||
| 
 | 
 | ||||||
| class SyncSearchViewModel { | class SyncSearchViewModel { | ||||||
|     private val repos = OAuth2API.SyncApis |     private val repos = OAuth2API.SyncApis | ||||||
|  | @ -20,15 +19,4 @@ class SyncSearchViewModel { | ||||||
|         override var posterHeaders: Map<String, String>? = null, |         override var posterHeaders: Map<String, String>? = null, | ||||||
|     ) : SearchResponse |     ) : 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.cast.framework.media.RemoteMediaClient | ||||||
| import com.google.android.gms.common.api.PendingResult | import com.google.android.gms.common.api.PendingResult | ||||||
| import com.google.android.gms.common.images.WebImage | 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.MetadataHolder | ||||||
| import com.lagradost.cloudstream3.ui.player.SubtitleData | import com.lagradost.cloudstream3.ui.player.SubtitleData | ||||||
| import com.lagradost.cloudstream3.ui.result.ResultEpisode | import com.lagradost.cloudstream3.ui.result.ResultEpisode | ||||||
|  | @ -64,7 +65,10 @@ object CastHelper { | ||||||
|         return builder.build() |         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 |         if (pending == null) return | ||||||
|         main { |         main { | ||||||
|             val res = withContext(Dispatchers.IO) { pending.await() } |             val res = withContext(Dispatchers.IO) { pending.await() } | ||||||
|  | @ -90,27 +94,15 @@ object CastHelper { | ||||||
|         startIndex: Int? = null, |         startIndex: Int? = null, | ||||||
|         startTime: Long? = null, |         startTime: Long? = null, | ||||||
|     ): Boolean { |     ): Boolean { | ||||||
|         if (this == null) return false |         try { | ||||||
|         if (episodes.isEmpty()) return false |             if (this == null) return false | ||||||
|         if (currentEpisodeIndex >= episodes.size) return false |             if (episodes.isEmpty()) return false | ||||||
|  |             if (currentEpisodeIndex >= episodes.size) return false | ||||||
| 
 | 
 | ||||||
|         val epData = episodes[currentEpisodeIndex] |             val epData = episodes[currentEpisodeIndex] | ||||||
| 
 | 
 | ||||||
|         val holder = |             val holder = | ||||||
|             MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks, subtitles) |                 MetadataHolder( | ||||||
| 
 |  | ||||||
|         val index = if (startIndex == null || startIndex < 0) 0 else startIndex |  | ||||||
| 
 |  | ||||||
|         val mediaItem = |  | ||||||
|             getMediaInfo(epData, holder, index, JSONObject(holder.toJson()), subtitles) |  | ||||||
| 
 |  | ||||||
|         awaitLinks( |  | ||||||
|             this.remoteMediaClient?.load( |  | ||||||
|                 MediaLoadRequestData.Builder().setMediaInfo(mediaItem).setCurrentTime(startTime ?: 0L).build() |  | ||||||
|             ) |  | ||||||
|         ) { |  | ||||||
|             if (currentLinks.size > index + 1) |  | ||||||
|                 startCast( |  | ||||||
|                     apiName, |                     apiName, | ||||||
|                     isMovie, |                     isMovie, | ||||||
|                     title, |                     title, | ||||||
|  | @ -118,11 +110,38 @@ object CastHelper { | ||||||
|                     currentEpisodeIndex, |                     currentEpisodeIndex, | ||||||
|                     episodes, |                     episodes, | ||||||
|                     currentLinks, |                     currentLinks, | ||||||
|                     subtitles, |                     subtitles | ||||||
|                     index + 1, |  | ||||||
|                     startTime |  | ||||||
|                 ) |                 ) | ||||||
|  | 
 | ||||||
|  |             val index = if (startIndex == null || startIndex < 0) 0 else startIndex | ||||||
|  | 
 | ||||||
|  |             val mediaItem = | ||||||
|  |                 getMediaInfo(epData, holder, index, JSONObject(holder.toJson()), subtitles) | ||||||
|  | 
 | ||||||
|  |             awaitLinks( | ||||||
|  |                 this.remoteMediaClient?.load( | ||||||
|  |                     MediaLoadRequestData.Builder().setMediaInfo(mediaItem) | ||||||
|  |                         .setCurrentTime(startTime ?: 0L).build() | ||||||
|  |                 ) | ||||||
|  |             ) { | ||||||
|  |                 if (currentLinks.size > index + 1) | ||||||
|  |                     startCast( | ||||||
|  |                         apiName, | ||||||
|  |                         isMovie, | ||||||
|  |                         title, | ||||||
|  |                         poster, | ||||||
|  |                         currentEpisodeIndex, | ||||||
|  |                         episodes, | ||||||
|  |                         currentLinks, | ||||||
|  |                         subtitles, | ||||||
|  |                         index + 1, | ||||||
|  |                         startTime | ||||||
|  |                     ) | ||||||
|  |             } | ||||||
|  |             return true | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             logError(e) | ||||||
|  |             return false | ||||||
|         } |         } | ||||||
|         return true |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -53,7 +53,7 @@ object SyncUtil { | ||||||
|      * valid sites are: Gogoanime, Twistmoe and 9anime*/ |      * valid sites are: Gogoanime, Twistmoe and 9anime*/ | ||||||
|     private suspend fun getIdsFromSlug( |     private suspend fun getIdsFromSlug( | ||||||
|         slug: String, |         slug: String, | ||||||
|         site: String = "GogoanimeGogoanime" |         site: String = "Gogoanime" | ||||||
|     ): Pair<String?, String?>? { |     ): Pair<String?, String?>? { | ||||||
|         Log.i(TAG, "getIdsFromSlug $slug $site") |         Log.i(TAG, "getIdsFromSlug $slug $site") | ||||||
|         try { |         try { | ||||||
|  | @ -76,6 +76,28 @@ object SyncUtil { | ||||||
|         return null |         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( |     data class MalSyncPage( | ||||||
|         @JsonProperty("identifier") val identifier: String?, |         @JsonProperty("identifier") val identifier: String?, | ||||||
|         @JsonProperty("type") val type: String?, |         @JsonProperty("type") val type: String?, | ||||||
|  |  | ||||||
|  | @ -180,17 +180,7 @@ | ||||||
|                     android:layout_height="match_parent" |                     android:layout_height="match_parent" | ||||||
|                     android:layout_gravity="end"> |                     android:layout_gravity="end"> | ||||||
| 
 | 
 | ||||||
|                 <com.lagradost.cloudstream3.ui.AutofitRecyclerView |                 <include layout="@layout/result_recommendations" /> | ||||||
|                         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" /> |  | ||||||
|             </FrameLayout> |             </FrameLayout> | ||||||
|         </com.discord.panels.OverlappingPanelsLayout> |         </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…
	
	Add table
		Add a link
		
	
		Reference in a new issue