diff --git a/app/build.gradle b/app/build.gradle index 1cd978ee..10fdff88 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,7 +36,7 @@ android { targetSdkVersion 30 versionCode 48 - versionName "2.10.28" + versionName "2.10.29" resValue "string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}" @@ -74,6 +74,8 @@ android { } } compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -143,7 +145,7 @@ dependencies { implementation 'com.jaredrummler:colorpicker:1.1.0' //run JS - implementation 'org.mozilla:rhino:1.7R4' + implementation 'org.mozilla:rhino:1.7.14' // TorrentStream //implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0' @@ -175,6 +177,11 @@ dependencies { // used for subtitle decoding https://github.com/albfernandez/juniversalchardet implementation 'com.github.albfernandez:juniversalchardet:2.4.0' - // play yt - implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT' + // slow af yt + //implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT' + + // newpipe yt + implementation 'com.github.TeamNewPipe:NewPipeExtractor:master-SNAPSHOT' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6dcd4e1b..b80154b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -155,11 +155,11 @@ + android:exported="false" /> + android:name=".ui.ControllerActivity" /> > = request.headers() + val dataToSend: ByteArray? = request.dataToSend() + var requestBody: RequestBody? = null + if (dataToSend != null) { + requestBody = RequestBody.create(null, dataToSend) + } + val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() + .method(httpMethod, requestBody).url(url) + .addHeader("User-Agent", USER_AGENT) + + for ((headerName, headerValueList) in headers) { + if (headerValueList.size > 1) { + requestBuilder.removeHeader(headerName) + for (headerValue in headerValueList) { + requestBuilder.addHeader(headerName, headerValue) + } + } else if (headerValueList.size == 1) { + requestBuilder.header(headerName, headerValueList[0]) + } + } + val response = client.newCall(requestBuilder.build()).execute() + if (response.code == 429) { + response.close() + throw ReCaptchaException("reCaptcha Challenge requested", url) + } + val body = response.body + var responseBodyToReturn: String? = null + if (body != null) { + responseBodyToReturn = body.string() + } + val latestUrl = response.request.url.toString() + return Response( + response.code, response.message, response.headers.toMultimap(), + responseBodyToReturn, latestUrl + ) + } + + companion object { + private const val USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0" + private var instance: DownloaderTestImpl? = null + + /** + * It's recommended to call exactly once in the entire lifetime of the application. + * + * @param builder if null, default builder will be used + * @return a new instance of [DownloaderTestImpl] + */ + fun init(builder: OkHttpClient.Builder?): DownloaderTestImpl? { + instance = DownloaderTestImpl( + builder ?: OkHttpClient.Builder() + ) + return instance + } + + fun getInstance(): DownloaderTestImpl? { + if (instance == null) { + init(null) + } + return instance + } + } + + init { + client = builder.readTimeout(30, TimeUnit.SECONDS).build() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 19e309fd..106ddea0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId import com.lagradost.cloudstream3.animeproviders.* import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider import com.lagradost.cloudstream3.movieproviders.* @@ -17,8 +18,10 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.ui.player.SubtitleData +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor import okhttp3.Interceptor import java.text.SimpleDateFormat import java.util.* @@ -39,7 +42,7 @@ object APIHolder { private const val defProvider = 0 - val allProviders by lazy { + val allProviders = arrayListOf( // Movie providers ElifilmsProvider(), @@ -132,7 +135,7 @@ object APIHolder { NginxProvider(), OlgplyProvider(), ) - } + fun initAll() { for (api in allProviders) { @@ -142,7 +145,7 @@ object APIHolder { } var apis: List = arrayListOf() - private var apiMap: Map? = null + var apiMap: Map? = null private fun initMap() { if (apiMap == null) @@ -297,6 +300,16 @@ object APIHolder { return realSet } + fun Context.updateHasTrailers() { + LoadResponse.isTrailersEnabled = getHasTrailers() + } + + private fun Context.getHasTrailers(): Boolean { + if (this.isTvSettings()) return false + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + return settingsManager.getBoolean(this.getString(R.string.show_trailers_key), true) + } + fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val currentPrefMedia = @@ -357,6 +370,7 @@ abstract class MainAPI { } fun overrideWithNewData(data: ProvidersInfoJson) { + if (!canBeOverridden) return this.name = data.name if (data.url.isNotBlank() && data.url != "NONE") this.mainUrl = data.url @@ -366,10 +380,11 @@ abstract class MainAPI { open var name = "NONE" open var mainUrl = "NONE" open var storedCredentials: String? = null + open var canBeOverridden: Boolean = true //open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id - open val lang = "en" // ISO_639_1 check SubtitleHelper + open var lang = "en" // ISO_639_1 check SubtitleHelper /**If link is stored in the "data" string, so links can be instantly loaded*/ open val instantLinkLoading = false @@ -852,7 +867,7 @@ interface LoadResponse { var rating: Int? // 0-10000 var tags: List? var duration: Int? // in minutes - var trailers: List? + var trailers: List? var recommendations: List? var actors: List? var comingSoon: Boolean @@ -862,6 +877,7 @@ interface LoadResponse { companion object { private val malIdPrefix = malApi.idPrefix private val aniListIdPrefix = aniListApi.idPrefix + var isTrailersEnabled = true @JvmName("addActorNames") fun LoadResponse.addActors(actors: List?) { @@ -883,6 +899,14 @@ interface LoadResponse { this.actors = actors?.map { actor -> ActorData(actor) } } + fun LoadResponse.getMalId() : String? { + return this.syncData[malIdPrefix] + } + + fun LoadResponse.getAniListId() : String? { + return this.syncData[aniListIdPrefix] + } + fun LoadResponse.addMalId(id: Int?) { this.syncData[malIdPrefix] = (id ?: return).toString() } @@ -895,27 +919,38 @@ interface LoadResponse { addImdbId(imdbUrlToIdNullable(url)) } - /**better to set trailers directly instead of calling this multiple times*/ - fun LoadResponse.addTrailer(trailerUrl: String?) { - if (trailerUrl == null) return + /**better to call addTrailer with mutible trailers directly instead of calling this multiple times*/ + suspend fun LoadResponse.addTrailer(trailerUrl: String?, referer: String? = null) { + if (!isTrailersEnabled || trailerUrl == null) return + try { + val newTrailers = loadExtractor(trailerUrl, referer) + addTrailer(newTrailers) + } catch (e: Exception) { + logError(e) + } + } + + fun LoadResponse.addTrailer(newTrailers: List) { if (this.trailers == null) { - this.trailers = listOf(trailerUrl) + this.trailers = newTrailers } else { - val update = this.trailers?.toMutableList() - update?.add(trailerUrl) + val update = this.trailers?.toMutableList() ?: mutableListOf() + update.addAll(newTrailers) this.trailers = update } } - fun LoadResponse.addTrailer(trailerUrls: List?) { - if(trailerUrls == null) return - if (this.trailers == null) { - this.trailers = trailerUrls - } else { - val update = this.trailers?.toMutableList() - update?.addAll(trailerUrls) - this.trailers = update - } + suspend fun LoadResponse.addTrailer(trailerUrls: List?, referer: String? = null) { + if (!isTrailersEnabled || trailerUrls == null) return + val newTrailers = trailerUrls.apmap { trailerUrl -> + try { + loadExtractor(trailerUrl, referer) + } catch (e: Exception) { + logError(e) + emptyList() + } + }.flatten().distinct() + addTrailer(newTrailers) } fun LoadResponse.addImdbId(id: String?) { @@ -995,7 +1030,7 @@ data class TorrentLoadResponse( override var rating: Int? = null, override var tags: List? = null, override var duration: Int? = null, - override var trailers: List? = null, + override var trailers: List? = null, override var recommendations: List? = null, override var actors: List? = null, override var comingSoon: Boolean = false, @@ -1023,7 +1058,7 @@ data class AnimeLoadResponse( override var rating: Int? = null, override var duration: Int? = null, - override var trailers: List? = null, + override var trailers: List? = null, override var recommendations: List? = null, override var actors: List? = null, override var comingSoon: Boolean = false, @@ -1036,12 +1071,12 @@ fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List?) { this.episodes[status] = episodes } -fun MainAPI.newAnimeLoadResponse( +suspend fun MainAPI.newAnimeLoadResponse( name: String, url: String, type: TvType, comingSoonIfNone: Boolean = true, - initializer: AnimeLoadResponse.() -> Unit = { }, + initializer: suspend AnimeLoadResponse.() -> Unit = { }, ): AnimeLoadResponse { val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type) builder.initializer() @@ -1070,7 +1105,7 @@ data class MovieLoadResponse( override var rating: Int? = null, override var tags: List? = null, override var duration: Int? = null, - override var trailers: List? = null, + override var trailers: List? = null, override var recommendations: List? = null, override var actors: List? = null, override var comingSoon: Boolean = false, @@ -1078,12 +1113,12 @@ data class MovieLoadResponse( override var posterHeaders: Map? = null, ) : LoadResponse -fun MainAPI.newMovieLoadResponse( +suspend fun MainAPI.newMovieLoadResponse( name: String, url: String, type: TvType, data: T?, - initializer: MovieLoadResponse.() -> Unit = { } + initializer: suspend MovieLoadResponse.() -> Unit = { } ): MovieLoadResponse { // just in case if (data is String) return newMovieLoadResponse( @@ -1106,12 +1141,12 @@ fun MainAPI.newMovieLoadResponse( return builder } -fun MainAPI.newMovieLoadResponse( +suspend fun MainAPI.newMovieLoadResponse( name: String, url: String, type: TvType, dataUrl: String, - initializer: MovieLoadResponse.() -> Unit = { } + initializer: suspend MovieLoadResponse.() -> Unit = { } ): MovieLoadResponse { val builder = MovieLoadResponse( name = name, @@ -1191,7 +1226,7 @@ data class TvSeriesLoadResponse( override var rating: Int? = null, override var tags: List? = null, override var duration: Int? = null, - override var trailers: List? = null, + override var trailers: List? = null, override var recommendations: List? = null, override var actors: List? = null, override var comingSoon: Boolean = false, @@ -1199,12 +1234,12 @@ data class TvSeriesLoadResponse( override var posterHeaders: Map? = null, ) : LoadResponse -fun MainAPI.newTvSeriesLoadResponse( +suspend fun MainAPI.newTvSeriesLoadResponse( name: String, url: String, type: TvType, episodes: List, - initializer: TvSeriesLoadResponse.() -> Unit = { } + initializer: suspend TvSeriesLoadResponse.() -> Unit = { } ): TvSeriesLoadResponse { val builder = TvSeriesLoadResponse( name = name, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index aadf00a7..51ebb41b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.initAll +import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent @@ -47,6 +48,7 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsGeneral import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadResult @@ -68,12 +70,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW +import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.nicehttp.Requests import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import org.schabi.newpipe.extractor.NewPipe import java.io.File import kotlin.concurrent.thread @@ -364,9 +368,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } fun test() { - //val youtubeLink = "https://www.youtube.com/watch?v=TxB48MEAmZw" + /*thread { + val youtubeLink = "https://www.youtube.com/watch?v=Zxem9rqJ5S0" + val url = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(youtubeLink) + println("ID:::: ${url.id}") + NewPipe.init(DownloaderTestImpl.getInstance()) + val service = ServiceList.YouTube + val s = object : YoutubeStreamExtractor( + service, + url + ) { + } + s.fetchPage() + val streams = s.videoStreams + println("STREAMS: ${streams.map { "url = "+ it.url + " extra= " + it.height + "|" + it.isVideoOnly + "\n" }}") + }*/ /* runBlocking { @@ -515,6 +533,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { apis = allProviders } + try { + getKey>(USER_PROVIDER_API)?.let { list -> + list.forEach { custom -> + allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } + ?.let { + allProviders.add(it.javaClass.newInstance().apply { + name = custom.name + lang = custom.lang + mainUrl = custom.url.trimEnd('/') + canBeOverridden = false + }) + } + } + } + apis = allProviders + APIHolder.apiMap = null + } catch (e: Exception) { + logError(e) + } + loadThemes(this) updateLocale() app.initClient(this) @@ -576,6 +614,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { loadCache() test() + NewPipe.init(DownloaderTestImpl.getInstance()) + updateHasTrailers() /*nav_view.setOnNavigationItemSelectedListener { item -> when (item.itemId) { R.id.navigation_home -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeWorldProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeWorldProvider.kt index e41a63b5..4a965206 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeWorldProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeWorldProvider.kt @@ -16,7 +16,7 @@ import org.jsoup.nodes.Element class AnimeWorldProvider : MainAPI() { override var mainUrl = "https://www.animeworld.tv" override var name = "AnimeWorld" - override val lang = "it" + override var lang = "it" override val hasMainPage = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimefenixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimefenixProvider.kt index 11eb01c2..d00543d4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimefenixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimefenixProvider.kt @@ -13,7 +13,7 @@ class AnimefenixProvider:MainAPI() { override var mainUrl = "https://animefenix.com" override var name = "Animefenix" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvIOProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvIOProvider.kt index 481c2b25..aadfdcad 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvIOProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvIOProvider.kt @@ -11,7 +11,7 @@ import kotlin.collections.ArrayList class AnimeflvIOProvider:MainAPI() { override var mainUrl = "https://animeflv.io" //Also scrapes from animeid.to override var name = "Animeflv.io" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt index 7e88ce75..df40481b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeflvProvider.kt @@ -24,7 +24,7 @@ class AnimeflvnetProvider : MainAPI() { override var mainUrl = "https://www3.animeflv.net" override var name = "Animeflv.net" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DreamSubProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DreamSubProvider.kt index 725e1054..e3ca3e28 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DreamSubProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DreamSubProvider.kt @@ -11,7 +11,7 @@ import org.jsoup.nodes.Element class DreamSubProvider : MainAPI() { override var mainUrl = "https://dreamsub.me" override var name = "DreamSub" - override val lang = "it" + override var lang = "it" override val hasMainPage = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt index 1287ae6f..f32d2ddd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GomunimeProvider.kt @@ -14,7 +14,7 @@ class GomunimeProvider : MainAPI() { override var name = "Gomunime" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/JKAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/JKAnimeProvider.kt index d6e215a9..c84c1fba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/JKAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/JKAnimeProvider.kt @@ -22,7 +22,7 @@ class JKAnimeProvider : MainAPI() { override var mainUrl = "https://jkanime.net" override var name = "JKAnime" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt index e41e9119..ce560949 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuramanimeProvider.kt @@ -12,7 +12,7 @@ class KuramanimeProvider : MainAPI() { override var name = "Kuramanime" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt index 14e841fc..5eec3a07 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KuronimeProvider.kt @@ -3,17 +3,18 @@ package com.lagradost.cloudstream3.animeproviders import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.mvvm.safeApiCall -import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.Jsoup import org.jsoup.nodes.Element -import java.util.ArrayList class KuronimeProvider : MainAPI() { override var mainUrl = "https://185.231.223.254" override var name = "Kuronime" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( @@ -139,7 +140,6 @@ class KuronimeProvider : MainAPI() { plot = description addTrailer(trailer) this.tags = tags - trailers = listOf(trailer) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt index 3681e6f0..e06d2e66 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MonoschinosProvider.kt @@ -24,7 +24,7 @@ class MonoschinosProvider : MainAPI() { override var mainUrl = "https://monoschinos2.com" override var name = "Monoschinos" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MundoDonghuaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MundoDonghuaProvider.kt index 19520ae6..54fdfe45 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MundoDonghuaProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/MundoDonghuaProvider.kt @@ -15,7 +15,7 @@ class MundoDonghuaProvider : MainAPI() { override var mainUrl = "https://www.mundodonghua.com" override var name = "MundoDonghua" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt index d844e043..0d9574e5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NeonimeProvider.kt @@ -11,7 +11,7 @@ class NeonimeProvider : MainAPI() { override var name = "Neonime" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt index f2851d0f..52d9cfd4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NineAnimeProvider.kt @@ -34,7 +34,7 @@ class NineAnimeProvider : MainAPI() { Pair("$mainUrl/ajax/home/widget?name=updated_sub&page=1", "Recently Updated (SUB)"), Pair( "$mainUrl/ajax/home/widget?name=updated_dub&page=1", - "Recently Updated (DUB)(DUB)" + "Recently Updated (DUB)" ), Pair( "$mainUrl/ajax/home/widget?name=updated_chinese&page=1", @@ -64,7 +64,8 @@ class NineAnimeProvider : MainAPI() { } //Credits to https://github.com/jmir1 - private val key = "c/aUAorINHBLxWTy3uRiPt8J+vjsOheFG1E0q2X9CYwDZlnmd4Kb5M6gSVzfk7pQ" //key credits to @Modder4869 + private val key = + "c/aUAorINHBLxWTy3uRiPt8J+vjsOheFG1E0q2X9CYwDZlnmd4Kb5M6gSVzfk7pQ" //key credits to @Modder4869 private fun getVrf(id: String): String? { val reversed = ue(encode(id) + "0000000").slice(0..5).reversed() @@ -175,7 +176,10 @@ class NineAnimeProvider : MainAPI() { return app.get(url).document.select("ul.anime-list li").mapNotNull { val title = it.selectFirst("a.name")!!.text() val href = - fixUrlNull(it.selectFirst("a")!!.attr("href"))?.replace(Regex("(\\?ep=(\\d+)\$)"), "") + fixUrlNull(it.selectFirst("a")!!.attr("href"))?.replace( + Regex("(\\?ep=(\\d+)\$)"), + "" + ) ?: return@mapNotNull null val image = it.selectFirst("a.poster img")!!.attr("src") AnimeSearchResponse( @@ -199,7 +203,8 @@ class NineAnimeProvider : MainAPI() { override suspend fun load(url: String): LoadResponse? { 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 poster = doc.selectFirst("aside.main div.thumb div img")!!.attr("src") val title = doc.selectFirst(".info .title")!!.text() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt index d3cf7772..5998f2a7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/NontonAnimeIDProvider.kt @@ -15,7 +15,7 @@ class NontonAnimeIDProvider : MainAPI() { override var name = "NontonAnimeID" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt index 906c20ff..9c01934d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/OploverzProvider.kt @@ -13,7 +13,7 @@ class OploverzProvider : MainAPI() { override var name = "Oploverz" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt new file mode 100644 index 00000000..9a059468 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt @@ -0,0 +1,76 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import org.schabi.newpipe.extractor.ServiceList +import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor +import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory +import org.schabi.newpipe.extractor.stream.VideoStream + +class YoutubeShortLinkExtractor : YoutubeExtractor() { + override val mainUrl = "https://youtu.be" + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/$id" + } +} + +open class YoutubeExtractor : ExtractorApi() { + override val mainUrl = "https://www.youtube.com" + override val requiresReferer = false + override val name = "YouTube" + + companion object { + private var ytVideos: MutableMap> = mutableMapOf() + } + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/watch?v=$id" + } + + override suspend fun getUrl(url: String, referer: String?): List? { + val streams = safeApiCall { + val streams = ytVideos[url] ?: let { + val link = + YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(url) + + val s = object : YoutubeStreamExtractor( + ServiceList.YouTube, + link + ) { + + } + s.fetchPage() + val streams = s.videoStreams ?: return@let emptyList() + ytVideos[url] = streams + streams + } + if (streams.isEmpty()) { + throw ErrorLoadingException("No Youtube streams") + } + + streams + //streams.sortedBy { it.height } + // .firstOrNull { !it.isVideoOnly && it.height > 0 } + // ?: throw ErrorLoadingException("No valid Youtube stream") + } + if (streams is Resource.Success) { + return streams.value.mapNotNull { + if (it.isVideoOnly || it.height <= 0) return@mapNotNull null + + ExtractorLink( + this.name, + this.name, + it.url ?: return@mapNotNull null, + "", + it.height + ) + } + } else { + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt index a9c6a463..b01d188c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt @@ -12,7 +12,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink class CrossTmdbProvider : TmdbProvider() { override var name = "MultiMovie" override val apiName = "MultiMovie" - override val lang = "en" + override var lang = "en" override val useMetaLoadResponse = true override val usesWebView = true override val supportedTypes = setOf(TvType.Movie) diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt index 38c2abea..0ab44b68 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt @@ -12,7 +12,7 @@ import com.lagradost.cloudstream3.utils.SyncUtil // wont be implemented class MultiAnimeProvider : MainAPI() { override var name = "MultiAnime" - override val lang = "en" + override var lang = "en" override val usesWebView = true override val supportedTypes = setOf(TvType.Anime) private val syncApi: SyncAPI = aniListApi @@ -61,7 +61,7 @@ class MultiAnimeProvider : MainAPI() { plot = res.synopsis tags = res.genres rating = res.publicScore - addTrailer(res.trailerUrl) + addTrailer(res.trailers) addAniListId(res.id.toIntOrNull()) recommendations = res.recommendations } diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index fdec2fc1..13cbd335 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -95,7 +95,7 @@ open class TmdbProvider : MainAPI() { } } - private fun TvShow.toLoadResponse(): TvSeriesLoadResponse { + private suspend fun TvShow.toLoadResponse(): TvSeriesLoadResponse { val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } ?.mapNotNull { season -> season.episodes?.map { episode -> @@ -167,7 +167,7 @@ open class TmdbProvider : MainAPI() { } } - private fun Movie.toLoadResponse(): MovieLoadResponse { + private suspend fun Movie.toLoadResponse(): MovieLoadResponse { return newMovieLoadResponse( this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink( this.imdb_id, diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt index 3a8435a2..f370a274 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.Qualities import org.jsoup.nodes.Element class AkwamProvider : MainAPI() { - override val lang = "ar" + override var lang = "ar" override var mainUrl = "https://akwam.to" override var name = "Akwam" override val usesWebView = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt index a0a7324b..d8ac1985 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt @@ -5,9 +5,11 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer + class AltadefinizioneProvider : MainAPI() { - override val lang = "it" + override var lang = "it" override var mainUrl = "https://altadefinizione.hair" override var name = "Altadefinizione" override val hasMainPage = true @@ -111,7 +113,10 @@ class AltadefinizioneProvider : MainAPI() { } val tags: List = document.select("#details > li:nth-child(1) > a").map { it.text() } - return newMovieLoadResponse( + + val trailerurl = document.selectFirst("#showtrailer > div > div > iframe")!!.attr("src") + + return newMovieLoadResponse( title, url, TvType.Movie, @@ -125,6 +130,7 @@ class AltadefinizioneProvider : MainAPI() { this.duration = null this.actors = actors this.tags = tags + addTrailer(trailerurl) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt index 81d6c835..50bc4244 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor class CineblogProvider : MainAPI() { - override val lang = "it" + override var lang = "it" override var mainUrl = "https://cb01.rip" override var name = "CineBlog" override val hasMainPage = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt index c650ba49..91fa8941 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CinecalidadProvider.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class CinecalidadProvider:MainAPI() { override var mainUrl = "https://cinecalidad.lol" override var name = "Cinecalidad" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CuevanaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CuevanaProvider.kt index 0703a124..771cdf3e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CuevanaProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CuevanaProvider.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class CuevanaProvider : MainAPI() { override var mainUrl = "https://cuevana3.me" override var name = "Cuevana" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DoramasYTProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DoramasYTProvider.kt index ab907aba..b216ec6f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DoramasYTProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DoramasYTProvider.kt @@ -23,7 +23,7 @@ class DoramasYTProvider : MainAPI() { override var mainUrl = "https://doramasyt.com" override var name = "DoramasYT" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt index 5b601e28..77945fa7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/DramaidProvider.kt @@ -15,7 +15,7 @@ class DramaidProvider : MainAPI() { override var name = "DramaId" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val hasChromecastSupport = false override val supportedTypes = setOf(TvType.AsianDrama) diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EgyBestProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EgyBestProvider.kt index 7fa54ad6..aae321a9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EgyBestProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EgyBestProvider.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import org.jsoup.nodes.Element class EgyBestProvider : MainAPI() { - override val lang = "ar" + override var lang = "ar" override var mainUrl = "https://www.egy.best" override var name = "EgyBest" override val usesWebView = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/ElifilmsProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/ElifilmsProvider.kt index 58167214..dd3e1d15 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/ElifilmsProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/ElifilmsProvider.kt @@ -1,19 +1,20 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.utils.* -import kotlin.collections.ArrayList +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor -class ElifilmsProvider:MainAPI() { +class ElifilmsProvider : MainAPI() { override var mainUrl: String = "https://elifilms.net" override var name: String = "Elifilms" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true override val supportedTypes = setOf( TvType.Movie, ) + override suspend fun getMainPage(): HomePageResponse { val items = ArrayList() val newest = app.get(mainUrl).document.selectFirst("a.fav_link.premiera")?.attr("href") @@ -42,6 +43,7 @@ class ElifilmsProvider:MainAPI() { if (items.size <= 0) throw ErrorLoadingException() return HomePageResponse(items) } + override suspend fun search(query: String): List { val url = "$mainUrl/?s=$query" val doc = app.get(url).document @@ -52,6 +54,7 @@ class ElifilmsProvider:MainAPI() { (MovieSearchResponse(name, href, this.name, TvType.Movie, poster, null)) } } + override suspend fun load(url: String): LoadResponse { val document = app.get(url, timeout = 120).document val title = document.selectFirst(".post_title h1")?.text() ?: "" @@ -73,6 +76,7 @@ class ElifilmsProvider:MainAPI() { tags ) } + override suspend fun loadLinks( data: String, isCasting: Boolean, diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EntrepeliculasyseriesProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EntrepeliculasyseriesProvider.kt index 8ad342b8..68103f8c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EntrepeliculasyseriesProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EntrepeliculasyseriesProvider.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class EntrepeliculasyseriesProvider:MainAPI() { override var mainUrl = "https://entrepeliculasyseries.nu" override var name = "EntrePeliculasySeries" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EstrenosDoramasProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EstrenosDoramasProvider.kt index b02f88fa..bddd9de0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EstrenosDoramasProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/EstrenosDoramasProvider.kt @@ -20,7 +20,7 @@ class EstrenosDoramasProvider : MainAPI() { override var mainUrl = "https://www23.estrenosdoramas.net" override var name = "EstrenosDoramas" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FaselHDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FaselHDProvider.kt index fc2258e0..9a21a42c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FaselHDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FaselHDProvider.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import org.jsoup.nodes.Element class FaselHDProvider : MainAPI() { - override val lang = "ar" + override var lang = "ar" override var mainUrl = "https://faselhd.io" override var name = "FaselHD" override val usesWebView = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmanProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmanProvider.kt index 14fa5d6f..8cf5f753 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmanProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmanProvider.kt @@ -11,7 +11,7 @@ import org.jsoup.select.Elements class FilmanProvider : MainAPI() { override var mainUrl = "https://filman.cc" override var name = "filman.cc" - override val lang = "pl" + override var lang = "pl" override val hasMainPage = true override val supportedTypes = setOf( TvType.Movie, diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FrenchStreamProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FrenchStreamProvider.kt index e3146a96..9923adca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FrenchStreamProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FrenchStreamProvider.kt @@ -12,7 +12,7 @@ class FrenchStreamProvider : MainAPI() { override var name = "French Stream" override val hasQuickSearch = false override val hasMainPage = true - override val lang = "fr" + override var lang = "fr" override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie) override suspend fun search(query: String): List { diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt index 42ed9dc6..fcaee9fb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMovie5.kt @@ -8,9 +8,9 @@ import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.Jsoup class HDMovie5 : MainAPI() { - override var mainUrl = "https://hdmovie5.mba" + override var mainUrl = "https://hdmovie2.tv" override var name = "HDMovie" - override val lang = "hi" + override var lang = "hi" override val hasQuickSearch = true override val hasMainPage = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt index e8f78b28..393bae12 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LayarKaca21Provider.kt @@ -11,7 +11,7 @@ class LayarKacaProvider : MainAPI() { override var mainUrl = "https://149.56.24.226" override var name = "LayarKaca" override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( TvType.Movie, diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt index bcc39647..b74a281c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt @@ -8,7 +8,7 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Element class MyCimaProvider : MainAPI() { - override val lang = "ar" + override var lang = "ar" override var mainUrl = "https://mycima.tv" override var name = "MyCima" override val usesWebView = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/NginxProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/NginxProvider.kt index b9472f60..4711d9cb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/NginxProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/NginxProvider.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities @@ -95,7 +96,7 @@ class NginxProvider : MainAPI() { this.plot = description this.rating = ratingAverage this.tags = tagsList - this.trailers = trailer + addTrailer(trailer) addPoster(poster, authHeader) } } else // a tv serie diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt index d29c4b58..0976c8b8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PeliSmartProvider.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class PeliSmartProvider: MainAPI() { override var mainUrl = "https://pelismart.com" override var name = "PeliSmart" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt index f635573c..005cbb7f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class PelisflixProvider : MainAPI() { override var mainUrl = "https://pelisflix.li" override var name = "Pelisflix" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt index 3aef6b4f..ae8d2718 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusHDProvider.kt @@ -8,7 +8,7 @@ import org.jsoup.nodes.Element class PelisplusHDProvider:MainAPI() { override var mainUrl = "https://pelisplushd.net" override var name = "PelisplusHD" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt index a9fd90a0..b198b4e4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt @@ -12,7 +12,7 @@ import org.jsoup.Jsoup */ open class PelisplusProviderTemplate : MainAPI() { - override val lang = "es" + override var lang = "es" open val homePageUrlList = listOf() // // mainUrl is good to have as a holder for the url to make future changes easier. diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyHDXyzProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyHDXyzProvider.kt index 1a5500ef..1e6b71b6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyHDXyzProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyHDXyzProvider.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class PinoyHDXyzProvider : MainAPI() { override var name = "Pinoy-HD" override var mainUrl = "https://www.pinoy-hd.xyz" - override val lang = "tl" + override var lang = "tl" override val supportedTypes = setOf(TvType.AsianDrama) override val hasDownloadSupport = true override val hasMainPage = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviePediaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviePediaProvider.kt index acc22786..7ed1be88 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviePediaProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviePediaProvider.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class PinoyMoviePediaProvider : MainAPI() { override var name = "Pinoy Moviepedia" override var mainUrl = "https://pinoymoviepedia.ru" - override val lang = "tl" + override var lang = "tl" override val supportedTypes = setOf(TvType.AsianDrama) override val hasDownloadSupport = true override val hasMainPage = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviesEsProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviesEsProvider.kt index b975255b..cee6107f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviesEsProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PinoyMoviesEsProvider.kt @@ -15,7 +15,7 @@ import org.jsoup.select.Elements class PinoyMoviesEsProvider : MainAPI() { override var name = "Pinoy Movies" override var mainUrl = "https://pinoymovies.es" - override val lang = "tl" + override var lang = "tl" override val supportedTypes = setOf(TvType.AsianDrama) override val hasDownloadSupport = false override val hasMainPage = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt index 789d6684..3a004b6b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/RebahinProvider.kt @@ -15,7 +15,7 @@ class RebahinProvider : MainAPI() { override var mainUrl = "http://167.88.14.149" override var name = "Rebahin" override val hasMainPage = true - override val lang = "id" + override var lang = "id" override val hasDownloadSupport = true override val supportedTypes = setOf( TvType.Movie, diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt index 94683604..7fca3179 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class SeriesflixProvider : MainAPI() { override var mainUrl = "https://seriesflix.video" override var name = "Seriesflix" - override val lang = "es" + override var lang = "es" override val hasMainPage = true override val hasChromecastSupport = true override val hasDownloadSupport = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt index 67be5257..188c4d9f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt @@ -127,7 +127,7 @@ data class TrailerElement( class StreamingcommunityProvider : MainAPI() { - override val lang = "it" + override var lang = "it" override var mainUrl = "https://streamingcommunity.press" override var name = "Streamingcommunity" override val hasMainPage = true @@ -284,7 +284,7 @@ class StreamingcommunityProvider : MainAPI() { val trailerinfojs = document.select("slider-trailer").attr("videos") val trailerinfo = parseJson>(trailerinfojs) val trailerurl: String? = if (trailerinfo.isNotEmpty()) { - "https://www.youtube.com/watch?v=${trailerinfo[0].id}" + "https://www.youtube.com/watch?v=${trailerinfo[0].url}" } else { null } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt index a598d159..8227bcd2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt @@ -4,9 +4,11 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer + class TantifilmProvider : MainAPI() { - override val lang = "it" + override var lang = "it" override var mainUrl = "https://www.tantifilm.rodeo" override var name = "Tantifilm" override val hasMainPage = true @@ -69,7 +71,6 @@ class TantifilmProvider : MainAPI() { } } - override suspend fun load(url: String): LoadResponse { val document = app.get(url).document val type = if (document.selectFirst("div.category-film")!!.text().contains("Serie") @@ -107,7 +108,7 @@ class TantifilmProvider : MainAPI() { } - + val trailerurl = document.selectFirst("#trailer_mob > iframe")!!.attr("src") if (type == TvType.TvSeries) { val list = ArrayList>() @@ -142,22 +143,18 @@ class TantifilmProvider : MainAPI() { } } } - return TvSeriesLoadResponse( + return newTvSeriesLoadResponse( title, url, - this.name, type, - episodeList, - fixUrlNull(poster), - year.toIntOrNull(), - descipt[0], - null, - rating, - null, - null, - null, - recomm - ) + episodeList) { + this.posterUrl= fixUrlNull(poster) + this.year = year.toIntOrNull() + this.plot= descipt[0] + this.rating= rating + this.recommendations = recomm + addTrailer(trailerurl) + } } else { val url2 = document.selectFirst("iframe")!!.attr("src") val actorpagelink = @@ -217,6 +214,7 @@ class TantifilmProvider : MainAPI() { this.tags = tags this.duration = duratio this.actors = actors + addTrailer(trailerurl) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt index 51335f6b..a0b3420a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt @@ -9,7 +9,7 @@ import org.jsoup.Jsoup class VfFilmProvider : MainAPI() { override var mainUrl = "https://vf-film.me" override var name = "vf-film.me" - override val lang = "fr" + override var lang = "fr" override val hasQuickSearch = false override val hasMainPage = false override val hasChromecastSupport = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt index 654406d8..552b0be0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt @@ -9,7 +9,7 @@ import org.jsoup.Jsoup class VfSerieProvider : MainAPI() { override var mainUrl = "https://vf-serie.org" override var name = "vf-serie.org" - override val lang = "fr" + override var lang = "fr" override val hasQuickSearch = false override val hasMainPage = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt index 3b16cb7f..87da6219 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -67,7 +67,7 @@ interface SyncAPI : OAuth2API { var studio: List? = null, var genres: List? = null, var synonyms: List? = null, - var trailerUrl: String? = null, + var trailers: List? = null, var isAdult : Boolean? = null, var posterUrl: String? = null, var backgroundPosterUrl : String? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 81c14979..9bab9381 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -142,6 +142,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { getUrlFromId(recMedia.id), recMedia.coverImage?.large ?: recMedia.coverImage?.medium ) + }, + trailers = when (season.trailer?.site?.lowercase()?.trim()) { + "youtube" -> listOf("https://www.youtube.com/watch?v=${season.trailer.id}") + else -> null } //TODO REST ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt new file mode 100644 index 00000000..84c540b3 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt @@ -0,0 +1,141 @@ +package com.lagradost.cloudstream3.syncproviders.providers + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.mvvm.logError + +// modified code from from https://github.com/saikou-app/saikou/blob/main/app/src/main/java/ani/saikou/others/Kitsu.kt +// GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md +object Kitsu { + private suspend fun getKitsuData(query: String): KitsuResponse { + val headers = mapOf( + "Content-Type" to "application/json", + "Accept" to "application/json", + "Connection" to "keep-alive", + "DNT" to "1", + "Origin" to "https://kitsu.io" + ) + + return app.post( + "https://kitsu.io/api/graphql", + headers = headers, + data = mapOf("query" to query) + ).parsed() + } + + private val cache: MutableMap, Map> = + mutableMapOf() + + suspend fun getEpisodesDetails( + malId: String?, + anilistId: String? + ): Map? { + if (anilistId != null) { + try { + val map = getKitsuEpisodesDetails(anilistId, "ANILIST_ANIME") + if (!map.isNullOrEmpty()) return map + } catch (e: Exception) { + logError(e) + } + } + if (malId != null) { + try { + val map = getKitsuEpisodesDetails(malId, "MYANIMELIST_ANIME") + if (!map.isNullOrEmpty()) return map + } catch (e: Exception) { + logError(e) + } + } + return null + } + + @Throws + suspend fun getKitsuEpisodesDetails(id: String, site: String): Map? { + require(id.isNotBlank()) { + "Black id" + } + + require(site.isNotBlank()) { + "invalid site" + } + + if (cache.containsKey(id to site)) { + return cache[id to site] + } + + val query = + """ +query { + lookupMapping(externalId: $id, externalSite: $site) { + __typename + ... on Anime { + id + episodes(first: 2000) { + nodes { + number + titles { + canonical + } + description + thumbnail { + original { + url + } + } + } + } + } + } +}""" + val result = getKitsuData(query) + val map = (result.data?.lookupMapping?.episodes?.nodes ?: return null).mapNotNull { ep -> + val num = ep?.num ?: return@mapNotNull null + num to ep + }.toMap() + if (map.isNotEmpty()) { + cache[id to site] = map + } + return map + } + + data class KitsuResponse( + val data: Data? = null + ) { + data class Data( + val lookupMapping: LookupMapping? = null + ) + + data class LookupMapping( + val id: String? = null, + val episodes: Episodes? = null + ) + + data class Episodes( + val nodes: List? = null + ) + + data class Node( + @JsonProperty("number") + val num: Int? = null, + val titles: Titles? = null, + val description: Description? = null, + val thumbnail: Thumbnail? = null + ) + + data class Description( + val en: String? = null + ) + + data class Thumbnail( + val original: Original? = null + ) + + data class Original( + val url: String? = null + ) + + data class Titles( + val canonical: String? = null + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index 9dd5b214..28a23731 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -33,6 +33,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { override val redirectUrl = "mallogin" override val idPrefix = "mal" override var mainUrl = "https://myanimelist.net" + val apiUrl = "https://api.myanimelist.net" override val icon = R.drawable.mal_logo override val requiresLogin = true @@ -62,7 +63,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } override suspend fun search(name: String): List { - val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" + val url = "$apiUrl/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" val auth = getAuth() ?: return emptyList() val res = app.get( url, headers = mapOf( @@ -179,7 +180,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { name = node?.title ?: return null, apiName = this.name, syncId = node.id.toString(), - url = "https://myanimelist.net/anime/${node.id}", + url = "$mainUrl/anime/${node.id}", posterUrl = node.main_picture?.large ) } @@ -187,7 +188,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { override suspend fun getResult(id: String): SyncAPI.SyncResult? { val internalId = id.toIntOrNull() ?: return null val url = - "https://api.myanimelist.net/v2/anime/$internalId?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,related_manga,recommendations,studios,statistics" + "$apiUrl/v2/anime/$internalId?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,created_at,updated_at,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,related_manga,recommendations,studios,statistics" val res = app.get( url, headers = mapOf( "Authorization" to "Bearer " + (getAuth() ?: return null) @@ -195,7 +196,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { ).text return mapper.readValue(res).let { malAnime -> SyncAPI.SyncResult( - id = malAnime.id?.toString()!!, + id = internalId.toString(), totalEpisodes = malAnime.numEpisodes, title = malAnime.title, publicScore = malAnime.mean?.toFloat()?.times(1000)?.toInt(), @@ -203,13 +204,14 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { synopsis = malAnime.synopsis, airStatus = when (malAnime.status) { "finished_airing" -> ShowStatus.Completed - "airing" -> ShowStatus.Ongoing + "currently_airing" -> ShowStatus.Ongoing + //"not_yet_aired" else -> null }, nextAiring = null, studio = malAnime.studios?.mapNotNull { it.name }, genres = malAnime.genres?.map { it.name }, - trailerUrl = null, + trailers = null, startDate = parseDate(malAnime.startDate), endDate = parseDate(malAnime.endDate), recommendations = malAnime.recommendations?.mapNotNull { rec -> @@ -260,7 +262,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { val currentCode = sanitizer["code"]!! val res = app.post( - "https://myanimelist.net/v1/oauth2/token", + "$mainUrl/v1/oauth2/token", data = mapOf( "client_id" to key, "code" to currentCode, @@ -292,7 +294,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { .replace("/", "_").replace("\n", "") val codeChallenge = codeVerifier val request = - "https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId" + "$mainUrl/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId" openBrowser(request) } @@ -318,7 +320,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { private suspend fun refreshToken() { try { val res = app.post( - "https://myanimelist.net/v1/oauth2/token", + "$mainUrl/v1/oauth2/token", data = mapOf( "client_id" to key, "grant_type" to "refresh_token", @@ -451,7 +453,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { // Very lackluster docs // https://myanimelist.net/apiconfig/references/api/v2#operation/users_user_id_animelist_get val url = - "https://api.myanimelist.net/v2/users/$user/animelist?fields=list_status,num_episodes,media_type,status,start_date,end_date,synopsis,alternative_titles,mean,genres,rank,num_list_users,nsfw,average_episode_duration,num_favorites,popularity,num_scoring_users,start_season,favorites_info,broadcast,created_at,updated_at&nsfw=1&limit=100&offset=$offset" + "$apiUrl/v2/users/$user/animelist?fields=list_status,num_episodes,media_type,status,start_date,end_date,synopsis,alternative_titles,mean,genres,rank,num_list_users,nsfw,average_episode_duration,num_favorites,popularity,num_scoring_users,start_season,favorites_info,broadcast,created_at,updated_at&nsfw=1&limit=100&offset=$offset" val res = app.get( url, headers = mapOf( "Authorization" to "Bearer $auth", @@ -463,7 +465,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { private suspend fun getDataAboutMalId(id: Int): SmallMalAnime? { // https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get val url = - "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status" + "$apiUrl/v2/anime/$id?fields=id,title,num_episodes,my_list_status" val res = app.get( url, headers = mapOf( "Authorization" to "Bearer " + (getAuth() ?: return null) @@ -481,7 +483,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { checkMalToken() while (!isDone) { val res = app.get( - "https://api.myanimelist.net/v2/users/$user/animelist?fields=list_status&limit=1000&offset=${index * 1000}", + "$apiUrl/v2/users/$user/animelist?fields=list_status&limit=1000&offset=${index * 1000}", headers = mapOf( "Authorization" to "Bearer " + (getAuth() ?: return) ), cacheTime = 0 @@ -532,10 +534,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } private suspend fun checkMalToken() { - if (unixTime > getKey( + if (unixTime > (getKey( accountId, MAL_UNIXTIME_KEY - ) ?: 0L + ) ?: 0L) ) { refreshToken() } @@ -544,7 +546,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { private suspend fun getMalUser(setSettings: Boolean = true): MalUser? { checkMalToken() val res = app.get( - "https://api.myanimelist.net/v2/users/@me", + "$apiUrl/v2/users/@me", headers = mapOf( "Authorization" to "Bearer " + (getAuth() ?: return null) ), cacheTime = 0 @@ -620,7 +622,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { ).filter { it.value != null } as Map return app.put( - "https://api.myanimelist.net/v2/anime/$id/my_list_status", + "$apiUrl/v2/anime/$id/my_list_status", headers = mapOf( "Authorization" to "Bearer " + (getAuth() ?: return null) ), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index d1ba5f12..b2219d1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -13,12 +13,12 @@ class APIRepository(val api: MainAPI) { val noneApi = object : MainAPI() { override var name = "None" override val supportedTypes = emptySet() - override val lang = "" + override var lang = "" } val randomApi = object : MainAPI() { override var name = "Random" override val supportedTypes = emptySet() - override val lang = "" + override var lang = "" } fun isInvalidData(data: String): Boolean { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index a5260e73..d667006d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -1,17 +1,11 @@ package com.lagradost.cloudstream3.ui.player -import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.Handler import android.os.Looper import android.util.Log -import android.util.SparseArray import android.widget.FrameLayout -import androidx.core.util.forEach -import at.huber.youtubeExtractor.VideoMeta -import at.huber.youtubeExtractor.YouTubeExtractor -import at.huber.youtubeExtractor.YtFile import com.google.android.exoplayer2.* import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource @@ -38,7 +32,6 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorUri -import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import java.io.File import javax.net.ssl.HttpsURLConnection @@ -334,7 +327,6 @@ class CS3IPlayer : IPlayer { } companion object { - private var ytVideos: MutableMap = mutableMapOf() private var simpleCache: SimpleCache? = null var requestSubtitleUpdate: (() -> Unit)? = null @@ -534,7 +526,6 @@ class CS3IPlayer : IPlayer { ) setHandleAudioBecomingNoisy(true) setPlaybackSpeed(playBackSpeed) - } } } @@ -711,7 +702,7 @@ class CS3IPlayer : IPlayer { if (playWhenReady) { when (playbackState) { Player.STATE_READY -> { - requestAutoFocus?.invoke() + } Player.STATE_ENDED -> { handleEvent(CSPlayerEvent.NextEpisode) @@ -740,6 +731,7 @@ class CS3IPlayer : IPlayer { override fun onIsPlayingChanged(isPlaying: Boolean) { super.onIsPlayingChanged(isPlaying) if (isPlaying) { + requestAutoFocus?.invoke() onRenderFirst() } } @@ -886,55 +878,9 @@ class CS3IPlayer : IPlayer { return Pair(subSources, activeSubtitles) } - - fun loadYtFile(context: Context, yt: YtFile) { - loadOnlinePlayer( - context, - ExtractorLink( - "YouTube", - "", - yt.url, - "", - yt.format?.height ?: Qualities.Unknown.value - ) - ) - } - private fun loadOnlinePlayer(context: Context, link: ExtractorLink) { Log.i(TAG, "loadOnlinePlayer $link") try { - if (link.url.contains("youtube.com")) { - val ytLink = link.url.replace("/embed/", "/watch?v=") - ytVideos[ytLink]?.let { - loadYtFile(context, it) - return - } - val ytExtractor = - @SuppressLint("StaticFieldLeak") - object : YouTubeExtractor(context) { - override fun onExtractionComplete( - ytFiles: SparseArray?, - videoMeta: VideoMeta? - ) { - var yt: YtFile? = null - ytFiles?.forEach { _, value -> - if ((yt?.format?.height ?: 0) < (value.format?.height - ?: -1) && (value.format?.audioBitrate ?: -1) > 0 - ) { - yt = value - } - } - yt?.let { ytf -> - ytVideos[ytLink] = ytf - loadYtFile(context, ytf) - } - } - } - Log.i(TAG, "YouTube extraction on $ytLink") - ytExtractor.extract(ytLink) - return - } - currentLink = link if (ignoreSSL) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 7e9174b7..bd94560a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -229,6 +229,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } } + val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat() + player_open_source?.let { + ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply { + duration = 200 + start() + } + } + + if (!isLocked) { player_ffwd_holder?.alpha = 1f player_rew_holder?.alpha = 1f @@ -251,6 +260,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } bottom_player_bar?.startAnimation(fadeAnimation) + player_open_source?.startAnimation(fadeAnimation) player_top_holder?.startAnimation(fadeAnimation) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index b7b1c57a..99924f22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.annotation.LayoutRes +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView @@ -21,7 +22,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager import kotlinx.android.synthetic.main.result_episode.view.* -import kotlinx.android.synthetic.main.result_episode.view.episode_holder import kotlinx.android.synthetic.main.result_episode.view.episode_text import kotlinx.android.synthetic.main.result_episode_large.view.* import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler @@ -47,6 +47,7 @@ const val ACTION_SHOW_OPTIONS = 10 const val ACTION_CLICK_DEFAULT = 11 const val ACTION_SHOW_TOAST = 12 +const val ACTION_SHOW_DESCRIPTION = 15 const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13 const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14 @@ -93,10 +94,10 @@ class EpisodeAdapter( @LayoutRes private var layout: Int = 0 fun updateLayout() { - layout = - if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout - R.layout.result_episode_large - else R.layout.result_episode + // layout = + // if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout + // R.layout.result_episode_large + // else R.layout.result_episode } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -105,7 +106,7 @@ class EpisodeAdapter( else R.layout.result_episode*/ return EpisodeCardViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false), + LayoutInflater.from(parent.context).inflate(R.layout.result_episode_both, parent, false), hasDownloadSupport, clickCallback, downloadClickCallback @@ -134,27 +135,39 @@ class EpisodeAdapter( ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { override var downloadButton = EasyDownloadButton() - private val episodeText: TextView = itemView.episode_text - private val episodeFiller: MaterialButton? = itemView.episode_filler - private val episodeRating: TextView? = itemView.episode_rating - private val episodeDescript: TextView? = itemView.episode_descript - private val episodeProgress: ContentLoadingProgressBar? = itemView.episode_progress - private val episodePoster: ImageView? = itemView.episode_poster - - private val episodeDownloadBar: ContentLoadingProgressBar = itemView.result_episode_progress_downloaded - private val episodeDownloadImage: ImageView = itemView.result_episode_download - - private val episodeHolder = itemView.episode_holder + var episodeDownloadBar: ContentLoadingProgressBar? = null + var episodeDownloadImage: ImageView? = null var localCard: ResultEpisode? = null @SuppressLint("SetTextI18n") fun bind(card: ResultEpisode) { localCard = card - val name = if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}" + val (parentView,otherView) = if(card.poster == null) { + itemView.episode_holder to itemView.episode_holder_large + } else { + itemView.episode_holder_large to itemView.episode_holder + } + parentView.isVisible = true + otherView.isVisible = false + + val episodeText: TextView = parentView.episode_text + val episodeFiller: MaterialButton? = parentView.episode_filler + val episodeRating: TextView? = parentView.episode_rating + val episodeDescript: TextView? = parentView.episode_descript + val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress + val episodePoster: ImageView? = parentView.episode_poster + + episodeDownloadBar = + parentView.result_episode_progress_downloaded + episodeDownloadImage = parentView.result_episode_download + + val name = + if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}" episodeFiller?.isVisible = card.isFiller == true - episodeText.text = name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name + episodeText.text = + name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name episodeText.isSelected = true // is needed for text repeating val displayPos = card.getDisplayPosition() @@ -171,16 +184,20 @@ class EpisodeAdapter( } if (card.rating != null) { - episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)?.format(card.rating.toFloat() / 10f) + episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format) + ?.format(card.rating.toFloat() / 10f) } else { episodeRating?.text = "" } - if (card.description != null) { - episodeDescript?.visibility = View.VISIBLE - episodeDescript?.text = card.description - } else { - episodeDescript?.visibility = View.GONE + episodeRating?.isGone = episodeRating?.text.isNullOrBlank() + + episodeDescript?.apply { + text = card.description ?: "" + isGone = text.isNullOrBlank() + setOnClickListener { + clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) + } } episodePoster?.setOnClickListener { @@ -192,34 +209,42 @@ class EpisodeAdapter( return@setOnLongClickListener true } - episodeHolder.setOnClickListener { + parentView.setOnClickListener { clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) } - if (episodeHolder.context.isTrueTvSettings()) { - episodeHolder.isFocusable = true - episodeHolder.isFocusableInTouchMode = true - episodeHolder.touchscreenBlocksFocus = false + if (parentView.context.isTrueTvSettings()) { + parentView.isFocusable = true + parentView.isFocusableInTouchMode = true + parentView.touchscreenBlocksFocus = false } - episodeHolder.setOnLongClickListener { + parentView.setOnLongClickListener { clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card)) return@setOnLongClickListener true } - episodeDownloadImage.isVisible = hasDownloadSupport - episodeDownloadBar.isVisible = hasDownloadSupport + episodeDownloadImage?.isVisible = hasDownloadSupport + episodeDownloadBar?.isVisible = hasDownloadSupport + reattachDownloadButton() } override fun reattachDownloadButton() { downloadButton.dispose() val card = localCard if (hasDownloadSupport && card != null) { - val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(itemView.context, card.id) + val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings( + itemView.context, + card.id + ) downloadButton.setUpButton( - downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null, + downloadInfo?.fileLength, + downloadInfo?.totalBytes, + episodeDownloadBar ?: return, + episodeDownloadImage ?: return, + null, VideoDownloadHelper.DownloadEpisodeCached( card.name, card.poster, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index a1ddebe6..a787bcf0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -41,6 +41,7 @@ import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getId +import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast @@ -50,6 +51,7 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.EasyDownloadButton +import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData @@ -88,8 +90,10 @@ 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.fragment_trailer.* import kotlinx.android.synthetic.main.result_recommendations.* import kotlinx.android.synthetic.main.result_sync.* +import kotlinx.android.synthetic.main.trailer_custom_layout.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking @@ -599,7 +603,7 @@ class ResultFragment : ResultTrailerPlayer() { setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f)) } - var currentTrailers: List = emptyList() + var currentTrailers: List = emptyList() var currentTrailerIndex = 0 override fun nextMirror() { @@ -608,48 +612,43 @@ class ResultFragment : ResultTrailerPlayer() { } override fun playerError(exception: Exception) { - if (player.getIsPlaying()) // because we dont want random toasts in player + if (player.getIsPlaying()) { // because we dont want random toasts in player super.playerError(exception) + } else { + nextMirror() + } } private fun loadTrailer(index: Int? = null) { - currentTrailers.getOrNull(index ?: currentTrailerIndex)?.let { trailer -> - //if(trailer.contains("youtube.com")) { // wont load in exo - // nextMirror() - // return - //} - context?.let { ctx -> - player.onPause() - player.loadPlayer( - ctx, - false, - ExtractorLink( - "", - "Trailer", + val isSuccess = + currentTrailers.getOrNull(index ?: currentTrailerIndex)?.let { trailer -> + context?.let { ctx -> + player.onPause() + player.loadPlayer( + ctx, + false, trailer, - "", - Qualities.Unknown.value - ), - null, - startPosition = 0L, - subtitles = emptySet(), - subtitle = null, - autoPlay = false - ) + null, + startPosition = 0L, + subtitles = emptySet(), + subtitle = null, + autoPlay = false + ) + true + } ?: run { + false + } + } ?: run { + false } - } + result_trailer_loading?.isVisible = isSuccess } - private fun setTrailers(trailers: List?) { - context?.let { ctx -> - if (ctx.isTvSettings()) return - val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) - val showTrailers = - settingsManager.getBoolean(ctx.getString(R.string.show_trailers_key), true) - if (!showTrailers) return - currentTrailers = trailers ?: emptyList() - loadTrailer() - } + private fun setTrailers(trailers: List?) { + context?.updateHasTrailers() + if (!LoadResponse.isTrailersEnabled) return + currentTrailers = trailers?.sortedBy { -it.quality } ?: emptyList() + loadTrailer() } private fun setActors(actors: List?) { @@ -758,6 +757,12 @@ class ResultFragment : ResultTrailerPlayer() { result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) + player_open_source?.setOnClickListener { + currentTrailers.getOrNull(currentTrailerIndex)?.let { + context?.openBrowser(it.url) + } + } + updateUIListener = ::updateUI val restart = arguments?.getBoolean(RESTART_BUNDLE) ?: false @@ -767,6 +772,7 @@ class ResultFragment : ResultTrailerPlayer() { activity?.window?.decorView?.clearFocus() hideKeyboard() + context?.updateHasTrailers() activity?.loadCache() activity?.fixPaddingStatusbar(result_top_bar) @@ -828,6 +834,12 @@ class ResultFragment : ResultTrailerPlayer() { } else if (dy < -5) { result_bookmark_fab?.extend() } + if (!isFullScreenPlayer && player.getIsPlaying()) { + if (scrollY > (player_background?.height ?: scrollY)) { + player.handleEvent(CSPlayerEvent.Pause) + } + } + //result_poster_blur_holder?.translationY = -scrollY.toFloat() }) @@ -975,6 +987,7 @@ class ResultFragment : ResultTrailerPlayer() { } ACTION_CHROME_CAST_EPISODE -> requireLinks(true) ACTION_CHROME_CAST_MIRROR -> requireLinks(true) + ACTION_SHOW_DESCRIPTION -> true else -> requireLinks(false) } if (!isLoaded) return@main // CANT LOAD @@ -984,6 +997,14 @@ class ResultFragment : ResultTrailerPlayer() { showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT) } + ACTION_SHOW_DESCRIPTION -> { + val builder: AlertDialog.Builder = + AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) + builder.setMessage(episodeClick.data.description ?: return@main) + .setTitle(R.string.torrent_plot) + .show() + } + ACTION_CLICK_DEFAULT -> { context?.let { ctx -> if (ctx.isConnectedToChromecast()) { @@ -1431,7 +1452,7 @@ class ResultFragment : ResultTrailerPlayer() { val d = meta.value result_sync_episodes?.progress = currentSyncProgress * 1000 setSyncMaxEpisodes(d.totalEpisodes) - viewModel.setMeta(d) + viewModel.setMeta(d, syncdata) } is Resource.Loading -> { result_sync_max_episodes?.text = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt index ebc2c5be..b513a4c1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt @@ -53,6 +53,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen screenHeight } + result_trailer_loading?.isVisible = false player_background?.apply { isVisible = true layoutParams = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index 633e32a4..cdeda5b2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -13,17 +13,22 @@ import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId +import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId 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.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.syncproviders.SyncAPI +import com.lagradost.cloudstream3.syncproviders.providers.Kitsu.getEpisodesDetails import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.player.IGenerator import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData +import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData @@ -70,7 +75,6 @@ class ResultViewModel : ViewModel() { val dubStatus: LiveData get() = _dubStatus private val _dubStatus: MutableLiveData = MutableLiveData() - private val page: MutableLiveData = MutableLiveData() val id: MutableLiveData = MutableLiveData() val selectedSeason: MutableLiveData = MutableLiveData(-2) val seasonSelections: MutableLiveData> = MutableLiveData() @@ -88,11 +92,12 @@ class ResultViewModel : ViewModel() { fun updateWatchStatus(status: WatchType) = viewModelScope.launch { val currentId = id.value ?: return@launch _watchStatus.postValue(status) - val resultPage = page.value + val resultPage = _resultResponse.value withContext(Dispatchers.IO) { setResultWatchState(currentId, status.internalId) - if (resultPage != null) { + if (resultPage != null && resultPage is Resource.Success) { + val resultPageData = resultPage.value val current = getBookmarkedData(currentId) val currentTime = System.currentTimeMillis() setBookmarkedData( @@ -101,12 +106,12 @@ class ResultViewModel : ViewModel() { currentId, current?.bookmarkedTime ?: currentTime, currentTime, - resultPage.name, - resultPage.url, - resultPage.apiName, - resultPage.type, - resultPage.posterUrl, - resultPage.year + resultPageData.name, + resultPageData.url, + resultPageData.apiName, + resultPageData.type, + resultPageData.posterUrl, + resultPageData.year ) ) } @@ -118,20 +123,29 @@ class ResultViewModel : ViewModel() { } var lastMeta: SyncAPI.SyncResult? = null - private fun applyMeta(resp: LoadResponse, meta: SyncAPI.SyncResult?): LoadResponse { - if (meta == null) return resp - lastMeta = meta - return resp.apply { + var lastSync: Map? = null + + private suspend fun applyMeta( + resp: LoadResponse, + meta: SyncAPI.SyncResult?, + syncs: Map? = null + ): Pair { + if (meta == null) return resp to false + var updateEpisodes = false + val out = resp.apply { Log.i(TAG, "applyMeta") duration = duration ?: meta.duration rating = rating ?: meta.publicScore tags = tags ?: meta.genres plot = if (plot.isNullOrBlank()) meta.synopsis else plot - addTrailer(meta.trailerUrl) posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl actors = actors ?: meta.actors + for ((k, v) in syncs ?: emptyMap()) { + syncData[k] = v + } + val realRecommendations = ArrayList() val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name) meta.recommendations?.forEach { rec -> @@ -142,15 +156,55 @@ class ResultViewModel : ViewModel() { recommendations = recommendations?.union(realRecommendations)?.toList() ?: realRecommendations + + argamap({ + addTrailer(meta.trailers) + }, { + if (this !is AnimeLoadResponse) return@argamap + val map = getEpisodesDetails(getMalId(), getAniListId()) + if (map.isNullOrEmpty()) return@argamap + updateEpisodes = DubStatus.values().map { dubStatus -> + val current = + this.episodes[dubStatus]?.sortedBy { it.episode ?: 0 }?.toMutableList() + if (current.isNullOrEmpty()) return@map false + val episodes = current.mapIndexed { index, ep -> ep.episode ?: (index + 1) } + var updateCount = 0 + map.forEach { (episode, node) -> + episodes.binarySearch(episode).let { index -> + current.getOrNull(index)?.let { currentEp -> + current[index] = currentEp.apply { + updateCount++ + this.description = this.description ?: node.description?.en + this.name = this.name ?: node.titles?.canonical + this.episode = this.episode ?: node.num ?: episodes[index] + this.posterUrl = this.posterUrl ?: node.thumbnail?.original?.url + } + } + } + } + this.episodes[dubStatus] = current + + updateCount > 0 + }.any { it } + }) } + return out to updateEpisodes } - fun setMeta(meta: SyncAPI.SyncResult) { - Log.i(TAG, "setMeta") - (result.value as? Resource.Success?)?.value?.let { resp -> - _resultResponse.postValue(Resource.Success(applyMeta(resp, meta))) + fun setMeta(meta: SyncAPI.SyncResult, syncs: Map?) = + viewModelScope.launch { + Log.i(TAG, "setMeta") + lastMeta = meta + lastSync = syncs + val (value, updateEpisodes) = ioWork { + (result.value as? Resource.Success?)?.value?.let { resp -> + return@ioWork applyMeta(resp, meta, syncs) + } + return@ioWork null to null + } + _resultResponse.postValue(Resource.Success(value ?: return@launch)) + if (updateEpisodes ?: return@launch) updateEpisodes(value, lastShowFillers) } - } private fun loadWatchStatus(localId: Int? = null) { val currentId = localId ?: id.value ?: return @@ -310,6 +364,159 @@ class ResultViewModel : ViewModel() { return name } + var lastShowFillers = false + private suspend fun updateEpisodes(loadResponse: LoadResponse, showFillers: Boolean) { + Log.i(TAG, "updateEpisodes") + try { + lastShowFillers = showFillers + val mainId = loadResponse.getId() + + when (loadResponse) { + is AnimeLoadResponse -> { + if (loadResponse.episodes.isEmpty()) { + _dubSubEpisodes.postValue(emptyMap()) + return + } + +// val status = getDub(mainId) + val statuses = loadResponse.episodes.map { it.key } + + // Extremely bruh to have to take in context here, but I'm not sure how to do this in a better way :( + val preferDub = context?.getApiDubstatusSettings() + ?.contains(DubStatus.Dubbed) == true + + // 3 statements because there can be only dub even if you do not prefer it. + val dubStatus = + if (preferDub && statuses.contains(DubStatus.Dubbed)) DubStatus.Dubbed + else if (!preferDub && statuses.contains(DubStatus.Subbed)) DubStatus.Subbed + else statuses.first() + + val fillerEpisodes = + if (showFillers) safeApiCall { getFillerEpisodes(loadResponse.name) } else null + + val existingEpisodes = HashSet() + val res = loadResponse.episodes.map { ep -> + val episodes = ArrayList() + val idIndex = ep.key.id + for ((index, i) in ep.value.withIndex()) { + val episode = i.episode ?: (index + 1) + val id = mainId + episode + idIndex * 1000000 + if (!existingEpisodes.contains(episode)) { + existingEpisodes.add(id) + episodes.add(buildResultEpisode( + loadResponse.name, + filterName(i.name), + i.posterUrl, + episode, + i.season, + i.data, + loadResponse.apiName, + id, + index, + i.rating, + i.description, + if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let { + it.contains(episode) && it[episode] == true + } ?: false else false, + loadResponse.type, + mainId + )) + } + } + + Pair(ep.key, episodes) + }.toMap() + + // These posts needs to be in this order as to make the preferDub in ResultFragment work + _dubSubEpisodes.postValue(res) + res[dubStatus]?.let { episodes -> + updateEpisodes(mainId, episodes, -1) + } + _dubStatus.postValue(dubStatus) + _dubSubSelections.postValue(loadResponse.episodes.keys) + } + + is TvSeriesLoadResponse -> { + val episodes = ArrayList() + val existingEpisodes = HashSet() + for ((index, episode) in loadResponse.episodes.sortedBy { + (it.season?.times(10000) ?: 0) + (it.episode ?: 0) + }.withIndex()) { + val episodeIndex = episode.episode ?: (index + 1) + val id = + mainId + (episode.season?.times(100000) ?: 0) + episodeIndex + 1 + if (!existingEpisodes.contains(id)) { + existingEpisodes.add(id) + episodes.add( + buildResultEpisode( + loadResponse.name, + filterName(episode.name), + episode.posterUrl, + episodeIndex, + episode.season, + episode.data, + loadResponse.apiName, + id, + index, + episode.rating, + episode.description, + null, + loadResponse.type, + mainId + ) + ) + } + } + updateEpisodes(mainId, episodes, -1) + } + is MovieLoadResponse -> { + buildResultEpisode( + loadResponse.name, + loadResponse.name, + null, + 0, + null, + loadResponse.dataUrl, + loadResponse.apiName, + (mainId), // HAS SAME ID + 0, + null, + null, + null, + loadResponse.type, + mainId + ).let { + updateEpisodes(mainId, listOf(it), -1) + } + } + is TorrentLoadResponse -> { + updateEpisodes( + mainId, listOf( + buildResultEpisode( + loadResponse.name, + loadResponse.name, + null, + 0, + null, + loadResponse.torrent ?: loadResponse.magnet ?: "", + loadResponse.apiName, + (mainId), // HAS SAME ID + 0, + null, + null, + null, + loadResponse.type, + mainId + ) + ), -1 + ) + } + } + } catch (e: Exception) { + logError(e) + } + } + fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch { _publicEpisodes.postValue(Resource.Loading()) _resultResponse.postValue(Resource.Loading(url)) @@ -356,8 +563,10 @@ class ResultViewModel : ViewModel() { when (data) { is Resource.Success -> { - val loadResponse = applyMeta(data.value, lastMeta) - page.postValue(loadResponse) + val loadResponse = if (lastMeta != null || lastSync != null) ioWork { + applyMeta(data.value, lastMeta, lastSync).first + } else data.value + _resultResponse.postValue(Resource.Success(loadResponse)) val mainId = loadResponse.getId() id.postValue(mainId) loadWatchStatus(mainId) @@ -375,148 +584,7 @@ class ResultViewModel : ViewModel() { System.currentTimeMillis(), ) ) - - when (loadResponse) { - is AnimeLoadResponse -> { - if (loadResponse.episodes.isEmpty()) { - _dubSubEpisodes.postValue(emptyMap()) - return@launch - } - -// val status = getDub(mainId) - val statuses = loadResponse.episodes.map { it.key } - - // Extremely bruh to have to take in context here, but I'm not sure how to do this in a better way :( - val preferDub = context?.getApiDubstatusSettings() - ?.contains(DubStatus.Dubbed) == true - - // 3 statements because there can be only dub even if you do not prefer it. - val dubStatus = - if (preferDub && statuses.contains(DubStatus.Dubbed)) DubStatus.Dubbed - else if (!preferDub && statuses.contains(DubStatus.Subbed)) DubStatus.Subbed - else statuses.first() - - val fillerEpisodes = - if (showFillers) safeApiCall { getFillerEpisodes(loadResponse.name) } else null - - val existingEpisodes = HashSet() - val res = loadResponse.episodes.map { ep -> - val episodes = ArrayList() - val idIndex = ep.key.id - for ((index, i) in ep.value.withIndex()) { - val episode = i.episode ?: (index + 1) - val id = mainId + episode + idIndex * 1000000 - if (!existingEpisodes.contains(episode)) { - existingEpisodes.add(id) - episodes.add(buildResultEpisode( - loadResponse.name, - filterName(i.name), - i.posterUrl, - episode, - i.season, - i.data, - apiName, - id, - index, - i.rating, - i.description, - if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let { - it.contains(episode) && it[episode] == true - } ?: false else false, - loadResponse.type, - mainId - )) - } - } - - Pair(ep.key, episodes) - }.toMap() - - // These posts needs to be in this order as to make the preferDub in ResultFragment work - _dubSubEpisodes.postValue(res) - res[dubStatus]?.let { episodes -> - updateEpisodes(mainId, episodes, -1) - } - _dubStatus.postValue(dubStatus) - _dubSubSelections.postValue(loadResponse.episodes.keys) - } - - is TvSeriesLoadResponse -> { - val episodes = ArrayList() - val existingEpisodes = HashSet() - for ((index, episode) in loadResponse.episodes.sortedBy { - (it.season?.times(10000) ?: 0) + (it.episode ?: 0) - }.withIndex()) { - val episodeIndex = episode.episode ?: (index + 1) - val id = - mainId + (episode.season?.times(100000) ?: 0) + episodeIndex + 1 - if (!existingEpisodes.contains(id)) { - existingEpisodes.add(id) - episodes.add( - buildResultEpisode( - loadResponse.name, - filterName(episode.name), - episode.posterUrl, - episodeIndex, - episode.season, - episode.data, - apiName, - id, - index, - episode.rating, - episode.description, - null, - loadResponse.type, - mainId - ) - ) - } - } - updateEpisodes(mainId, episodes, -1) - } - is MovieLoadResponse -> { - buildResultEpisode( - loadResponse.name, - loadResponse.name, - null, - 0, - null, - loadResponse.dataUrl, - loadResponse.apiName, - (mainId), // HAS SAME ID - 0, - null, - null, - null, - loadResponse.type, - mainId - ).let { - updateEpisodes(mainId, listOf(it), -1) - } - } - is TorrentLoadResponse -> { - updateEpisodes( - mainId, listOf( - buildResultEpisode( - loadResponse.name, - loadResponse.name, - null, - 0, - null, - loadResponse.torrent ?: loadResponse.magnet ?: "", - loadResponse.apiName, - (mainId), // HAS SAME ID - 0, - null, - null, - null, - loadResponse.type, - mainId - ) - ), -1 - ) - } - } + updateEpisodes(loadResponse, showFillers) } else -> Unit } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 22ca83a3..781736a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -6,12 +6,18 @@ import android.os.Build import android.os.Bundle import android.os.Environment import android.view.View +import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager +import com.fasterxml.jackson.annotation.JsonProperty import com.hippo.unifile.UniFile +import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError @@ -20,9 +26,15 @@ import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard +import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath +import kotlinx.android.synthetic.main.add_remove_sites.* +import kotlinx.android.synthetic.main.add_site_input.* import java.io.File class SettingsGeneral : PreferenceFragmentCompat() { @@ -31,6 +43,17 @@ class SettingsGeneral : PreferenceFragmentCompat() { setUpToolbar(R.string.category_general) } + data class CustomSite( + @JsonProperty("parentJavaClass") // javaClass.simpleName + val parentJavaClass: String, + @JsonProperty("name") + val name: String, + @JsonProperty("url") + val url: String, + @JsonProperty("lang") + val lang: String, + ) + // Open file picker private val pathPicker = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> @@ -64,6 +87,94 @@ class SettingsGeneral : PreferenceFragmentCompat() { setPreferencesFromResource(R.xml.settins_general, rootKey) val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) + fun getCurrent(): MutableList { + return getKey>(USER_PROVIDER_API)?.toMutableList() + ?: mutableListOf() + } + + fun showAdd() { + val providers = allProviders.distinctBy { it.javaClass }.sortedBy { it.name } + activity?.showDialog( + providers.map { "${it.name} (${it.mainUrl})" }, + -1, + context?.getString(R.string.add_site_pref) ?: return, + true, + {}) { selection -> + val provider = providers.getOrNull(selection) ?: return@showDialog + + val builder = + AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom) + .setView(R.layout.add_site_input) + + val dialog = builder.create() + dialog.show() + + dialog.text2?.text = provider.name + dialog.apply_btt?.setOnClickListener { + val name = dialog.site_name_input?.text?.toString() + val url = dialog.site_url_input?.text?.toString() + val lang = dialog.site_lang_input?.text?.toString() + val realLang = if (lang.isNullOrBlank()) provider.lang else lang + if (url.isNullOrBlank() || name.isNullOrBlank() || realLang.length != 2) { + showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT) + return@setOnClickListener + } + + val current = getCurrent() + val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang) + current.add(newSite) + setKey(USER_PROVIDER_API, current.toTypedArray()) + + dialog.dismissSafe(activity) + } + dialog.cancel_btt?.setOnClickListener { + dialog.dismissSafe(activity) + } + } + } + + fun showDelete() { + val current = getCurrent() + + activity?.showMultiDialog( + current.map { it.name }, + listOf(), + context?.getString(R.string.remove_site_pref) ?: return, + {}) { indexes -> + current.removeAll(indexes.map { current[it] }) + setKey(USER_PROVIDER_API, current.toTypedArray()) + } + } + + fun showAddOrDelete() { + val builder = + AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom) + .setView(R.layout.add_remove_sites) + + val dialog = builder.create() + dialog.show() + + dialog.add_site?.setOnClickListener { + showAdd() + dialog.dismissSafe(activity) + } + dialog.remove_site?.setOnClickListener { + showDelete() + dialog.dismissSafe(activity) + } + } + + getPref(R.string.override_site_key)?.setOnPreferenceClickListener { _ -> + + if (getCurrent().isEmpty()) { + showAdd() + } else { + showAddOrDelete() + } + + return@setOnPreferenceClickListener true + } + getPref(R.string.legal_notice_key)?.setOnPreferenceClickListener { val builder: AlertDialog.Builder = AlertDialog.Builder(it.context, R.style.AlertDialogCustom) @@ -72,7 +183,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { builder.show() return@setOnPreferenceClickListener true } - + getPref(R.string.dns_key)?.setOnPreferenceClickListener { val prefNames = resources.getStringArray(R.array.dns_pref) val prefValues = resources.getIntArray(R.array.dns_pref_values) @@ -177,6 +288,5 @@ class SettingsGeneral : PreferenceFragmentCompat() { } catch (e: Exception) { e.printStackTrace() } - } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt index da32abaa..d5cb06f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -3,28 +3,31 @@ package com.lagradost.cloudstream3.utils import android.os.Handler import android.os.Looper import com.lagradost.cloudstream3.mvvm.logError -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.* object Coroutines { - fun main(work: suspend (() -> Unit)) : Job { + fun main(work: suspend (() -> Unit)): Job { return CoroutineScope(Dispatchers.Main).launch { work() } } - fun ioSafe(work: suspend (() -> Unit)) : Job { + fun ioSafe(work: suspend (() -> Unit)): Job { return CoroutineScope(Dispatchers.IO).launch { try { work() - } catch (e : Exception) { + } catch (e: Exception) { logError(e) } } } + suspend fun ioWork(work: suspend (() -> T)): T { + return withContext(Dispatchers.IO) { + work() + } + } + fun runOnMainThread(work: (() -> Unit)) { val mainHandler = Handler(Looper.getMainLooper()) mainHandler.post { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index 3ad95730..ade1fc38 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -14,6 +14,7 @@ const val DOWNLOAD_HEADER_CACHE = "download_header_cache" const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache" const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha_key" const val HOMEPAGE_API = "home_api_used" +const val USER_PROVIDER_API = "user_custom_sites" const val PREFERENCES_NAME = "rebuild_preference" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 65c3c2c7..48622b22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -106,6 +106,19 @@ suspend fun loadExtractor( return false } +suspend fun loadExtractor( + url: String, + referer: String? = null, +): List { + for (extractor in extractorApis) { + if (url.startsWith(extractor.mainUrl)) { + return extractor.getSafeUrl(url, referer) ?: emptyList() + + } + } + return emptyList() +} + val extractorApis: Array = arrayOf( //AllProvider(), WcoStream(), @@ -205,6 +218,9 @@ val extractorApis: Array = arrayOf( KotakAnimeid(), Neonime8n(), Neonime7n(), + + YoutubeExtractor(), + YoutubeShortLinkExtractor(), ) fun getExtractorApiFromName(name: String): ExtractorApi { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt index 911d58e7..14d1b055 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt @@ -11,9 +11,11 @@ object FillerEpisodeCheck { private const val MAIN_URL = "https://www.animefillerlist.com" var list: HashMap? = null + var cache: HashMap> = hashMapOf() private fun fixName(name: String): String { - return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ").replace("[^a-zA-Z0-9 ]".toRegex(), "") + return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ") + .replace("[^a-zA-Z0-9 ]".toRegex(), "") } private suspend fun getFillerList(): Boolean { @@ -61,6 +63,9 @@ object FillerEpisodeCheck { suspend fun getFillerEpisodes(query: String): HashMap? { try { + cache[query]?.let { + return it + } if (!getFillerList()) return null val localList = list ?: return null @@ -75,9 +80,15 @@ object FillerEpisodeCheck { "(\\d+)" // year ) val blackListRegex = - Regex(""" (${blackList.joinToString(separator = "|").replace("(", "\\(").replace(")", "\\)")})""") + Regex( + """ (${ + blackList.joinToString(separator = "|").replace("(", "\\(") + .replace(")", "\\)") + })""" + ) - val realQuery = fixName(query.replace(blackListRegex, "")).replace("shippuuden", "shippuden") + val realQuery = + fixName(query.replace(blackListRegex, "")).replace("shippuuden", "shippuden") if (!localList.containsKey(realQuery)) return null val href = localList[realQuery]?.replace(MAIN_URL, "") ?: return null // JUST IN CASE val result = app.get("$MAIN_URL$href").text @@ -90,6 +101,7 @@ object FillerEpisodeCheck { hashMap[episodeNumber] = type } } + cache[query] = hashMap return hashMap } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 0a069cec..814cf95b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -14,23 +14,33 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.setImage object SingleSelectionHelper { - fun Activity.showOptionSelectStringRes( + fun Activity?.showOptionSelectStringRes( view: View?, poster: String?, options: List, tvOptions: List = listOf(), callback: (Pair) -> Unit ) { - this.showOptionSelect(view, poster, options.map { this.getString(it) },tvOptions.map { this.getString(it) }, callback) + if(this == null) return + + this.showOptionSelect( + view, + poster, + options.map { this.getString(it) }, + tvOptions.map { this.getString(it) }, + callback + ) } - private fun Activity.showOptionSelect( + private fun Activity?.showOptionSelect( view: View?, poster: String?, options: List, tvOptions: List, callback: (Pair) -> Unit ) { + if(this == null) return + if (this.isTvSettings()) { val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) @@ -41,12 +51,13 @@ object SingleSelectionHelper { dialog.findViewById(R.id.listview1)?.let { listView -> listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE - listView.adapter = ArrayAdapter(this, R.layout.sort_bottom_single_choice_color).apply { - addAll(tvOptions) - } + listView.adapter = + ArrayAdapter(this, R.layout.sort_bottom_single_choice_color).apply { + addAll(tvOptions) + } listView.setOnItemClickListener { _, _, i, _ -> - callback.invoke(Pair(true,i)) + callback.invoke(Pair(true, i)) dialog.dismissSafe(this) } } @@ -62,12 +73,12 @@ object SingleSelectionHelper { s ) }) { - callback(Pair(false,this.itemId)) + callback(Pair(false, this.itemId)) } } } - fun Activity.showDialog( + fun Activity?.showDialog( dialog: Dialog, items: List, selectedIndex: List, @@ -77,6 +88,8 @@ object SingleSelectionHelper { callback: (List) -> Unit, dismissCallback: () -> Unit ) { + if(this == null) return + val realShowApply = showApply || isMultiSelect val listView = dialog.findViewById(R.id.listview1)!! val textView = dialog.findViewById(R.id.text1)!! @@ -145,8 +158,7 @@ object SingleSelectionHelper { } - - private fun Activity.showInputDialog( + private fun Activity?.showInputDialog( dialog: Dialog, value: String, name: String, @@ -154,6 +166,8 @@ object SingleSelectionHelper { callback: (String) -> Unit, dismissCallback: () -> Unit ) { + if(this == null) return + val inputView = dialog.findViewById(R.id.nginx_text_input)!! val textView = dialog.findViewById(R.id.text1)!! val applyButton = dialog.findViewById(R.id.apply_btt)!! @@ -184,13 +198,15 @@ object SingleSelectionHelper { } - fun Activity.showMultiDialog( + fun Activity?.showMultiDialog( items: List, selectedIndex: List, name: String, dismissCallback: () -> Unit, callback: (List) -> Unit, ) { + if(this == null) return + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) .setView(R.layout.bottom_selection_dialog) @@ -200,7 +216,7 @@ object SingleSelectionHelper { showDialog(dialog, items, selectedIndex, name, true, true, callback, dismissCallback) } - fun Activity.showDialog( + fun Activity?.showDialog( items: List, selectedIndex: Int, name: String, @@ -208,6 +224,8 @@ object SingleSelectionHelper { dismissCallback: () -> Unit, callback: (Int) -> Unit, ) { + if(this == null) return + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) .setView(R.layout.bottom_selection_dialog) @@ -227,14 +245,15 @@ object SingleSelectionHelper { } /** Only for a low amount of items */ - fun Activity.showBottomDialog( + fun Activity?.showBottomDialog( items: List, selectedIndex: Int, name: String, showApply: Boolean, - dismissCallback: () -> Unit, + dismissCallback: () -> Unit, callback: (Int) -> Unit, ) { + if (this == null) return val builder = BottomSheetDialog(this) builder.setContentView(R.layout.bottom_selection_dialog) @@ -252,12 +271,12 @@ object SingleSelectionHelper { ) } - fun Activity.showNginxTextInputDialog( - name: String, - value: String, - textInputType: Int?, - dismissCallback: () -> Unit, - callback: (String) -> Unit, + fun Activity.showNginxTextInputDialog( + name: String, + value: String, + textInputType: Int?, + dismissCallback: () -> Unit, + callback: (String) -> Unit, ) { val builder = BottomSheetDialog(this) // probably the stuff at the bottom builder.setContentView(R.layout.bottom_input_dialog) // input layout diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml index 70046c48..9fdd2903 100644 --- a/app/src/main/res/drawable/ic_baseline_add_24.xml +++ b/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/layout/add_remove_sites.xml b/app/src/main/res/layout/add_remove_sites.xml new file mode 100644 index 00000000..9ef6ad6a --- /dev/null +++ b/app/src/main/res/layout/add_remove_sites.xml @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/add_site_input.xml b/app/src/main/res/layout/add_site_input.xml new file mode 100644 index 00000000..1c61f8b4 --- /dev/null +++ b/app/src/main/res/layout/add_site_input.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 4c1aef8a..3a9de7ad 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -138,6 +138,34 @@ android:background="?attr/primaryBlackBackground" android:orientation="vertical"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_large.xml b/app/src/main/res/layout/result_episode_large.xml index 5842d42d..8fc917ec 100644 --- a/app/src/main/res/layout/result_episode_large.xml +++ b/app/src/main/res/layout/result_episode_large.xml @@ -5,7 +5,7 @@ android:nextFocusLeft="@id/episode_poster" android:nextFocusRight="@id/result_episode_download" - android:id="@+id/episode_holder" + android:id="@+id/episode_holder_large" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -142,11 +142,13 @@ diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 37a32184..ac155ad1 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -58,7 +58,6 @@ android:background="@color/black_overlay" /> - + + + android:layout_marginEnd="20dp" + android:layout_width="30dp" + android:layout_height="30dp" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1232736..a4a22cb5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ bottom_title_key poster_ui_key subtitles_encoding_key + override_site_key %d %s | %sMB @@ -382,6 +383,10 @@ DNS over HTTPS Useful for bypassing ISP blocks + Clone site + Remove site + Add a clone of an existing site, with a different url + Download path Nginx server url @@ -440,6 +445,9 @@ MyCoolUsername hello@world.com 127.0.0.1 + MyCoolSite + example.com + Language code (en)